1+ #include " Fuji_Internal.h"
2+
3+ #if MF_MIDI == MF_DRIVER_ALSA
4+ #include " MFMidi_Internal.h"
5+ #include < alsa/asoundlib.h>
6+
7+ struct MFMidiLinux_MidiDevice
8+ {
9+ snd_seq_addr_t send;
10+ snd_seq_port_subscribe_t *subscription;
11+ MFMidiDeviceInfo info;
12+ bool bBuffered;
13+
14+ MFMidiEvent *pEvents;
15+ uint32 numEvents;
16+ uint32 numAllocated;
17+ uint32 numEventsRead;
18+
19+ struct ChannelState
20+ {
21+ uint8 lastNotes[128 ];
22+ uint8 notes[128 ];
23+ uint8 control[128 ];
24+ uint8 program;
25+ uint8 channel_pressure;
26+ uint16 pitch;
27+ } channels[16 ];
28+ };
29+
30+
31+
32+ static int numDevices = 0 ;
33+ static MFMidiLinux_MidiDevice *pDevices = NULL ;
34+
35+ snd_seq_t *sequencer;
36+ snd_seq_event_t *event;
37+ char * port_name = " fuji" ;
38+ snd_seq_addr_t dest;
39+ static const int read_bits = SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
40+
41+ inline int MFMidi_GetNumPorts (snd_seq_client_info_t * client_info, snd_seq_port_info_t * port_info)
42+ {
43+ int ports = 0 , count;
44+ MFDebug_Warn (1 ," ALSA MIDI looking for devices\n " );
45+
46+ snd_seq_client_info_set_client (client_info, -1 );
47+ while (snd_seq_query_next_client (sequencer, client_info) >= 0 ) {
48+
49+ // filter clients we are not interested in
50+ if ( MFString_Compare (snd_seq_client_info_get_name (client_info)," System" ) == 0 ||
51+ MFString_Compare (snd_seq_client_info_get_name (client_info)," Midi Through" ) == 0 ||
52+ snd_seq_client_info_get_client (client_info) == dest.client ||
53+ snd_seq_client_info_get_type (client_info) == SND_SEQ_USER_CLIENT ) { continue ; }
54+
55+ MFDebug_Warn (3 ,MFStr (" ALSA MIDI client: %s (%i)\n " ,snd_seq_client_info_get_name (client_info),snd_seq_client_info_get_client (client_info)));
56+ snd_seq_port_info_set_client (port_info, snd_seq_client_info_get_client (client_info));
57+ snd_seq_port_info_set_port (port_info, -1 );
58+
59+ count = 0 ;
60+ while (snd_seq_query_next_port (sequencer,port_info) >= 0 ) {
61+ if ((snd_seq_port_info_get_capability (port_info) & read_bits) == read_bits) { count++; }
62+ }
63+ ports += count;
64+ }
65+ return ports;
66+ }
67+
68+ void MFMidi_InitModulePlatformSpecific ()
69+ {
70+
71+ if ( snd_seq_open (&sequencer, " hw" , SND_SEQ_OPEN_INPUT, 0 ) < 0 ) { MFDebug_Error (" Can't open alsa MIDI\n " ); exit (1 ); }
72+ snd_seq_set_client_name (sequencer,port_name);
73+
74+ dest.client = snd_seq_client_id (sequencer);
75+ snd_seq_set_client_name (sequencer,port_name);
76+ dest.port = snd_seq_create_simple_port ( sequencer, " listen:in" ,
77+ SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
78+ SND_SEQ_PORT_TYPE_APPLICATION);
79+
80+ snd_seq_client_info_t * client_info;
81+ snd_seq_port_info_t * port_info;
82+ int count;
83+
84+ snd_seq_client_info_malloc (&client_info);
85+ snd_seq_port_info_malloc (&port_info);
86+
87+ numDevices = MFMidi_GetNumPorts (client_info,port_info);
88+
89+ MFDebug_Warn (3 ,MFStr (" ALSA MIDI found %i devices" ,numDevices));
90+
91+ if (numDevices) pDevices = (MFMidiLinux_MidiDevice*)MFHeap_Alloc (sizeof (MFMidiLinux_MidiDevice)*numDevices);
92+
93+ int deviceNum = 0 ;
94+ snd_seq_client_info_set_client (client_info, -1 );
95+
96+ while (snd_seq_query_next_client (sequencer, client_info) >= 0 ) {
97+
98+ // filter clients we are not interested in including us and currently any
99+ if ( MFString_Compare (snd_seq_client_info_get_name (client_info)," System" ) == 0 ||
100+ MFString_Compare (snd_seq_client_info_get_name (client_info)," Midi Through" ) == 0 ||
101+ snd_seq_client_info_get_client (client_info) == dest.client ||
102+ snd_seq_client_info_get_type (client_info) == SND_SEQ_USER_CLIENT ) { continue ; }
103+
104+ snd_seq_port_info_set_client (port_info, snd_seq_client_info_get_client (client_info));
105+ snd_seq_port_info_set_port (port_info, -1 );
106+
107+ while (snd_seq_query_next_port (sequencer,port_info) >= 0 ) {
108+
109+ if ((snd_seq_port_info_get_capability (port_info) & read_bits) == read_bits) {
110+ MFString_Copy (pDevices[deviceNum].info .name ,snd_seq_client_info_get_name (client_info));
111+ pDevices[deviceNum].info .status = MFMS_Available;
112+ pDevices[deviceNum].send .client = snd_seq_port_info_get_client (port_info);
113+ pDevices[deviceNum].send .port = snd_seq_port_info_get_port (port_info);
114+ printf (" device %i client %i port %i\n " , deviceNum, pDevices[deviceNum].send .client , pDevices[deviceNum].send .port );
115+ deviceNum++;
116+ }
117+ }
118+ }
119+
120+ snd_seq_client_info_free (client_info);
121+ snd_seq_port_info_free (port_info);
122+ }
123+
124+ void MFMidi_DeinitModulePlatformSpecific ()
125+ {
126+ for (int i=0 ; i < numDevices; ++i)
127+ {
128+ if (pDevices[i].info .status > MFMS_Available)
129+ {
130+ MFDebug_Warn (1 , MFStr (" MIDI device not closed: %s" , pDevices[i].info .name ));
131+ // reset midi
132+ // close midi
133+ }
134+ }
135+ }
136+
137+ void MFMidi_Process (snd_seq_event_t *event) {
138+ printf (" event->type %i sender %i.%i\n " ,event->type ,event->source .client ,event->source .port );
139+
140+ MFMidiLinux_MidiDevice * targetDevice = NULL ;
141+ for (int i=0 ; i < numDevices; i++) {
142+ if (event->source .client == pDevices[i].send .port && event->source .port == pDevices->send .port ) {
143+ targetDevice = pDevices[i];
144+ break ;
145+ }
146+ }
147+
148+
149+ if (targetDevice == NULL ) { return ; }
150+
151+ switch (event->type )
152+ {
153+ case SND_SEQ_EVENT_NOTEON:
154+ targetDevice->channels [event->data .control .channel ].notes [event->data .note .note ] = event->data .note .velocity ;
155+ break ;
156+ case SND_SEQ_EVENT_NOTEOFF:
157+ targetDevice->channels [event->data .control .channel ].notes [event->data .note .note ] = 0 ;
158+ break ;
159+ case SND_SEQ_EVENT_CONTROLLER:
160+ targetDevice->channels [event->data .control .channel ].control [event->data .control .param ] = event->data .control .value ;
161+ break ;
162+ case SND_SEQ_EVENT_CHANPRESS:
163+ targetDevice->channels [event->data .control .channel ].channel_pressure = event->data .control .value ;
164+ break ;
165+ case SND_SEQ_EVENT_PGMCHANGE:
166+ targetDevice->channels [event->data .control .channel ].program = event->data .control .value ;
167+ break ;
168+ case SND_SEQ_EVENT_PITCHBEND:
169+ targetDevice->channels [event->data .control .channel ].pitch = event->data .control .value ;
170+ break ;
171+ }
172+ }
173+
174+ void MFMidi_UpdateInternal ()
175+ {
176+ // TODO reimpliment this in a separate thread with a ring buffer in order to work with lower latencies.
177+ if (snd_seq_event_input_pending (sequencer,1 ))
178+ {
179+ snd_seq_event_t *event = NULL ;
180+ snd_seq_event_input (sequencer,&event);
181+ do { MFMidi_Process (event); }
182+ while (snd_seq_event_input_pending (sequencer,0 ));
183+ }
184+ }
185+
186+ MF_API int MFMidi_GetNumDevices ()
187+ {
188+ return numDevices;
189+ }
190+
191+ MF_API const char *MFMidi_GetDeviceName (int deviceId)
192+ {
193+ MFDebug_Assert (deviceId < numDevices, " Invalid device ID!" );
194+ return pDevices[deviceId].info .name ;
195+ }
196+
197+ MF_API MFMidiDeviceStatus MFMidi_GetStatus (int deviceId)
198+ {
199+ MFDebug_Assert (deviceId < numDevices, " Invalid device ID!" );
200+ return pDevices[deviceId].info .status ;
201+ }
202+
203+ MF_API MFMidiInput *MFMidi_OpenInput (int deviceId, bool bBuffered, MFMidiEventCallback *pEventCallback)
204+ {
205+ MFDebug_Assert (deviceId < numDevices, " Invalid device ID!" );
206+ MFMidiLinux_MidiDevice *pDevice = &pDevices[deviceId];
207+ // TODO open midi device
208+
209+ pDevice->bBuffered = bBuffered;
210+ pDevice->numAllocated = 256 ;
211+ pDevice->pEvents = (MFMidiEvent*)MFHeap_Alloc (sizeof (MFMidiEvent) * pDevice->numAllocated );
212+
213+ pDevice->info .status = MFMS_Ready;
214+
215+ snd_seq_port_subscribe_t * subscription;
216+ snd_seq_port_subscribe_malloc (&subscription);
217+ snd_seq_port_subscribe_set_dest (subscription,&dest);
218+ snd_seq_port_subscribe_set_queue (subscription, bBuffered);
219+ snd_seq_port_subscribe_set_exclusive (subscription, 0 );
220+ snd_seq_port_subscribe_set_time_update (subscription, 0 );
221+ snd_seq_port_subscribe_set_time_real (subscription, 0 );
222+
223+ printf (" numDevices %i\n " , numDevices);
224+ for (int i = 0 ; i < numDevices; i++) {
225+ snd_seq_port_subscribe_set_sender (subscription,&(pDevices[i].send ));
226+ MFDebug_Warn (3 ,MFStr (" ALSA MIDI: connecting to %i client %i port %i\n " ,i,pDevices[i].send .client ,pDevices[i].send .port ));
227+ if (snd_seq_get_port_subscription (sequencer, subscription) == 0 ) { MFDebug_Warn (3 ," Connection is already subscribed\n " ); continue ; }
228+ if (snd_seq_subscribe_port (sequencer, subscription) < 0 ) { MFDebug_Warn (1 ,MFStr (" Connection failed (%s)\n " , snd_strerror (errno)));}
229+ }
230+
231+ snd_seq_port_subscribe_free (subscription);
232+
233+ return (MFMidiInput*) pDevice;
234+ }
235+
236+ MF_API void MFMidi_CloseInput (MFMidiInput *pMidiInput)
237+ {
238+ MFMidiLinux_MidiDevice *pDevice = (MFMidiLinux_MidiDevice*)pMidiInput;
239+
240+ MFHeap_Free (pDevice->pEvents );
241+
242+ // reset device
243+ // close device
244+ // for linux I am just going to unsubscribe.
245+
246+ snd_seq_unsubscribe_port (sequencer,pDevice->subscription );
247+ snd_seq_port_subscribe_free (pDevice->subscription );
248+
249+ pDevice->info .status = MFMS_Available;
250+
251+ }
252+
253+ MF_API uint32 MFMidi_GetState (MFMidiInput *pMidiInput, MFMidiDataType type, int channel, int note)
254+ {
255+ MFMidiLinux_MidiDevice *pDevice = (MFMidiLinux_MidiDevice *)pMidiInput;
256+
257+ switch (type)
258+ {
259+ case MFMD_Note:
260+ return pDevice->channels [channel].notes [note];
261+ case MFMD_Controller:
262+ return pDevice->channels [channel].control [note];
263+ case MFMD_Program:
264+ return pDevice->channels [channel].program ;
265+ case MFMD_PitchWheel:
266+ return pDevice->channels [channel].pitch ;
267+ }
268+ return 0 ;
269+ }
270+
271+ MF_API uint32 MFMidi_WasPressed (MFMidiInput *pMidiInput, int channel, int note)
272+ {
273+ MFMidiLinux_MidiDevice *pDevice = (MFMidiLinux_MidiDevice*)pMidiInput;
274+
275+ return pDevice->channels [channel].notes [note] && !pDevice->channels [channel].lastNotes [note];
276+ }
277+
278+ MF_API uint32 MFMidi_WasReleased (MFMidiInput *pMidiInput, int channel, int note)
279+ {
280+ MFMidiLinux_MidiDevice *pDevice = (MFMidiLinux_MidiDevice*)pMidiInput;
281+
282+ return !pDevice->channels [channel].notes [note] && pDevice->channels [channel].lastNotes [note];
283+ }
284+
285+ MF_API bool MFMidi_Start (MFMidiInput *pMidiInput)
286+ {
287+ MFMidiLinux_MidiDevice *pDevice = (MFMidiLinux_MidiDevice*)pMidiInput;
288+
289+ MFDebug_Assert (pDevice->info .status == MFMS_Ready, MFStr (" Midi device not ready: %s" , pDevice->info .name ));
290+
291+ pDevice->numEvents = pDevice->numEventsRead = 0 ;
292+ pDevice->info .status = MFMS_Active;
293+ return true ;
294+ }
295+
296+
297+ MF_API void MFMidi_Stop (MFMidiInput *pMidiInput)
298+ {
299+ MFMidiLinux_MidiDevice *pDevice = (MFMidiLinux_MidiDevice*)pMidiInput;
300+ pDevice->info .status = MFMS_Ready;
301+ }
302+
303+ MF_API size_t MFMidi_GetEvents (MFMidiInput *pMidiInput, MFMidiEvent *pEvents, size_t maxEvents, bool bPeek)
304+ {
305+ MFMidiLinux_MidiDevice *pDevice = (MFMidiLinux_MidiDevice*)pMidiInput;
306+
307+ if (pDevice->numEvents == 0 )
308+ return 0 ;
309+
310+ uint32 toRead = MFMin ((uint32)maxEvents, pDevice->numEvents - pDevice->numEventsRead );
311+ MFCopyMemory (pEvents, pDevice->pEvents + pDevice->numEventsRead , sizeof (MFMidiEvent)*toRead);
312+
313+ if (!bPeek)
314+ {
315+ pDevice->numEventsRead += toRead;
316+ if (pDevice->numEventsRead == pDevice->numEvents )
317+ pDevice->numEvents = pDevice->numEventsRead = 0 ;
318+ }
319+
320+ return toRead;
321+ }
322+
323+ #endif // MF_MIDI == MF_DRIVER_LINUX
0 commit comments