ALSA fixes, MIDI fixes and some new features, misc fixes and a
[terminatorX.git] / src / tX_midiin.cc
1 /*
2   terminatorX - realtime audio scratching software
3   Copyright (C) 2002 Arthur Peters
4         
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9  
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14  
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  
19   File: tX_midiin.cc
20  
21   Description: Implements MIDI input to control turntable parameters.
22   
23         Changes (Alexander K├Ânig <alex@lisas.de>:
24         - Using a glib GIOCallback instead of polling events
25         - Updating the treeview immedialtey after bind/unbind_clicked
26         - Adding "remove binding" option
27         - Adding destroy handler for the GUI
28         - moving printf to tX_* macros
29         - removing some debug code
30 */    
31
32 #include "tX_midiin.h"
33 #include "tX_vtt.h"
34 #include "tX_glade_interface.h"
35 #include "tX_glade_support.h"
36
37 #ifdef USE_ALSA_MIDI_IN
38 #include "tX_global.h"
39 #include <iostream>
40 #include "tX_engine.h"
41
42 using namespace std;
43
44 static gboolean midi_callback(GIOChannel *source, GIOCondition condition, gpointer data) {
45         tX_midiin *midi=(tX_midiin *) data;
46         midi->check_event();
47         
48         return TRUE;
49 }
50
51 tX_midiin::tX_midiin()
52 {
53         is_open=false;
54         sp_to_learn=NULL;
55         learn_dialog=NULL;
56         
57         if (snd_seq_open(&ALSASeqHandle, "default", SND_SEQ_OPEN_INPUT, 0) < 0) {
58                 tX_error("tX_midiin(): failed to open the default sequencer device.");
59                 return;
60         }
61         snd_seq_set_client_name(ALSASeqHandle, "terminatorX");
62         portid =
63                 snd_seq_create_simple_port(ALSASeqHandle,
64                                                                    "Control Input",
65                                                                    SND_SEQ_PORT_CAP_WRITE
66                                                                    | SND_SEQ_PORT_CAP_SUBS_WRITE,
67                                                                    SND_SEQ_PORT_TYPE_APPLICATION);
68         if (portid < 0) {
69                 tX_error("tX_midiin(): error creating sequencer port.");
70                 return;
71         }
72
73         snd_seq_nonblock( ALSASeqHandle, 1 );
74         
75         struct pollfd fds[32];
76         
77         int res=snd_seq_poll_descriptors (ALSASeqHandle, fds, 32, POLLIN);
78
79         if (res!=1) {
80                 tX_error("Failed to poll ALSA descriptors: %i.\n", res);
81         }
82         
83         GIOChannel *ioc=g_io_channel_unix_new(fds[0].fd);
84         g_io_add_watch(ioc, (GIOCondition)( G_IO_IN ), midi_callback, (gpointer) this);
85         g_io_channel_unref(ioc);
86         
87         is_open=true;
88
89         tX_debug("tX_midiin(): sequencer successfully opened."); 
90 }
91
92 tX_midiin::~tX_midiin()
93 {
94         if (is_open) {
95                 snd_seq_close(ALSASeqHandle);
96                 tX_debug("tX_midiin(): sequencer closed.");
97         }
98 }
99
100 int tX_midiin::check_event()
101 {
102         snd_seq_event_t *ev;
103                 
104         while( snd_seq_event_input(ALSASeqHandle, &ev) != -EAGAIN )
105         {
106
107                 //MidiEvent::type MessageType=MidiEvent::NONE;
108                 //int Volume=0,Note=0,EventDevice=0;
109                 tX_midievent event;
110                 event.is_noteon = false;
111                 bool event_usable = true;
112                 printf("type: %i\n", ev->type);
113                 switch (ev->type) {
114                         case SND_SEQ_EVENT_CONTROLLER: 
115                                 printf("ctrl: p: %i, v: %i, c: %i\n", ev->data.control.param, ev->data.control.value, ev->data.control.channel);
116                                 event.type = tX_midievent::CC;
117                                 event.number = ev->data.control.param;
118                                 event.value = ev->data.control.value / 127.0;
119                                 event.channel = ev->data.control.channel;
120                                 break;
121                         case SND_SEQ_EVENT_PITCHBEND:
122                                 event.type = tX_midievent::PITCHBEND;
123                                 event.number = ev->data.control.param;
124                                 event.value = (ev->data.control.value + 8191.0) / 16382.0; // 127.0;
125                                 event.channel = ev->data.control.channel;
126                                 break;
127                         case SND_SEQ_EVENT_CONTROL14:
128                                 event.type = tX_midievent::CC14;
129                                 event.number = ev->data.control.param;
130                                 event.value = ev->data.control.value / 16383.0;
131                                 event.channel = ev->data.control.channel;
132                                 break;
133                         case SND_SEQ_EVENT_REGPARAM:
134                                 printf("rpn: p: %i, v: %i, c: %i\n", ev->data.control.param, ev->data.control.value, ev->data.control.channel);
135                                 event.type = tX_midievent::RPN;
136                                 event.number = ev->data.control.param;
137                                 event.value = ev->data.control.value / 16383.0;
138                                 event.channel = ev->data.control.channel;
139                                 break;
140                         case SND_SEQ_EVENT_NONREGPARAM:
141                                 printf("nrpn: p: %i, v: %i, c: %i\n", ev->data.control.param, ev->data.control.value, ev->data.control.channel);
142                                 event.type = tX_midievent::NRPN;
143                                 event.number = ev->data.control.param;
144                                 event.value = ev->data.control.value / 16383.0;
145                                 event.channel = ev->data.control.channel;
146                                 break;                                          
147                         case SND_SEQ_EVENT_NOTEON:
148                                 event.type = tX_midievent::NOTE;
149                                 event.number = ev->data.note.note;
150                                 event.value = ev->data.note.velocity / 127.0;
151                                 event.channel = ev->data.note.channel;
152
153                                 event.is_noteon = true;
154                                 if( event.value == 0 )
155                                         event.is_noteon = false;
156                                 break;
157                         case SND_SEQ_EVENT_NOTEOFF: 
158                                 event.type = tX_midievent::NOTE;
159                                 event.number = ev->data.note.note;
160                                 event.value = ev->data.note.velocity / 127.0;
161                                 event.channel = ev->data.note.channel;
162                                 
163                                 event.is_noteon = false;
164                                 break;
165                         default:
166                                 event_usable = false;
167                 }
168
169                 snd_seq_free_event(ev);
170                 
171                 if( event_usable ) {
172                         if (event.channel<0 || event.channel>15) {
173                                 tX_error("tX_midiin::check_event(): invaild event channel %i.", event.channel);
174                                 return -1;
175                         }
176
177                         if (sp_to_learn) {
178                                 sp_to_learn->bound_midi_event=event;
179                                 sp_to_learn=NULL;
180                                 
181                                 if (learn_dialog) {
182                                         gtk_widget_destroy(learn_dialog);
183                                         learn_dialog=NULL;
184                                 }
185                         } else {
186                                 // This should be solved with a hash table. Possibly.
187                                 
188                                 list <tX_seqpar *> :: iterator sp;                      
189                                 
190                                 for (sp=tX_seqpar::all.begin(); sp!=tX_seqpar::all.end(); sp++) {
191                                         if ( (*sp)->bound_midi_event.type_matches (event) ) {
192                                                 (*sp)->handle_midi_input (event);
193                                         }
194                                 }
195                         }
196                         
197                         last_event = event;
198                 }
199
200         }
201         return 1;
202 }
203
204 void tX_midiin::configure_bindings( vtt_class* vtt )
205 {
206         list <tX_seqpar *> :: iterator sp;
207
208         GType types[3] = { G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER };
209         GtkListStore* model = gtk_list_store_newv(3, types);
210         GtkTreeIter iter;
211         char tempstr[128];
212         
213         for (sp=tX_seqpar::all.begin(); sp!=tX_seqpar::all.end(); sp++) {
214                 if (((*sp)->is_mappable) && ((*sp)->vtt) == (void*) vtt) {
215                         
216                         snprintf( tempstr, sizeof(tempstr), "Type: %d, Number: %d, Channel: %d",
217                                           (*sp)->bound_midi_event.type, (*sp)->bound_midi_event.number,
218                                           (*sp)->bound_midi_event.channel );
219
220                         gtk_list_store_append( model, &iter );
221                         gtk_list_store_set( model, &iter,
222                                                                 0, (*sp)->get_name(),
223                                                                 1, tempstr,
224                                                                 2, (*sp),
225                                                                 -1 );
226                 }
227         }
228
229         // it will delete itself.
230         new midi_binding_gui(GTK_TREE_MODEL(model), this);
231 }
232
233 tX_midiin::midi_binding_gui::midi_binding_gui ( GtkTreeModel* _model, tX_midiin* _midi )
234         : model(_model), midi( _midi )
235 {
236         GtkWidget *hbox1;
237         GtkWidget *scrolledwindow1;
238         GtkWidget *vbox1;
239         GtkWidget *label1;
240         GtkWidget *frame1;
241         
242         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
243         gtk_window_set_title (GTK_WINDOW (window), "Configure MIDI Bindings");
244         gtk_window_set_default_size(GTK_WINDOW(window), 600, 260);
245         
246         hbox1 = gtk_hbox_new (FALSE, 2);
247         gtk_widget_show (hbox1);
248         gtk_container_add (GTK_CONTAINER (window), hbox1);
249         gtk_container_set_border_width(GTK_CONTAINER(hbox1), 4);
250         
251         scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
252         gtk_widget_show (scrolledwindow1);
253         gtk_box_pack_start (GTK_BOX (hbox1), scrolledwindow1, TRUE, TRUE, 0);
254         
255         parameter_treeview = gtk_tree_view_new_with_model (model);
256         gtk_widget_show (parameter_treeview);
257         gtk_container_add (GTK_CONTAINER (scrolledwindow1), parameter_treeview);
258         
259         GtkCellRenderer   *renderer = gtk_cell_renderer_text_new ();
260         gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( parameter_treeview ),
261                                                                                            -1, "Parameter", renderer,
262                                                                                            "text", 0,
263                                                                                            NULL );
264         gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( parameter_treeview ),
265                                                                                            -1, "Event", renderer,
266                                                                                            "text", 1,
267                                                                                            NULL );
268         gtk_tree_view_set_headers_visible( GTK_TREE_VIEW(parameter_treeview), TRUE );
269         
270         vbox1 = gtk_vbox_new (FALSE, 0);
271         gtk_widget_show (vbox1);
272         gtk_box_pack_start (GTK_BOX (hbox1), vbox1, FALSE, FALSE, 0);
273         
274         label1 = gtk_label_new ("Selected MIDI Event:");
275         gtk_widget_show (label1);
276         gtk_box_pack_start (GTK_BOX (vbox1), label1, FALSE, FALSE, 0);
277         gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT);
278         
279         frame1 = gtk_frame_new (NULL);
280         gtk_widget_show (frame1);
281         gtk_box_pack_start (GTK_BOX (vbox1), frame1, TRUE, TRUE, 0);
282         gtk_container_set_border_width (GTK_CONTAINER (frame1), 2);
283         gtk_frame_set_label_align (GTK_FRAME (frame1), 0, 0);
284         gtk_frame_set_shadow_type (GTK_FRAME (frame1), GTK_SHADOW_IN);
285         
286         midi_event_info = gtk_label_new ("Use a MIDI thing to select it.");
287         gtk_widget_show (midi_event_info);
288         gtk_container_add (GTK_CONTAINER (frame1), midi_event_info);
289         gtk_label_set_justify (GTK_LABEL (midi_event_info), GTK_JUSTIFY_LEFT);
290         
291         bind_button = gtk_button_new_with_mnemonic ("Bind");
292         gtk_widget_show (bind_button);
293         gtk_box_pack_start (GTK_BOX (vbox1), bind_button, FALSE, FALSE, 0);
294         
295         GtkWidget* unbind_button = gtk_button_new_with_mnemonic ("Remove Binding");
296         gtk_widget_show (unbind_button);
297         gtk_box_pack_start (GTK_BOX (vbox1), unbind_button, FALSE, FALSE, 0);   
298         
299         GtkWidget* close_button = gtk_button_new_with_mnemonic ("Close");
300         gtk_widget_show (close_button);
301         gtk_box_pack_start (GTK_BOX (vbox1), close_button, FALSE, FALSE, 0);
302         
303         gtk_signal_connect(GTK_OBJECT(bind_button), "clicked", (GtkSignalFunc) bind_clicked, (void *) this);
304         gtk_signal_connect(GTK_OBJECT(unbind_button), "clicked", (GtkSignalFunc) unbind_clicked, (void *) this);        
305         gtk_signal_connect(GTK_OBJECT(close_button), "clicked", (GtkSignalFunc) close_clicked, (void *) this);
306         gtk_signal_connect(GTK_OBJECT(window), "destroy", (GtkSignalFunc) close_clicked, (void *) this);
307         
308         timer_tag = gtk_timeout_add( 100, (GtkFunction) timer, (void *) this);
309         
310         gtk_widget_show_all( GTK_WIDGET( window ) );
311 }
312
313 void tX_midiin::midi_binding_gui::window_closed(GtkWidget *widget, gpointer _this )
314 {
315         tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
316
317         delete this_;
318 }
319
320 void tX_midiin::midi_binding_gui::unbind_clicked( GtkButton *button, gpointer _this )
321 {
322         tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
323         GtkTreeModel* model;
324         GtkTreeSelection* selection;
325         GtkTreeIter iter;
326         char tmpstr[128];
327         tX_seqpar* param;
328
329         selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(this_->parameter_treeview) );
330         gtk_tree_selection_get_selected( selection, &model, &iter );
331         gtk_tree_model_get( model, &iter, 2, &param, -1 );
332         
333         param->bound_midi_event.type=tX_midievent::NONE;
334         param->bound_midi_event.number=0;
335         param->bound_midi_event.channel=0;
336         
337         snprintf( tmpstr, sizeof(tmpstr), "Type: %d, Number: %d, Channel: %d",
338                                 param->bound_midi_event.type, param->bound_midi_event.number,
339                                 param->bound_midi_event.channel );
340
341         gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, param->get_name(), 1, tmpstr, 2, param, -1 );       
342 }
343
344
345 void tX_midiin::midi_binding_gui::bind_clicked( GtkButton *button, gpointer _this )
346 {
347         tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
348         GtkTreeModel* model;
349         GtkTreeSelection* selection;
350         GtkTreeIter iter;
351         char tmpstr[128];
352         tX_seqpar* param;
353
354         selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(this_->parameter_treeview) );
355         gtk_tree_selection_get_selected( selection, &model, &iter );
356         gtk_tree_model_get( model, &iter, 2, &param, -1 );
357         
358         param->bound_midi_event = this_->last_event;
359         
360         snprintf( tmpstr, sizeof(tmpstr), "Type: %d, Number: %d, Channel: %d",
361                                 param->bound_midi_event.type, param->bound_midi_event.number,
362                                 param->bound_midi_event.channel );
363
364         gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, param->get_name(), 1, tmpstr, 2, param, -1 );       
365 }
366
367 void tX_midiin::midi_binding_gui::close_clicked( GtkButton *button, gpointer _this )
368 {
369         tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
370         
371         gtk_widget_destroy( this_->window );
372 }
373
374 gint tX_midiin::midi_binding_gui::timer( gpointer _this )
375 {
376         tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
377         tX_midievent tmpevent = this_->midi->get_last_event();
378
379         if( tmpevent.type_matches( this_->last_event ) )
380                 return TRUE;
381         
382         this_->last_event = tmpevent;
383         this_->last_event.clear_non_type();
384
385         snprintf( this_->tempstr, sizeof(this_->tempstr),
386                           "Type: %d (CC=%d, NOTE=%d)\nNumber: %d\nChannel: %d\n",
387                           this_->last_event.type, tX_midievent::CC, tX_midievent::NOTE,
388                           this_->last_event.number,
389                           this_->last_event.channel );
390
391         gtk_label_set_text( GTK_LABEL(this_->midi_event_info), this_->tempstr );
392
393         return TRUE;
394 }
395
396 tX_midiin::midi_binding_gui::~midi_binding_gui ()
397 {
398         gtk_timeout_remove( timer_tag );
399 }
400
401 void tX_midiin::set_midi_learn_sp(tX_seqpar *sp)
402 {
403         char buffer[512];
404         
405         if (learn_dialog) {
406                 gtk_widget_destroy(learn_dialog);
407         }
408         
409         sp_to_learn=sp;
410         
411         if (!sp_to_learn) return;
412         
413         learn_dialog=create_tX_midilearn();
414         GtkWidget *label=lookup_widget(learn_dialog, "midilabel");
415         
416         sprintf(buffer, "Learning MIDI mapping for <b>%s</b>\nfor turntable <b>%s</b>.\n\nWaiting for MIDI event...", sp->get_name(), sp->get_vtt_name());
417         gtk_label_set_markup(GTK_LABEL(label), buffer);
418         gtk_widget_show(learn_dialog);
419         
420         g_signal_connect(G_OBJECT(lookup_widget(learn_dialog, "cancel")), "clicked", G_CALLBACK (tX_midiin::midi_learn_cancel), this);
421         g_signal_connect(G_OBJECT(learn_dialog), "destroy", G_CALLBACK (tX_midiin::midi_learn_destroy), this);
422 }
423
424 void tX_midiin::cancel_midi_learn()
425 {
426         sp_to_learn=NULL;
427         learn_dialog=NULL;
428 }
429
430 gboolean tX_midiin::midi_learn_cancel(GtkWidget *widget, tX_midiin *midi)
431 {
432         midi->sp_to_learn=NULL;
433         gtk_widget_destroy(midi->learn_dialog);
434 }
435
436 gboolean tX_midiin::midi_learn_destroy(GtkWidget *widget, tX_midiin *midi)
437 {
438         midi->cancel_midi_learn();
439 }
440
441 void tX_midiin::store_connections(FILE *rc, char *indent) 
442 {
443         gzFile *rz;
444         
445         tX_store("%s<midi_connections>\n", indent);
446         strcat(indent, "\t");
447
448         snd_seq_addr_t my_addr;
449         my_addr.client=snd_seq_client_id(ALSASeqHandle);
450         my_addr.port=portid;
451
452         snd_seq_query_subscribe_t *subs;
453         snd_seq_query_subscribe_alloca(&subs);
454         snd_seq_query_subscribe_set_root(subs, &my_addr);
455         snd_seq_query_subscribe_set_type(subs, SND_SEQ_QUERY_SUBS_WRITE);
456         snd_seq_query_subscribe_set_index(subs, 0);
457         
458         while (snd_seq_query_port_subscribers(ALSASeqHandle, subs) >= 0) {
459                 const snd_seq_addr_t *addr;
460                 addr = snd_seq_query_subscribe_get_addr(subs);
461                 
462                 tX_store("%s<link client=\"%i\" port=\"%i\"/>\n", indent, addr->client, addr->port);
463                 snd_seq_query_subscribe_set_index(subs, snd_seq_query_subscribe_get_index(subs) + 1);
464         }       
465                 
466         indent[strlen(indent)-1]=0;
467         tX_store("%s</midi_connections>\n", indent);    
468 }
469
470 void tX_midiin::restore_connections(xmlNodePtr node)
471 {
472         snd_seq_addr_t my_addr;
473         my_addr.client=snd_seq_client_id(ALSASeqHandle);
474         my_addr.port=portid;
475         
476         if (xmlStrcmp(node->name, (xmlChar *) "midi_connections")==0) {
477                 for (xmlNodePtr cur=node->xmlChildrenNode; cur != NULL; cur = cur->next) {
478                         if (cur->type == XML_ELEMENT_NODE) {
479                                 if (xmlStrcmp(cur->name, (xmlChar *) "link")==0) {
480                                         char *buffer;
481                                         int client=-1;
482                                         int port=-1;
483                                         
484                                         buffer=(char *) xmlGetProp(cur, (xmlChar *) "client");
485                                         if (buffer) {
486                                                 sscanf(buffer, "%i", &client);
487                                         }
488                                         
489                                         buffer=(char *) xmlGetProp(cur, (xmlChar *) "port");
490                                         if (buffer) {
491                                                 sscanf(buffer, "%i", &port);
492                                         }
493                                         
494                                         if ((port>=0) && (client>=0)) {
495                                                 snd_seq_addr_t sender_addr;
496                                                 sender_addr.client=client;
497                                                 sender_addr.port=port;
498                                                 
499                                                 snd_seq_port_subscribe_t *subs;
500                                                 snd_seq_port_subscribe_alloca(&subs);
501                                                 snd_seq_port_subscribe_set_sender(subs, &sender_addr);
502                                                 snd_seq_port_subscribe_set_dest(subs, &my_addr);
503
504                                                 if (snd_seq_subscribe_port(ALSASeqHandle, subs) < 0) {
505                                                         tX_error("tX_midiin::restore_connections() -> failed to connect to: %d:%d.", port, client);
506                                                 }
507                                         } else {
508                                                 tX_error("tX_midiin::restore_connections() -> invalid port: %d:%d.", port, client);
509                                         }
510                                         
511                                 } else {
512                                         tX_error("tX_midiin::restore_connections() -> invalid element: %s.", cur->name);
513                                 }
514                         }
515                 }
516         } else {
517                 tX_error("tX_midiin::restore_connections() -> invalid XML element.");
518         }
519 }
520
521
522 extern "C" {
523         void tX_midiin_store_connections(FILE *rc, char *indent);
524         void tX_midiin_restore_connections(xmlNodePtr node);
525 };
526
527 void tX_midiin_store_connections(FILE *rc, char *indent) 
528 {
529         tX_engine::get_instance()->get_midi()->store_connections(rc, indent);
530 }
531
532 void tX_midiin_restore_connections(xmlNodePtr node) 
533 {
534         tX_engine::get_instance()->get_midi()->restore_connections(node);
535 }
536
537 #endif // USE_ALSA_MIDI_IN