/* * Copyleft (C) 2000 David Griffiths * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "Midi.h" #include "unistd.h" #include "sys/types.h" #include "signal.h" #include "pthread.h" static const int MIDI_SCANBUFSIZE=256; static const int MIDI_KEYOFFSET=0; static const unsigned char STATUS_START = 0x80; static const unsigned char STATUS_NOTE_OFF = 0x80; static const unsigned char STATUS_NOTE_ON = 0x90; static const unsigned char STATUS_AFTERTOUCH = 0xa0; static const unsigned char STATUS_CONTROL_CHANGE = 0xb0; static const unsigned char STATUS_PROG_CHANGE = 0xc0; static const unsigned char STATUS_CHANNEL_PRESSURE = 0xd0; static const unsigned char STATUS_PITCH_WHEEL = 0xe0; static const unsigned char STATUS_END = 0xf0; static const unsigned char SYSEX_START = 0xf0; static const unsigned char SYSEX_TERMINATOR = 0xf7; static const unsigned char MIDI_CLOCK = 0xf8; static const unsigned char ACTIVE_SENSE = 0xfe; static int NKEYS = 30; MidiDevice *MidiDevice::m_Singleton; string MidiDevice::m_AppName; #if __APPLE__ #define read AppleRead #endif #ifdef USE_OSS_MIDI string MidiDevice::m_DeviceName; #endif void MidiDevice::Init(const string &name, Type t) { if (!m_Singleton) { m_AppName=name; m_Singleton=new MidiDevice(t); } } MidiDevice::MidiDevice(Type t) : m_Poly (1), m_Clock (1.0f), m_ClockCount (0) { #if __APPLE__ AppleOpen(); #endif #ifdef USE_ALSA_MIDI AlsaOpen(); #endif #ifdef USE_OSS_MIDI if (!OssOpen()) return; #endif m_Mutex = new pthread_mutex_t; pthread_mutex_init (m_Mutex, NULL); pthread_create (&m_MidiReader, NULL, (void*(*)(void*))MidiDevice::MidiReaderCallback, (void*)this); } MidiDevice::~MidiDevice() { pthread_mutex_lock (m_Mutex); pthread_cancel (m_MidiReader); pthread_mutex_unlock (m_Mutex); pthread_mutex_destroy (m_Mutex); #if __APPLE__ AppleClose(); #endif #ifdef USE_ALSA_MIDI AlsaClose(); #endif #ifdef USE_OSS_MIDI OssClose(); #endif } // returns the next event off the list, or an // empty event if the list is exhausted MidiEvent MidiDevice::GetEvent(int Device) { if (Device<0 || Device>15) { cerr<<"GetEvent: Invalid Midi device "<15) { cerr<<"SendEvent: Invalid Midi device "<=STATUS_END && *c!=MIDI_CLOCK); } // collect events deals with the byte level messages, and sorts // and filters them into distinct messages we can handle easily void MidiDevice::OssCollectEvents() { unsigned char buf[1]; int count,n,nn; bool MessageSent; unsigned char data[3],last=0; // constantly scan for relevent input, // and write it to the pipe // filters out unhandled messages, and attempts to build // coherent messages to send to the midi handler bool InSysex=false; for(;;) { OssReadByte(buf); if (*buf==MIDI_CLOCK) { m_ClockCount++; if (m_ClockCount==6) { m_Clock=-m_Clock; m_ClockCount=0; } } else if (*buf>=STATUS_START) // we've got a status byte { if (*buf==SYSEX_TERMINATOR) InSysex=false; // find out if it's an opcode if(*buf>=STATUS_START && *buf<=STATUS_END) { InSysex=false; last=data[0]=*buf; if (data[0]>=STATUS_PROG_CHANGE && data[0]=STATUS_PROG_CHANGE && data[0]= STATUS_NOTE_OFF && midi[0] < STATUS_NOTE_ON) { MessageType=MidiEvent::OFF; Note=midi[1]-MIDI_KEYOFFSET; EventDevice=midi[0]-STATUS_NOTE_OFF; } // note on else if (midi[0] >= STATUS_NOTE_ON && midi[0] < STATUS_AFTERTOUCH) { Volume = midi[2]; // cope with Roland equipment, where note on's // with zero velocity are sent as note offs. if (Volume) MessageType=MidiEvent::ON; else MessageType=MidiEvent::OFF; Note=midi[1]-MIDI_KEYOFFSET; EventDevice=midi[0]-STATUS_NOTE_ON; } // aftertouch else if (midi[0] >= STATUS_AFTERTOUCH && midi[0] < STATUS_CONTROL_CHANGE) { MessageType=MidiEvent::AFTERTOUCH; Note=midi[1]-MIDI_KEYOFFSET; Volume=midi[2]; EventDevice=midi[0]-STATUS_AFTERTOUCH; } // parameter else if (midi[0] >= STATUS_CONTROL_CHANGE && midi[0] < STATUS_PROG_CHANGE) { MessageType=MidiEvent::PARAMETER; Note=midi[1]; Volume=midi[2]; EventDevice=midi[0]-STATUS_CONTROL_CHANGE; } // channel pressure else if (midi[0] >= STATUS_CHANNEL_PRESSURE && midi[0] < STATUS_PITCH_WHEEL) { MessageType=MidiEvent::CHANNELPRESSURE; Volume=midi[1]; EventDevice=midi[0]-STATUS_CHANNEL_PRESSURE; } // note pitchbend else if (midi[0] >= STATUS_PITCH_WHEEL && midi[0] < STATUS_END) { MessageType=MidiEvent::PITCHBEND; // should probably take the first byte into account too? Volume=midi[2]; EventDevice=midi[0]-STATUS_PITCH_WHEEL; } if (EventDevice<0 || EventDevice>15) { cerr<<"Error - Midi device "< 0) { for (l1 = 0; l1 < seq_nfds; l1++) { if (pfds[l1].revents > 0) { snd_seq_event_t *ev; // this line looks suspect to me (Andy Preston) // int l1; MidiEvent::type MessageType=MidiEvent::NONE; int Volume=0, Note=0, EventDevice=0; do { snd_seq_event_input (seq_rhandle, &ev); if ((ev->type == SND_SEQ_EVENT_NOTEON) && (ev->data.note.velocity == 0)) { ev->type = SND_SEQ_EVENT_NOTEOFF; } switch (ev->type) { case SND_SEQ_EVENT_PITCHBEND: // Andy Preston MessageType=MidiEvent::PITCHBEND; Volume = (char)((ev->data.control.value / 8192.0)*256); break; case SND_SEQ_EVENT_CONTROLLER: MessageType=MidiEvent::PARAMETER; Note = ev->data.control.param; Volume = ev->data.control.value; break; case SND_SEQ_EVENT_NOTEON: MessageType=MidiEvent::ON; EventDevice = ev->data.control.channel; Note = ev->data.note.note; Volume = ev->data.note.velocity; break; case SND_SEQ_EVENT_NOTEOFF: MessageType=MidiEvent::ON; EventDevice = ev->data.control.channel; Note = ev->data.note.note; break; } pthread_mutex_lock (m_Mutex); m_EventVec[EventDevice].push (MidiEvent (MessageType, Note, Volume)); pthread_mutex_unlock (m_Mutex); snd_seq_free_event (ev); } while (snd_seq_event_input_pending(seq_rhandle, 0) > 0); } } } } delete [] pfds; } void MidiDevice::AlsaSendEvent (int Device, const MidiEvent &Event) { //As Alsa only supports a read or write, we use the write handle here to send //our MIDI events snd_seq_event_t ev; snd_seq_ev_clear (&ev); snd_seq_ev_set_direct (&ev); snd_seq_ev_set_subs (&ev); snd_seq_ev_set_source (&ev, 0); switch (Event.GetType()) { case MidiEvent::ON: ev.type = SND_SEQ_EVENT_NOTEON; break; case MidiEvent::OFF: ev.type = SND_SEQ_EVENT_NOTEOFF; break; /* case MidiEvent::PARAMETER: ev.type = SND_SEQ_EVENT_CONTROLLER; ev.data.control.param = Event.GetNote(); ev.data.control.value = break; case MidiEvent::PITCHBEND: ev.type = SND_SEQ_EVENT_PITCHBEND; ev.data.control.param = Event.GetNote(); ev.data.control.value = break;*/ default: break; } ev.data.note.velocity = (char)Event.GetVolume()*127; ev.data.control.channel = Device; ev.data.note.note=Event.GetNote(); snd_seq_event_output(seq_whandle, &ev); snd_seq_drain_output(seq_whandle); } void MidiDevice::AlsaOpen() { int client_id, port_id; //Alsa apears to require two handles, one for read and one for write //so we try to open one first for input and then one for output //open input handle if (snd_seq_open(&seq_rhandle, "default", SND_SEQ_OPEN_INPUT, 0) < 0) { fprintf(stderr, "Error opening ALSA input sequencer.\n"); exit(1); } //setup our input name as seen by other apps, and get corresponding client id snd_seq_set_client_name(seq_rhandle, m_AppName.c_str()); client_id = snd_seq_client_id(seq_rhandle); //try and create our actual input port capable of being written to by MIDI outputs if ((port_id = snd_seq_create_simple_port(seq_rhandle, m_AppName.c_str(), SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_APPLICATION) < 0)) { fprintf(stderr, "Error creating input sequencer port.\n"); } //open output handle if (snd_seq_open(&seq_whandle, "default", SND_SEQ_OPEN_OUTPUT, 0) < 0) { fprintf(stderr, "Error opening ALSA ouput sequencer.\n"); exit(1); } //setup our output name as seen by other apps, and get corresponding client id snd_seq_set_client_name(seq_whandle, m_AppName.c_str()); client_id = snd_seq_client_id(seq_whandle); //try and create our actual output port capable of being read from by MIDI inputs if ((port_id = snd_seq_create_simple_port(seq_whandle, m_AppName.c_str(), SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_APPLICATION) < 0)) { fprintf(stderr, "Error creating output sequencer port.\n"); } } #endif //////////////////////////////////////////// Apple Code Only ///////////////////////////////////////// #if __APPLE__ void MidiDevice::AppleOpen() { m_ReadFillIndex = m_ReadReadIndex = 0; OSStatus err = 0; mMIDISource = NULL; mMIDIClient = NULL; mMIDIDestination = NULL; err = MIDIClientCreate(CFSTR("org.pawpal.ssm"), NULL, NULL, &mMIDIClient); if (err) printf("MIDIClientCreate failed returned %d\n", err); if (!err) { err = MIDISourceCreate(mMIDIClient, CFSTR("SpiralSynth"), &mMIDISource); if (err) printf("MIDIInputPortCreate failed returned %d\n", err); } if (!err) { err = MIDIDestinationCreate(mMIDIClient, CFSTR("SpiralSynth"), sMIDIRead, this, &mMIDIDestination); MIDIObjectSetIntegerProperty(mMIDIDestination, kMIDIPropertyUniqueID, 'SSmP'); } } void MidiDevice::AppleClose() { if (mMIDIDestination) MIDIEndpointDispose(mMIDIDestination); if (mMIDISource) MIDIEndpointDispose(mMIDISource); mMIDISource = NULL; if (mMIDIClient) MIDIClientDispose(mMIDIClient); mMIDIClient = NULL; } int MidiDevice::AppleWrite(int dummy, unsigned char *outbuffer, int maxlen) { return 0; } int MidiDevice::AppleRead(int dummy, unsigned char *outbuffer, int maxlen) { if (!mMIDIClient) return -1; int len = 0; do { while (m_ReadReadIndex == m_ReadFillIndex) usleep(1000); // 1ms int readl = m_ReadFillIndex - m_ReadReadIndex; if (readl < 0) readl += midi_ReadSize; // wrapped while (len < maxlen && readl-- > 0) { int r = m_ReadReadIndex; outbuffer[len++] = m_ReadBuffer[r]; r++; m_ReadReadIndex = r % midi_ReadSize; } } while (len < maxlen); return len; } void MidiDevice::sMIDIRead(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon) { MidiDevice & t = *((MidiDevice*)readProcRefCon); const MIDIPacket *packet = &pktlist->packet[0]; for (int i = 0; i < (int)pktlist->numPackets; i++) { const MIDIPacket & p = *packet; for (int b = 0; b < p.length; b++) { // printf("%02x ", p.data[b]); int d = t.m_ReadFillIndex; t.m_ReadBuffer[d] = p.data[b]; d++; t.m_ReadFillIndex = d % midi_ReadSize; } // printf("\n"); packet = MIDIPacketNext(packet); } } #endif