@@ -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+ uint 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 ()
@@ -314,8 +312,8 @@ 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 ];
319317
320318 // Get event size to advance Position
321319 if (event[2 ] < 0x80000000 )
@@ -331,31 +329,27 @@ bool AlsaMIDIDevice::PullEvent()
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;
361355 snd_seq_ev_set_fixed (&Event);
@@ -374,10 +368,10 @@ void AlsaMIDIDevice::PlayerLoop()
374368 const std::chrono::microseconds buffer_step (40000 );
375369
376370 // TODO: fill in error handling throughout this.
377- snd_seq_queue_tempo_t * tempo;
371+ snd_seq_queue_tempo_t * tempo;
378372 snd_seq_queue_tempo_alloca (&tempo);
379373 snd_seq_queue_tempo_set_tempo (tempo, InitialTempo);
380- snd_seq_queue_tempo_set_ppq (tempo, TimeDiv );
374+ snd_seq_queue_tempo_set_ppq (tempo, Division );
381375 snd_seq_set_queue_tempo (sequencer.handle , QueueId, tempo);
382376
383377 snd_seq_start_queue (sequencer.handle , QueueId, NULL );
@@ -386,7 +380,7 @@ void AlsaMIDIDevice::PlayerLoop()
386380 Tempo = InitialTempo;
387381 int buffered_ticks = 0 ;
388382
389- snd_seq_queue_status_t * status;
383+ snd_seq_queue_status_t * status;
390384 snd_seq_queue_status_malloc (&status);
391385
392386 while (!Exit)
@@ -399,34 +393,36 @@ void AlsaMIDIDevice::PlayerLoop()
399393 }
400394
401395 // 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 ;
396+ int current_event_tick = buffered_ticks + CurrentEventTickDelta ;
403397 snd_seq_get_queue_status (sequencer.handle , QueueId, status);
404398 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) ;
399+ int ticks_until_current_ev = current_event_tick - queue_tick;
400+ auto time_until_current_ev = std::chrono::microseconds (ticks_until_current_ev * Tempo / Division );
401+ auto schedule_time = time_until_current_ev - buffer_step;
408402 if (schedule_time >= buffer_step)
409403 {
410- ExitCond.wait_for (lock, schedule_time);
411- continue ;
404+ if (ExitCond.wait_for (lock, schedule_time) == std::cv_status::no_timeout)
405+ {
406+ break ;
407+ }
412408 }
413- if (tick_delta < 0 )
409+ if (ticks_until_current_ev < 0 )
414410 { // Can be triggered on rare occasions on playback start.
415411 // 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 );
412+ ZMusic_Printf (ZMUSIC_MSG_NOTIFY, " Alsa sequencer underrun: %d ticks!\n " , ticks_until_current_ev );
417413 }
418414
419415 // We found an event worthy of sending to the sequencer
420- HandleEvent (Event, next_event_tick );
421- buffered_ticks = next_event_tick ;
416+ HandleEvent (Event, current_event_tick );
417+ buffered_ticks = current_event_tick ;
422418 Position += PositionOffset;
423419 }
424420
425421 snd_seq_ev_clear (&Event); // Event is cleared to be used in reset messages in Stop()
426422 snd_seq_queue_status_free (status);
427423}
428424
429- // Requires QueueId to be started first for non-zero tick position
425+ // Requires QueueId to be started first for non-zero tick positioned events.
430426void AlsaMIDIDevice::HandleEvent (snd_seq_event_t &event, uint tick)
431427{
432428 snd_seq_ev_set_source (&event, PortId);
@@ -468,7 +464,10 @@ void AlsaMIDIDevice::Stop()
468464{
469465 Exit = true ;
470466 ExitCond.notify_all ();
471- PlayerThread.join ();
467+ if (PlayerThread.joinable ())
468+ {
469+ PlayerThread.join ();
470+ }
472471 snd_seq_drop_output (sequencer.handle ); // This drops events in the sequencer, the sequencer is still usable
473472
474473 // Reset all channels to prevent hanging notes
@@ -489,37 +488,36 @@ bool AlsaMIDIDevice::Pause(bool paused)
489488}
490489
491490
492- int AlsaMIDIDevice::StreamOut (MidiHeader *header )
491+ int AlsaMIDIDevice::StreamOut (MidiHeader* data )
493492{
494- header ->lpNext = NULL ;
495- if (Events == NULL )
493+ data ->lpNext = nullptr ;
494+ if (Events == nullptr )
496495 {
497- Events = header ;
496+ Events = data ;
498497 Position = 0 ;
499498 }
500499 else
501500 {
502- MidiHeader **p;
503-
504- for (p = &Events; *p != NULL ; p = &(*p)->lpNext )
501+ MidiHeader** p;
502+ for (p = &Events; *p != nullptr ; p = &(*p)->lpNext )
505503 { }
506- *p = header ;
504+ *p = data ;
507505 }
508506 return 0 ;
509507}
510508
511509
512- int AlsaMIDIDevice::StreamOutSync (MidiHeader *header )
510+ int AlsaMIDIDevice::StreamOutSync (MidiHeader* data )
513511{
514- return StreamOut (header );
512+ return StreamOut (data );
515513}
516514
517515bool AlsaMIDIDevice::Update ()
518516{
519517 return true ;
520518}
521519
522- MIDIDevice * CreateAlsaMIDIDevice (int mididevice)
520+ MIDIDevice* CreateAlsaMIDIDevice (int mididevice)
523521{
524522 return new AlsaMIDIDevice (mididevice, miscConfig.snd_midiprecache );
525523}
0 commit comments