1 #ifdef _HAVE_ALSA_
2 /*
3  * alsabackend.c
4  * ALSA sequencer MIDI backend.
5  *
6  * for Denemo, a gtk+ frontend to GNU Lilypond
7  * Copyright (C) 2011  Dominic Sacré
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  */
14 
15 #include "audio/alsabackend.h"
16 #include "audio/midi.h"
17 
18 #include <alsa/asoundlib.h>
19 #include <glib.h>
20 
21 
22 static char const *ALSA_SEQ_CLIENT_NAME = "denemo";
23 
24 static int const PLAYBACK_INTERVAL = 5000;
25 
26 
27 static snd_seq_t *seq;
28 
29 static int in_port_id;
30 static int out_port_id;
31 
32 static snd_midi_event_t *parser;
33 
34 static GThread *process_thread;
35 static GCond *process_cond;
36 static gboolean quit_thread = FALSE;
37 
38 static gboolean reset = FALSE;
39 
40 static double playback_start_time;
41 
42 
43 static gpointer
process_thread_func(gpointer data)44 process_thread_func (gpointer data)
45 {
46   GMutex *mutex = g_mutex_new ();
47   gint64 end_time;
48   g_mutex_lock (mutex);
49 
50   for (;;)
51     {
52       end_time = g_get_monotonic_time () +  (PLAYBACK_INTERVAL * G_TIME_SPAN_SECOND)/1000000;
53       g_cond_wait_until (process_cond, mutex, end_time);
54       if (g_atomic_int_get (&quit_thread))
55         {
56           break;
57         }
58 
59       snd_seq_event_t *pev;
60       while (snd_seq_event_input (seq, &pev) >= 0)
61         {
62           unsigned char buffer[3];
63 
64           snd_midi_event_reset_decode (parser);
65           if (snd_midi_event_decode (parser, buffer, sizeof (buffer), pev) > 0)
66             {
67               input_midi_event (MIDI_BACKEND, 0, buffer);
68             }
69         }
70 
71 
72       GTimeVal tv;
73       g_get_current_time (&tv);
74       double now = (double) tv.tv_sec + tv.tv_usec / 1000000.0;
75       double playback_time = now - playback_start_time;
76 
77       unsigned char event_data[3];
78       size_t event_length;
79       double event_time;
80 
81       double until_time = playback_time + PLAYBACK_INTERVAL / 1000000.0;
82 
83       if (reset)
84         {
85           int n;
86           for (n = 0; n < 16; ++n)
87             {
88               snd_seq_event_t ev;
89               snd_seq_ev_set_controller (&ev, n, 123, 0);
90               snd_seq_ev_set_subs (&ev);
91               snd_seq_ev_set_direct (&ev);
92               snd_seq_ev_set_source (&ev, out_port_id);
93               snd_seq_event_output_direct (seq, &ev);
94             }
95           reset = FALSE;
96         }
97 
98       while (read_event_from_queue (MIDI_BACKEND, event_data, &event_length, &event_time, until_time))
99         {
100           snd_seq_event_t ev;
101 
102           snd_midi_event_reset_encode (parser);
103           snd_midi_event_encode (parser, event_data, event_length, &ev);
104 
105           snd_seq_ev_set_subs (&ev);
106           snd_seq_ev_set_direct (&ev);
107           snd_seq_ev_set_source (&ev, out_port_id);
108 
109           snd_seq_event_output_direct (seq, &ev);
110         }
111 
112       if (is_playing ())
113         {
114           update_playback_time (TIMEBASE_PRIO_MIDI, playback_time);
115         }
116     }
117   g_mutex_free (mutex);
118   return NULL;
119 }
120 
121 
122 
123 static int
alsa_seq_initialize(DenemoPrefs * config)124 alsa_seq_initialize (DenemoPrefs * config)
125 {
126   g_message ("Initializing ALSA sequencer MIDI backend");
127 
128   // create sequencer client
129   if (snd_seq_open (&seq, "hw", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK) < 0)
130     {
131       g_warning ("error opening alsa sequencer");
132       return -1;
133     }
134 
135   snd_seq_set_client_name (seq, ALSA_SEQ_CLIENT_NAME);
136 
137   // create input port
138   in_port_id = snd_seq_create_simple_port (seq, "midi_in", SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_APPLICATION);
139   if (in_port_id < 0)
140     {
141       g_warning ("error creating sequencer output port");
142       return -1;
143     }
144 
145   // create output port
146   out_port_id = snd_seq_create_simple_port (seq, "midi_out", SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_APPLICATION);
147 
148   if (in_port_id < 0)
149     {
150       g_warning ("error creating sequencer output port");
151       return -1;
152     }
153 
154   // initialize MIDI event parser
155   if (snd_midi_event_new (12, &parser))
156     {
157       g_warning ("error initializing MIDI event parser");
158       return -1;
159     }
160   snd_midi_event_init (parser);
161   snd_midi_event_no_status (parser, 1);
162 
163 
164   process_cond = g_cond_new ();
165 
166   process_thread =  g_thread_try_new ("ALSA process", process_thread_func, NULL, NULL);
167 
168   return 0;
169 }
170 
171 
172 static int
alsa_seq_destroy()173 alsa_seq_destroy ()
174 {
175   g_message ("Destroying ALSA sequencer MIDI backend");
176 
177   g_atomic_int_set (&quit_thread, TRUE);
178   g_cond_signal (process_cond);
179   g_thread_join (process_thread);
180 
181   g_cond_free (process_cond);
182 
183 
184   snd_midi_event_free (parser);
185 
186   snd_seq_delete_port (seq, in_port_id);
187   snd_seq_delete_port (seq, out_port_id);
188 
189   snd_seq_close (seq);
190 
191   return 0;
192 }
193 
194 
195 static int
alsa_seq_reconfigure(DenemoPrefs * config)196 alsa_seq_reconfigure (DenemoPrefs * config)
197 {
198   alsa_seq_destroy ();
199   return alsa_seq_initialize (config);
200 }
201 
202 
203 static int
alsa_seq_start_playing()204 alsa_seq_start_playing ()
205 {
206   GTimeVal tv;
207   g_get_current_time (&tv);
208   playback_start_time = (double) tv.tv_sec + tv.tv_usec / 1000000.0;
209   playback_start_time -= get_playback_time ();
210   return 0;
211 }
212 
213 
214 static int
alsa_seq_stop_playing()215 alsa_seq_stop_playing ()
216 {
217   reset = TRUE;
218   return 0;
219 }
220 
221 
222 static int
alsa_seq_panic()223 alsa_seq_panic ()
224 {
225   reset = TRUE;
226   return 0;
227 }
228 
229 
230 backend_t alsa_seq_midi_backend = {
231   alsa_seq_initialize,
232   alsa_seq_destroy,
233   alsa_seq_reconfigure,
234   alsa_seq_start_playing,
235   alsa_seq_stop_playing,
236   alsa_seq_panic,
237 };
238 #endif //_HAVE_ALSA_
239