zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

commit 5212c8fa0e90132ab8e2d4647c336e863f96f510
parent bf3140e92de0df4a1db61d3da5a0ee87e64349a6
Author: Hans Petter Selasky <hps@selasky.org>
Date:   Fri, 24 Oct 2014 12:54:44 +0200

Improve OSS support
- Add support for using OSS MIDI and OSS AUDIO simultaneously.
- Add simple MIDI command parser, which also verifies the correctness of
MIDI commands.
- Add support for handling OSS MIDI PIPEs and FIFOs instead of only
regular character devices.
- Fix OSS MIDI and OSS AUDIO teardow
- Add support for overriding the OSS MIDI device name by specifying a
"MIDI_DEVICE" environment variable, similar to the existing
"DSP_DEVICE" environment variable.

Signed-off-by: Hans Petter Selasky <hps@selasky.org>

Diffstat:
Msrc/Nio/OssEngine.cpp | 345+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/Nio/OssEngine.h | 27++++++++++++++++++++++-----
2 files changed, 271 insertions(+), 101 deletions(-)

diff --git a/src/Nio/OssEngine.cpp b/src/Nio/OssEngine.cpp @@ -33,13 +33,153 @@ #include <sys/ioctl.h> #include <unistd.h> #include <iostream> +#include <signal.h> #include "InMgr.h" using namespace std; +/* + * The following statemachine converts MIDI commands to USB MIDI + * packets, derived from Linux's usbmidi.c, which was written by + * "Clemens Ladisch". It is used to figure out when a MIDI command is + * complete, without having to read the first byte of the next MIDI + * command. This is useful when connecting to so-called system PIPEs + * and FIFOs. See "man mkfifo". + * + * Return values: + * 0: No command + * Else: Command is complete + */ +static unsigned char +OssMidiParse(struct OssMidiParse &midi_parse, + unsigned char cn, unsigned char b) +{ + unsigned char p0 = (cn << 4); + + if(b >= 0xf8) { + midi_parse.temp_0[0] = p0 | 0x0f; + midi_parse.temp_0[1] = b; + midi_parse.temp_0[2] = 0; + midi_parse.temp_0[3] = 0; + midi_parse.temp_cmd = midi_parse.temp_0; + return (1); + + } else if(b >= 0xf0) { + switch (b) { + case 0xf0: /* system exclusive begin */ + midi_parse.temp_1[1] = b; + midi_parse.state = OSSMIDI_ST_SYSEX_1; + break; + case 0xf1: /* MIDI time code */ + case 0xf3: /* song select */ + midi_parse.temp_1[1] = b; + midi_parse.state = OSSMIDI_ST_1PARAM; + break; + case 0xf2: /* song position pointer */ + midi_parse.temp_1[1] = b; + midi_parse.state = OSSMIDI_ST_2PARAM_1; + break; + case 0xf4: /* unknown */ + case 0xf5: /* unknown */ + midi_parse.state = OSSMIDI_ST_UNKNOWN; + break; + case 0xf6: /* tune request */ + midi_parse.temp_1[0] = p0 | 0x05; + midi_parse.temp_1[1] = 0xf6; + midi_parse.temp_1[2] = 0; + midi_parse.temp_1[3] = 0; + midi_parse.temp_cmd = midi_parse.temp_1; + midi_parse.state = OSSMIDI_ST_UNKNOWN; + return (1); + + case 0xf7: /* system exclusive end */ + switch (midi_parse.state) { + case OSSMIDI_ST_SYSEX_0: + midi_parse.temp_1[0] = p0 | 0x05; + midi_parse.temp_1[1] = 0xf7; + midi_parse.temp_1[2] = 0; + midi_parse.temp_1[3] = 0; + midi_parse.temp_cmd = midi_parse.temp_1; + midi_parse.state = OSSMIDI_ST_UNKNOWN; + return (1); + case OSSMIDI_ST_SYSEX_1: + midi_parse.temp_1[0] = p0 | 0x06; + midi_parse.temp_1[2] = 0xf7; + midi_parse.temp_1[3] = 0; + midi_parse.temp_cmd = midi_parse.temp_1; + midi_parse.state = OSSMIDI_ST_UNKNOWN; + return (1); + case OSSMIDI_ST_SYSEX_2: + midi_parse.temp_1[0] = p0 | 0x07; + midi_parse.temp_1[3] = 0xf7; + midi_parse.temp_cmd = midi_parse.temp_1; + midi_parse.state = OSSMIDI_ST_UNKNOWN; + return (1); + } + midi_parse.state = OSSMIDI_ST_UNKNOWN; + break; + } + } else if(b >= 0x80) { + midi_parse.temp_1[1] = b; + if((b >= 0xc0) && (b <= 0xdf)) { + midi_parse.state = OSSMIDI_ST_1PARAM; + } else { + midi_parse.state = OSSMIDI_ST_2PARAM_1; + } + } else { /* b < 0x80 */ + switch (midi_parse.state) { + case OSSMIDI_ST_1PARAM: + if(midi_parse.temp_1[1] < 0xf0) { + p0 |= midi_parse.temp_1[1] >> 4; + } else { + p0 |= 0x02; + midi_parse.state = OSSMIDI_ST_UNKNOWN; + } + midi_parse.temp_1[0] = p0; + midi_parse.temp_1[2] = b; + midi_parse.temp_1[3] = 0; + midi_parse.temp_cmd = midi_parse.temp_1; + return (1); + case OSSMIDI_ST_2PARAM_1: + midi_parse.temp_1[2] = b; + midi_parse.state = OSSMIDI_ST_2PARAM_2; + break; + case OSSMIDI_ST_2PARAM_2: + if(midi_parse.temp_1[1] < 0xf0) { + p0 |= midi_parse.temp_1[1] >> 4; + midi_parse.state = OSSMIDI_ST_2PARAM_1; + } else { + p0 |= 0x03; + midi_parse.state = OSSMIDI_ST_UNKNOWN; + } + midi_parse.temp_1[0] = p0; + midi_parse.temp_1[3] = b; + midi_parse.temp_cmd = midi_parse.temp_1; + return (1); + case OSSMIDI_ST_SYSEX_0: + midi_parse.temp_1[1] = b; + midi_parse.state = OSSMIDI_ST_SYSEX_1; + break; + case OSSMIDI_ST_SYSEX_1: + midi_parse.temp_1[2] = b; + midi_parse.state = OSSMIDI_ST_SYSEX_2; + break; + case OSSMIDI_ST_SYSEX_2: + midi_parse.temp_1[0] = p0 | 0x04; + midi_parse.temp_1[3] = b; + midi_parse.temp_cmd = midi_parse.temp_1; + midi_parse.state = OSSMIDI_ST_SYSEX_0; + return (1); + default: + break; + } + } + return (0); +} + OssEngine::OssEngine() - :AudioOut(), engThread(NULL) + :AudioOut(), audioThread(NULL), midiThread(NULL) { name = "OSS"; @@ -48,6 +188,7 @@ OssEngine::OssEngine() audio.smps = new short[synth->buffersize * 2]; memset(audio.smps, 0, synth->bufferbytes); + memset(&midi.state, 0, sizeof(midi.state)); } OssEngine::~OssEngine() @@ -67,11 +208,12 @@ bool OssEngine::openAudio() int snd_format = AFMT_S16_LE; int snd_samplerate = synth->samplerate; - const char *device = config.cfg.LinuxOSSWaveOutDev; - if(getenv("DSP_DEVICE")) - device = getenv("DSP_DEVICE"); + const char *device = getenv("DSP_DEVICE"); + if(device == NULL) + device = config.cfg.LinuxOSSWaveOutDev; - audio.handle = open(device, O_WRONLY, 0); + /* NOTE: PIPEs and FIFOs can block when opening them */ + audio.handle = open(device, O_WRONLY, O_NONBLOCK); if(audio.handle == -1) { cerr << "ERROR - I can't open the " << device << '.' << endl; @@ -84,13 +226,11 @@ bool OssEngine::openAudio() ioctl(audio.handle, SNDCTL_DSP_SAMPLESIZE, &snd_bitsize); ioctl(audio.handle, SNDCTL_DSP_SETFRAGMENT, &snd_fragment); - if(!getMidiEn()) { - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - engThread = new pthread_t; - pthread_create(engThread, &attr, _thread, this); - } + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + audioThread = new pthread_t; + pthread_create(audioThread, &attr, _audioThreadCb, this); return true; } @@ -102,12 +242,12 @@ void OssEngine::stopAudio() return; audio.handle = -1; - if(!getMidiEn() && engThread) - pthread_join(*engThread, NULL); - delete engThread; - engThread = NULL; - + /* close handle first, so that write() exits */ close(handle); + + pthread_join(*audioThread, NULL); + delete audioThread; + audioThread = NULL; } bool OssEngine::Start() @@ -165,19 +305,22 @@ bool OssEngine::openMidi() if(handle != -1) return true; //already open - handle = open(config.cfg.LinuxOSSSeqInDev, O_RDONLY, 0); + const char *device = getenv("MIDI_DEVICE"); + if(device == NULL) + device = config.cfg.LinuxOSSSeqInDev; + + /* NOTE: PIPEs and FIFOs can block when opening them */ + handle = open(device, O_RDONLY, O_NONBLOCK); if(-1 == handle) return false; midi.handle = handle; - if(!getAudioEn()) { - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - engThread = new pthread_t; - pthread_create(engThread, &attr, _thread, this); - } + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + midiThread = new pthread_t; + pthread_create(midiThread, &attr, _midiThreadCb, this); return true; } @@ -190,96 +333,106 @@ void OssEngine::stopMidi() midi.handle = -1; - if(!getAudioEn() && engThread) { - pthread_join(*engThread, NULL); - delete engThread; - engThread = NULL; - } - + /* close handle first, so that read() exits */ close(handle); + + pthread_join(*midiThread, NULL); + delete midiThread; + midiThread = NULL; } -void *OssEngine::_thread(void *arg) +void *OssEngine::_audioThreadCb(void *arg) { - return (static_cast<OssEngine *>(arg))->thread(); + return (static_cast<OssEngine *>(arg))->audioThreadCb(); } -void *OssEngine::thread() +void *OssEngine::_midiThreadCb(void *arg) { - unsigned char tmp[4] = {0, 0, 0, 0}; + return (static_cast<OssEngine *>(arg))->midiThreadCb(); +} + +void *OssEngine::audioThreadCb() +{ + /* + * In case the audio device is a PIPE/FIFO, + * we need to ignore any PIPE signals: + */ + signal(SIGPIPE, SIG_IGN); + set_realtime(); - while(getAudioEn() || getMidiEn()) { - if(getAudioEn()) { - const Stereo<float *> smps = getNext(); - - float l, r; - for(int i = 0; i < synth->buffersize; ++i) { - l = smps.l[i]; - r = smps.r[i]; - - if(l < -1.0f) - l = -1.0f; - else + while(getAudioEn()) { + const Stereo<float *> smps = getNext(); + + float l, r; + for(int i = 0; i < synth->buffersize; ++i) { + l = smps.l[i]; + r = smps.r[i]; + + if(l < -1.0f) + l = -1.0f; + else if(l > 1.0f) l = 1.0f; - if(r < -1.0f) - r = -1.0f; - else + if(r < -1.0f) + r = -1.0f; + else if(r > 1.0f) r = 1.0f; - audio.smps[i * 2] = (short int) (l * 32767.0f); - audio.smps[i * 2 + 1] = (short int) (r * 32767.0f); - } - int handle = audio.handle; - if(handle != -1) - write(handle, audio.smps, synth->buffersize * 4); // *2 because is 16 bit, again * 2 because is stereo - else - break; + audio.smps[i * 2] = (short int) (l * 32767.0f); + audio.smps[i * 2 + 1] = (short int) (r * 32767.0f); } - //Collect up to 30 midi events - for(int k = 0; k < 30 && getMidiEn(); ++k) { - static char escaped; - - memset(tmp, 0, 4); - - if(escaped) { - tmp[0] = escaped; - escaped = 0; - } - else { - getMidi(tmp); - if(!(tmp[0] & 0x80)) - continue; - } - getMidi(tmp + 1); - if(tmp[1] & 0x80) { - escaped = tmp[1]; - tmp[1] = 0; - } - else { - getMidi(tmp + 2); - if(tmp[2] & 0x80) { - escaped = tmp[2]; - tmp[2] = 0; - } - else { - getMidi(tmp + 3); - if(tmp[3] & 0x80) { - escaped = tmp[3]; - tmp[3] = 0; - } - } - } - midiProcess(tmp[0], tmp[1], tmp[2]); - } + int error; + do { + /* make a copy of handle, in case of OSS audio disable */ + int handle = audio.handle; + if(handle == -1) + goto done; + /* 2x because is 16 bit, again 2x because it is stereo */ + error = write(handle, audio.smps, synth->buffersize * 4); + } while (error == -1 && errno == EINTR); + + if(error == -1) + goto done; } +done: pthread_exit(NULL); return NULL; } -void OssEngine::getMidi(unsigned char *midiPtr) +void *OssEngine::midiThreadCb() { - read(midi.handle, midiPtr, 1); + /* + * In case the MIDI device is a PIPE/FIFO, + * we need to ignore any PIPE signals: + */ + signal(SIGPIPE, SIG_IGN); + set_realtime(); + while(getMidiEn()) { + unsigned char tmp; + int error; + do { + /* make a copy of handle, in case of OSS MIDI disable */ + int handle = midi.handle; + if(handle == -1) + goto done; + error = read(handle, &tmp, 1); + } while (error == -1 && errno == EINTR); + + /* check that we got one byte */ + if(error != 1) + goto done; + + /* feed MIDI byte into statemachine */ + if(OssMidiParse(midi.state, 0, tmp)) { + /* we got a complete MIDI command */ + midiProcess(midi.state.temp_cmd[1], + midi.state.temp_cmd[2], + midi.state.temp_cmd[3]); + } + } +done: + pthread_exit(NULL); + return NULL; } diff --git a/src/Nio/OssEngine.h b/src/Nio/OssEngine.h @@ -28,6 +28,20 @@ #include "AudioOut.h" #include "MidiIn.h" +struct OssMidiParse { + unsigned char *temp_cmd; + unsigned char temp_0[4]; + unsigned char temp_1[4]; + unsigned char state; +#define OSSMIDI_ST_UNKNOWN 0 /* scan for command */ +#define OSSMIDI_ST_1PARAM 1 +#define OSSMIDI_ST_2PARAM_1 2 +#define OSSMIDI_ST_2PARAM_2 3 +#define OSSMIDI_ST_SYSEX_0 4 +#define OSSMIDI_ST_SYSEX_1 5 +#define OSSMIDI_ST_SYSEX_2 6 +}; + class OssEngine:public AudioOut, MidiIn { public: @@ -43,13 +57,16 @@ class OssEngine:public AudioOut, MidiIn void setMidiEn(bool nval); bool getMidiEn() const; - protected: - void *thread(); - static void *_thread(void *arg); + void *audioThreadCb(); + static void *_audioThreadCb(void *arg); + + void *midiThreadCb(); + static void *_midiThreadCb(void *arg); private: - pthread_t *engThread; + pthread_t *audioThread; + pthread_t *midiThread; //Audio bool openAudio(); @@ -64,9 +81,9 @@ class OssEngine:public AudioOut, MidiIn //Midi bool openMidi(); void stopMidi(); - void getMidi(unsigned char *midiPtr); struct midi { + struct OssMidiParse state; int handle; bool en; bool run;