def receive_sysex(midi_in: MidiIn) -> List[int]: message = None while True: raw_tuple = midi_in.get_message() if raw_tuple: message = raw_tuple[0] break sleep(0.01) assert len(message) == 117 assert message[4] == 103 # Flip 4-th position from DEC 103 (HEX 67) to DEC 100 (HEX 64) # to flip patch from 'Receive' to 'Send' message[4] = 100 message_hex = ''.join([str(hex(m))[2:].zfill(2) for m in message]) logging.debug(f'- RECEIVED {len(message)} BYTES. SYSEX:\n{message_hex}') return message
class InStream(threading.Thread): """ Polls midi-in and publish the events Based on the midiin_callback code from python rt-midi examples """ def __init__(self, interpreters, port): super(InStream, self).__init__() self.interpreters = interpreters self.port = port self.wallclock = timenow() try: self.midiin = MidiIn().open_port(port) print("Listening for midi on input port {}".format(port)) except Exception: print("WARNING: failed to open MIDI-in port {0}".format(port)) self.done = False self.run = self.run_bound if Parameters.SP.ATTRACTOR_MODE == 1 else self.run_att # print(self.run.__name__) self.start() def run_att(self): """ For use with attractor mode 2, which is old and bad """ # TODO experimental timings buffer_time = 0.05 while not self.done: started = timenow() buffer = list() while (timenow() - started) < buffer_time: msg = self.midiin.get_message() if msg: buffer.append(msg) sleep(0.002) chord_time = buffer_time if len(buffer) >= 1: if buffer[0]: chord_time = buffer[0][1] for msg in buffer: message, delta_time = msg # TODO for now only listen to NOTE_ON & OFF if 128 <= message[0] < 160: self.wallclock += delta_time # print("[%s] @%0.6f %r" % (self.port_name, self.wallclock, message)) # publish the message to every interpreter for interp in self.interpreters: interp.backwards_interpret(message, chord_time) sleep(0.01) # TODO need this? def run_bound(self): """ For use with attractor mode 1, and is not yet done """ # gather <event_time> seconds of notes and analyse them for: # - pitch min & range # - time min & range # - dynam min & range # - (if possible, the duration of notes, from note off messages) event_time = 0.5 # TODO maybe this should adapt to the pace of the music? buffer_time = 0.05 while not self.done: started = timenow() p_min, d_min, t_min = [float('inf')] * 3 p_max, d_max, t_max = [0] * 3 event_notes = list() # a single event while timenow() - started < event_time: b_started = timenow() delta_t = -1 # buffer, so chords count as simultaneous notes while timenow() - b_started < buffer_time: msg = self.midiin.get_message() if msg: message, d_t = msg if delta_t == -1: delta_t = d_t event_notes.append((message, delta_t)) sleep(0.002) num_note_ons = 0 # analyse event_notes for all the fun stuff for (message, time) in event_notes: if 144 <= message[0] < 160: note_on, pitch, dynam = message p_min = min(p_min, pitch) p_max = max(p_max, pitch) d_min = min(d_min, dynam) d_max = max(d_max, dynam) t_min = min(t_min, time) t_max = max(t_max, time) num_note_ons += 1 # TODO duration elif 128 < message[0] < 144: # until we do duration, we don't care pass # if there's anything to report if num_note_ons: p_rng, d_rng, t_rng = (p_max - p_min), (d_max - d_min), (t_max - t_min) for interp in self.interpreters: # print(p_min, p_rng, d_min, d_rng, t_min, t_rng) interp.change_bounds(p_min, p_rng, d_min, d_rng, t_min, t_rng) # pass sleep(0.5)