@@ -57,29 +57,30 @@ class AlsaMIDIDevice : public MIDIDevice
5757 int GetTechnology () const override ;
5858 int SetTempo (int tempo) override ;
5959 int SetTimeDiv (int timediv) override ;
60- int StreamOut (MidiHeader * data) override ;
61- int StreamOutSync (MidiHeader * data) override ;
60+ int StreamOut (MidiHeader* data) override ;
61+ int StreamOutSync (MidiHeader* data) override ;
6262 int Resume () override ;
6363 void Stop () override ;
64- bool FakeVolume () override { return true ; }; // Not sure if we even can control the volume this way with Alsa, so make it fake.
64+ bool FakeVolume () override { return true ; }; // Not sure if we even can control the volume this way with Alsa, so make it fake.
6565 bool Pause (bool paused) override ;
6666 void InitPlayback () override ;
6767 bool Update () override ;
68- bool CanHandleSysex () const override { return true ; } // Assume we can, let Alsa sort it out.
69- void PrecacheInstruments (const uint16_t * instruments, int count) override ;
68+ bool CanHandleSysex () const override { return true ; } // Assume we can, let Alsa sort it out.
69+ void PrecacheInstruments (const uint16_t * instruments, int count) override ;
7070
7171protected:
7272 bool PullEvent ();
7373 void PlayerLoop ();
7474 void HandleEvent (snd_seq_event_t &event, uint tick);
7575
7676 AlsaSequencer &sequencer;
77- MidiHeader * Events = nullptr ;
77+ MidiHeader* Events = nullptr ;
7878 snd_seq_event_t Event;
79+ std::array<uint8_t , 3 > ShortMsgBuffer;
7980 snd_midi_event_t * Coder = nullptr ;
8081 uint32_t Position = 0 ;
8182 uint32_t PositionOffset;
82- uint NextEventTickDelta ;
83+ uint32_t CurrentEventTickDelta ;
8384
8485 const static int IntendedPortId = 0 ;
8586 bool Connected = false ;
@@ -91,12 +92,12 @@ class AlsaMIDIDevice : public MIDIDevice
9192 int Technology;
9293 bool Precache;
9394
94- int InitialTempo = 480000 ;
95+ int InitialTempo = 500000 ;
9596 int Tempo;
96- int TimeDiv = 480 ;
97+ int Division = 100 ; // PPQN
9798
9899 std::thread PlayerThread;
99- volatile bool Exit = false ;
100+ volatile bool Exit;
100101 std::mutex Mutex;
101102 std::condition_variable ExitCond;
102103};
@@ -125,7 +126,7 @@ int AlsaMIDIDevice::Open()
125126
126127 if (PortId < 0 )
127128 {
128- snd_seq_port_info_t * pinfo;
129+ snd_seq_port_info_t * pinfo;
129130 snd_seq_port_info_alloca (&pinfo);
130131
131132 snd_seq_port_info_set_port (pinfo, IntendedPortId);
@@ -200,12 +201,12 @@ int AlsaMIDIDevice::SetTempo(int tempo)
200201
201202int AlsaMIDIDevice::SetTimeDiv (int timediv)
202203{
203- TimeDiv = timediv;
204+ Division = timediv;
204205 return 0 ;
205206}
206207
207208// This is meant to mirror WinMIDIDevice::PrecacheInstruments
208- void AlsaMIDIDevice::PrecacheInstruments (const uint16_t * instruments, int count)
209+ void AlsaMIDIDevice::PrecacheInstruments (const uint16_t * instruments, int count)
209210{
210211 // Setting snd_midiprecache to false disables this precaching, since it
211212 // does involve sleeping for more than a miniscule amount of time.
@@ -215,7 +216,6 @@ void AlsaMIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count)
215216 }
216217 uint8_t bank[16 ] = {0 };
217218 uint8_t i, chan;
218- std::array<uint8_t , 3 > message;
219219
220220 for (i = 0 , chan = 0 ; i < count; ++i)
221221 {
@@ -227,29 +227,29 @@ void AlsaMIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count)
227227 {
228228 if (bank[9 ] != banknum)
229229 {
230- message = { MIDI_CTRLCHANGE | 9 , 0 , banknum };
231- snd_midi_event_encode (Coder, message .data (), 3 , &Event);
230+ ShortMsgBuffer = { MIDI_CTRLCHANGE | 9 , 0 , banknum };
231+ snd_midi_event_encode (Coder, ShortMsgBuffer .data (), 3 , &Event);
232232 HandleEvent (Event, 0 );
233233 bank[9 ] = banknum;
234234 }
235- message = { MIDI_NOTEON | 9 , instr, 1 };
236- snd_midi_event_encode (Coder, message .data (), 3 , &Event);
235+ ShortMsgBuffer = { MIDI_NOTEON | 9 , instr, 1 };
236+ snd_midi_event_encode (Coder, ShortMsgBuffer .data (), 3 , &Event);
237237 HandleEvent (Event, 0 );
238238 }
239239 else
240240 { // Melodic
241241 if (bank[chan] != banknum)
242242 {
243- message = { MIDI_CTRLCHANGE | 9 , 0 , banknum };
244- snd_midi_event_encode (Coder, message .data (), 3 , &Event);
243+ ShortMsgBuffer = { MIDI_CTRLCHANGE | 9 , 0 , banknum };
244+ snd_midi_event_encode (Coder, ShortMsgBuffer .data (), 3 , &Event);
245245 HandleEvent (Event, 0 );
246246 bank[chan] = banknum;
247247 }
248- message = { (uint8_t )(MIDI_PRGMCHANGE | chan), (uint8_t )instruments[i] };
249- snd_midi_event_encode (Coder, message .data (), 2 , &Event);
248+ ShortMsgBuffer = { (uint8_t )(MIDI_PRGMCHANGE | chan), (uint8_t )instruments[i] };
249+ snd_midi_event_encode (Coder, ShortMsgBuffer .data (), 2 , &Event);
250250 HandleEvent (Event, 0 );
251- message = { (uint8_t )(MIDI_NOTEON | chan), 60 , 1 };
252- snd_midi_event_encode (Coder, message .data (), 3 , &Event);
251+ ShortMsgBuffer = { (uint8_t )(MIDI_NOTEON | chan), 60 , 1 };
252+ snd_midi_event_encode (Coder, ShortMsgBuffer .data (), 3 , &Event);
253253 HandleEvent (Event, 0 );
254254 if (++chan == 9 )
255255 { // Skip the percussion channel
@@ -265,8 +265,8 @@ void AlsaMIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count)
265265 for (chan = 15 ; chan-- != 0 ; )
266266 {
267267 // Turn all notes off
268- message = { (uint8_t )(MIDI_CTRLCHANGE | chan), 123 , 0 };
269- snd_midi_event_encode (Coder, message .data (), 3 , &Event);
268+ ShortMsgBuffer = { (uint8_t )(MIDI_CTRLCHANGE | chan), 123 , 0 };
269+ snd_midi_event_encode (Coder, ShortMsgBuffer .data (), 3 , &Event);
270270 HandleEvent (Event, 0 );
271271 }
272272 // And now chan is back at 0, ready to start the cycle over.
@@ -277,13 +277,11 @@ void AlsaMIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count)
277277 {
278278 if (bank[i] != 0 )
279279 {
280- message = { MIDI_CTRLCHANGE | 9 , 0 , 0 };
281- snd_midi_event_encode (Coder, message .data (), 3 , &Event);
280+ ShortMsgBuffer = { MIDI_CTRLCHANGE | 9 , 0 , 0 };
281+ snd_midi_event_encode (Coder, ShortMsgBuffer .data (), 3 , &Event);
282282 HandleEvent (Event, 0 );
283283 }
284284 }
285- // Wait until all events are processed
286- snd_seq_sync_output_queue (sequencer.handle );
287285}
288286
289287bool AlsaMIDIDevice::PullEvent ()
@@ -299,10 +297,10 @@ bool AlsaMIDIDevice::PullEvent()
299297 }
300298
301299 if (Position >= Events->dwBytesRecorded )
302- { // All events in the "Events" buffer were used, point to next buffer
300+ { // All events in the buffer were used, point to next buffer
303301 Events = Events->lpNext ;
304302 Position = 0 ;
305- if (Callback != NULL )
303+ if (Callback)
306304 { // This ensures that we always have 2 unused buffers after 1 is used up.
307305 // omit this nested "if" block if you want to use up the 2 buffers before requesting new buffers
308306 Callback (CallbackData);
@@ -314,51 +312,46 @@ bool AlsaMIDIDevice::PullEvent()
314312 return false ;
315313 }
316314
317- uint32_t * event = (uint32_t *)(Events->lpData + Position);
318- NextEventTickDelta = event[0 ];
315+ uint32_t * event = (uint32_t *)(Events->lpData + Position);
316+ CurrentEventTickDelta = event[0 ]; // First 4 bytes of event
319317
320318 // Get event size to advance Position
321- if (event[2 ] < 0x80000000 )
322- { // Short message
323- PositionOffset = 12 ;
319+ if (event[2 ] < 0x80000000 ) // Short message (event[2] is the combined status/data bytes)
320+ {
321+ PositionOffset = 12 ; // 4 bytes delta time, 4 bytes reserved, 4 bytes MIDI message (up to 3 bytes + padding)
324322 }
325- else
326- { // Long message
323+ else // Long message or meta-event (event[2] holds type and parameter length)
324+ {
327325 PositionOffset = 12 + ((MEVENT_EVENTPARM (event[2 ]) + 3 ) & ~3 );
328326 }
329327
330328 // Pulling event out of buffer
331329 switch (MEVENT_EVENTTYPE (event[2 ]))
332330 {
333331 case MEVENT_TEMPO:
334- {
335- int tempo = MEVENT_EVENTPARM (event[2 ]);
336- snd_seq_ev_set_queue_tempo (&Event, QueueId, tempo);
332+ snd_seq_ev_set_queue_tempo (&Event, QueueId, MEVENT_EVENTPARM (event[2 ]));
337333 break ;
338- }
339- case MEVENT_LONGMSG: // SysEx messages...
334+ case MEVENT_LONGMSG: // SysEx message...
340335 {
341- uint8_t * data = (uint8_t *)&event[3 ];
342- int len = MEVENT_EVENTPARM (event[2 ]);
343- if (len > 2 && data[0 ] == 0xF0 && data[len - 1 ] == 0xF7 )
336+ int long_msg_len = MEVENT_EVENTPARM (event[2 ]);
337+ uint8_t * long_msg_data = (uint8_t *)&event[3 ];
338+ // Ensure valid sysex message
339+ if (long_msg_len > 2 && long_msg_data[0 ] == 0xF0 && long_msg_data[long_msg_len - 1 ] == 0xF7 )
344340 {
345- snd_seq_ev_set_sysex (&Event, len , (void *)data );
341+ snd_seq_ev_set_sysex (&Event, long_msg_len , (void *)long_msg_data );
346342 break ;
347343 }
348344 }
349345 case 0 : // Short MIDI event
350- {
351- uint8_t status = event[2 ] & 0xFF ;
352- uint8_t param1 = (event[2 ] >> 8 ) & 0x7f ;
353- uint8_t param2 = (event[2 ] >> 16 ) & 0x7f ;
354- uint8_t message[] = {status, param1, param2};
355- // This silently ignore extra bytes, so no message length logic is needed.
356- snd_midi_event_encode (Coder, message, 3 , &Event);
346+ ShortMsgBuffer = { (uint8_t )(event[2 ] & 0xff ), // Status
347+ (uint8_t )((event[2 ] >> 8 ) & 0xff ), // Data 1
348+ (uint8_t )((event[2 ] >> 16 ) & 0xff ) }; // Data 2
349+
350+ // This silently ignores extra bytes, so no message length logic is needed.
351+ snd_midi_event_encode (Coder, ShortMsgBuffer.data (), 3 , &Event);
357352 break ;
358- }
359353 default : // We didn't really recognize the event, treat it as a NOP
360354 Event.type = SND_SEQ_EVENT_NONE;
361- snd_seq_ev_set_fixed (&Event);
362355 }
363356 return true ;
364357}
@@ -374,10 +367,10 @@ void AlsaMIDIDevice::PlayerLoop()
374367 const std::chrono::microseconds buffer_step (40000 );
375368
376369 // TODO: fill in error handling throughout this.
377- snd_seq_queue_tempo_t * tempo;
370+ snd_seq_queue_tempo_t * tempo;
378371 snd_seq_queue_tempo_alloca (&tempo);
379372 snd_seq_queue_tempo_set_tempo (tempo, InitialTempo);
380- snd_seq_queue_tempo_set_ppq (tempo, TimeDiv );
373+ snd_seq_queue_tempo_set_ppq (tempo, Division );
381374 snd_seq_set_queue_tempo (sequencer.handle , QueueId, tempo);
382375
383376 snd_seq_start_queue (sequencer.handle , QueueId, NULL );
@@ -386,7 +379,7 @@ void AlsaMIDIDevice::PlayerLoop()
386379 Tempo = InitialTempo;
387380 int buffered_ticks = 0 ;
388381
389- snd_seq_queue_status_t * status;
382+ snd_seq_queue_status_t * status;
390383 snd_seq_queue_status_malloc (&status);
391384
392385 while (!Exit)
@@ -399,34 +392,43 @@ void AlsaMIDIDevice::PlayerLoop()
399392 }
400393
401394 // Figure out if we should sleep (the event is too far in the future for us to care), and for how long
402- int next_event_tick = buffered_ticks + NextEventTickDelta ;
395+ int current_event_tick = buffered_ticks + CurrentEventTickDelta ;
403396 snd_seq_get_queue_status (sequencer.handle , QueueId, status);
404397 int queue_tick = snd_seq_queue_status_get_tick_time (status);
405- int tick_delta = next_event_tick - queue_tick;
406- auto usecs = std::chrono::microseconds (tick_delta * Tempo / TimeDiv );
407- auto schedule_time = std::max ( std::chrono::microseconds ( 0 ), usecs - buffer_step) ;
398+ int ticks_until_current_ev = current_event_tick - queue_tick;
399+ auto time_until_current_ev = std::chrono::microseconds (ticks_until_current_ev * Tempo / Division );
400+ auto schedule_time = time_until_current_ev - buffer_step;
408401 if (schedule_time >= buffer_step)
409402 {
410- ExitCond.wait_for (lock, schedule_time);
411- continue ;
403+ if (ExitCond.wait_for (lock, schedule_time) == std::cv_status::no_timeout)
404+ {
405+ break ;
406+ }
412407 }
413- if (tick_delta < 0 )
414- { // Can be triggered on rare occasions on playback start.
408+ if (ticks_until_current_ev < 0 )
409+ { // Can be triggered on playback start.
415410 // Message shouldn't be shown by default like other midi backends here.
416- ZMusic_Printf (ZMUSIC_MSG_NOTIFY, " Alsa sequencer underrun: %d ticks!\n " , tick_delta );
411+ ZMusic_Printf (ZMUSIC_MSG_NOTIFY, " Alsa sequencer underrun: %d ticks!\n " , ticks_until_current_ev );
417412 }
418413
419414 // We found an event worthy of sending to the sequencer
420- HandleEvent (Event, next_event_tick);
421- buffered_ticks = next_event_tick;
415+ if (Event.type != SND_SEQ_EVENT_NONE)
416+ {
417+ HandleEvent (Event, current_event_tick);
418+ }
419+ else
420+ { // No reason to run NOP events through event handling logic; just clear "Event" for the next event pulling.
421+ snd_seq_ev_clear (&Event);
422+ }
423+ buffered_ticks = current_event_tick;
422424 Position += PositionOffset;
423425 }
424426
425427 snd_seq_ev_clear (&Event); // Event is cleared to be used in reset messages in Stop()
426428 snd_seq_queue_status_free (status);
427429}
428430
429- // Requires QueueId to be started first for non-zero tick position
431+ // Requires QueueId to be started first for non-zero tick positioned events.
430432void AlsaMIDIDevice::HandleEvent (snd_seq_event_t &event, uint tick)
431433{
432434 snd_seq_ev_set_source (&event, PortId);
@@ -468,7 +470,10 @@ void AlsaMIDIDevice::Stop()
468470{
469471 Exit = true ;
470472 ExitCond.notify_all ();
471- PlayerThread.join ();
473+ if (PlayerThread.joinable ())
474+ {
475+ PlayerThread.join ();
476+ }
472477 snd_seq_drop_output (sequencer.handle ); // This drops events in the sequencer, the sequencer is still usable
473478
474479 // Reset all channels to prevent hanging notes
@@ -489,37 +494,36 @@ bool AlsaMIDIDevice::Pause(bool paused)
489494}
490495
491496
492- int AlsaMIDIDevice::StreamOut (MidiHeader *header )
497+ int AlsaMIDIDevice::StreamOut (MidiHeader* data )
493498{
494- header ->lpNext = NULL ;
495- if (Events == NULL )
499+ data ->lpNext = nullptr ;
500+ if (Events == nullptr )
496501 {
497- Events = header ;
502+ Events = data ;
498503 Position = 0 ;
499504 }
500505 else
501506 {
502- MidiHeader **p;
503-
504- for (p = &Events; *p != NULL ; p = &(*p)->lpNext )
507+ MidiHeader** p;
508+ for (p = &Events; *p != nullptr ; p = &(*p)->lpNext )
505509 { }
506- *p = header ;
510+ *p = data ;
507511 }
508512 return 0 ;
509513}
510514
511515
512- int AlsaMIDIDevice::StreamOutSync (MidiHeader *header )
516+ int AlsaMIDIDevice::StreamOutSync (MidiHeader* data )
513517{
514- return StreamOut (header );
518+ return StreamOut (data );
515519}
516520
517521bool AlsaMIDIDevice::Update ()
518522{
519523 return true ;
520524}
521525
522- MIDIDevice * CreateAlsaMIDIDevice (int mididevice)
526+ MIDIDevice* CreateAlsaMIDIDevice (int mididevice)
523527{
524528 return new AlsaMIDIDevice (mididevice, miscConfig.snd_midiprecache );
525529}
0 commit comments