/* ZynAddSubFX - a software synthesizer OSSaudiooutput.C - Audio output for Open Sound System Copyright (C) 2002-2005 Nasca Octavian Paul Author: Nasca Octavian Paul This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. 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 (version 2 or later) for more details. You should have received a copy of the GNU General Public License (version 2) along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "OssEngine.h" #include "../Misc/Util.h" #include "../Misc/Config.h" #include "../globals.h" #include #include #include #include #include #include #include #include #include #include #include #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(const SYNTH_T &synth, const oss_devs_t& oss_devs) :AudioOut(synth), audioThread(NULL), midiThread(NULL), linux_oss_wave_out_dev(oss_devs.linux_wave_out), linux_oss_seq_in_dev(oss_devs.linux_seq_in) { name = "OSS"; midi.handle = -1; audio.handle = -1; /* allocate worst case audio buffer */ audio.smps.ps32 = new int[synth.buffersize * 2]; memset(audio.smps.ps32, 0, sizeof(int) * synth.buffersize * 2); memset(&midi.state, 0, sizeof(midi.state)); } OssEngine::~OssEngine() { Stop(); delete [] audio.smps.ps32; } bool OssEngine::openAudio() { int x; if(audio.handle != -1) return true; //already open int snd_fragment; int snd_stereo = 1; //stereo; int snd_samplerate = synth.samplerate; const char *device = getenv("DSP_DEVICE"); if(device == NULL) device = linux_oss_wave_out_dev; /* 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; return false; } ioctl(audio.handle, SNDCTL_DSP_RESET, NULL); /* Figure out the correct format first */ int snd_format16 = AFMT_S16_NE; #ifdef AFMT_S32_NE int snd_format32 = AFMT_S32_NE; if (ioctl(audio.handle, SNDCTL_DSP_SETFMT, &snd_format32) == 0) { audio.is32bit = true; } else #endif if (ioctl(audio.handle, SNDCTL_DSP_SETFMT, &snd_format16) == 0) { audio.is32bit = false; } else { cerr << "ERROR - I cannot set DSP format for " << device << '.' << endl; goto error; } ioctl(audio.handle, SNDCTL_DSP_STEREO, &snd_stereo); ioctl(audio.handle, SNDCTL_DSP_SPEED, &snd_samplerate); if (snd_samplerate != (int)synth.samplerate) { cerr << "ERROR - Cannot set samplerate for " << device << ". " << snd_samplerate << " != " << synth.samplerate << endl; goto error; } /* compute buffer size for 16-bit stereo samples */ audio.buffersize = 4 * synth.buffersize; if (audio.is32bit) audio.buffersize *= 2; for (x = 4; x < 20; x++) { if ((1 << x) >= audio.buffersize) break; } snd_fragment = 0x20000 | x; /* 2x buffer */ ioctl(audio.handle, SNDCTL_DSP_SETFRAGMENT, &snd_fragment); 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; error: close(audio.handle); audio.handle = -1; return false; } void OssEngine::stopAudio() { int handle = audio.handle; if(handle == -1) //already closed return; audio.handle = -1; /* close handle first, so that write() exits */ close(handle); pthread_join(*audioThread, NULL); delete audioThread; audioThread = NULL; } bool OssEngine::Start() { bool good = true; if(!openAudio()) { cerr << "Failed to open OSS audio" << endl; good = false; } if(!openMidi()) { cerr << "Failed to open OSS midi" << endl; good = false; } return good; } void OssEngine::Stop() { stopAudio(); stopMidi(); } void OssEngine::setMidiEn(bool nval) { if(nval) openMidi(); else stopMidi(); } bool OssEngine::getMidiEn() const { return midi.handle != -1; } void OssEngine::setAudioEn(bool nval) { if(nval) openAudio(); else stopAudio(); } bool OssEngine::getAudioEn() const { return audio.handle != -1; } bool OssEngine::openMidi() { int handle = midi.handle; if(handle != -1) return true; //already open const char *device = getenv("MIDI_DEVICE"); if(device == NULL) device = linux_oss_seq_in_dev; /* NOTE: PIPEs and FIFOs can block when opening them */ handle = open(device, O_RDONLY, O_NONBLOCK); if(-1 == handle) return false; midi.handle = handle; 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; } void OssEngine::stopMidi() { int handle = midi.handle; if(handle == -1) //already closed return; midi.handle = -1; /* close handle first, so that read() exits */ close(handle); pthread_join(*midiThread, NULL); delete midiThread; midiThread = NULL; } void *OssEngine::_audioThreadCb(void *arg) { return (static_cast(arg))->audioThreadCb(); } void *OssEngine::_midiThreadCb(void *arg) { return (static_cast(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()) { const Stereo 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; if (audio.is32bit) { audio.smps.ps32[i * 2] = (int) (l * 2147483647.0f); audio.smps.ps32[i * 2 + 1] = (int) (r * 2147483647.0f); } else {/* 16bit */ audio.smps.ps16[i * 2] = (short int) (l * 32767.0f); audio.smps.ps16[i * 2 + 1] = (short int) (r * 32767.0f); } } int error; do { /* make a copy of handle, in case of OSS audio disable */ int handle = audio.handle; if(handle == -1) goto done; error = write(handle, audio.smps.ps32, audio.buffersize); } while (error == -1 && errno == EINTR); if(error == -1) goto done; } done: pthread_exit(NULL); return NULL; } void *OssEngine::midiThreadCb() { /* * 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; }