Hi list!

More than a week ago I boldly stated that I would send you a patch for MIDI input in cinelerra soon. I'm a little late, but here it is. And, as everyone here knows, a small video shows more than words can tell. You can see the patch in action here:

http://www.youtube.com/watch?v=ruJVAiUYf7g

Remember this is a first attempt, so don't expect anything too grand. The patch allows scrolling through assets, using jogwheels to move through the viewer and the compositor, and various MIDI key bindings for cinelerra functions.

All MIDI assignments are static at the time, and most of the functions have only been superficially tested.

Basic technical description:

- I added a new class, MidiControl, in the same fashion as other features are factored in Cinelerra.

- I hooked that class into the automake configuration and into the main window startup code.

- The constructor opens an ALSA sequencer device, which has to be connected by an outside app (jack patchbay, for instance).

- The class inherits Thread and is able to run a MIDI polling loop in the background.

- Inside the MIDI event handling loop, there are switch-case-statements which handle individual MIDI commands.

- The code called there is taken from various key handlers in cinelerra, with minor parameterization changes.


My next step: understand how preferences are saved and provide a preferences pane or a new config window for the MIDI section.

I'm having special difficulty in extracting the code for automation control and for drag-editing, which would both be fantastic to have on the MIDI control surface. Because the original code needs to handle mouse press, drag, and release events separately, collecting the corresponding code is a bit painful... So I'll defer that to a later stage.

The patch is relative to revision 1055, so everyone should be able to apply it without trouble.

Any comments are welcome!

- Ján

Index: cinelerra/midicontrol.inc
===================================================================
--- cinelerra/midicontrol.inc	(revision 0)
+++ cinelerra/midicontrol.inc	(revision 0)
@@ -0,0 +1,6 @@
+#ifndef MIDICONTROL_INC
+#define MIDICONTROL_INC
+
+class MidiControl;
+
+#endif
Index: cinelerra/Makefile.am
===================================================================
--- cinelerra/Makefile.am	(revision 1055)
+++ cinelerra/Makefile.am	(working copy)
@@ -208,6 +208,7 @@
 		    menueffects.C \
 		    menuveffects.C \
 		    meterpanel.C \
+		    midicontrol.C \
 		    module.C \
 		    mtimebar.C \
 		    mwindow.C \
Index: cinelerra/midicontrol.C
===================================================================
--- cinelerra/midicontrol.C	(revision 0)
+++ cinelerra/midicontrol.C	(revision 0)
@@ -0,0 +1,330 @@
+#include "edl.h"
+#include "edlsession.h"
+#include "localsession.h"
+#include "tracks.h"
+#include "mbuttons.h"
+#include "mwindow.h"
+#include "mwindowgui.h"
+#include "vwindow.h"
+#include "vplayback.h"
+#include "vwindowgui.h"
+#include "cwindow.h"
+#include "cplayback.h"
+#include "transportque.h"
+#include "cwindowgui.h"
+#include "awindow.h"
+#include "awindowgui.h"
+#include "mainsession.h"
+#include "mainclock.h"
+
+#include "midicontrol.h"
+
+#define DEBUG(w) printf("%s %d - %s: %s\n", __FILE__, __LINE__,  __PRETTY_FUNCTION__, (w))
+
+/*
+ * Storing the configuration: Enumerate functions to be automated. Each function can
+ * be used by keypress, keyrelease, CCabs or CCrel events. (sliders require CCabs or CCrel,
+ * triggers require keypress or keyrelease.
+ * func_num, func_type (key or cc), subtype (press/release or abs/rel), channel, key/cc 
+ */
+
+
+MidiControl::MidiControl(MWindow *mwindow)
+ : Thread()
+{
+	int err;
+
+	DEBUG("entering..");
+	this->mwindow = mwindow;
+	mwindow_needs_update = 0;
+
+	// Open the MIDI device here
+
+	err = snd_seq_open(&seq_handle,  "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
+	if (err != 0) {
+		// Block MIDI feature silently.
+	}
+
+	snd_seq_set_client_name(seq_handle, "Cinelerra");
+
+	snd_seq_port_info_alloca(&port_info);
+	snd_seq_port_info_set_capability(port_info, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE |
+					 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ );
+	snd_seq_port_info_set_type(port_info, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION );
+	snd_seq_port_info_set_midi_channels(port_info, 16);
+	snd_seq_port_info_set_name(port_info, "Cinelerra");
+	port = snd_seq_create_port(seq_handle, port_info);	
+	channel = 0;
+}
+
+MidiControl::~MidiControl()
+{
+	DEBUG("entering...");
+	// Cleanup code here
+	snd_seq_close(seq_handle);
+}
+
+void MidiControl::handle_event(snd_seq_event_t *ev)
+{
+	int diff;
+	double abs;
+	char channel;
+	char midicontrol;
+	char midivalue;
+
+	EDL *edl;
+	double total_length, current, frame_rate, delta;
+
+	double new_position;
+
+	switch(ev->type) {
+	case SND_SEQ_EVENT_CONTROLLER:
+		// handle_cc(ev->data.control)
+		channel = ev->data.control.channel;
+		midicontrol = ev->data.control.param;
+		midivalue = ev->data.control.value;
+		printf("MidiControl: CC chan %d, ctrl %d, val %d\n", channel, midicontrol, midivalue);
+		diff = midivalue - 64;
+		abs = (midivalue < 64) ? midivalue / 128. : (midivalue-1) / 126.;
+		/*
+		 * search for param in list of params
+		 * for each match: interpret as rel/abs?
+		 *   choose correct function (switch)
+		 *   run the function.
+		 */
+		switch (ev->data.control.param) {
+		// Control 1: Left wheel for view window.
+		case 1:
+			edl = mwindow->vwindow->get_edl();
+			if (!diff) break;
+			if (!edl) break;
+			total_length = edl->tracks->total_length();
+			current = mwindow->vwindow->gui->slider->get_value();
+			frame_rate = edl->session->frame_rate;
+			// Calculate difference in half-frames
+			delta = (double)diff / frame_rate / 4.;
+			current += delta;
+			if (current > total_length) current = total_length;
+			if (current < 0) current = 0;
+			mwindow->vwindow->gui->transport->handle_transport(STOP, 0, 0);
+			edl->local_session->set_selectionstart(current);
+			edl->local_session->set_selectionend(current);
+			mwindow->vwindow->gui->slider->set_position();
+			mwindow->vwindow->playback_engine->que->send_command(CURRENT_FRAME,
+									     CHANGE_NONE,
+									     edl,
+									     1);
+			break;
+	
+		// Control 3: Right wheel for compositor window.
+		case 3:
+			edl = mwindow->edl;
+			if (!diff) break;
+			if (!edl) break;
+			total_length = edl->tracks->total_playable_length();
+			current = edl->local_session->get_selectionstart(1);
+			frame_rate = edl->session->frame_rate;
+			// Calculate difference in half-frames
+			delta = (double)diff / frame_rate / 4.;
+			current += delta;
+			if (current > total_length) current = total_length;
+			if (current < 0) current = 0;
+			mwindow->gui->mbuttons->transport->handle_transport(STOP, 1, 0, 0);
+			// current = edl->align_to_frame(current, 1);
+			edl->local_session->set_selectionstart(current);
+			edl->local_session->set_selectionend(current);
+			mwindow_needs_update = 1;
+			mwindow->cwindow->gui->slider->set_position();
+			mwindow->cwindow->playback_engine->que->send_command(CURRENT_FRAME,
+									     CHANGE_NONE,
+									     edl,
+									     1);
+			break;
+#if 0
+		case 17:
+			// Dragging an edit handle
+			TrackCanvas *track_canvas = mwindow->gui->canvas;
+			edl = mwindow->edl;
+			current = mwindow->session->drag_position;
+			frame_rate = edl->session->frame_rate;
+			// Calculate difference in half-frames
+			delta = (double)diff / frame_rate / 4.;
+
+			new_position = current + delta;
+			new_position = 
+				edl->align_to_frame(new_position, 0);
+			if(new_position != mwindow->session->drag_position) {
+				mwindow->session->drag_position = new_position;
+				mwindow->gui->mainclock->update(new_position);
+			}
+			break;
+#endif
+		}
+		break;
+	case SND_SEQ_EVENT_NOTEON:
+		channel = ev->data.note.channel;
+		midicontrol = ev->data.note.note;
+		midivalue = ev->data.note.velocity;
+		printf("MidiControl: KeyOn chan %d, note %d, vel %d\n", channel, midicontrol, midivalue);
+		if (!(ev->data.note.velocity)) break;
+		switch (ev->data.note.note) {
+		case 17: // VIEWER_SET_INPOINT
+			mwindow->vwindow->set_inpoint(); break;
+		case 21: // VIEWER_SET_OUTPOINT
+			mwindow->vwindow->set_outpoint(); break;
+		case 16: // VIEWER_SPLICE
+			mwindow->vwindow->gui->edit_panel->splice_selection(); break;
+		case 22: // VIEWER_OVERWRITE
+			mwindow->vwindow->gui->edit_panel->overwrite_selection(); break;
+		case 18: // VIEWER_GOTO_START
+			mwindow->vwindow->goto_start(); break;
+		case 20: // VIEWER_GOTO_END
+			mwindow->vwindow->goto_end(); break;
+		case 55: // VIEWER_PLAY
+			mwindow->vwindow->gui->transport->handle_transport(NORMAL_FWD, 0, 0); break;
+		case 23: // TOGGLE_EDIT_MODE
+			mwindow->toggle_editing_mode(); break;
+		case 65: // ASSETS_NEXT
+			AWindowAssets *asset_list;
+			asset_list = mwindow->awindow->gui->asset_list;
+			asset_list->select_next(0);
+			asset_list->center_selection();
+			break;
+		case 67: // ASSETS_PREV
+			asset_list = mwindow->awindow->gui->asset_list;
+			asset_list->select_previous(0);
+			asset_list->center_selection();
+			break;
+		case 69: // ASSETS_VIEW
+			asset_list = mwindow->awindow->gui->asset_list;
+			if (!(asset_list->get_selection(0, 0))) break;
+                        if (!(((AssetPicon*)(asset_list->get_selection(0, 0)))->asset)) break;
+			mwindow->vwindow->change_source(((AssetPicon*)(asset_list->get_selection(0, 0)))->asset);
+			break;
+		case 68: // PREV_EDIT_HANDLE
+			mwindow->gui->mbuttons->transport->handle_transport(STOP, 1, 0, 0);
+			mwindow->gui->lock_window("MidiControl::handle_event");
+			mwindow->prev_edit_handle(0); // 1 to hold shift (select range)
+			mwindow->gui->unlock_window();
+			break;
+		case 66: // NEXT_EDIT_HANDLE
+			mwindow->gui->mbuttons->transport->handle_transport(STOP, 1, 0, 0);
+			mwindow->gui->lock_window("MidiControl::handle_event");
+			mwindow->next_edit_handle(0); // 1 to hold shift (select range)
+			mwindow->gui->unlock_window();
+			break;
+		case 34: // GOTO_START
+			mwindow->gui->mbuttons->transport->handle_transport(REWIND, 1);
+			mwindow->gui->lock_window("MidiControl::handle_event");
+			mwindow->goto_start();
+			mwindow->gui->unlock_window();
+			break;
+		case 36: // GOTO_END
+			mwindow->gui->mbuttons->transport->handle_transport(GOTO_END, 1);
+			mwindow->gui->lock_window("MidiControl::handle_event");
+			mwindow->goto_end();
+			mwindow->gui->unlock_window();
+			break;
+		case 33: // SET_INPOINT
+			mwindow->set_inpoint(1);
+			break;
+		case 37: // SET_OUTPOINT
+			mwindow->set_outpoint(1);
+			break;
+		case 54: // PLAY
+			mwindow->gui->mbuttons->transport->handle_transport(NORMAL_FWD, 1); break;
+		}
+		break;
+	case SND_SEQ_EVENT_NOTEOFF:
+		channel = ev->data.note.channel;
+		midicontrol = ev->data.note.note;
+		midivalue = ev->data.note.velocity;
+		printf("MidiControl: KeyOff chan %d, note %d, vel %d\n", channel, midicontrol, midivalue);
+		break;
+	case SND_SEQ_EVENT_PITCHBEND:
+		channel = ev->data.control.channel;
+		int value = ev->data.control.value;
+		printf("MidiControl: Pitch chan %d, val %d\n", channel, value);
+		break;
+	}
+	
+}
+
+void MidiControl::run()
+{
+	int num_polls;
+	struct pollfd *polls;
+	int rt, idle;
+
+	DEBUG("entering...");
+	// MIDI listener here
+	num_polls = snd_seq_poll_descriptors_count(seq_handle, POLLIN);
+	polls = (pollfd *)alloca(sizeof(*polls) * num_polls);
+	snd_seq_poll_descriptors(seq_handle, polls, num_polls, POLLIN);
+
+	while (true) {
+		DEBUG("poll loop...");
+		rt = poll(polls, num_polls, 1000);
+		if (rt < 0) continue;
+		idle = true;
+		do {
+			snd_seq_event_t * ev;
+			if (snd_seq_event_input(seq_handle, &ev) >= 0 && ev) {
+				handle_event(ev);
+				idle = false;
+			}
+		} while (snd_seq_event_input_pending(seq_handle, 0) > 0);
+		// Deferred slow main window update happens here:
+		if (mwindow_needs_update && idle) {
+			mwindow_needs_update = 0;
+			mwindow->gui->lock_window();
+			mwindow->find_cursor();
+			mwindow->gui->update(1, 1, 1, 1, 1, 1, 0);
+			mwindow->gui->unlock_window();
+		}
+	}
+}
+
+
+
+
+void MidiControl::send_key(int key, int velocity)
+{
+	snd_seq_event_t ev;
+	DEBUG("entering...");
+
+	// Initialize the event structure
+	snd_seq_ev_set_direct(&ev);
+	snd_seq_ev_set_source(&ev, port);
+
+	// Send to all subscribers
+	snd_seq_ev_set_dest(&ev, SND_SEQ_ADDRESS_SUBSCRIBERS, 0);
+
+	// Send keypress event
+        snd_seq_ev_set_noteon(&ev, channel, key, velocity);
+	snd_seq_event_output_direct(seq_handle, &ev);
+}
+
+void MidiControl::send_cc(int control, int value)
+{
+	snd_seq_event_t ev;
+	DEBUG("entering...");
+
+	// Initialize the event structure
+	snd_seq_ev_set_direct(&ev);
+	snd_seq_ev_set_source(&ev, port);
+
+	// Send to all subscribers
+	snd_seq_ev_set_dest(&ev, SND_SEQ_ADDRESS_SUBSCRIBERS, 0);
+
+	// Send control change
+	snd_seq_ev_set_controller(&ev, channel, control, value);
+	snd_seq_event_output_direct(seq_handle, &ev);
+}
+
+void MidiControl::set_channel(int channel)
+{
+	DEBUG("entering...");
+	this->channel = channel;
+}
+
Index: cinelerra/midicontrol.h
===================================================================
--- cinelerra/midicontrol.h	(revision 0)
+++ cinelerra/midicontrol.h	(revision 0)
@@ -0,0 +1,38 @@
+#ifndef MIDICONTROL_H
+#define MIDICONTROL_H
+
+
+#include "mwindow.inc"
+#include "thread.h"
+#include <alsa/asoundlib.h>
+
+// ================================= Listen to MIDI input in a thread
+// Listens to a MIDI input and performs callbacks accordingly.
+
+class MidiControl : public Thread
+{
+public:
+	MidiControl(MWindow *mwindow);
+	~MidiControl();
+
+	void run();
+
+	void set_channel(int ch);
+
+	void send_key(int key, int velocity);
+	void send_cc(int param, int value);
+
+private:
+	void handle_event(snd_seq_event_t *ev);
+
+	MWindow *mwindow;
+	int mwindow_needs_update;
+
+	int channel;
+
+	snd_seq_t *seq_handle;
+	snd_seq_port_info_t *port_info;
+	int port;
+};
+
+#endif
Index: cinelerra/mwindow.C
===================================================================
--- cinelerra/mwindow.C	(revision 1055)
+++ cinelerra/mwindow.C	(working copy)
@@ -82,6 +82,7 @@
 #include "wavecache.h"
 #include "zoombar.h"
 #include "exportedl.h"
+#include "midicontrol.h"
 
 #include <string.h>
 
@@ -647,6 +648,11 @@
 	channeldb_v4l2jpeg->load("channeldb_v4l2jpeg");
 }
 
+void MWindow::init_midicontrol()
+{
+	midi_control = new MidiControl(this);
+}
+
 void MWindow::init_menus()
 {
 	char string[BCTEXTLEN];
@@ -1311,6 +1317,8 @@
 SET_TRACE
 	init_channeldb();
 SET_TRACE
+	init_midicontrol();
+SET_TRACE
 
 	init_gui();
 	init_gwindow();
@@ -1353,6 +1361,8 @@
 	hide_splash();
 SET_TRACE
 	init_shm();
+
+	midi_control->start(); 
 }
 
 
Index: cinelerra/mwindow.h
===================================================================
--- cinelerra/mwindow.h	(revision 1055)
+++ cinelerra/mwindow.h	(working copy)
@@ -62,6 +62,7 @@
 #include "videowindow.inc"
 #include "vwindow.inc"
 #include "wavecache.inc"
+#include "midicontrol.inc"
 
 #include <stdint.h>
 
@@ -410,6 +411,8 @@
 	MainUndo *undo;
 	BC_Hash *defaults;
 	Assets *assets;
+// MIDI Control
+	MidiControl *midi_control;
 // CICaches for drawing timeline only
 	CICache *audio_cache, *video_cache;
 // Frame cache for drawing timeline only.
@@ -527,6 +530,7 @@
 	void init_levelwindow();
 	void init_viewer();
 	void init_cache();
+	void init_midicontrol();
 	void init_menus();
 	void init_indexes();
 	void init_gui();

Reply via email to