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

Add accurate midi timestamps #477

Merged
merged 6 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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