Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

midi_file_player.py + multiprocessing #99

Open
Stemby opened this issue Nov 7, 2020 · 7 comments
Open

midi_file_player.py + multiprocessing #99

Stemby opened this issue Nov 7, 2020 · 7 comments

Comments

@Stemby
Copy link

Stemby commented Nov 7, 2020

Hi,
I'm trying to modify this example in order to make it compatible with multiprocessing.

Mainly I had to rename some global variables:

#!/usr/bin/env python3

import sys
import threading

import jack
from mido import MidiFile
from multiprocessing import Process

midi_file = '../Musica/Mozart/k265/Zwölf-Variationen.midi'

try:
    mid = iter(MidiFile(midi_file))
except Exception as e:
    sys.exit(type(e).__name__ + ' while loading MIDI: ' + str(e))

client = jack.Client('Luces')
midi_port = client.midi_outports.register('output')
midi_event = threading.Event()
midi_msg = next(mid)
midi_fs = None  # sampling rate
midi_offset = 0

@client.set_process_callback
def process(frames):
    global midi_offset
    global midi_msg
    midi_port.clear_buffer()
    while True:
        if midi_offset >= frames:
            midi_offset -= frames
            return  # We'll take care of this in the next block ...
        # Note: This may raise an exception:
        midi_port.write_midi_event(midi_offset, midi_msg.bytes())
        try:
            midi_msg = next(mid)
        except StopIteration:
            midi_event.set()
            raise jack.CallbackExit
        midi_offset += round(midi_msg.time * midi_fs)

@client.set_samplerate_callback
def samplerate(samplerate):
    global midi_fs
    midi_fs = samplerate

@client.set_shutdown_callback
def shutdown(status, reason):
    print('JACK shutdown:', reason, status)
    midi_event.set()

def run_midi():
    with client:
        print('Playing', repr(midi_file), '... press Ctrl+C to stop')
        try:
            midi_event.wait()
        except KeyboardInterrupt:
            print('\nInterrupted by user')

if __name__ == "__main__":
    processes = [Process(target=run_midi)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

Now I get this error at the end of the execution:

Playing '../Musica/Mozart/k265/Zwölf-Variationen.midi' ... press Ctrl+C to stop
Cannot read socket fd = 5 err = Success
CheckRes error
JackSocketClientChannel read fail
JACK shutdown: JACK server has been closed <jack.Status 0x21: failure, server_error>

How can I fix it?

Thank you!

Carlo

@HaHeho
Copy link

HaHeho commented Nov 7, 2020

What OS are you on? (there is some specifics of spawning child processes with multiprocessing that do not work on Windows quite the way they work on Linux or macOS)

Also, I believe you want to use multiprocessing.Event instead of threading.Event in order to be consistent. This is to begin with and most probably not going to solve your problem (yet).

@Stemby
Copy link
Author

Stemby commented Nov 8, 2020

Hi @HaHeho,
thank you for your reply.

What OS are you on?

Debian GNU/Linux (testing) with kernel realtime:

$ uname -r
5.9.0-1-rt-amd64

I believe you want to use multiprocessing.Event instead of threading.Event in order to be consistent.

Yes, this makes sense.

Here is my current code:

#!/usr/bin/env python3

import sys
import jack
from mido import MidiFile
from multiprocessing import Process, Event

midi_file = '../Musica/Mozart/k265/Zwölf-Variationen.midi'

try:
    mid = iter(MidiFile(midi_file))
except Exception as e:
    sys.exit(type(e).__name__ + ' while loading MIDI: ' + str(e))

client = jack.Client('Luces')
midi_port = client.midi_outports.register('output')
midi_event = Event()
midi_msg = next(mid)
midi_fs = None  # sampling rate
midi_offset = 0

@client.set_process_callback
def process(frames):
    global midi_offset
    global midi_msg
    midi_port.clear_buffer()
    while True:
        if midi_offset >= frames:
            midi_offset -= frames
            return  # We'll take care of this in the next block ...
        # Note: This may raise an exception:
        midi_port.write_midi_event(midi_offset, midi_msg.bytes())
        try:
            midi_msg = next(mid)
        except StopIteration:
            midi_event.set()
            raise jack.CallbackExit
        midi_offset += round(midi_msg.time * midi_fs)

@client.set_samplerate_callback
def samplerate(samplerate):
    global midi_fs
    midi_fs = samplerate

@client.set_shutdown_callback
def shutdown(status, reason):
    print('JACK shutdown:', reason, status)
    midi_event.set()

def run_midi():
    with client:
        print('Playing', repr(midi_file), '... press Ctrl+C to stop')
        try:
            midi_event.wait()
        except KeyboardInterrupt:
            print('\nInterrupted by user')

if __name__ == "__main__":
#    run_midi()
    processes = [Process(target=run_midi)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

When calling run_midi() (by commenting the multiprocessing part) now I get this output at the end of the execution:

Playing '../Musica/Mozart/k265/Zwölf-Variationen.midi' ... press Ctrl+C to stop
Cannot read socket fd = -1 err = Success
Could not read result type = 7

but after that the command line is available. So, something wrong happens with multiprocessing.Event() used as replacement of threading.Event().

When running the code I posted here (by commenting the run_midi() call) I get this output at the end of the execution:

Playing '../Musica/Mozart/k265/Zwölf-Variationen.midi' ... press Ctrl+C to stop
Cannot read socket fd = 5 err = Success
CheckRes error
JackSocketClientChannel read fail
JACK shutdown: JACK server has been closed <jack.Status 0x21: failure, server_error>

and the process doesn't stop: so no differences compared to my previous attempt with multiprocessing+threading.

Thanks!

@mgeier
Copy link
Member

mgeier commented Nov 11, 2020

@Stemby Let's take a step back: do you really need multiprocessing?

This is really hard to get right and some libraries might not support it very well. I don't know if the jack module does or not.

If you don't really need multiprocessing, you should do yourself the favor and not use it.

If you are willing to load the whole audio file into memory (the MIDI file is already in memory), you might not even need threading!

If you really need multiprocessing, you should try to simplify your reproducible example:

  • remove the mido dependency. Just send some dummy MIDI messages.
  • remove everything else that's not needed to reproduce the problem

Then we can start debugging ...

@Stemby
Copy link
Author

Stemby commented Nov 12, 2020

Thanks @mgeier!

do you really need multiprocessing?

I'm not sure. I suppose it would be the must solid solution for my purpose (if it works properly). At the moment I'm testing a simpler way (threading) and postponing the migration to multiprocessing.

If you really need multiprocessing, you should try to simplify your reproducible example

OK, thank you!

@HaHeho
Copy link

HaHeho commented Nov 12, 2020

do you really need multiprocessing?

Isn't that the only way to realize file playback (either audio or MIDI) as an independent component which can be e.g. invoked or remote controlled by other parts of an application?

If then multiprocessing or threading is used probably doesn't change the method or required effort of the implementation, or does it?

Since quite a while, I was thinking to create a simple reference example for (audio) file playback which utilizes multiprocessing (and/or maybe also one with threading). @mgeier do you think that would be useful, especially with regards of investigating the latter part of:

This is really hard to get right and some libraries might not support it very well. I don't know if the jack module does or not.

@mgeier
Copy link
Member

mgeier commented Nov 12, 2020

do you really need multiprocessing?

Isn't that the only way to realize file playback (either audio or MIDI) as an independent component which can be e.g. invoked or remote controlled by other parts of an application?

It depends on what exactly you mean by "independent component", but I don't think so.

In simple cases, neither threading nor multiprocessing are needed, because the audio callback runs in a separate thread anyway, and whatever else has to be done, can be done in the main thread.

But if multiple things have to be done concurrently in the main thread, a solution has to be found.
I guess the next best solution is to use threading, but probably async/await (with or without asyncio) could be used as well.
multiprocessing should only be used if it's really needed.

If then multiprocessing or threading is used probably doesn't change the method or required effort of the implementation, or does it?

In my experience it very much does, because multiprocessing is harder to use than it might seem at first glance.

The resulting code is certainly less intuitive, because you have to think about which parts of the program state will be duplicated in multiple processes, which often isn't very obvious.

Since quite a while, I was thinking to create a simple reference example for (audio) file playback which utilizes multiprocessing (and/or maybe also one with threading). @mgeier do you think that would be useful, especially with regards of investigating the latter part of:

This is really hard to get right and some libraries might not support it very well. I don't know if the jack module does or not.

Yes, having an example to test whether the jack module works with multiprocessing would be great!

But it would be really good if the example would somehow represent a situation where multiprocessing matters.

Of course it should be as simple as possible, but it would be good for illustration if one of the processes would do some CPU-intensive work.

@mgeier
Copy link
Member

mgeier commented May 25, 2021

@Stemby Any news on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants