Skip to content

Commit 7f1578a

Browse files
authored
Merge pull request #477 from stephen322/midi_reorg
Add accurate midi timestamps
2 parents e21428f + b2b9656 commit 7f1578a

File tree

7 files changed

+103
-101
lines changed

7 files changed

+103
-101
lines changed

lib/color_mode.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def LoadSettings(self, ledsettings):
4040
"""Called whenever settings change"""
4141
pass
4242

43-
def NoteOn(self, midi_event, midi_state, note_position):
43+
def NoteOn(self, midi_event, midi_time, midi_state, note_position):
4444
"""Primary high-level function for ColorMode
4545
4646
Called on midi note-on
@@ -74,7 +74,7 @@ def LoadSettings(self, ledsettings):
7474
self.green = ledsettings.get_color("Green")
7575
self.blue = ledsettings.get_color("Blue")
7676

77-
def NoteOn(self, midi_event: mido.Message, midi_state, note_position):
77+
def NoteOn(self, midi_event: mido.Message, midi_time, midi_state, note_position):
7878
return (self.red, self.green, self.blue)
7979

8080

@@ -85,7 +85,7 @@ def LoadSettings(self, ledsettings):
8585
self.multicolor_index = 0
8686
self.multicolor_iteration = ledsettings.multicolor_iteration
8787

88-
def NoteOn(self, midi_event: mido.Message, midi_state, note_position):
88+
def NoteOn(self, midi_event: mido.Message, midi_time, midi_state, note_position):
8989
chosen_color = self.get_random_multicolor_in_range(midi_event.note)
9090
return chosen_color
9191

@@ -141,7 +141,7 @@ def LoadSettings(self, ledsettings):
141141
self.timeshift = int(ledsettings.rainbow_timeshift)
142142
self.timeshift_start = time.time()
143143

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

@@ -165,7 +165,7 @@ def LoadSettings(self, ledsettings):
165165
self.speed_period_in_seconds = ledsettings.speed_period_in_seconds
166166
self.speed_max_notes = ledsettings.speed_max_notes
167167

168-
def NoteOn(self, midi_event: mido.Message, midi_state, note_position):
168+
def NoteOn(self, midi_event: mido.Message, midi_time, midi_state, note_position):
169169
current_time = time.time()
170170
self.notes_in_last_period.append(current_time)
171171
return self.speed_get_colors()
@@ -204,7 +204,7 @@ def LoadSettings(self, ledsettings):
204204
"green": int(ledsettings.usersettings.get_setting_value("gradient_end_green")),
205205
"blue": int(ledsettings.usersettings.get_setting_value("gradient_end_blue"))}
206206

207-
def NoteOn(self, midi_event: mido.Message, midi_state, note_position):
207+
def NoteOn(self, midi_event: mido.Message, midi_time, midi_state, note_position):
208208
return self.gradient_get_colors(note_position)
209209

210210
def gradient_get_colors(self, position):
@@ -224,7 +224,7 @@ def LoadSettings(self, ledsettings):
224224
self.key_in_scale = ledsettings.key_in_scale
225225
self.key_not_in_scale = ledsettings.key_not_in_scale
226226

227-
def NoteOn(self, midi_event: mido.Message, midi_state, note_position):
227+
def NoteOn(self, midi_event: mido.Message, midi_time, midi_state, note_position):
228228
scale_colors = get_scale_color(self.scale_key, midi_event.note, self.key_in_scale, self.key_not_in_scale)
229229
return scale_colors
230230

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

238-
def NoteOn(self, midi_event: mido.Message, midi_state, note_position=None):
238+
def NoteOn(self, midi_event: mido.Message, midi_time, midi_state, note_position):
239239
x = int(((255 * powercurve(midi_event.velocity / 127, self.curve / 100)
240240
* (self.scale / 100) % 256) + self.offset) % 256)
241241
x2 = colorsys.hsv_to_rgb(x / 255, 1, (midi_event.velocity / 127) * 0.3 + 0.7)

lib/functions.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def shift(lst, num_shifts):
5959

6060

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

6464
if song_path in saving.is_playing_midi.keys():
6565
menu.render_message(song_path, "Already playing", 2000)
@@ -81,10 +81,10 @@ def play_midi(song_path, midiports, saving, menu, ledsettings, ledstrip):
8181
for message in mid:
8282
if song_path in saving.is_playing_midi.keys():
8383
if not t0:
84-
t0 = time.time()
84+
t0 = time.perf_counter()
8585

8686
total_delay += message.time
87-
current_time = (time.time() - t0) + message.time
87+
current_time = (time.perf_counter() - t0) + message.time
8888
drift = total_delay - current_time
8989

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

97+
msg_timestamp = time.perf_counter() + delay
9798
if delay > 0:
9899
time.sleep(delay)
99100
if not message.is_meta:
100101
midiports.playport.send(message)
101-
midiports.pending_queue.append(message.copy(time=0))
102+
midiports.midifile_queue.append((message.copy(time=0), msg_timestamp))
102103

103104
else:
104-
midiports.pending_queue.clear()
105+
midiports.midifile_queue.clear()
105106
strip = ledstrip.strip
106107
fastColorWipe(strip, True, ledsettings)
107108
break
108-
print('play time: {:.2f} s (expected {:.2f})'.format(time.time() - t0, total_delay))
109-
# print('play time: {:.2f} s (expected {:.2f})'.format(time.time() - t0, length))
109+
print('play time: {:.2f} s (expected {:.2f})'.format(time.perf_counter() - t0, total_delay))
110+
# print('play time: {:.2f} s (expected {:.2f})'.format(time.perf_counter() - t0, length))
110111
# saving.is_playing_midi = False
111112
except FileNotFoundError:
112113
menu.render_message(song_path, "File not found", 2000)
@@ -201,14 +202,14 @@ def screensaver(menu, midiports, saving, ledstrip, ledsettings):
201202
while True:
202203
manage_idle_animation(ledstrip, ledsettings, menu)
203204

204-
if (time.time() - saving.start_time) > 3600 and delay < 0.5 and menu.screensaver_is_running is False:
205+
if (time.perf_counter() - saving.start_time) > 3600 and delay < 0.5 and menu.screensaver_is_running is False:
205206
delay = 0.9
206207
interval = 5 / float(delay)
207208
cpu_history = [None] * int(interval)
208209
cpu_average = 0
209210
i = 0
210211

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

@@ -276,7 +277,7 @@ def screensaver(menu, midiports, saving, ledstrip, ledsettings):
276277
try:
277278
if str(midiports.inport.poll()) != "None":
278279
menu.screensaver_is_running = False
279-
saving.start_time = time.time()
280+
saving.start_time = time.perf_counter()
280281
menu.screen_status = 1
281282
GPIO.output(24, 1)
282283
midiports.reconnect_ports()
@@ -287,7 +288,7 @@ def screensaver(menu, midiports, saving, ledstrip, ledsettings):
287288
pass
288289
if GPIO.input(KEY2) == 0:
289290
menu.screensaver_is_running = False
290-
saving.start_time = time.time()
291+
saving.start_time = time.perf_counter()
291292
menu.screen_status = 1
292293
GPIO.output(24, 1)
293294
midiports.reconnect_ports()

lib/learnmidi.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,11 @@ def learn_midi(self):
350350
wrong_notes = []
351351
self.predict_future_notes(absolute_idx, end_idx, notes_to_press)
352352
while not set(notes_to_press).issubset(notes_pressed) and self.is_started_midi:
353-
for msg_in in self.midiports.inport.iter_pending():
353+
while self.midiports.midi_queue:
354+
msg_in, msg_timestamp = self.midiports.midi_queue.popleft()
355+
if msg_in.type not in ("note_on", "note_off"):
356+
continue
357+
354358
note = int(find_between(str(msg_in), "note=", " "))
355359

356360
if "note_off" in str(msg_in):

lib/ledstrip.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ def __init__(self, usersettings, ledsettings):
3636
self.LED_BRIGHTNESS, self.LED_CHANNEL, ws.WS2811_STRIP_GRB)
3737
# Intialize the library (must be called once before other functions).
3838
self.strip.begin()
39+
if "releaseGIL" in dir(self.strip):
40+
self.strip.releaseGIL()
3941
self.change_gamma(self.led_gamma)
4042

4143
def change_gamma(self, value):

lib/midiports.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
import mido
22
from lib import connectall
33
import time
4+
from collections import deque
45

56
class MidiPorts:
67
def __init__(self, usersettings):
78
self.usersettings = usersettings
8-
self.pending_queue = []
9+
# midi queues will contain a tuple (midi_msg, timestamp)
10+
self.midifile_queue = deque()
11+
self.midi_queue = deque()
912
self.last_activity = 0
1013
self.inport = None
1114
self.playport = None
12-
self.midipending = []
15+
self.midipending = None
1316

1417
# checking if the input port was previously set by the user
1518
port = self.usersettings.get_setting_value("input_port")
1619
if port != "default":
1720
try:
18-
self.inport = mido.open_input(port)
21+
self.inport = mido.open_input(port, callback=self.msg_callback)
1922
print("Inport loaded and set to " + port)
2023
except:
2124
print("Can't load input port: " + port)
@@ -24,7 +27,7 @@ def __init__(self, usersettings):
2427
try:
2528
for port in mido.get_input_names():
2629
if "Through" not in port and "RPi" not in port and "RtMidOut" not in port and "USB-USB" not in port:
27-
self.inport = mido.open_input(port)
30+
self.inport = mido.open_input(port, callback=self.msg_callback)
2831
self.usersettings.change_setting_value("input_port", port)
2932
print("Inport set to " + port)
3033
break
@@ -66,7 +69,7 @@ def change_port(self, port, portname):
6669
destroy_old = None
6770
if port == "inport":
6871
destory_old = self.inport
69-
self.inport = mido.open_input(portname)
72+
self.inport = mido.open_input(portname, callback=self.msg_callback)
7073
self.usersettings.change_setting_value("input_port", portname)
7174
elif port == "playport":
7275
destory_old = self.playport
@@ -84,7 +87,7 @@ def reconnect_ports(self):
8487
try:
8588
destroy_old = self.inport
8689
port = self.usersettings.get_setting_value("input_port")
87-
self.inport = mido.open_input(port)
90+
self.inport = mido.open_input(port, callback=self.msg_callback)
8891
if destroy_old is not None:
8992
time.sleep(0.002)
9093
destroy_old.close()
@@ -99,3 +102,6 @@ def reconnect_ports(self):
99102
destroy_old.close()
100103
except:
101104
print("Can't reconnect play port: " + port)
105+
106+
def msg_callback(self, msg):
107+
self.midi_queue.append((msg, time.perf_counter()))

lib/savemidi.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def __init__(self):
1313
self.menu = None
1414
self.is_recording = False
1515
self.is_playing_midi = {}
16-
self.start_time = time.time()
16+
self.start_time = time.perf_counter()
1717

1818
def add_instance(self, menu):
1919
self.menu = menu
@@ -54,12 +54,9 @@ def save(self, filename):
5454
self.mid = MidiFile(None, None, 0, 20000) # 20000 is a ticks_per_beat value
5555
self.track = MidiTrack()
5656
self.mid.tracks.append(self.track)
57-
previous_message_time = None
57+
previous_message_time = self.first_note_time
5858
for message in multicolor_track:
59-
if previous_message_time is not None:
60-
time_delay = message[1] - previous_message_time
61-
else:
62-
time_delay = 0
59+
time_delay = message[1] - previous_message_time
6360
previous_message_time = message[1]
6461

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

8077
def restart_time(self):
81-
self.start_time = time.time()
78+
self.start_time = time.perf_counter()

0 commit comments

Comments
 (0)