From fdea4486f8c9010d523e61ff040ca3d6711c4943 Mon Sep 17 00:00:00 2001 From: "Tim E. Real" Date: Thu, 28 Jan 2010 03:15:12 +0000 Subject: Initial Jack Midi support. --- muse/ChangeLog | 1 + muse/muse/driver/Makefile.am | 3 +- muse/muse/driver/jack.cpp | 153 +++++++++++++++++++++- muse/muse/driver/jackmidi.cpp | 289 ++++++++++++++++++++++++++++++++++++++++++ muse/muse/driver/jackmidi.h | 59 +++++++++ muse/muse/mididev.cpp | 8 ++ muse/muse/midiseq.cpp | 11 +- 7 files changed, 518 insertions(+), 6 deletions(-) create mode 100644 muse/muse/driver/jackmidi.cpp create mode 100644 muse/muse/driver/jackmidi.h diff --git a/muse/ChangeLog b/muse/ChangeLog index 2aa745d8..9d027c74 100644 --- a/muse/ChangeLog +++ b/muse/ChangeLog @@ -3,6 +3,7 @@ * Added: Arranger track list: Quick 'right-click' or 'ctrl-click' or 'ctrl-mouse-wheel' toggling of Track On/Off. (T356) - Note this includes midi tracks now! Remains to be seen whether allowing midi off is useful and will work, or should be filtered out. **TODO: Turn off remaining controls in midi strips, turn off actual midi playback and record. + * Feature: Initial Jack midi support. Imported from Larry Valkama's GIT repo. (T356) 26.01.2010 * Fixed: Import midi 'replace' broken last fixes. (T356) * Fixed: External midi sync: Wait until first clock after start to start transport, and >= second clock to increment ticks. (T356) diff --git a/muse/muse/driver/Makefile.am b/muse/muse/driver/Makefile.am index 6f23ed43..689b6c95 100644 --- a/muse/muse/driver/Makefile.am +++ b/muse/muse/driver/Makefile.am @@ -5,5 +5,6 @@ noinst_LIBRARIES = libdriver.a AM_CXXFLAGS += $(JACK_CFLAGS) libdriver_a_SOURCES = audiodev.h alsamidi.cpp alsamidi.h jack.cpp jackaudio.h \ - dummyaudio.cpp alsatimer.cpp alsatimer.h timerdev.h rtctimer.cpp rtctimer.h + dummyaudio.cpp alsatimer.cpp alsatimer.h timerdev.h rtctimer.cpp rtctimer.h \ + jackmidi.cpp diff --git a/muse/muse/driver/jack.cpp b/muse/muse/driver/jack.cpp index 396f6b35..4674f460 100644 --- a/muse/muse/driver/jack.cpp +++ b/muse/muse/driver/jack.cpp @@ -11,6 +11,7 @@ #include //#include #include +#include #include "audio.h" #include "globals.h" @@ -22,6 +23,9 @@ #include "sync.h" #include "utils.h" +#include "jackmidi.h" + + #define JACK_DEBUG 0 //#include "errorhandler.h" @@ -35,6 +39,15 @@ extern void undoSetuid(); #include #endif +extern int jackmidi_pi[2]; +extern int jackmidi_po[2]; + +jack_port_t *midi_port_in[JACK_MIDI_CHANNELS]; +jack_port_t *midi_port_out[JACK_MIDI_CHANNELS]; + +muse_jack_midi_buffer jack_midi_out_data[JACK_MIDI_CHANNELS]; +muse_jack_midi_buffer jack_midi_in_data[JACK_MIDI_CHANNELS]; + JackAudioDevice* jackAudio; //--------------------------------------------------------- @@ -105,12 +118,117 @@ static void jack_thread_init (void* ) // data } //--------------------------------------------------------- -// processAudio +// processAudio + Midi // JACK callback //--------------------------------------------------------- +void +print_triplet(unsigned char *data) +{ + int a,b,c; + a = b = c = 0; + memcpy(&a, data, 1); + memcpy(&b, data+1, 1); + memcpy(&c, data+2, 1); + fprintf(stderr, "%x,%x,%x", a, b, c); +} + +void handle_jack_midi_in_events(jack_nframes_t frames) +{ + char buf = 0; + int i,j; + jack_midi_event_t midi_event; + unsigned char t,n,v; + + for(j = 0; j < JACK_MIDI_CHANNELS; j++){ + void *midi_buffer_in = jack_port_get_buffer(midi_port_in[j], frames); + int event_count = jack_midi_get_event_count(midi_buffer_in); + + for(i = 0; i < event_count; i++){ + jack_midi_event_get(&midi_event, midi_buffer_in, i); + t = midi_event.buffer[0]; + n = midi_event.buffer[1]; + v = midi_event.buffer[2]; + if(((*(midi_event.buffer) & 0xf0)) == 0x90){ + fprintf(stderr, "jack-midi-in-event: ON_ time=%d %u ", midi_event.time, + midi_event.size); + print_triplet(midi_event.buffer); + fprintf(stderr, "\n"); + }else if(((*(midi_event.buffer)) & 0xf0) == 0x80){ + fprintf(stderr, "jack-midi-in-event: OFF time=%d %u ", midi_event.time, + midi_event.size); + print_triplet(midi_event.buffer); + fprintf(stderr, "\n"); + }else{ + fprintf(stderr, "jack-midi-in-event: ??? time=%d %u ", midi_event.time, + midi_event.size); + print_triplet(midi_event.buffer); + fprintf(stderr, "\n"); + } + jack_midi_in_data[j].buffer[0] = t; + jack_midi_in_data[j].buffer[1] = n; + jack_midi_in_data[j].buffer[2] = v; + jack_midi_in_data[j].buffer[3] = 1; + fprintf(stderr, "handle_jack_midi_in_events() w\n"); + write(jackmidi_pi[1], &buf, 1); + fprintf(stderr, "handle_jack_midi_in_events() wd\n"); + } + } +} + +void handle_jack_midi_out_events(jack_nframes_t frames) +{ + unsigned char *data; + void *port_buf; + int i,j,n,x; + + for(i = 0; i < JACK_MIDI_CHANNELS; i++){ + /* jack-midi-clear any old events */ + while(jack_midi_out_data[i].buffer[jack_midi_out_data[i].take*4+3] == 2){ + port_buf = jack_port_get_buffer(midi_port_out[i], frames); + jack_midi_clear_buffer(port_buf); + jack_midi_out_data[i].buffer[jack_midi_out_data[i].take*4+3] = 0; + /* point the take to the next slot */ + jack_midi_out_data[i].take++; + if(jack_midi_out_data[i].take >= JACK_MIDI_BUFFER_SIZE){ + jack_midi_out_data[i].take = 0; + } + } + /* check if any incoming midi-events from muse */ + if(jack_midi_out_data[i].give != jack_midi_out_data[i].take){ + + if(jack_midi_out_data[i].give > jack_midi_out_data[i].take){ + n = jack_midi_out_data[i].give - jack_midi_out_data[i].take; + }else{ + n = jack_midi_out_data[i].give + + (JACK_MIDI_BUFFER_SIZE - jack_midi_out_data[i].take); + } + port_buf = jack_port_get_buffer(midi_port_out[i], frames); + jack_midi_clear_buffer(port_buf); + /* FIX: midi events has different sizes, compare note-on to + program-change. We should first walk over the events + counting the size. */ + data = jack_midi_event_reserve(port_buf, 0, n*3); + x = jack_midi_out_data[i].take; + for(j = 0; j < n; j++){ + data[j*3+0] = jack_midi_out_data[i].buffer[x*4+0]; + data[j*3+1] = jack_midi_out_data[i].buffer[x*4+1]; + data[j*3+2] = jack_midi_out_data[i].buffer[x*4+2]; + /* after having copied the buffer over to the jack-buffer, + * mark the muses midi-out buffer as 'need-cleaning' */ + jack_midi_out_data[i].buffer[x*4+3] = 2; + x++; + if(x >= JACK_MIDI_BUFFER_SIZE){ + x = 0; + } + } + } + } +} static int processAudio(jack_nframes_t frames, void*) - { +{ + handle_jack_midi_in_events(frames); + handle_jack_midi_out_events(frames); // if (JACK_DEBUG) // printf("processAudio - >>>>\n"); segmentSize = frames; @@ -122,8 +240,8 @@ static int processAudio(jack_nframes_t frames, void*) } // if (JACK_DEBUG) // printf("processAudio - <<<<\n"); - return 0; - } + return 0; +} //--------------------------------------------------------- // processSync @@ -382,6 +500,33 @@ bool initJackAudio() } undoSetuid(); + /* setup midi input/output */ + memset(jack_midi_out_data, 0, JACK_MIDI_CHANNELS * sizeof(muse_jack_midi_buffer)); + memset(jack_midi_in_data, 0, JACK_MIDI_CHANNELS * sizeof(muse_jack_midi_buffer)); + if(client){ + for(i = 0; i < JACK_MIDI_CHANNELS; i++){ + char buf[80]; + snprintf(buf, 80, "muse-jack-midi-in-%d", i+1); + midi_port_in[i] = jack_port_register(client, buf, + JACK_DEFAULT_MIDI_TYPE, + JackPortIsInput, 0); + if(midi_port_in[i] == NULL){ + fprintf(stderr, "failed to register jack-midi-in\n"); + exit(-1); + } + snprintf(buf, 80, "muse-jack-midi-out-%d", i+1); + midi_port_out[i] = jack_port_register(client, buf, + JACK_DEFAULT_MIDI_TYPE, + JackPortIsOutput, 0); + if(midi_port_out == NULL){ + fprintf(stderr, "failed to register jack-midi-out\n"); + exit(-1); + } + } + }else{ + fprintf(stderr, "WARNING NO muse-jack midi connection\n"); + } + if (client) { audioDevice = jackAudio; return false; diff --git a/muse/muse/driver/jackmidi.cpp b/muse/muse/driver/jackmidi.cpp new file mode 100644 index 00000000..fd9c87ce --- /dev/null +++ b/muse/muse/driver/jackmidi.cpp @@ -0,0 +1,289 @@ +//========================================================= +// MusE +// Linux Music Editor +// $Id: jackmidi.cpp,v 1.1.1.1 2010/01/27 09:06:43 terminator356 Exp $ +// (C) Copyright 1999-2010 Werner Schweer (ws@seh.de) +//========================================================= + +#include + +#include +#include + +#include "jackmidi.h" +#include "globals.h" +#include "midi.h" +#include "mididev.h" +#include "../midiport.h" +#include "../midiseq.h" +#include "../midictrl.h" +#include "../audio.h" +#include "mpevent.h" +//#include "sync.h" + +int jackmidi_pi[2]; +int jackmidi_po[2]; + +extern muse_jack_midi_buffer jack_midi_out_data[JACK_MIDI_CHANNELS]; +extern muse_jack_midi_buffer jack_midi_in_data[JACK_MIDI_CHANNELS]; + +MidiJackDevice* gmdev = NULL; + +int* jackSeq; +//static snd_seq_addr_t musePort; + +//--------------------------------------------------------- +// MidiAlsaDevice +//--------------------------------------------------------- + +MidiJackDevice::MidiJackDevice(const int& a, const QString& n) + : MidiDevice(n) +{ + adr = a; + init(); +} + +//--------------------------------------------------------- +// select[RW]fd +//--------------------------------------------------------- + +int MidiJackDevice::selectRfd() +{ + return jackmidi_pi[0]; +} + +int MidiJackDevice::selectWfd() +{ + return jackmidi_po[0]; +} + +//--------------------------------------------------------- +// open +//--------------------------------------------------------- + +QString MidiJackDevice::open() +{ + _readEnable = true; + _writeEnable = true; + + + return QString("OK"); +} + +//--------------------------------------------------------- +// close +//--------------------------------------------------------- + +void MidiJackDevice::close() +{ + _readEnable = false; + _writeEnable = false; +} + +//--------------------------------------------------------- +// putEvent +//--------------------------------------------------------- + +/* FIX: if we fail to transmit the event, + * we return false (indicating OK). Otherwise + * it seems muse will retry forever + */ +bool MidiJackDevice::putMidiEvent(const MidiPlayEvent& event) +{ + int give, channel = event.channel(); + int x; + + if(channel >= JACK_MIDI_CHANNELS) return false; + + /* buffer up events, because jack eats them in chunks, if + * the buffer is full, there isn't so much to do, than + * drop the event + */ + give = jack_midi_out_data[channel].give; + if(jack_midi_out_data[channel].buffer[give*4+3]){ + fprintf(stderr, "WARNING: muse-to-jack midi-buffer is full, channel=%u\n", channel); + return false; + } + /* copy event(note-on etc..), pitch and volume */ + /* see http://www.midi.org/techspecs/midimessages.php */ + switch(event.type()){ + case ME_NOTEOFF: + jack_midi_out_data[channel].buffer[give*4+0] = 0x80; + jack_midi_out_data[channel].buffer[give*4+1] = event.dataA() & 0x7f; + jack_midi_out_data[channel].buffer[give*4+2] = event.dataB() & 0x7f; + break; + case ME_NOTEON: + jack_midi_out_data[channel].buffer[give*4+0] = 0x90; + jack_midi_out_data[channel].buffer[give*4+1] = event.dataA() & 0x7f; + jack_midi_out_data[channel].buffer[give*4+2] = event.dataB() & 0x7f; + break; + case ME_CONTROLLER: + jack_midi_out_data[channel].buffer[give*4+0] = 0xb0; + jack_midi_out_data[channel].buffer[give*4+1] = event.dataA() & 0x7f; + jack_midi_out_data[channel].buffer[give*4+2] = event.dataB() & 0x7f; + break; + case ME_PROGRAM: + jack_midi_out_data[channel].buffer[give*4+0] = 0xc0; + jack_midi_out_data[channel].buffer[give*4+1] = event.dataA() & 0x7f; + jack_midi_out_data[channel].buffer[give*4+2] = 0; + break; + case ME_PITCHBEND: + jack_midi_out_data[channel].buffer[give*4+0] = 0xE0; + /* convert muse pitch-bend to midi standard */ + x = 0x2000 + event.dataA(); + jack_midi_out_data[channel].buffer[give*4+1] = x & 0x7f; + jack_midi_out_data[channel].buffer[give*4+2] = (x >> 8) & 0x7f; + break; + default: + fprintf(stderr, "jack-midi-out %u WARNING: unknown event %x\n", channel, event.type()); + return false; + } + jack_midi_out_data[channel].buffer[give*4+3] = 1; /* mark state of this slot */ + /* finally increase give position */ + give++; + if(give >= JACK_MIDI_BUFFER_SIZE){ + give = 0; + } + jack_midi_out_data[channel].give = give; + return false; +} + +//--------------------------------------------------------- +// putEvent +// return false if event is delivered +//--------------------------------------------------------- + +bool MidiJackDevice::putEvent(int* event) +{ + int *y; y = event; + return false; +} + +//--------------------------------------------------------- +// initMidiJack +// return true on error +//--------------------------------------------------------- + +bool initMidiJack() +{ + int adr = 0; + + memset(jack_midi_out_data, 0, JACK_MIDI_CHANNELS * sizeof(muse_jack_midi_buffer)); + memset(jack_midi_in_data, 0, JACK_MIDI_CHANNELS * sizeof(muse_jack_midi_buffer)); + + MidiJackDevice* dev = new MidiJackDevice(adr, QString("jack-midi")); + dev->setrwFlags(3); /* set read and write flags */ + if(pipe(jackmidi_pi) < 0){ + fprintf(stderr, "cant create midi-jack input pipe\n"); + } + if(pipe(jackmidi_po) < 0){ + fprintf(stderr, "cant create midi-jack output pipe\n"); + } + midiDevices.add(dev); + gmdev = dev; /* proclaim the global jack-midi instance */ + + return false; +} + +struct JackPort { + int adr; + char* name; + int flags; + JackPort(int a, const char* s, int f) { + adr = a; + name = strdup(s); + flags = f; + } + }; + +static std::list portList; + +//--------------------------------------------------------- +// jackScanMidiPorts +//--------------------------------------------------------- + +void jackScanMidiPorts() +{ + int adr; + const char* name; + + portList.clear(); + adr = 0; + name = strdup("namex"); + portList.push_back(JackPort(adr, name, 0)); + // + // check for devices to add + // + for (std::list::iterator k = portList.begin(); k != portList.end(); ++k) { + iMidiDevice i = midiDevices.begin(); + for (;i != midiDevices.end(); ++i) { + //MidiJackDevice* d = dynamic_cast(*i); + break; + //if (d == 0) continue; + //if ((k->adr.client == d->adr.client) && (k->adr.port == d->adr.port)) { + // break; + //} + } + if (i == midiDevices.end()) { + // add device + MidiJackDevice* dev = new MidiJackDevice(k->adr, QString(k->name)); + dev->setrwFlags(k->flags); + midiDevices.add(dev); + } + } +} + +//--------------------------------------------------------- +// processInput +//--------------------------------------------------------- +static void handle_jack_midi_in(int channel) +{ + MidiRecordEvent event; + int t,n,v; + t = jack_midi_in_data[channel].buffer[0]; + n = jack_midi_in_data[channel].buffer[1]; + v = jack_midi_in_data[channel].buffer[2]; + + event.setType(0); // mark as unused + event.setPort(gmdev->midiPort()); + event.setB(0); + + if(t == 0x90){ /* note on */ + fprintf(stderr, "jackProcessMidiInput note-on\n"); + event.setChannel(channel); + event.setType(ME_NOTEON); + event.setA(n); + event.setB(v); + }else if (t == 0x80){ /* note off */ + fprintf(stderr, "jackProcessMidiInput note-off\n"); + event.setChannel(channel); + event.setType(ME_NOTEOFF); + event.setA(n); + event.setB(v); + }else{ + fprintf(stderr, "WARNING: unknown midi-in on channel %d: %x,%x,%x\n", + channel, t, n, v); + return; + } + if(event.type()){ + gmdev->recordEvent(event); + midiPorts[gmdev->midiPort()].syncInfo().trigActDetect(event.channel()); + } +} + +void MidiJackDevice::processInput() +{ + char buf; + int i,s; + read(gmdev->selectRfd(), &buf, 1); + + s = 1; + for(i = 0; i < JACK_MIDI_CHANNELS; i++){ + if(jack_midi_in_data[i].buffer[3]){ + s = 0; + handle_jack_midi_in(i); + jack_midi_in_data[i].buffer[3] = 0; + } + } +} + diff --git a/muse/muse/driver/jackmidi.h b/muse/muse/driver/jackmidi.h new file mode 100644 index 00000000..cf651714 --- /dev/null +++ b/muse/muse/driver/jackmidi.h @@ -0,0 +1,59 @@ +//========================================================= +// MusE +// Linux Music Editor +// $Id: jackmidi.h,v 1.1.1.1 2010/01/27 09:06:43 terminator356 Exp $ +// (C) Copyright 1999-2010 Werner Schweer (ws@seh.de) +//========================================================= + +#ifndef __JACKMIDI_H__ +#define __JACKMIDI_H__ + +#include + +#include "mididev.h" + +/* jack-midi channels */ +#define JACK_MIDI_CHANNELS 32 +/* jack-midi buffer size */ +#define JACK_MIDI_BUFFER_SIZE 32 + +typedef struct { + int give; + int take; + /* 32 parallel midi events, where each event contains three + * midi-bytes and one busy-byte */ + char buffer[4 * JACK_MIDI_BUFFER_SIZE]; +} muse_jack_midi_buffer; + +//--------------------------------------------------------- +// MidiJackDevice +//--------------------------------------------------------- + +class MidiJackDevice : public MidiDevice { + public: + int adr; + + private: + virtual QString open(); + virtual void close(); + bool putEvent(int*); + virtual bool putMidiEvent(const MidiPlayEvent&); + + public: + MidiJackDevice() {} + MidiJackDevice(const int&, const QString& name); + virtual ~MidiJackDevice() {} + virtual int selectRfd(); + virtual int selectWfd(); + virtual void processInput(); + }; + +extern bool initMidiJack(); +extern int jackSelectRfd(); +extern int jackSelectWfd(); +extern void jackProcessMidiInput(); +extern void jackScanMidiPorts(); + +#endif + + diff --git a/muse/muse/mididev.cpp b/muse/muse/mididev.cpp index aec9a51d..48a8fa9f 100644 --- a/muse/muse/mididev.cpp +++ b/muse/muse/mididev.cpp @@ -29,6 +29,7 @@ extern void initMidiSerial(); #endif extern bool initMidiAlsa(); +extern bool initMidiJack(); MidiDeviceList midiDevices; extern void processMidiInputTransformPlugins(MEvent&); @@ -49,6 +50,13 @@ void initMidiDevices() "your configuration."); exit(-1); } + if(initMidiJack()) + { + QMessageBox::critical(NULL, "MusE fatal error.", "MusE failed to initialize the\n" + "Jack midi subsystem, check\n" + "your configuration."); + exit(-1); + } } //--------------------------------------------------------- diff --git a/muse/muse/midiseq.cpp b/muse/muse/midiseq.cpp index cecef2ab..e10427e5 100644 --- a/muse/muse/midiseq.cpp +++ b/muse/muse/midiseq.cpp @@ -22,6 +22,7 @@ #include "midictrl.h" #include "audio.h" #include "driver/alsamidi.h" +//#include "driver/jackmidi.h" #include "sync.h" #include "synth.h" #include "song.h" @@ -377,16 +378,24 @@ void MidiSeq::updatePollFd() for (iMidiDevice imd = midiDevices.begin(); imd != midiDevices.end(); ++imd) { MidiDevice* dev = *imd; int port = dev->midiPort(); + const QString name = dev->name(); if (port == -1) continue; if ((dev->rwFlags() & 0x2) || (extSyncFlag.value() //&& (rxSyncPort == port || rxSyncPort == -1))) { //&& (dev->syncInfo().MCIn()))) { && (midiPorts[port].syncInfo().MCIn()))) { + if(dev->selectRfd() < 0){ + fprintf(stderr, "WARNING: read-file-descriptor for {%s} is negative\n", name.latin1()); + } addPollFd(dev->selectRfd(), POLLIN, ::midiRead, this, dev); } - if (dev->bytesToWrite()) + if (dev->bytesToWrite()){ + if(dev->selectWfd() < 0){ + fprintf(stderr, "WARNING: write-file-descriptor for {%s} is negative\n", name.latin1()); + } addPollFd(dev->selectWfd(), POLLOUT, ::midiWrite, this, dev); + } } // special handling for alsa midi: // (one fd for all devices) -- cgit v1.2.3