|
@@ -0,0 +1,758 @@
|
|
|
|
+#include "native_midi/native_midi.h"
|
|
|
|
+#include "native_midi/native_midi_common.h"
|
|
|
|
+
|
|
|
|
+#include <SDL_thread.h>
|
|
|
|
+#include <alsa/asoundlib.h>
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+typedef struct {
|
|
|
|
+ uint32_t tick;
|
|
|
|
+ uint8_t type, channel, data1, data2;
|
|
|
|
+ union {
|
|
|
|
+ int32_t pitch;
|
|
|
|
+ int32_t tempo;
|
|
|
|
+ uint32_t len;
|
|
|
|
+ };
|
|
|
|
+ uint8_t *sysex;
|
|
|
|
+ uint64_t time;
|
|
|
|
+} midi_event_info;
|
|
|
|
+
|
|
|
|
+struct _NativeMidiSong {
|
|
|
|
+ snd_seq_t *sequencer;
|
|
|
|
+ int src_client_id;
|
|
|
|
+ int src_port_id;
|
|
|
|
+ int dst_client_id;
|
|
|
|
+ int dst_port_id;
|
|
|
|
+ int queue;
|
|
|
|
+
|
|
|
|
+ midi_event_info *events;
|
|
|
|
+ unsigned int timediv;
|
|
|
|
+
|
|
|
|
+ volatile int playing;
|
|
|
|
+ int current_volume;
|
|
|
|
+ volatile int new_volume;
|
|
|
|
+ int looping;
|
|
|
|
+
|
|
|
|
+ SDL_Thread *thread;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+static const uint8_t alsa_event_types[8] = {
|
|
|
|
+ SND_SEQ_EVENT_NOTEOFF,
|
|
|
|
+ SND_SEQ_EVENT_NOTEON,
|
|
|
|
+ SND_SEQ_EVENT_KEYPRESS,
|
|
|
|
+ SND_SEQ_EVENT_CONTROLLER,
|
|
|
|
+ SND_SEQ_EVENT_PGMCHANGE,
|
|
|
|
+ SND_SEQ_EVENT_CHANPRESS,
|
|
|
|
+ SND_SEQ_EVENT_PITCHBEND,
|
|
|
|
+ SND_SEQ_EVENT_NONE
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+static int native_midi_available = -1;
|
|
|
|
+static int dst_client_id = 0;
|
|
|
|
+static int dst_port_id = 0;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+static void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...)
|
|
|
|
+{
|
|
|
|
+ // do nothing
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int create_src_port(NativeMidiSong *song)
|
|
|
|
+{
|
|
|
|
+ snd_seq_port_info_t *pinfo;
|
|
|
|
+
|
|
|
|
+ snd_seq_port_info_alloca(&pinfo);
|
|
|
|
+
|
|
|
|
+ snd_seq_port_info_set_name(pinfo, "PAL-midi");
|
|
|
|
+
|
|
|
|
+ snd_seq_port_info_set_capability(pinfo, 0);
|
|
|
|
+ snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
|
|
|
|
+
|
|
|
|
+ if (snd_seq_create_port(song->sequencer, pinfo) < 0) return -1;
|
|
|
|
+
|
|
|
|
+ song->src_port_id = snd_seq_port_info_get_port(pinfo);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int find_dst_port(NativeMidiSong *song, const char *midi_client)
|
|
|
|
+{
|
|
|
|
+ snd_seq_client_info_t *cinfo;
|
|
|
|
+ snd_seq_port_info_t *pinfo;
|
|
|
|
+ int client_id, port_id;
|
|
|
|
+
|
|
|
|
+ if (midi_client != NULL)
|
|
|
|
+ {
|
|
|
|
+ snd_seq_addr_t addr;
|
|
|
|
+ if (snd_seq_parse_address(song->sequencer, &addr, midi_client) < 0) return -1;
|
|
|
|
+
|
|
|
|
+ song->dst_client_id = addr.client;
|
|
|
|
+ song->dst_port_id = addr.port;
|
|
|
|
+
|
|
|
|
+ snd_seq_port_info_alloca(&pinfo);
|
|
|
|
+
|
|
|
|
+ if (snd_seq_get_any_port_info(song->sequencer, song->dst_client_id, song->dst_port_id, pinfo) < 0) return -2;
|
|
|
|
+
|
|
|
|
+ if ((snd_seq_port_info_get_capability(pinfo) & (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT)) != (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE))
|
|
|
|
+ {
|
|
|
|
+ return -3;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ client_id = -1;
|
|
|
|
+
|
|
|
|
+ snd_seq_client_info_alloca(&cinfo);
|
|
|
|
+ snd_seq_port_info_alloca(&pinfo);
|
|
|
|
+
|
|
|
|
+ snd_seq_client_info_set_client(cinfo, -1);
|
|
|
|
+ while (snd_seq_query_next_client(song->sequencer, cinfo) >= 0)
|
|
|
|
+ {
|
|
|
|
+ snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
|
|
|
|
+ snd_seq_port_info_set_port(pinfo, -1);
|
|
|
|
+
|
|
|
|
+ while (snd_seq_query_next_port(song->sequencer, pinfo) >= 0)
|
|
|
|
+ {
|
|
|
|
+ if ( ((snd_seq_port_info_get_capability(pinfo) & (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT)) == (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE)) &&
|
|
|
|
+ (snd_seq_port_info_get_type(pinfo) & (SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_MIDI_GM))
|
|
|
|
+ )
|
|
|
|
+ {
|
|
|
|
+ if (snd_seq_port_info_get_midi_channels(pinfo))
|
|
|
|
+ {
|
|
|
|
+ song->dst_client_id = snd_seq_client_info_get_client(cinfo);
|
|
|
|
+ song->dst_port_id = snd_seq_port_info_get_port(pinfo);
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ else if (client_id == -1)
|
|
|
|
+ {
|
|
|
|
+ client_id = snd_seq_client_info_get_client(cinfo);
|
|
|
|
+ port_id = snd_seq_port_info_get_port(pinfo);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (client_id != -1)
|
|
|
|
+ {
|
|
|
|
+ song->dst_client_id = client_id;
|
|
|
|
+ song->dst_port_id = port_id;
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ return -4;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int open_alsa_port(NativeMidiSong *song)
|
|
|
|
+{
|
|
|
|
+ if (song == NULL) return -1;
|
|
|
|
+ if (song->sequencer != NULL) return -2;
|
|
|
|
+
|
|
|
|
+ if (snd_seq_open(&(song->sequencer), "default", SND_SEQ_OPEN_DUPLEX, 0) < 0)
|
|
|
|
+ {
|
|
|
|
+ song->sequencer = NULL;
|
|
|
|
+ return -3;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (snd_seq_set_client_name(song->sequencer, "PAL-midi") < 0)
|
|
|
|
+ {
|
|
|
|
+ snd_seq_close(song->sequencer);
|
|
|
|
+ song->sequencer = NULL;
|
|
|
|
+ return -4;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ song->src_client_id = snd_seq_client_id(song->sequencer);
|
|
|
|
+ if (song->src_client_id < 0)
|
|
|
|
+ {
|
|
|
|
+ snd_seq_close(song->sequencer);
|
|
|
|
+ song->sequencer = NULL;
|
|
|
|
+ return -5;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (dst_client_id == 0)
|
|
|
|
+ {
|
|
|
|
+ if (find_dst_port(song, getenv("MIDI_CLIENT")) < 0)
|
|
|
|
+ {
|
|
|
|
+ snd_seq_close(song->sequencer);
|
|
|
|
+ song->sequencer = NULL;
|
|
|
|
+ return -6;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ song->dst_client_id = dst_client_id;
|
|
|
|
+ song->dst_port_id = dst_port_id;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (create_src_port(song) < 0)
|
|
|
|
+ {
|
|
|
|
+ snd_seq_close(song->sequencer);
|
|
|
|
+ song->sequencer = NULL;
|
|
|
|
+ return -7;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ song->queue = snd_seq_alloc_named_queue(song->sequencer, "PAL-midi");
|
|
|
|
+ if (song->queue < 0)
|
|
|
|
+ {
|
|
|
|
+ snd_seq_delete_port(song->sequencer, song->src_port_id);
|
|
|
|
+ snd_seq_close(song->sequencer);
|
|
|
|
+ song->sequencer = NULL;
|
|
|
|
+ return -8;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (snd_seq_connect_to(song->sequencer, song->src_port_id, song->dst_client_id, song->dst_port_id) < 0)
|
|
|
|
+ {
|
|
|
|
+ snd_seq_free_queue(song->sequencer, song->queue);
|
|
|
|
+ snd_seq_delete_port(song->sequencer, song->src_port_id);
|
|
|
|
+ snd_seq_close(song->sequencer);
|
|
|
|
+ song->sequencer = NULL;
|
|
|
|
+ return -9;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void close_alsa_port(NativeMidiSong *song)
|
|
|
|
+{
|
|
|
|
+ if (song->sequencer != NULL)
|
|
|
|
+ {
|
|
|
|
+ snd_seq_disconnect_to(song->sequencer, song->src_port_id, song->dst_client_id, song->dst_port_id);
|
|
|
|
+ snd_seq_free_queue(song->sequencer, song->queue);
|
|
|
|
+ snd_seq_delete_port(song->sequencer, song->src_port_id);
|
|
|
|
+ snd_seq_close(song->sequencer);
|
|
|
|
+
|
|
|
|
+ song->sequencer = NULL;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int convert_event_list(NativeMidiSong *song, MIDIEvent *eventlist)
|
|
|
|
+{
|
|
|
|
+ int eventcount = 0;
|
|
|
|
+
|
|
|
|
+ MIDIEvent* eventlist1;
|
|
|
|
+ for (eventlist1 = eventlist; eventlist1 != NULL; eventlist1 = eventlist1->next)
|
|
|
|
+ {
|
|
|
|
+ eventcount++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ midi_event_info *events = calloc(eventcount + 1, sizeof(midi_event_info));
|
|
|
|
+ if (events == NULL) return -1;
|
|
|
|
+
|
|
|
|
+ events[0].tick = 0;
|
|
|
|
+ events[0].type = SND_SEQ_EVENT_NONE;
|
|
|
|
+ events[0].len = 0;
|
|
|
|
+ events[0].sysex = NULL;
|
|
|
|
+ events[0].time = 0;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ unsigned int tempo, tempo_tick;
|
|
|
|
+ uint64_t tempo_time;
|
|
|
|
+
|
|
|
|
+ tempo = 500000; // 500000 MPQN = 120 BPM
|
|
|
|
+ tempo_tick = 0;
|
|
|
|
+ tempo_time = 0;
|
|
|
|
+
|
|
|
|
+ unsigned int time_division = song->timediv;
|
|
|
|
+
|
|
|
|
+ eventcount = 0;
|
|
|
|
+ for (; eventlist != NULL; eventlist = eventlist->next)
|
|
|
|
+ {
|
|
|
|
+ midi_event_info event;
|
|
|
|
+
|
|
|
|
+ event.tick = eventlist->time;
|
|
|
|
+ event.sysex = NULL;
|
|
|
|
+ event.type = SND_SEQ_EVENT_NONE;
|
|
|
|
+
|
|
|
|
+ // calculate event time in nanoseconds
|
|
|
|
+ {
|
|
|
|
+ div_t divres;
|
|
|
|
+
|
|
|
|
+ divres = div(event.tick - tempo_tick, time_division);
|
|
|
|
+
|
|
|
|
+ event.time = ( ((1000 * divres.rem) * (uint64_t)tempo) / time_division )
|
|
|
|
+ + ( (divres.quot * (uint64_t)tempo) * 1000 )
|
|
|
|
+ + tempo_time
|
|
|
|
+ ;
|
|
|
|
+
|
|
|
|
+ //event.time = ( (((event.tick - tempo_tick) * (uint64_t) 1000) * tempo) / time_division ) + tempo_time;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ int status = (eventlist->status & 0xf0) >> 4;
|
|
|
|
+ switch (status)
|
|
|
|
+ {
|
|
|
|
+ case MIDI_STATUS_NOTE_OFF:
|
|
|
|
+ case MIDI_STATUS_NOTE_ON:
|
|
|
|
+ case MIDI_STATUS_AFTERTOUCH:
|
|
|
|
+ case MIDI_STATUS_CONTROLLER:
|
|
|
|
+ case MIDI_STATUS_PROG_CHANGE:
|
|
|
|
+ case MIDI_STATUS_PRESSURE:
|
|
|
|
+ case MIDI_STATUS_PITCH_WHEEL:
|
|
|
|
+ event.type = alsa_event_types[status - 8];
|
|
|
|
+ event.channel = eventlist->status & 0x0f;
|
|
|
|
+ event.data1 = eventlist->data[0];
|
|
|
|
+ event.data2 = eventlist->data[1];
|
|
|
|
+ if (status == MIDI_STATUS_PITCH_WHEEL)
|
|
|
|
+ {
|
|
|
|
+ event.pitch = ( ((int32_t)event.data1) | (((int32_t)event.data2) << 7) ) - 0x2000;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case MIDI_STATUS_SYSEX:
|
|
|
|
+ if (eventlist->status == 0xff) // meta events
|
|
|
|
+ {
|
|
|
|
+ if (eventlist->data[0] == 0x51) // set tempo
|
|
|
|
+ {
|
|
|
|
+ event.type = SND_SEQ_EVENT_TEMPO;
|
|
|
|
+ event.channel = eventlist->extraData[0];
|
|
|
|
+ event.data1 = eventlist->extraData[1];
|
|
|
|
+ event.data2 = eventlist->extraData[2];
|
|
|
|
+ event.tempo = (((uint32_t)event.channel) << 16) | (((uint32_t)event.data1) << 8) | ((uint32_t)event.data2);
|
|
|
|
+
|
|
|
|
+ tempo = event.tempo;
|
|
|
|
+ tempo_tick = event.tick;
|
|
|
|
+ tempo_time = event.time;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if ((eventlist->status == 0xf0) || (eventlist->status == 0xf7)) // sysex
|
|
|
|
+ {
|
|
|
|
+ event.type = SND_SEQ_EVENT_SYSEX;
|
|
|
|
+ event.len = eventlist->extraLen + (eventlist->status == 0xf0)?1:0;
|
|
|
|
+ if (event.len)
|
|
|
|
+ {
|
|
|
|
+ event.sysex = (uint8_t *) malloc(event.len);
|
|
|
|
+ if (event.sysex != NULL)
|
|
|
|
+ {
|
|
|
|
+ if (eventlist->status == 0xf0)
|
|
|
|
+ {
|
|
|
|
+ event.sysex[0] = 0xf0;
|
|
|
|
+ memcpy(event.sysex + 1, eventlist->extraData, eventlist->extraLen);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ memcpy(event.sysex, eventlist->extraData, eventlist->extraLen);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (event.type != SND_SEQ_EVENT_NONE)
|
|
|
|
+ {
|
|
|
|
+ eventcount++;
|
|
|
|
+ events[eventcount] = event;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ events[0].len = eventcount;
|
|
|
|
+
|
|
|
|
+ song->events = events;
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void free_midi_events(NativeMidiSong *song)
|
|
|
|
+{
|
|
|
|
+ unsigned int index;
|
|
|
|
+
|
|
|
|
+ if (song->events != NULL)
|
|
|
|
+ {
|
|
|
|
+ for (index = song->events[0].len; index != 0; index--)
|
|
|
|
+ {
|
|
|
|
+ if (song->events[index].sysex != NULL)
|
|
|
|
+ {
|
|
|
|
+ free(song->events[index].sysex);
|
|
|
|
+ song->events[index].sysex = NULL;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ free(song->events);
|
|
|
|
+ song->events = NULL;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+static int play_midi(void *_song)
|
|
|
|
+{
|
|
|
|
+ NativeMidiSong *song = (NativeMidiSong *)_song;
|
|
|
|
+
|
|
|
|
+ midi_event_info *events;
|
|
|
|
+ unsigned int current_event, base_tick, last_tick, num_events;
|
|
|
|
+ uint64_t base_time;
|
|
|
|
+
|
|
|
|
+ // set queue tempo
|
|
|
|
+ {
|
|
|
|
+ snd_seq_queue_tempo_t *queue_tempo;
|
|
|
|
+
|
|
|
|
+ snd_seq_queue_tempo_alloca(&queue_tempo);
|
|
|
|
+
|
|
|
|
+ snd_seq_queue_tempo_set_tempo(queue_tempo, 500000); // 120 BPM
|
|
|
|
+ snd_seq_queue_tempo_set_ppq(queue_tempo, song->timediv);
|
|
|
|
+
|
|
|
|
+ if (0 > snd_seq_set_queue_tempo(song->sequencer, song->queue, queue_tempo))
|
|
|
|
+ {
|
|
|
|
+ return 2;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // start play
|
|
|
|
+ if (0 > snd_seq_start_queue(song->sequencer, song->queue, NULL))
|
|
|
|
+ {
|
|
|
|
+ return 3;
|
|
|
|
+ }
|
|
|
|
+ if (0 > snd_seq_drain_output(song->sequencer))
|
|
|
|
+ {
|
|
|
|
+ return 4;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ events = song->events;
|
|
|
|
+ num_events = events[0].len;
|
|
|
|
+ current_event = 1;
|
|
|
|
+ base_tick = 0;
|
|
|
|
+ base_time = 0;
|
|
|
|
+ last_tick = 0;
|
|
|
|
+
|
|
|
|
+ snd_seq_sync_output_queue(song->sequencer);
|
|
|
|
+
|
|
|
|
+ int do_sleep;
|
|
|
|
+ snd_seq_queue_status_t *queue_status;
|
|
|
|
+ const snd_seq_real_time_t *real_time;
|
|
|
|
+ int64_t time_diff, base_diff;
|
|
|
|
+ snd_seq_event_t event;
|
|
|
|
+
|
|
|
|
+ snd_seq_queue_status_alloca(&queue_status);
|
|
|
|
+
|
|
|
|
+ snd_seq_ev_clear(&event);
|
|
|
|
+ event.queue = song->queue;
|
|
|
|
+ event.source.port = song->src_port_id;
|
|
|
|
+ event.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
|
|
|
|
+
|
|
|
|
+ do_sleep = 0;
|
|
|
|
+ while (1)
|
|
|
|
+ {
|
|
|
|
+ if (do_sleep)
|
|
|
|
+ {
|
|
|
|
+ do_sleep = 0;
|
|
|
|
+
|
|
|
|
+ SDL_Delay(10);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!song->playing) break;
|
|
|
|
+
|
|
|
|
+ if (current_event > num_events)
|
|
|
|
+ {
|
|
|
|
+ if (!song->looping)
|
|
|
|
+ {
|
|
|
|
+ song->playing = 0;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // looping
|
|
|
|
+ base_tick += events[num_events].tick;
|
|
|
|
+ base_time += events[num_events].time;
|
|
|
|
+
|
|
|
|
+ current_event = 1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ((song->new_volume != song->current_volume) && (events[current_event].tick != 0))
|
|
|
|
+ {
|
|
|
|
+ int chan;
|
|
|
|
+
|
|
|
|
+ song->current_volume = song->new_volume;
|
|
|
|
+
|
|
|
|
+ snd_seq_ev_set_fixed(&event);
|
|
|
|
+ event.type = SND_SEQ_EVENT_CONTROLLER;
|
|
|
|
+ event.time.tick = base_tick + events[current_event - 1].tick;
|
|
|
|
+ event.dest.client = song->dst_client_id;
|
|
|
|
+ event.dest.port = song->dst_port_id;
|
|
|
|
+ event.data.control.param = MIDI_CTL_MSB_MAIN_VOLUME;
|
|
|
|
+ event.data.control.value = song->current_volume;
|
|
|
|
+
|
|
|
|
+ for (chan = 0; chan < 16; chan++)
|
|
|
|
+ {
|
|
|
|
+ event.data.control.channel = chan;
|
|
|
|
+ snd_seq_event_output(song->sequencer, &event);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ snd_seq_drain_output(song->sequencer);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (0 > snd_seq_get_queue_status(song->sequencer, song->queue, queue_status))
|
|
|
|
+ {
|
|
|
|
+ do_sleep = 1;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ real_time = snd_seq_queue_status_get_real_time(queue_status);
|
|
|
|
+
|
|
|
|
+ base_diff = ((real_time->tv_sec * (uint64_t)1000000000) + real_time->tv_nsec) - base_time;
|
|
|
|
+
|
|
|
|
+ time_diff = events[current_event].time - base_diff;
|
|
|
|
+
|
|
|
|
+ if (time_diff >= 100000000) // 100ms
|
|
|
|
+ {
|
|
|
|
+ do_sleep = 1;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ do
|
|
|
|
+ {
|
|
|
|
+ // add events to queue
|
|
|
|
+ event.type = events[current_event].type;
|
|
|
|
+ event.time.tick = base_tick + events[current_event].tick;
|
|
|
|
+ event.dest.client = song->dst_client_id;
|
|
|
|
+ event.dest.port = song->dst_port_id;
|
|
|
|
+
|
|
|
|
+ switch (event.type)
|
|
|
|
+ {
|
|
|
|
+ case SND_SEQ_EVENT_NOTEON:
|
|
|
|
+ case SND_SEQ_EVENT_NOTEOFF:
|
|
|
|
+ case SND_SEQ_EVENT_KEYPRESS:
|
|
|
|
+ snd_seq_ev_set_fixed(&event);
|
|
|
|
+ event.data.note.channel = events[current_event].channel;
|
|
|
|
+ event.data.note.note = events[current_event].data1;
|
|
|
|
+ event.data.note.velocity = events[current_event].data2;
|
|
|
|
+ break;
|
|
|
|
+ case SND_SEQ_EVENT_CONTROLLER:
|
|
|
|
+ snd_seq_ev_set_fixed(&event);
|
|
|
|
+ event.data.control.channel = events[current_event].channel;
|
|
|
|
+ event.data.control.param = events[current_event].data1;
|
|
|
|
+ event.data.control.value = events[current_event].data2;
|
|
|
|
+ break;
|
|
|
|
+ case SND_SEQ_EVENT_PGMCHANGE:
|
|
|
|
+ case SND_SEQ_EVENT_CHANPRESS:
|
|
|
|
+ snd_seq_ev_set_fixed(&event);
|
|
|
|
+ event.data.control.channel = events[current_event].channel;
|
|
|
|
+ event.data.control.value = events[current_event].data1;
|
|
|
|
+ break;
|
|
|
|
+ case SND_SEQ_EVENT_PITCHBEND:
|
|
|
|
+ snd_seq_ev_set_fixed(&event);
|
|
|
|
+ event.data.control.channel = events[current_event].channel;
|
|
|
|
+ event.data.control.value = events[current_event].pitch;
|
|
|
|
+ break;
|
|
|
|
+ case SND_SEQ_EVENT_SYSEX:
|
|
|
|
+ snd_seq_ev_set_variable(&event, events[current_event].len, events[current_event].sysex);
|
|
|
|
+ break;
|
|
|
|
+ case SND_SEQ_EVENT_TEMPO:
|
|
|
|
+ snd_seq_ev_set_fixed(&event);
|
|
|
|
+ event.dest.client = SND_SEQ_CLIENT_SYSTEM;
|
|
|
|
+ event.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;
|
|
|
|
+ event.data.queue.queue = song->queue;
|
|
|
|
+ event.data.queue.param.value = events[current_event].tempo;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ snd_seq_event_output(song->sequencer, &event);
|
|
|
|
+
|
|
|
|
+ current_event++;
|
|
|
|
+ if (current_event > num_events) break;
|
|
|
|
+ time_diff = events[current_event].time - base_diff;
|
|
|
|
+ } while (time_diff < 100000000); // 100ms
|
|
|
|
+
|
|
|
|
+ snd_seq_drain_output(song->sequencer);
|
|
|
|
+
|
|
|
|
+ last_tick = event.time.tick;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // stop playing
|
|
|
|
+ event.time.tick = last_tick;
|
|
|
|
+
|
|
|
|
+ snd_seq_sync_output_queue(song->sequencer);
|
|
|
|
+
|
|
|
|
+ event.type = SND_SEQ_EVENT_CONTROLLER;
|
|
|
|
+ event.dest.client = song->dst_client_id;
|
|
|
|
+ event.dest.port = song->dst_port_id;
|
|
|
|
+
|
|
|
|
+ int chan;
|
|
|
|
+ for (chan = 0; chan < 16; chan++)
|
|
|
|
+ {
|
|
|
|
+ snd_seq_ev_set_fixed(&event);
|
|
|
|
+ event.data.control.channel = chan;
|
|
|
|
+ event.data.control.param = MIDI_CTL_ALL_NOTES_OFF; // All notes off (this message stops all the notes that are currently playing)
|
|
|
|
+ event.data.control.value = 0;
|
|
|
|
+
|
|
|
|
+ snd_seq_event_output(song->sequencer, &event);
|
|
|
|
+
|
|
|
|
+ snd_seq_ev_set_fixed(&event);
|
|
|
|
+ event.data.control.channel = chan;
|
|
|
|
+ event.data.control.param = MIDI_CTL_RESET_CONTROLLERS; // All controllers off (this message clears all the controller values for this channel, back to their default values)
|
|
|
|
+ event.data.control.value = 0;
|
|
|
|
+
|
|
|
|
+ snd_seq_event_output(song->sequencer, &event);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ snd_seq_ev_set_fixed(&event);
|
|
|
|
+ event.type = SND_SEQ_EVENT_STOP;
|
|
|
|
+ event.dest.client = SND_SEQ_CLIENT_SYSTEM;
|
|
|
|
+ event.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;
|
|
|
|
+ event.data.queue.queue = song->queue;
|
|
|
|
+
|
|
|
|
+ snd_seq_event_output(song->sequencer, &event);
|
|
|
|
+
|
|
|
|
+ snd_seq_drain_output(song->sequencer);
|
|
|
|
+
|
|
|
|
+ snd_seq_sync_output_queue(song->sequencer);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+#ifdef __cplusplus
|
|
|
|
+extern "C"
|
|
|
|
+{
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+int native_midi_detect()
|
|
|
|
+{
|
|
|
|
+ if (native_midi_available != -1) return native_midi_available;
|
|
|
|
+
|
|
|
|
+ NativeMidiSong *song = calloc(1, sizeof(NativeMidiSong));
|
|
|
|
+ if (song == NULL) return 0;
|
|
|
|
+
|
|
|
|
+ snd_lib_error_set_handler(error_handler);
|
|
|
|
+
|
|
|
|
+ if (open_alsa_port(song) < 0)
|
|
|
|
+ {
|
|
|
|
+ free(song);
|
|
|
|
+ native_midi_available = 0;
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ native_midi_available = 1;
|
|
|
|
+ dst_client_id = song->dst_client_id;
|
|
|
|
+ dst_port_id = song->dst_port_id;
|
|
|
|
+
|
|
|
|
+ close_alsa_port(song);
|
|
|
|
+ free(song);
|
|
|
|
+
|
|
|
|
+ return 1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+NativeMidiSong *native_midi_loadsong(const char *midifile)
|
|
|
|
+{
|
|
|
|
+ // Attempt to load the midi file
|
|
|
|
+ SDL_RWops *rw = SDL_RWFromFile(midifile, "rb");
|
|
|
|
+ if (rw == NULL) return NULL;
|
|
|
|
+
|
|
|
|
+ NativeMidiSong *song = native_midi_loadsong_RW(rw);
|
|
|
|
+ SDL_RWclose(rw);
|
|
|
|
+ return song;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw)
|
|
|
|
+{
|
|
|
|
+ NativeMidiSong *song = calloc(1, sizeof(NativeMidiSong));
|
|
|
|
+ if (song == NULL) return NULL;
|
|
|
|
+
|
|
|
|
+ Uint16 division;
|
|
|
|
+ MIDIEvent *eventlist = CreateMIDIEventList(rw, &division);
|
|
|
|
+ if (eventlist == NULL)
|
|
|
|
+ {
|
|
|
|
+ free(song);
|
|
|
|
+ return NULL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ song->current_volume = 128;
|
|
|
|
+ song->new_volume = 128;
|
|
|
|
+ song->timediv = division;
|
|
|
|
+
|
|
|
|
+ if (convert_event_list(song, eventlist) < 0)
|
|
|
|
+ {
|
|
|
|
+ FreeMIDIEventList(eventlist);
|
|
|
|
+ free(song);
|
|
|
|
+ return NULL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ FreeMIDIEventList(eventlist);
|
|
|
|
+
|
|
|
|
+ if (open_alsa_port(song) < 0)
|
|
|
|
+ {
|
|
|
|
+ free_midi_events(song);
|
|
|
|
+ free(song);
|
|
|
|
+ return NULL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return song;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+void native_midi_freesong(NativeMidiSong *song)
|
|
|
|
+{
|
|
|
|
+ if (song != NULL)
|
|
|
|
+ {
|
|
|
|
+ native_midi_stop(song);
|
|
|
|
+ close_alsa_port(song);
|
|
|
|
+ free_midi_events(song);
|
|
|
|
+ free(song);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+void native_midi_start(NativeMidiSong *song, int looping)
|
|
|
|
+{
|
|
|
|
+ if (song != NULL)
|
|
|
|
+ {
|
|
|
|
+ native_midi_stop(song);
|
|
|
|
+
|
|
|
|
+ song->playing = 1;
|
|
|
|
+ song->looping = looping;
|
|
|
|
+
|
|
|
|
+#if SDL_VERSION_ATLEAST(2,0,0)
|
|
|
|
+ song->thread = SDL_CreateThread(&play_midi, "PAL-midi", (void *)song);
|
|
|
|
+#else
|
|
|
|
+ song->thread = SDL_CreateThread(&play_midi, (void *)song);
|
|
|
|
+#endif
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+void native_midi_stop(NativeMidiSong *song)
|
|
|
|
+{
|
|
|
|
+ if (song != NULL)
|
|
|
|
+ {
|
|
|
|
+ song->playing = 0;
|
|
|
|
+ if (song->thread != NULL)
|
|
|
|
+ {
|
|
|
|
+ SDL_WaitThread(song->thread, NULL);
|
|
|
|
+ song->thread = NULL;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+int native_midi_active(NativeMidiSong *song)
|
|
|
|
+{
|
|
|
|
+ return (song && song->playing) ? 1 : 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+void native_midi_setvolume(NativeMidiSong *song, int volume)
|
|
|
|
+{
|
|
|
|
+ if (song != NULL)
|
|
|
|
+ {
|
|
|
|
+ if (volume > 127) volume = 127;
|
|
|
|
+ if (volume < 0) volume = 0;
|
|
|
|
+ song->new_volume = volume;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+const char *native_midi_error(NativeMidiSong *song)
|
|
|
|
+{
|
|
|
|
+ return "";
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#ifdef __cplusplus
|
|
|
|
+}
|
|
|
|
+#endif
|
|
|
|
+
|