spatialaudio / jackclient-python

🂻 JACK Audio Connection Kit (JACK) Client for Python :snake:
https://jackclient-python.readthedocs.io/
MIT License
132 stars 26 forks source link

How to use write_midi_event ? #60

Closed icubex closed 5 years ago

icubex commented 5 years ago

Couldn't find any clear example how to use write_midi_event so came up with the following working code (a basic virtual MIDI cable) that you might add to your examples section. Any comments ?

import jack

midiInBuffer = []
midiOutBuffer = []

client = jack.Client('jack')

midi_in = client.midi_inports.register('midi_in')
midi_out = client.midi_outports.register('midi_out')

@client.set_process_callback

def process(frames):
  #MIDI sent
  midi_out.clear_buffer()
  if (len(midiOutBuffer)):
    for msg in midiOutBuffer:
      midi_out.write_midi_event(0, msg)
      print('MIDI sent = ', msg)
    midiOutBuffer.clear()
  #MIDI received
  for offset, data in midi_in.incoming_midi_events():
    msg = struct.unpack('3B', data)
    midiInBuffer.append(msg)
    print('MIDI rcvd = ', msg)

client.activate()

print(client.midi_inports)
print(client.midi_outports)

#forward all incoming messages from midi_in to midi_out
while True:
  for msg in midiInBuffer
    midiOutBuffer.append(msg)
    print('MIDI fwd = ', msg)
  midiInBuffer.clear()
mgeier commented 5 years ago

Thanks for this code example!

It doesn't quite work, though. It seems to work, but it doesn't actually forward all messages.

The problem is that you are accessing and modifying the Python lists midiInBuffer and midiOutBuffer from two different threads.

If you want to transport events from one thread to another, you should use something like jack.RingBuffer from this module or queue.Queue from the Python standard library.

Also, it is unclear to me why you transfer the messages out of the callback into the main thread, just to transfer them back into the callback without doing anything else?

Why don't you just forward the messages in the callback?

Then you'll have an example very similar to midi_chords.py. If you remove about half of the process function, you'll get just the forwarding feature:

https://github.com/spatialaudio/jackclient-python/blob/3462e73f18547f9a2a78de310711ba71c7f68d60/examples/midi_chords.py#L23-L28

mgeier commented 5 years ago

Related issue #47.

icubex commented 5 years ago

You are so right ! I changed the example as follows:

import jack
import struct

client = jack.Client('jack')

midi_in = client.midi_inports.register('midi_in')
midi_out = client.midi_outports.register('midi_out')

@client.set_process_callback

def process(frames):
  midi_out.clear_buffer()
  #forward received MIDI data
  for offset, data in midi_in.incoming_midi_events():
    midi_out.write_midi_event(offset, data)
    #unpack data for printing
    msg = struct.unpack('3B', data)
    print('MIDI rcvd = ', msg)

client.activate()

#print all ports
print(client.get_ports())

#print client ports
#print(client.midi_inports)
#print(client.midi_outports)

#connect to MIDI data source
#client.connect('MyMIDISoftware:midi_out', midi_in)

#connect to MIDI data destination
#client.connect(midi_out, MyOtherMIDISoftware:midi_in')

#do something else like running a GUI to configure MIDI source/destination connections
with client:
    print('#' * 80)
    print('press Return to quit')
    print('#' * 80)
    input()
mgeier commented 5 years ago

Yes, now it nearly works. You are only missing an import statement:

import struct

And please note that you are waiting in a so-called "tight loop", that means the while loop is executed as fast as the CPU is able to. You should look at a CPU meter while your program is running!

Have a look at my examples for other ways of waiting that don't use all available CPU power.

Now your example is very close to my midi_chords.py example, isn't it?

BTW, I've just uploaded a new MIDI example: #61

What do you think about it?

icubex commented 5 years ago

Ah yes, I forgot to copy the struct stmt over from my RPi where I was working on this. The while loop is bad indeed so I copied your code from midi_chords.py. And yes the example is very close to your midi_chords.py now. Not sure why I wasn't able to understand how to use write_midi_event from midi_chords.py back in November when I was working on this, see https://github.com/I-CubeX/PythonExamples/blob/master/AnalogReadMIDI.py. Thanks again for your help, and for the new example (one can't have enough examples .. ) !