Skip to content

Commit 02b23f0

Browse files
committed
#20 additionnal enhancements to behavior of MIDI playback on Mac
1 parent 95bc810 commit 02b23f0

File tree

3 files changed

+80
-39
lines changed

3 files changed

+80
-39
lines changed

easy_abc.py

+38-24
Original file line numberDiff line numberDiff line change
@@ -4143,7 +4143,7 @@ def __init__(self, parent, ID, app_dir, settings, options):
41434143

41444144
self.play_timer = wx.Timer(self)
41454145
self.Bind(wx.EVT_TIMER, self.OnPlayTimer, self.play_timer)
4146-
self.play_timer.Start(50) #FAU:MIDIPLAY: Todo need to check if this is still needed
4146+
self.play_timer.Start(50)
41474147
self.music_update_thread.start()
41484148
self.update_multi_tunes_menu_items()
41494149

@@ -4492,12 +4492,15 @@ def OnMouseWheel(self, evt):
44924492
evt.Skip()
44934493

44944494
def play(self):
4495+
self.play_timer.Start(50)
44954496
if self.settings.get('follow_score', False) and self.current_page_index != 0:
44964497
self.select_page(0)
44974498
wx.CallAfter(self.mc.Play)
44984499

44994500
def stop_playing(self):
45004501
self.mc.Stop()
4502+
#FAU:remove highlighted notes
4503+
self.music_pane.draw_notes_highlighted(None)
45014504
#FAU:MIDIPLAY: play timer can be stopped no need to update progress slider
45024505
self.play_timer.Stop()
45034506
self.play_button.SetBitmap(self.play_bitmap)
@@ -4573,11 +4576,12 @@ def do_load_media_file(self, path):
45734576
self.mc.Play() # does not start playing but triggers OnMediaLoaded event
45744577
#FAU:MIDIPLAY: added support for playback for Mac with SMF player For now kept apart from Windows
45754578
#FAU:MIDIPLAY: %%TODO%% verify if can be merged with preceeding if
4576-
elif wx.Platform == "__WXMAC__":
4577-
self.mc.Play()
4579+
#FAU:MIDIPLAY: 20250125 Not needed as correctly started based on OnMediaLoaded
4580+
#elif wx.Platform == "__WXMAC__":
4581+
# self.mc.Play()
45784582
#FAU:MIDIPLAY: Start timer to be able to have progress bar updated
4579-
self.play_timer.Start(20)
4580-
self.play_button.SetBitmap(self.pause_bitmap)
4583+
# self.play_timer.Start(20)
4584+
# self.play_button.SetBitmap(self.pause_bitmap)
45814585
else:
45824586
wx.MessageBox(_("Unable to load %s: Unsupported format?") % path,
45834587
_("Error"), wx.ICON_ERROR | wx.OK)
@@ -4588,7 +4592,7 @@ def play():
45884592
# time.sleep(0.3) # 1.3.6.4 [JWDJ] on Mac the first note is skipped the first time. hope this helps
45894593
# self.mc.Seek(self.play_start_offset, wx.FromStart)
45904594
self.play_button.SetBitmap(self.pause_bitmap)
4591-
self.progress_slider.SetRange(0, self.mc.Length())
4595+
self.progress_slider.SetRange(0, int(self.mc.Length())) #FAU:MIDIPLAY: mplay might return a float. thus forcing an int
45924596
self.progress_slider.SetValue(0)
45934597
self.OnBpmSlider(None)
45944598
self.update_playback_rate()
@@ -4600,10 +4604,16 @@ def play():
46004604
wx.CallAfter(play)
46014605

46024606
def OnAfterStop(self):
4607+
self.set_loop_midi_playback(False)
46034608
# 1.3.6.3 [SS] 2015-05-04
46044609
self.stop_playing()
4605-
self.reset_BpmSlider()
4606-
self.flip_tempobox(False)
4610+
#FAU preserve latest bpm choice
4611+
#self.reset_BpmSlider()
4612+
#FAU20250125: Do not hide it if supported
4613+
if self.settings['midiplayer_path']:
4614+
self.flip_tempobox(False)
4615+
if wx.Platform != "__WXMSW__":
4616+
self.toolbar.Realize() # 1.3.6.4 [JWDJ] fixes toolbar repaint bug for Windows
46074617

46084618
def OnToolRecord(self, evt):
46094619
if self.record_thread and self.record_thread.is_running:
@@ -4622,22 +4632,24 @@ def OnToolRecord(self, evt):
46224632
self.record_thread.start()
46234633

46244634
def OnToolStop(self, evt):
4625-
self.set_loop_midi_playback(False)
4626-
self.stop_playing()
4635+
#FAU 20250125: Cleaning, trying to centralised what is common to Stop avoiding of mon to Stop instead of multiple call
4636+
self.OnAfterStop()
4637+
#self.set_loop_midi_playback(False)
4638+
#self.stop_playing()
46274639
# 1.3.6.3 [SS] 2015-04-03
46284640
#self.play_panel.Show(False)
4629-
self.flip_tempobox(False)
4630-
self.progress_slider.SetValue(0)
4641+
#self.flip_tempobox(False)
4642+
#self.progress_slider.SetValue(0)
46314643
# self.reset_BpmSlider() #[EPO] 2018-11-20 make sticky - this is new functionality
4632-
if wx.Platform != "__WXMSW__":
4633-
self.toolbar.Realize() # 1.3.6.4 [JWDJ] fixes toolbar repaint bug for Windows
4644+
#if wx.Platform != "__WXMSW__":
4645+
# self.toolbar.Realize() # 1.3.6.4 [JWDJ] fixes toolbar repaint bug for Windows
46344646
if self.record_thread and self.record_thread.is_running:
46354647
self.OnToolRecord(None)
4636-
if self.uses_fluidsynth:
4637-
self.OnAfterStop()
4648+
#if self.uses_fluidsynth:
4649+
# self.OnAfterStop()
46384650

46394651
def OnSeek(self, evt):
4640-
self.mc.Seek(self.progress_slider.GetValue()) #FAU:MIDIPLAY: %%TODO%% verify on Mac if strange behavior still present
4652+
self.mc.Seek(self.progress_slider.GetValue())
46414653

46424654
def OnZoomSlider(self, evt):
46434655
old_factor = self.zoom_factor
@@ -4660,26 +4672,28 @@ def OnPlayTimer(self, evt):
46604672

46614673
if wx.Platform == "__WXMAC__": #FAU:MIDIPLAY: Used to give the hand to MIDI player
46624674
delta = self.mc.IdlePlay()
4675+
#print(self.mc.get_songinfo)
46634676
if delta == 0:
46644677
if self.loop_midi_playback:
46654678
self.mc.Seek(0)
46664679
else:
4667-
self.stop_playing()
4668-
4680+
self.mc.is_play_started = False
4681+
46694682
offset = self.mc.Tell()
46704683
if offset >= self.progress_slider.Max:
46714684
length = self.mc.Length()
46724685
self.progress_slider.SetRange(0, int(length)) #FAU:MIDIPLAY: mplay might return a float. thus forcing an int
4673-
4686+
46744687
if self.settings.get('follow_score', False):
46754688
self.queue_number_follow_score += 1
46764689
queue_number = self.queue_number_follow_score
4677-
wx.CallLater(1, self.FollowScore, offset, queue_number) #[EPO] 2018-11-20 first arg 0 causes exception
4678-
4690+
#wx.CallLater(1, self.FollowScore, offset, queue_number) #[EPO] 2018-11-20 first arg 0 causes exception
4691+
self.FollowScore(offset, queue_number)
4692+
46794693
self.progress_slider.SetValue(offset)
4680-
elif self.started_playing and self.uses_fluidsynth and not self.mc.is_paused:
4694+
elif self.started_playing and not self.mc.is_paused: #and self.uses_fluidsynth
46814695
self.started_playing = False
4682-
self.OnToolStop(None)
4696+
wx.CallLater(500, self.OnAfterStop)
46834697

46844698
def FollowScore(self, offset, queue_number):
46854699
if self.queue_number_follow_score != queue_number:

mplay/smf_easyabc.py

+22-9
Original file line numberDiff line numberDiff line change
@@ -345,11 +345,10 @@ def read(self, path):
345345
for ev in self.ev:
346346
(at, message, me_type, data) = ev
347347
if message == 0xff and me_type == 0x51:
348-
self.playing_time += (at - start) / self.division * \
349-
tempo / 1000
348+
self.playing_time += (at - start) * tempo / self.division / 1000
350349
start = at
351350
tempo = (data[0] << 16) | (data[1] << 8) | data[2]
352-
self.playing_time += (at - start) / self.division * tempo / 1000
351+
self.playing_time += (at - start) * tempo / self.division / 1000
353352

354353
def fileinfo(self):
355354
hsecs = self.playing_time // 10
@@ -392,7 +391,7 @@ def getsongposition(self):
392391
else:
393392
now = (time() - self.elapsed_time) * 1000
394393
ticks = int(now * self.division * 1000 / self.tempo)
395-
return ticks
394+
return int(ticks)
396395

397396
def beatinfo(self):
398397
if self.pause != 0:
@@ -461,6 +460,15 @@ def songposition(self, beat):
461460
break
462461
self.next += 1
463462

463+
def gotosongposition(self, song_position):
464+
#Need to turn off all currently note played otherwise will keep sounding
465+
for ch in range(16):
466+
self.allnotesoff(ch)
467+
self.songposition(song_position)
468+
if self.pause != 0:
469+
self.pause = time()
470+
471+
464472
def setsong(self, **info):
465473
#print("FAU setsong")
466474
if 'shift' in info:
@@ -484,15 +492,13 @@ def setsong(self, **info):
484492
self.tempo = 60000000 / self.bpm #* 4 / self.denominator
485493
self.elapsed_time = now - (now - self.elapsed_time) * \
486494
self.tempo / tempo
495+
elif 'goto' in info:
496+
self.gotosongposition(info['goto']/self.division)
487497
elif 'bar' in info:
488498
now = (time() - self.elapsed_time) * 1000
489499
beat = int(now * 1000 / self.tempo)
490500
beat += 4 * info['bar'] - (beat % 4)
491-
for ch in range(16):
492-
self.allnotesoff(ch)
493-
self.songposition(beat)
494-
if self.pause != 0:
495-
self.pause = time()
501+
self.gotosongposition(beat)
496502
elif 'action' in info:
497503
if info['action'] == 'exit':
498504
for ch in range(16):
@@ -668,6 +674,13 @@ def read(path):
668674
def play(midi, dev, wait=True):
669675
return midi.play(dev, wait)
670676

677+
#FAU20250125: Added to seek to a position
678+
#songposition (time-elapsed_time)*1000*division*1000/tempo
679+
#beat (time-elapsedtime)*1000*1000/tempo
680+
#playing_time (at_fin - at_start) / self.division * tempo / 1000
681+
682+
def songposition(midi, song_position):
683+
return midi.songposition(song_position/midi.division)
671684

672685
def fileinfo(midi):
673686
return midi.fileinfo()

mplaysmfplayer.py

+20-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from midiplayer import MidiPlayer
1616
from mplay.smf_easyabc import read, play, fileinfo, songinfo, beatinfo, lyrics, chordinfo, \
1717
setsong, channelinfo, setchannel, families, instruments
18+
setsong, channelinfo, setchannel, families, instruments, songposition
1819

1920
if sys.platform == 'darwin':
2021
from mplay.darwinmidi import midiDevice
@@ -54,6 +55,8 @@ def __init__(self, parent_window=None, backend=None):
5455
def Load(self, path):
5556
if os.path.exists(path):
5657
self.midi_file = read(path)
58+
#FAU:MIDIPLAY: 20250125 Add the call to the EasyABC event
59+
self.OnAfterLoad.fire()
5760
return True
5861
return False
5962

@@ -70,9 +73,14 @@ def Play(self):
7073

7174
#FAU 20201229: Function is added to allow playing on timer Play() is used to keep same interface for EasyABC for the pause feature
7275
def IdlePlay(self):
73-
delta = play(self.midi_file, self.device, False)
74-
self.is_really_playing = True
75-
return delta
76+
if self.is_playing:
77+
delta = play(self.midi_file, self.device, False)
78+
self.is_really_playing = True
79+
#if delta == 0:
80+
# self.OnAfterStop.fire()
81+
return delta
82+
else:
83+
return 0.04
7684

7785
def Pause(self):
7886
setsong(self.midi_file, action='pause')
@@ -88,10 +96,10 @@ def Stop(self):
8896
def Seek(self, time):
8997
if time > self.Length() or time < 0:
9098
return
91-
self.midi_file.songposition(time)
92-
99+
setsong(self.midi_file, goto=time)
100+
93101
def Length(self):
94-
return self.midi_file.playing_time
102+
return self.midi_file.playing_time-960
95103

96104
def Tell(self):
97105
return self.midi_file.getsongposition()
@@ -121,5 +129,11 @@ def supports_tempo_change_while_playing(self):
121129
@property
122130
def unit_is_midi_tick(self):
123131
return True
132+
133+
@property
134+
def get_songinfo(self):
135+
return songinfo(self.midi_file)
136+
137+
124138

125139

0 commit comments

Comments
 (0)