Mostly ALSA fixing - Alex
[terminatorX.git] / terminatorX / 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
35 #ifdef USE_ALSA_MIDI_IN
36 #include "tX_global.h"
37 #include <iostream>
38
39 using namespace std;
40
41 static gboolean midi_callback(GIOChannel *source, GIOCondition condition, gpointer data) {
42         tX_midiin *midi=(tX_midiin *) data;
43         midi->check_event();
44 }
45
46 tX_midiin::tX_midiin()
47 {
48         
49         int portid;
50         is_open=false;
51         
52         if (snd_seq_open(&ALSASeqHandle, "default", SND_SEQ_OPEN_INPUT, 0) < 0) {
53                 tX_error("tX_midiin(): failed to open the default sequencer device.");
54                 return;
55         }
56         snd_seq_set_client_name(ALSASeqHandle, "terminatorX");
57         portid =
58                 snd_seq_create_simple_port(ALSASeqHandle,
59                                                                    "Control Input",
60                                                                    SND_SEQ_PORT_CAP_WRITE
61                                                                    | SND_SEQ_PORT_CAP_SUBS_WRITE,
62                                                                    SND_SEQ_PORT_TYPE_APPLICATION);
63         if (portid < 0) {
64                 tX_error("tX_midiin(): error creating sequencer port.");
65                 return;
66         }
67
68         snd_seq_nonblock( ALSASeqHandle, 1 );
69         
70         struct pollfd fds[32];
71         
72         int res=snd_seq_poll_descriptors (ALSASeqHandle, fds, 32, POLLIN);
73
74         if (res!=1) {
75                 tX_error("Failed to poll ALSA descriptors: %i.\n", res);
76         }
77         
78         GIOChannel *ioc=g_io_channel_unix_new(fds[0].fd);
79         g_io_add_watch(ioc, (GIOCondition)( G_IO_IN ), midi_callback, (gpointer) this);
80         g_io_channel_unref(ioc);
81         
82         is_open=true;
83
84         tX_debug("tX_midiin(): sequencer successfully opened."); 
85 }
86
87 tX_midiin::~tX_midiin()
88 {
89         snd_seq_close(ALSASeqHandle);
90         tX_debug("tX_midiin(): sequencer closed."); 
91 }
92
93 int tX_midiin::check_event()
94 {
95         snd_seq_event_t *ev;
96                 
97         while( snd_seq_event_input(ALSASeqHandle, &ev) != -EAGAIN )
98         {
99
100                 //MidiEvent::type MessageType=MidiEvent::NONE;
101                 //int Volume=0,Note=0,EventDevice=0;
102                 tX_midievent event;
103                 event.is_noteon = false;
104                 bool event_usable = true;
105                 
106                 switch (ev->type) {
107                         case SND_SEQ_EVENT_CONTROLLER: 
108                                 event.type = tX_midievent::CC;
109                                 event.number = ev->data.control.param;
110                                 event.value = ev->data.control.value / 127.0;
111                                 event.channel = ev->data.control.channel;
112                                 break;
113                         case SND_SEQ_EVENT_PITCHBEND:
114                                 event.type = tX_midievent::PITCHBEND;
115                                 event.number = ev->data.control.param;
116                                 event.value = ev->data.control.value / 127.0;
117                                 event.channel = ev->data.control.channel;
118                                 break;
119                         case SND_SEQ_EVENT_NOTEON:
120                                 event.type = tX_midievent::NOTE;
121                                 event.number = ev->data.note.note;
122                                 event.value = ev->data.note.velocity / 127.0;
123                                 event.channel = ev->data.note.channel;
124
125                                 event.is_noteon = true;
126                                 if( event.value == 0 )
127                                         event.is_noteon = false;
128                                 break;
129                         case SND_SEQ_EVENT_NOTEOFF: 
130                                 event.type = tX_midievent::NOTE;
131                                 event.number = ev->data.note.note;
132                                 event.value = ev->data.note.velocity / 127.0;
133                                 event.channel = ev->data.note.channel;
134                                 
135                                 event.is_noteon = false;
136                                 break;
137                         default:
138                                 event_usable = false;
139                 }
140
141                 snd_seq_free_event(ev);
142                 
143                 if( event_usable )
144                 {
145                         if (event.channel<0 || event.channel>15)
146                         {
147                                 tX_error("tX_midiin::check_event(): invaild event channel %i.", event.channel);
148                                 return -1;
149                         }
150
151                         // This should be solved with a hash table. Possibly.
152                         
153                         list <tX_seqpar *> :: iterator sp;                      
154                         
155                         for (sp=tX_seqpar::all.begin(); sp!=tX_seqpar::all.end(); sp++) {
156                                 if ( (*sp)->bound_midi_event.type_matches (event) ) {
157                                         (*sp)->handle_midi_input (event);
158                                 }
159                         }
160
161                         last_event = event;
162                 }
163
164         }
165         return 1;
166 }
167
168 void tX_midiin::configure_bindings( vtt_class* vtt )
169 {
170         list <tX_seqpar *> :: iterator sp;
171
172         GType types[3] = { G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER };
173         GtkListStore* model = gtk_list_store_newv(3, types);
174         GtkTreeIter iter;
175         char tempstr[128];
176         
177         for (sp=tX_seqpar::all.begin(); sp!=tX_seqpar::all.end(); sp++) {
178                 if (((*sp)->is_mappable) && ((*sp)->vtt) == (void*) vtt) {
179                         
180                         snprintf( tempstr, sizeof(tempstr), "Type: %d, Number: %d, Channel: %d",
181                                           (*sp)->bound_midi_event.type, (*sp)->bound_midi_event.number,
182                                           (*sp)->bound_midi_event.channel );
183
184                         gtk_list_store_append( model, &iter );
185                         gtk_list_store_set( model, &iter,
186                                                                 0, (*sp)->get_name(),
187                                                                 1, tempstr,
188                                                                 2, (*sp),
189                                                                 -1 );
190                 }
191         }
192
193         // it will delete itself.
194         new midi_binding_gui(GTK_TREE_MODEL(model), this);
195 }
196
197 tX_midiin::midi_binding_gui::midi_binding_gui ( GtkTreeModel* _model, tX_midiin* _midi )
198         : model(_model), midi( _midi )
199 {
200         GtkWidget *hbox1;
201         GtkWidget *scrolledwindow1;
202         GtkWidget *vbox1;
203         GtkWidget *label1;
204         GtkWidget *frame1;
205         
206         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
207         gtk_window_set_title (GTK_WINDOW (window), "Configure MIDI Bindings");
208         gtk_window_set_default_size(GTK_WINDOW(window), 600, 260);
209         
210         hbox1 = gtk_hbox_new (FALSE, 2);
211         gtk_widget_show (hbox1);
212         gtk_container_add (GTK_CONTAINER (window), hbox1);
213         gtk_container_set_border_width(GTK_CONTAINER(hbox1), 4);
214         
215         scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
216         gtk_widget_show (scrolledwindow1);
217         gtk_box_pack_start (GTK_BOX (hbox1), scrolledwindow1, TRUE, TRUE, 0);
218         
219         parameter_treeview = gtk_tree_view_new_with_model (model);
220         gtk_widget_show (parameter_treeview);
221         gtk_container_add (GTK_CONTAINER (scrolledwindow1), parameter_treeview);
222         
223         GtkCellRenderer   *renderer = gtk_cell_renderer_text_new ();
224         gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( parameter_treeview ),
225                                                                                            -1, "Parameter", renderer,
226                                                                                            "text", 0,
227                                                                                            NULL );
228         gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( parameter_treeview ),
229                                                                                            -1, "Event", renderer,
230                                                                                            "text", 1,
231                                                                                            NULL );
232         gtk_tree_view_set_headers_visible( GTK_TREE_VIEW(parameter_treeview), TRUE );
233         
234         vbox1 = gtk_vbox_new (FALSE, 0);
235         gtk_widget_show (vbox1);
236         gtk_box_pack_start (GTK_BOX (hbox1), vbox1, FALSE, FALSE, 0);
237         
238         label1 = gtk_label_new ("Selected MIDI Event:");
239         gtk_widget_show (label1);
240         gtk_box_pack_start (GTK_BOX (vbox1), label1, FALSE, FALSE, 0);
241         gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT);
242         
243         frame1 = gtk_frame_new (NULL);
244         gtk_widget_show (frame1);
245         gtk_box_pack_start (GTK_BOX (vbox1), frame1, TRUE, TRUE, 0);
246         gtk_container_set_border_width (GTK_CONTAINER (frame1), 2);
247         gtk_frame_set_label_align (GTK_FRAME (frame1), 0, 0);
248         gtk_frame_set_shadow_type (GTK_FRAME (frame1), GTK_SHADOW_IN);
249         
250         midi_event_info = gtk_label_new ("Use a MIDI thing to select it.");
251         gtk_widget_show (midi_event_info);
252         gtk_container_add (GTK_CONTAINER (frame1), midi_event_info);
253         gtk_label_set_justify (GTK_LABEL (midi_event_info), GTK_JUSTIFY_LEFT);
254         
255         bind_button = gtk_button_new_with_mnemonic ("Bind");
256         gtk_widget_show (bind_button);
257         gtk_box_pack_start (GTK_BOX (vbox1), bind_button, FALSE, FALSE, 0);
258         
259         GtkWidget* unbind_button = gtk_button_new_with_mnemonic ("Remove Binding");
260         gtk_widget_show (unbind_button);
261         gtk_box_pack_start (GTK_BOX (vbox1), unbind_button, FALSE, FALSE, 0);   
262         
263         GtkWidget* close_button = gtk_button_new_with_mnemonic ("Close");
264         gtk_widget_show (close_button);
265         gtk_box_pack_start (GTK_BOX (vbox1), close_button, FALSE, FALSE, 0);
266         
267         gtk_signal_connect(GTK_OBJECT(bind_button), "clicked", (GtkSignalFunc) bind_clicked, (void *) this);
268         gtk_signal_connect(GTK_OBJECT(unbind_button), "clicked", (GtkSignalFunc) unbind_clicked, (void *) this);        
269         gtk_signal_connect(GTK_OBJECT(close_button), "clicked", (GtkSignalFunc) close_clicked, (void *) this);
270         gtk_signal_connect(GTK_OBJECT(window), "destroy", (GtkSignalFunc) close_clicked, (void *) this);
271         
272         timer_tag = gtk_timeout_add( 100, (GtkFunction) timer, (void *) this);
273         
274         gtk_widget_show_all( GTK_WIDGET( window ) );
275 }
276
277 void tX_midiin::midi_binding_gui::window_closed(GtkWidget *widget, gpointer _this )
278 {
279         tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
280
281         delete this_;
282 }
283
284 void tX_midiin::midi_binding_gui::unbind_clicked( GtkButton *button, gpointer _this )
285 {
286         tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
287         GtkTreeModel* model;
288         GtkTreeSelection* selection;
289         GtkTreeIter iter;
290         char tmpstr[128];
291         tX_seqpar* param;
292
293         selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(this_->parameter_treeview) );
294         gtk_tree_selection_get_selected( selection, &model, &iter );
295         gtk_tree_model_get( model, &iter, 2, &param, -1 );
296         
297         param->bound_midi_event.type=tX_midievent::NONE;
298         param->bound_midi_event.number=0;
299         param->bound_midi_event.channel=0;
300         
301         snprintf( tmpstr, sizeof(tmpstr), "Type: %d, Number: %d, Channel: %d",
302                                 param->bound_midi_event.type, param->bound_midi_event.number,
303                                 param->bound_midi_event.channel );
304
305         gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, param->get_name(), 1, tmpstr, 2, param, -1 );       
306 }
307
308
309 void tX_midiin::midi_binding_gui::bind_clicked( GtkButton *button, gpointer _this )
310 {
311         tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
312         GtkTreeModel* model;
313         GtkTreeSelection* selection;
314         GtkTreeIter iter;
315         char tmpstr[128];
316         tX_seqpar* param;
317
318         selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(this_->parameter_treeview) );
319         gtk_tree_selection_get_selected( selection, &model, &iter );
320         gtk_tree_model_get( model, &iter, 2, &param, -1 );
321         
322         param->bound_midi_event = this_->last_event;
323         
324         snprintf( tmpstr, sizeof(tmpstr), "Type: %d, Number: %d, Channel: %d",
325                                 param->bound_midi_event.type, param->bound_midi_event.number,
326                                 param->bound_midi_event.channel );
327
328         gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, param->get_name(), 1, tmpstr, 2, param, -1 );       
329 }
330
331 void tX_midiin::midi_binding_gui::close_clicked( GtkButton *button, gpointer _this )
332 {
333         tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
334         
335         gtk_widget_destroy( this_->window );
336
337         delete this_;
338 }
339
340 gint tX_midiin::midi_binding_gui::timer( gpointer _this )
341 {
342         tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
343         tX_midievent tmpevent = this_->midi->get_last_event();
344
345         if( tmpevent.type_matches( this_->last_event ) )
346                 return TRUE;
347         
348         this_->last_event = tmpevent;
349         this_->last_event.clear_non_type();
350
351         snprintf( this_->tempstr, sizeof(this_->tempstr),
352                           "Type: %d (CC=%d, NOTE=%d)\nNumber: %d\nChannel: %d\n",
353                           this_->last_event.type, tX_midievent::CC, tX_midievent::NOTE,
354                           this_->last_event.number,
355                           this_->last_event.channel );
356
357         gtk_label_set_text( GTK_LABEL(this_->midi_event_info), this_->tempstr );
358
359         return TRUE;
360 }
361
362 tX_midiin::midi_binding_gui::~midi_binding_gui ()
363 {
364         gtk_timeout_remove( timer_tag );
365 }
366
367 #endif // USE_ALSA_MIDI_IN