Skip to content

Commit

Permalink
Merge pull request #477 from stephen322/midi_reorg
Browse files Browse the repository at this point in the history
Add accurate midi timestamps
  • Loading branch information
onlaj authored Oct 16, 2023
2 parents e21428f + b2b9656 commit 7f1578a
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 101 deletions.
16 changes: 8 additions & 8 deletions lib/color_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def LoadSettings(self, ledsettings):
"""Called whenever settings change"""
pass

def NoteOn(self, midi_event, midi_state, note_position):
def NoteOn(self, midi_event, midi_time, midi_state, note_position):
"""Primary high-level function for ColorMode
Called on midi note-on
Expand Down Expand Up @@ -74,7 +74,7 @@ def LoadSettings(self, ledsettings):
self.green = ledsettings.get_color("Green")
self.blue = ledsettings.get_color("Blue")

def NoteOn(self, midi_event: mido.Message, midi_state, note_position):
def NoteOn(self, midi_event: mido.Message, midi_time, midi_state, note_position):
return (self.red, self.green, self.blue)


Expand All @@ -85,7 +85,7 @@ def LoadSettings(self, ledsettings):
self.multicolor_index = 0
self.multicolor_iteration = ledsettings.multicolor_iteration

def NoteOn(self, midi_event: mido.Message, midi_state, note_position):
def NoteOn(self, midi_event: mido.Message, midi_time, midi_state, note_position):
chosen_color = self.get_random_multicolor_in_range(midi_event.note)
return chosen_color

Expand Down Expand Up @@ -141,7 +141,7 @@ def LoadSettings(self, ledsettings):
self.timeshift = int(ledsettings.rainbow_timeshift)
self.timeshift_start = time.time()

def NoteOn(self, midi_event: mido.Message, midi_state, note_position):
def NoteOn(self, midi_event: mido.Message, midi_time, midi_state, note_position):
shift = (time.time() - self.timeshift_start) * self.timeshift
return self.calculate_rainbow_colors(note_position, shift)

Expand All @@ -165,7 +165,7 @@ def LoadSettings(self, ledsettings):
self.speed_period_in_seconds = ledsettings.speed_period_in_seconds
self.speed_max_notes = ledsettings.speed_max_notes

def NoteOn(self, midi_event: mido.Message, midi_state, note_position):
def NoteOn(self, midi_event: mido.Message, midi_time, midi_state, note_position):
current_time = time.time()
self.notes_in_last_period.append(current_time)
return self.speed_get_colors()
Expand Down Expand Up @@ -204,7 +204,7 @@ def LoadSettings(self, ledsettings):
"green": int(ledsettings.usersettings.get_setting_value("gradient_end_green")),
"blue": int(ledsettings.usersettings.get_setting_value("gradient_end_blue"))}

def NoteOn(self, midi_event: mido.Message, midi_state, note_position):
def NoteOn(self, midi_event: mido.Message, midi_time, midi_state, note_position):
return self.gradient_get_colors(note_position)

def gradient_get_colors(self, position):
Expand All @@ -224,7 +224,7 @@ def LoadSettings(self, ledsettings):
self.key_in_scale = ledsettings.key_in_scale
self.key_not_in_scale = ledsettings.key_not_in_scale

def NoteOn(self, midi_event: mido.Message, midi_state, note_position):
def NoteOn(self, midi_event: mido.Message, midi_time, midi_state, note_position):
scale_colors = get_scale_color(self.scale_key, midi_event.note, self.key_in_scale, self.key_not_in_scale)
return scale_colors

Expand All @@ -235,7 +235,7 @@ def LoadSettings(self, ledsettings):
self.scale = int(ledsettings.velocityrainbow_scale)
self.curve = int(ledsettings.velocityrainbow_curve)

def NoteOn(self, midi_event: mido.Message, midi_state, note_position=None):
def NoteOn(self, midi_event: mido.Message, midi_time, midi_state, note_position):
x = int(((255 * powercurve(midi_event.velocity / 127, self.curve / 100)
* (self.scale / 100) % 256) + self.offset) % 256)
x2 = colorsys.hsv_to_rgb(x / 255, 1, (midi_event.velocity / 127) * 0.3 + 0.7)
Expand Down
23 changes: 12 additions & 11 deletions lib/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def shift(lst, num_shifts):


def play_midi(song_path, midiports, saving, menu, ledsettings, ledstrip):
midiports.pending_queue.append(mido.Message('note_on'))
midiports.midifile_queue.append((mido.Message('note_on'), time.perf_counter()))

if song_path in saving.is_playing_midi.keys():
menu.render_message(song_path, "Already playing", 2000)
Expand All @@ -81,10 +81,10 @@ def play_midi(song_path, midiports, saving, menu, ledsettings, ledstrip):
for message in mid:
if song_path in saving.is_playing_midi.keys():
if not t0:
t0 = time.time()
t0 = time.perf_counter()

total_delay += message.time
current_time = (time.time() - t0) + message.time
current_time = (time.perf_counter() - t0) + message.time
drift = total_delay - current_time

if drift < 0:
Expand All @@ -94,19 +94,20 @@ def play_midi(song_path, midiports, saving, menu, ledsettings, ledstrip):
if delay < 0:
delay = 0

msg_timestamp = time.perf_counter() + delay
if delay > 0:
time.sleep(delay)
if not message.is_meta:
midiports.playport.send(message)
midiports.pending_queue.append(message.copy(time=0))
midiports.midifile_queue.append((message.copy(time=0), msg_timestamp))

else:
midiports.pending_queue.clear()
midiports.midifile_queue.clear()
strip = ledstrip.strip
fastColorWipe(strip, True, ledsettings)
break
print('play time: {:.2f} s (expected {:.2f})'.format(time.time() - t0, total_delay))
# print('play time: {:.2f} s (expected {:.2f})'.format(time.time() - t0, length))
print('play time: {:.2f} s (expected {:.2f})'.format(time.perf_counter() - t0, total_delay))
# print('play time: {:.2f} s (expected {:.2f})'.format(time.perf_counter() - t0, length))
# saving.is_playing_midi = False
except FileNotFoundError:
menu.render_message(song_path, "File not found", 2000)
Expand Down Expand Up @@ -201,14 +202,14 @@ def screensaver(menu, midiports, saving, ledstrip, ledsettings):
while True:
manage_idle_animation(ledstrip, ledsettings, menu)

if (time.time() - saving.start_time) > 3600 and delay < 0.5 and menu.screensaver_is_running is False:
if (time.perf_counter() - saving.start_time) > 3600 and delay < 0.5 and menu.screensaver_is_running is False:
delay = 0.9
interval = 5 / float(delay)
cpu_history = [None] * int(interval)
cpu_average = 0
i = 0

if int(menu.screen_off_delay) > 0 and ((time.time() - saving.start_time) > (int(menu.screen_off_delay) * 60)):
if int(menu.screen_off_delay) > 0 and ((time.perf_counter() - saving.start_time) > (int(menu.screen_off_delay) * 60)):
menu.screen_status = 0
GPIO.output(24, 0)

Expand Down Expand Up @@ -276,7 +277,7 @@ def screensaver(menu, midiports, saving, ledstrip, ledsettings):
try:
if str(midiports.inport.poll()) != "None":
menu.screensaver_is_running = False
saving.start_time = time.time()
saving.start_time = time.perf_counter()
menu.screen_status = 1
GPIO.output(24, 1)
midiports.reconnect_ports()
Expand All @@ -287,7 +288,7 @@ def screensaver(menu, midiports, saving, ledstrip, ledsettings):
pass
if GPIO.input(KEY2) == 0:
menu.screensaver_is_running = False
saving.start_time = time.time()
saving.start_time = time.perf_counter()
menu.screen_status = 1
GPIO.output(24, 1)
midiports.reconnect_ports()
Expand Down
6 changes: 5 additions & 1 deletion lib/learnmidi.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,11 @@ def learn_midi(self):
wrong_notes = []
self.predict_future_notes(absolute_idx, end_idx, notes_to_press)
while not set(notes_to_press).issubset(notes_pressed) and self.is_started_midi:
for msg_in in self.midiports.inport.iter_pending():
while self.midiports.midi_queue:
msg_in, msg_timestamp = self.midiports.midi_queue.popleft()
if msg_in.type not in ("note_on", "note_off"):
continue

note = int(find_between(str(msg_in), "note=", " "))

if "note_off" in str(msg_in):
Expand Down
2 changes: 2 additions & 0 deletions lib/ledstrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ def __init__(self, usersettings, ledsettings):
self.LED_BRIGHTNESS, self.LED_CHANNEL, ws.WS2811_STRIP_GRB)
# Intialize the library (must be called once before other functions).
self.strip.begin()
if "releaseGIL" in dir(self.strip):
self.strip.releaseGIL()
self.change_gamma(self.led_gamma)

def change_gamma(self, value):
Expand Down
18 changes: 12 additions & 6 deletions lib/midiports.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import mido
from lib import connectall
import time
from collections import deque

class MidiPorts:
def __init__(self, usersettings):
self.usersettings = usersettings
self.pending_queue = []
# midi queues will contain a tuple (midi_msg, timestamp)
self.midifile_queue = deque()
self.midi_queue = deque()
self.last_activity = 0
self.inport = None
self.playport = None
self.midipending = []
self.midipending = None

# checking if the input port was previously set by the user
port = self.usersettings.get_setting_value("input_port")
if port != "default":
try:
self.inport = mido.open_input(port)
self.inport = mido.open_input(port, callback=self.msg_callback)
print("Inport loaded and set to " + port)
except:
print("Can't load input port: " + port)
Expand All @@ -24,7 +27,7 @@ def __init__(self, usersettings):
try:
for port in mido.get_input_names():
if "Through" not in port and "RPi" not in port and "RtMidOut" not in port and "USB-USB" not in port:
self.inport = mido.open_input(port)
self.inport = mido.open_input(port, callback=self.msg_callback)
self.usersettings.change_setting_value("input_port", port)
print("Inport set to " + port)
break
Expand Down Expand Up @@ -66,7 +69,7 @@ def change_port(self, port, portname):
destroy_old = None
if port == "inport":
destory_old = self.inport
self.inport = mido.open_input(portname)
self.inport = mido.open_input(portname, callback=self.msg_callback)
self.usersettings.change_setting_value("input_port", portname)
elif port == "playport":
destory_old = self.playport
Expand All @@ -84,7 +87,7 @@ def reconnect_ports(self):
try:
destroy_old = self.inport
port = self.usersettings.get_setting_value("input_port")
self.inport = mido.open_input(port)
self.inport = mido.open_input(port, callback=self.msg_callback)
if destroy_old is not None:
time.sleep(0.002)
destroy_old.close()
Expand All @@ -99,3 +102,6 @@ def reconnect_ports(self):
destroy_old.close()
except:
print("Can't reconnect play port: " + port)

def msg_callback(self, msg):
self.midi_queue.append((msg, time.perf_counter()))
11 changes: 4 additions & 7 deletions lib/savemidi.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def __init__(self):
self.menu = None
self.is_recording = False
self.is_playing_midi = {}
self.start_time = time.time()
self.start_time = time.perf_counter()

def add_instance(self, menu):
self.menu = menu
Expand Down Expand Up @@ -54,12 +54,9 @@ def save(self, filename):
self.mid = MidiFile(None, None, 0, 20000) # 20000 is a ticks_per_beat value
self.track = MidiTrack()
self.mid.tracks.append(self.track)
previous_message_time = None
previous_message_time = self.first_note_time
for message in multicolor_track:
if previous_message_time is not None:
time_delay = message[1] - previous_message_time
else:
time_delay = 0
time_delay = message[1] - previous_message_time
previous_message_time = message[1]

if message[0] == "note":
Expand All @@ -78,4 +75,4 @@ def save(self, filename):
self.menu.render_message("File saved", filename + ".mid", 1500)

def restart_time(self):
self.start_time = time.time()
self.start_time = time.perf_counter()
Loading

0 comments on commit 7f1578a

Please sign in to comment.