2 terminatorX - realtime audio scratching software
3 Copyright (C) 2002 Arthur Peters
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.
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.
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.
21 Description: Implements MIDI input to control turntable parameters.
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
32 #include "tX_midiin.h"
34 #include "tX_glade_interface.h"
35 #include "tX_glade_support.h"
36 #include "tX_dialog.h"
38 #ifdef USE_ALSA_MIDI_IN
39 #include "tX_global.h"
41 #include "tX_engine.h"
45 static gboolean midi_callback(GIOChannel *source, GIOCondition condition, gpointer data) {
46 tX_midiin *midi=(tX_midiin *) data;
52 tX_midiin::tX_midiin()
58 if (snd_seq_open(&ALSASeqHandle, "default", SND_SEQ_OPEN_INPUT, 0) < 0) {
59 tX_error("tX_midiin(): failed to open the default sequencer device.");
62 snd_seq_set_client_name(ALSASeqHandle, "terminatorX");
64 snd_seq_create_simple_port(ALSASeqHandle,
66 SND_SEQ_PORT_CAP_WRITE
67 | SND_SEQ_PORT_CAP_SUBS_WRITE,
68 SND_SEQ_PORT_TYPE_APPLICATION);
70 tX_error("tX_midiin(): error creating sequencer port.");
74 snd_seq_nonblock( ALSASeqHandle, 1 );
76 struct pollfd fds[32];
78 int res=snd_seq_poll_descriptors (ALSASeqHandle, fds, 32, POLLIN);
81 tX_error("Failed to poll ALSA descriptors: %i.\n", res);
84 GIOChannel *ioc=g_io_channel_unix_new(fds[0].fd);
85 g_io_add_watch(ioc, (GIOCondition)( G_IO_IN ), midi_callback, (gpointer) this);
86 g_io_channel_unref(ioc);
90 tX_debug("tX_midiin(): sequencer successfully opened.");
93 tX_midiin::~tX_midiin()
96 snd_seq_close(ALSASeqHandle);
97 tX_debug("tX_midiin(): sequencer closed.");
101 int tX_midiin::check_event()
105 while( snd_seq_event_input(ALSASeqHandle, &ev) != -EAGAIN )
108 //MidiEvent::type MessageType=MidiEvent::NONE;
109 //int Volume=0,Note=0,EventDevice=0;
111 event.is_noteon = false;
112 bool event_usable = true;
115 case SND_SEQ_EVENT_CONTROLLER:
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;
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;
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;
133 case SND_SEQ_EVENT_REGPARAM:
134 event.type = tX_midievent::RPN;
135 event.number = ev->data.control.param;
136 event.value = ev->data.control.value / 16383.0;
137 event.channel = ev->data.control.channel;
139 case SND_SEQ_EVENT_NONREGPARAM:
140 event.type = tX_midievent::NRPN;
141 event.number = ev->data.control.param;
142 event.value = ev->data.control.value / 16383.0;
143 event.channel = ev->data.control.channel;
145 case SND_SEQ_EVENT_NOTEON:
146 event.type = tX_midievent::NOTE;
147 event.number = ev->data.note.note;
148 event.value = ev->data.note.velocity / 127.0;
149 event.channel = ev->data.note.channel;
151 event.is_noteon = true;
152 if( event.value == 0 )
153 event.is_noteon = false;
155 case SND_SEQ_EVENT_NOTEOFF:
156 event.type = tX_midievent::NOTE;
157 event.number = ev->data.note.note;
158 event.value = ev->data.note.velocity / 127.0;
159 event.channel = ev->data.note.channel;
161 event.is_noteon = false;
164 event_usable = false;
167 snd_seq_free_event(ev);
170 if (event.channel<0 || event.channel>15) {
171 tX_error("tX_midiin::check_event(): invaild event channel %i.", event.channel);
176 sp_to_learn->bound_midi_event=event;
180 gtk_widget_destroy(learn_dialog);
184 // This should be solved with a hash table. Possibly.
186 list <tX_seqpar *> :: iterator sp;
188 for (sp=tX_seqpar::all.begin(); sp!=tX_seqpar::all.end(); sp++) {
189 if ( (*sp)->bound_midi_event.type_matches (event) ) {
190 (*sp)->handle_midi_input (event);
202 void tX_midiin::configure_bindings( vtt_class* vtt )
204 list <tX_seqpar *> :: iterator sp;
206 GType types[3] = { G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER };
207 GtkListStore* model = gtk_list_store_newv(3, types);
211 for (sp=tX_seqpar::all.begin(); sp!=tX_seqpar::all.end(); sp++) {
212 if (((*sp)->is_mappable) && ((*sp)->vtt) == (void*) vtt) {
214 snprintf( tempstr, sizeof(tempstr), "Type: %d, Number: %d, Channel: %d",
215 (*sp)->bound_midi_event.type, (*sp)->bound_midi_event.number,
216 (*sp)->bound_midi_event.channel );
218 gtk_list_store_append( model, &iter );
219 gtk_list_store_set( model, &iter,
220 0, (*sp)->get_name(),
227 // it will delete itself.
228 new midi_binding_gui(GTK_TREE_MODEL(model), this);
231 tX_midiin::midi_binding_gui::midi_binding_gui ( GtkTreeModel* _model, tX_midiin* _midi )
232 : model(_model), midi( _midi )
235 GtkWidget *scrolledwindow1;
240 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
241 gtk_window_set_title (GTK_WINDOW (window), "Configure MIDI Bindings");
242 gtk_window_set_default_size(GTK_WINDOW(window), 600, 260);
244 hbox1 = gtk_hbox_new (FALSE, 2);
245 gtk_widget_show (hbox1);
246 gtk_container_add (GTK_CONTAINER (window), hbox1);
247 gtk_container_set_border_width(GTK_CONTAINER(hbox1), 4);
249 scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
250 gtk_widget_show (scrolledwindow1);
251 gtk_box_pack_start (GTK_BOX (hbox1), scrolledwindow1, TRUE, TRUE, 0);
253 parameter_treeview = gtk_tree_view_new_with_model (model);
254 gtk_widget_show (parameter_treeview);
255 gtk_container_add (GTK_CONTAINER (scrolledwindow1), parameter_treeview);
257 GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
258 gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( parameter_treeview ),
259 -1, "Parameter", renderer,
262 gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( parameter_treeview ),
263 -1, "Event", renderer,
266 gtk_tree_view_set_headers_visible( GTK_TREE_VIEW(parameter_treeview), TRUE );
268 vbox1 = gtk_vbox_new (FALSE, 0);
269 gtk_widget_show (vbox1);
270 gtk_box_pack_start (GTK_BOX (hbox1), vbox1, FALSE, FALSE, 0);
272 label1 = gtk_label_new ("Selected MIDI Event:");
273 gtk_widget_show (label1);
274 gtk_box_pack_start (GTK_BOX (vbox1), label1, FALSE, FALSE, 0);
275 gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT);
277 frame1 = gtk_frame_new (NULL);
278 gtk_widget_show (frame1);
279 gtk_box_pack_start (GTK_BOX (vbox1), frame1, TRUE, TRUE, 0);
280 gtk_container_set_border_width (GTK_CONTAINER (frame1), 2);
281 gtk_frame_set_label_align (GTK_FRAME (frame1), 0, 0);
282 gtk_frame_set_shadow_type (GTK_FRAME (frame1), GTK_SHADOW_IN);
284 midi_event_info = gtk_label_new ("Use a MIDI thing to select it.");
285 gtk_widget_show (midi_event_info);
286 gtk_container_add (GTK_CONTAINER (frame1), midi_event_info);
287 gtk_label_set_justify (GTK_LABEL (midi_event_info), GTK_JUSTIFY_LEFT);
289 bind_button = gtk_button_new_with_mnemonic ("Bind");
290 gtk_widget_show (bind_button);
291 gtk_box_pack_start (GTK_BOX (vbox1), bind_button, FALSE, FALSE, 0);
293 GtkWidget* unbind_button = gtk_button_new_with_mnemonic ("Remove Binding");
294 gtk_widget_show (unbind_button);
295 gtk_box_pack_start (GTK_BOX (vbox1), unbind_button, FALSE, FALSE, 0);
297 GtkWidget* close_button = gtk_button_new_with_mnemonic ("Close");
298 gtk_widget_show (close_button);
299 gtk_box_pack_start (GTK_BOX (vbox1), close_button, FALSE, FALSE, 0);
301 g_signal_connect(G_OBJECT(bind_button), "clicked", (GtkSignalFunc) bind_clicked, (void *) this);
302 g_signal_connect(G_OBJECT(unbind_button), "clicked", (GtkSignalFunc) unbind_clicked, (void *) this);
303 g_signal_connect(G_OBJECT(close_button), "clicked", (GtkSignalFunc) close_clicked, (void *) this);
304 g_signal_connect(G_OBJECT(window), "destroy", (GtkSignalFunc) close_clicked, (void *) this);
306 timer_tag = gtk_timeout_add( 100, (GtkFunction) timer, (void *) this);
308 gtk_widget_show_all( GTK_WIDGET( window ) );
311 void tX_midiin::midi_binding_gui::window_closed(GtkWidget *widget, gpointer _this )
313 tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
318 void tX_midiin::midi_binding_gui::unbind_clicked( GtkButton *button, gpointer _this )
320 tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
322 GtkTreeSelection* selection;
327 selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(this_->parameter_treeview) );
328 gtk_tree_selection_get_selected( selection, &model, &iter );
329 gtk_tree_model_get( model, &iter, 2, ¶m, -1 );
331 param->bound_midi_event.type=tX_midievent::NONE;
332 param->bound_midi_event.number=0;
333 param->bound_midi_event.channel=0;
335 snprintf( tmpstr, sizeof(tmpstr), "Type: %d, Number: %d, Channel: %d",
336 param->bound_midi_event.type, param->bound_midi_event.number,
337 param->bound_midi_event.channel );
339 gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, param->get_name(), 1, tmpstr, 2, param, -1 );
343 void tX_midiin::midi_binding_gui::bind_clicked( GtkButton *button, gpointer _this )
345 tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
347 GtkTreeSelection* selection;
352 selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(this_->parameter_treeview) );
353 gtk_tree_selection_get_selected( selection, &model, &iter );
354 gtk_tree_model_get( model, &iter, 2, ¶m, -1 );
356 param->bound_midi_event = this_->last_event;
358 snprintf( tmpstr, sizeof(tmpstr), "Type: %d, Number: %d, Channel: %d",
359 param->bound_midi_event.type, param->bound_midi_event.number,
360 param->bound_midi_event.channel );
362 gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, param->get_name(), 1, tmpstr, 2, param, -1 );
365 void tX_midiin::midi_binding_gui::close_clicked( GtkButton *button, gpointer _this )
367 tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
369 gtk_widget_destroy( this_->window );
372 gint tX_midiin::midi_binding_gui::timer( gpointer _this )
374 tX_midiin::midi_binding_gui* this_ = (tX_midiin::midi_binding_gui*)_this;
375 tX_midievent tmpevent = this_->midi->get_last_event();
377 if( tmpevent.type_matches( this_->last_event ) )
380 this_->last_event = tmpevent;
381 this_->last_event.clear_non_type();
383 snprintf( this_->tempstr, sizeof(this_->tempstr),
384 "Type: %d (CC=%d, NOTE=%d)\nNumber: %d\nChannel: %d\n",
385 this_->last_event.type, tX_midievent::CC, tX_midievent::NOTE,
386 this_->last_event.number,
387 this_->last_event.channel );
389 gtk_label_set_text( GTK_LABEL(this_->midi_event_info), this_->tempstr );
394 tX_midiin::midi_binding_gui::~midi_binding_gui ()
396 gtk_timeout_remove( timer_tag );
399 void tX_midiin::set_midi_learn_sp(tX_seqpar *sp)
404 gtk_widget_destroy(learn_dialog);
409 if (!sp_to_learn) return;
411 learn_dialog=create_tX_midilearn();
412 tX_set_icon(learn_dialog);
413 GtkWidget *label=lookup_widget(learn_dialog, "midilabel");
415 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());
416 gtk_label_set_markup(GTK_LABEL(label), buffer);
417 gtk_widget_show(learn_dialog);
419 g_signal_connect(G_OBJECT(lookup_widget(learn_dialog, "cancel")), "clicked", G_CALLBACK (tX_midiin::midi_learn_cancel), this);
420 g_signal_connect(G_OBJECT(learn_dialog), "destroy", G_CALLBACK (tX_midiin::midi_learn_destroy), this);
423 void tX_midiin::cancel_midi_learn()
429 gboolean tX_midiin::midi_learn_cancel(GtkWidget *widget, tX_midiin *midi)
431 midi->sp_to_learn=NULL;
432 gtk_widget_destroy(midi->learn_dialog);
435 gboolean tX_midiin::midi_learn_destroy(GtkWidget *widget, tX_midiin *midi)
437 midi->cancel_midi_learn();
440 void tX_midiin::store_connections(FILE *rc, char *indent)
444 tX_store("%s<midi_connections>\n", indent);
445 strcat(indent, "\t");
447 snd_seq_addr_t my_addr;
448 my_addr.client=snd_seq_client_id(ALSASeqHandle);
451 snd_seq_query_subscribe_t *subs;
452 snd_seq_query_subscribe_alloca(&subs);
453 snd_seq_query_subscribe_set_root(subs, &my_addr);
454 snd_seq_query_subscribe_set_type(subs, SND_SEQ_QUERY_SUBS_WRITE);
455 snd_seq_query_subscribe_set_index(subs, 0);
457 while (snd_seq_query_port_subscribers(ALSASeqHandle, subs) >= 0) {
458 const snd_seq_addr_t *addr;
459 addr = snd_seq_query_subscribe_get_addr(subs);
461 tX_store("%s<link client=\"%i\" port=\"%i\"/>\n", indent, addr->client, addr->port);
462 snd_seq_query_subscribe_set_index(subs, snd_seq_query_subscribe_get_index(subs) + 1);
465 indent[strlen(indent)-1]=0;
466 tX_store("%s</midi_connections>\n", indent);
469 void tX_midiin::restore_connections(xmlNodePtr node)
471 snd_seq_addr_t my_addr;
472 my_addr.client=snd_seq_client_id(ALSASeqHandle);
475 if (xmlStrcmp(node->name, (xmlChar *) "midi_connections")==0) {
476 for (xmlNodePtr cur=node->xmlChildrenNode; cur != NULL; cur = cur->next) {
477 if (cur->type == XML_ELEMENT_NODE) {
478 if (xmlStrcmp(cur->name, (xmlChar *) "link")==0) {
483 buffer=(char *) xmlGetProp(cur, (xmlChar *) "client");
485 sscanf(buffer, "%i", &client);
488 buffer=(char *) xmlGetProp(cur, (xmlChar *) "port");
490 sscanf(buffer, "%i", &port);
493 if ((port>=0) && (client>=0)) {
494 snd_seq_addr_t sender_addr;
495 sender_addr.client=client;
496 sender_addr.port=port;
498 snd_seq_port_subscribe_t *subs;
499 snd_seq_port_subscribe_alloca(&subs);
500 snd_seq_port_subscribe_set_sender(subs, &sender_addr);
501 snd_seq_port_subscribe_set_dest(subs, &my_addr);
503 if (snd_seq_subscribe_port(ALSASeqHandle, subs) < 0) {
504 tX_error("tX_midiin::restore_connections() -> failed to connect to: %d:%d.", port, client);
507 tX_error("tX_midiin::restore_connections() -> invalid port: %d:%d.", port, client);
511 tX_error("tX_midiin::restore_connections() -> invalid element: %s.", cur->name);
516 tX_error("tX_midiin::restore_connections() -> invalid XML element.");
522 void tX_midiin_store_connections(FILE *rc, char *indent);
523 void tX_midiin_restore_connections(xmlNodePtr node);
526 void tX_midiin_store_connections(FILE *rc, char *indent)
528 tX_engine::get_instance()->get_midi()->store_connections(rc, indent);
531 void tX_midiin_restore_connections(xmlNodePtr node)
533 tX_engine::get_instance()->get_midi()->restore_connections(node);
536 #endif // USE_ALSA_MIDI_IN