Skip to content

Commit 9bbfaa2

Browse files
committed
AlsaMIDIDevice and CoreMIDIDevice small refactor
1 parent cf9be31 commit 9bbfaa2

2 files changed

Lines changed: 181 additions & 204 deletions

File tree

source/mididevices/music_alsa_mididevice.cpp

Lines changed: 88 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -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

7171
protected:
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

201202
int 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

289287
bool 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.
430432
void 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

517521
bool 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

Comments
 (0)