Skip to content

Commit

Permalink
added MIDI feedback protection and used in S-1 patch
Browse files Browse the repository at this point in the history
  • Loading branch information
cpmpercussion committed Sep 25, 2024
1 parent 348cff6 commit 18eb5fa
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 3 deletions.
5 changes: 4 additions & 1 deletion configs/roland-s-1.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ timescale = 1
[midi]
in_device = "S-1"
out_device = "S-1"
feedback_protection = true
input = [
["control_change", 3, 13], # osc lfo
["note_on", 3], # notes input
["control_change", 3, 19], # osc square level
["control_change", 3, 20], # osc tri level
["control_change", 3, 21], # osc sub level
Expand All @@ -55,3 +56,5 @@ output = [
# ["control_change", 3, 13], # osc lfo
# ["control_change", 3, 3], # lfo rate
# ["note_on", 3], # notes input

# ["control_change", 3, 13], # osc lfo
23 changes: 21 additions & 2 deletions impsy/impsio.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ class MIDIServer(IOServer):
"""Handles MIDI IO for IMPSY."""


def __init__(self, config, callback, dense_callback) -> None:
def __init__(self, config, callback, dense_callback, feedback_protection=False, feedback_threshold=0.02) -> None:
super().__init__(config, callback, dense_callback)
self.dimension = self.config["model"][
"dimension"
Expand All @@ -423,7 +423,14 @@ def __init__(self, config, callback, dense_callback) -> None:
self.midi_output_mapping = self.config["midi"]["output"]
self.midi_input_mapping = self.config["midi"]["input"]
# self.websocket_send_midi = None # TODO implement some kind generic MIDI callback for other output channels.

self.feedback_protection = feedback_protection
self.feedback_threshold = feedback_threshold # default is 0.02s
# Load feedback protection from config
if "feedback_protection" in self.config["midi"]:
self.feedback_protection = self.config["midi"]["feedback_protection"]
if "feedback_threshold" in self.config["midi"]:
self.feedback_threshold = self.config["midi"]["feedback_threshold"]
self.last_midi_message_time = datetime.datetime.now()

def send(self, output_values) -> None:
"""Sends sound commands via MIDI"""
Expand All @@ -442,13 +449,25 @@ def send(self, output_values) -> None:
# store last midi note if it was a note_on.
if msg.type == 'note_on':
self.last_midi_notes[msg.channel] = msg.note
self.last_midi_message_time = datetime.datetime.now()


def handle(self) -> None:
"""Handle MIDI input messages that might come from mido"""
if self.midi_in_port is None:
return # fail early if MIDI not open.
for message in self.midi_in_port.iter_pending():
if message.type in ["clock", "sysex"]:
# ignore these message types.
continue
if self.feedback_protection:
time_since_last_output = (datetime.datetime.now() - self.last_midi_message_time).total_seconds()
if time_since_last_output < self.feedback_threshold:
if message.type == "note_on" and message.note == self.last_midi_notes[message.channel]:
# click.secho(f"MIDI feedback detected: {time_since_last_output}s, {message}", fg="red")
# detected a feedback message (same note as last output in a short time) so skip this message
continue

try:
index, value = midi_message_to_index_value(message, self.midi_input_mapping)
self.callback(index, value)
Expand Down

0 comments on commit 18eb5fa

Please sign in to comment.