zynaddsubfx

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

commit 221bd7d5f94f271f044d875985d596c45d16ba80
parent 4de1f117af65e3c6a8d13f73e5794adb9d4b2ee1
Author: Hans Petter Selasky <hps@selasky.org>
Date:   Wed, 29 Oct 2014 11:20:39 +0100

Add new OSS MULTI audio backend:
Add new OSS compatible backend which can output each part as a
separate stereo channel. Typically you will need audio hardware which
supports more than 2 channels.  This is also useful for live mixing
and recording through virtual OSS devices.

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

Diffstat:
Msrc/Nio/CMakeLists.txt | 2+-
Msrc/Nio/EngineMgr.cpp | 2++
Asrc/Nio/OssMultiEngine.cpp | 294+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Nio/OssMultiEngine.h | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 364 insertions(+), 1 deletion(-)

diff --git a/src/Nio/CMakeLists.txt b/src/Nio/CMakeLists.txt @@ -43,7 +43,7 @@ if(AlsaEnable) endif(AlsaEnable) if(OssEnable) - list(APPEND zynaddsubfx_nio_SRCS OssEngine.cpp) + list(APPEND zynaddsubfx_nio_SRCS OssEngine.cpp OssMultiEngine.cpp) endif(OssEnable) diff --git a/src/Nio/EngineMgr.cpp b/src/Nio/EngineMgr.cpp @@ -9,6 +9,7 @@ #include "NulEngine.h" #if OSS #include "OssEngine.h" +#include "OssMultiEngine.h" #endif #if ALSA #include "AlsaEngine.h" @@ -36,6 +37,7 @@ EngineMgr::EngineMgr() engines.push_back(defaultEng); #if OSS engines.push_back(new OssEngine()); + engines.push_back(new OssMultiEngine()); #endif #if ALSA engines.push_back(new AlsaEngine()); diff --git a/src/Nio/OssMultiEngine.cpp b/src/Nio/OssMultiEngine.cpp @@ -0,0 +1,294 @@ +/* + ZynAddSubFX - a software synthesizer + + OssMultiEngine.cpp - Multi channel audio output for Open Sound System + Copyright (C) 2014 Hans Petter Selasky + + 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 <cstring> +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/soundcard.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <iostream> +#include <signal.h> + +#include "Nio.h" +#include "../Misc/Master.h" +#include "../Misc/Part.h" +#include "../Misc/MiddleWare.h" +#include "../Misc/Util.h" + +#include "OssMultiEngine.h" + +extern MiddleWare *middleware; + +using namespace std; + +OssMultiEngine :: OssMultiEngine() +{ + /* setup variables */ + name = "OSS-MULTI"; + audioThread = 0; + handle = -1; + channels = 0; + en = false; + is32bit = false; + buffersize = 0; + + /* compute worst case buffer size */ + maxbuffersize = NUM_MIDI_PARTS * sizeof(int) * synth->buffersize * 2; + /* allocate buffer */ + smps.ps32 = new int[maxbuffersize / sizeof(int)]; + memset(smps.ps32, 0, maxbuffersize); +} + +OssMultiEngine :: ~OssMultiEngine() +{ + Stop(); + delete [] smps.ps32; +} + +bool +OssMultiEngine :: openAudio() +{ + int snd_samplerate; + int snd_format32; + int snd_format16; + int snd_fragment; + int x; + + /* check if already open */ + if (handle != -1) + return (true); + + const char *device = getenv("DSP_DEVICE"); + if(device == 0) + device = config.cfg.LinuxOSSWaveOutDev; + + /* NOTE: PIPEs and FIFOs can block when opening them */ + handle = open(device, O_WRONLY, O_NONBLOCK); + if (handle == -1) { + cerr << "ERROR - I can't open the " + << device << '.' << endl; + return (false); + } + ioctl(handle, SNDCTL_DSP_RESET, 0); + + /* Figure out the correct format first */ +#if defined(AFMT_S16_NE) && defined(AFMT_S32_NE) + snd_format32 = AFMT_S32_NE; + snd_format16 = AFMT_S16_NE; +#elif defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) +#if BYTE_ORDER == LITTLE_ENDIAN + snd_format32 = AFMT_S32_LE; + snd_format16 = AFMT_S16_LE; +#else + snd_format32 = AFMT_S32_BE; + snd_format16 = AFMT_S16_BE; +#endif +#else + snd_format32 = AFMT_S32_LE; + snd_format16 = AFMT_S16_LE; +#endif + + if (ioctl(handle, SNDCTL_DSP_SETFMT, &snd_format32) == 0) { + is32bit = true; + } else if (ioctl(handle, SNDCTL_DSP_SETFMT, &snd_format16) == 0) { + is32bit = false; + } else { + cerr << "ERROR - Cannot set DSP format for " + << device << '.' << endl; + goto error; + } + for (x = NUM_MIDI_PARTS * 2; x >= 2; x -= 2) { + if (ioctl(handle, SNDCTL_DSP_CHANNELS, &x) == 0) + break; + } + if (x == 0) { + cerr << "ERROR - Cannot set DSP channels for " + << device << '.' << endl; + goto error; + } + channels = x; + + snd_samplerate = synth->samplerate; + + ioctl(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 samples */ + buffersize = 2 * synth->buffersize * channels; + if (is32bit) + buffersize *= 2; + + for (x = 4; x < 20; x++) { + if ((1 << x) >= buffersize) + break; + } + + snd_fragment = 0x20000 | x; /* 2x buffer */ + + ioctl(handle, SNDCTL_DSP_SETFRAGMENT, &snd_fragment); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + pthread_create(&audioThread, &attr, _audioThreadCb, this); + + return (true); + +error: + close(handle); + handle = -1; + return (false); +} + +void +OssMultiEngine :: stopAudio() +{ + int fd = handle; + + /* check if already closed */ + if (fd == -1) + return; + handle = -1; + + /* close handle first, so that write() exits */ + close(fd); + + pthread_join(audioThread, 0); + audioThread = 0; +} + +bool +OssMultiEngine :: Start() +{ + return (openAudio()); +} + +void +OssMultiEngine :: Stop() +{ + stopAudio(); +} + +void +OssMultiEngine :: setAudioEn(bool enable) +{ + if (enable) + openAudio(); + else + stopAudio(); +} + +bool +OssMultiEngine :: getAudioEn() const +{ + return (handle != -1); +} + +void * +OssMultiEngine :: _audioThreadCb(void *arg) +{ + return (static_cast<OssMultiEngine *>(arg))->audioThreadCb(); +} + +void * +OssMultiEngine :: 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()) { + int error; + float l; + float r; + int x; + int y; + + /* get next buffer */ + getNext(); + + /* extract audio from the "channels / 2" first parts */ + for (x = 0; x != channels; x += 2) { + Part *part = middleware->spawnMaster()->part[x / 2]; + + if (is32bit) { + for (y = 0; y != synth->buffersize; y++) { + l = part->partoutl[y]; + if (l < -1.0f) + l = -1.0f; + else if (l > 1.0f) + l = 1.0f; + smps.ps32[y * channels + x] = (int)(l * 2147483647.0f); + r = part->partoutr[y]; + if (r < -1.0f) + r = -1.0f; + else if (r > 1.0f) + r = 1.0f; + smps.ps32[y * channels + x + 1] = (int)(r * 2147483647.0f); + } + } else { + for (y = 0; y != synth->buffersize; y++) { + l = part->partoutl[y]; + if (l < -1.0f) + l = -1.0f; + else if (l > 1.0f) + l = 1.0f; + smps.ps16[y * channels + x] = (short int)(l * 32767.0f); + r = part->partoutr[y]; + if (r < -1.0f) + r = -1.0f; + else if (r > 1.0f) + r = 1.0f; + smps.ps16[y * channels + x + 1] = (short int)(r * 32767.0f); + } + } + } + + /* write audio buffer to DSP device */ + do { + /* make a copy of handle, in case of OSS audio disable */ + int fd = handle; + if (fd == -1) + goto done; + error = write(fd, smps.ps32, buffersize); + } while (error == -1 && errno == EINTR); + + if(error == -1) + goto done; + } +done: + pthread_exit(0); + return (0); +} diff --git a/src/Nio/OssMultiEngine.h b/src/Nio/OssMultiEngine.h @@ -0,0 +1,67 @@ +/* + ZynAddSubFX - a software synthesizer + + OssMultiEngine.h - Multi channel audio output for Open Sound System + Copyright (C) 2014 Hans Petter Selasky + + 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 + +*/ + +#ifndef OSS_MULTI_ENGINE_H +#define OSS_MULTI_ENGINE_H + +#include <sys/time.h> +#include "../globals.h" +#include "AudioOut.h" + +class OssMultiEngine : public AudioOut +{ +public: + OssMultiEngine(); + ~OssMultiEngine(); + + bool Start(); + void Stop(); + + void setAudioEn(bool nval); + bool getAudioEn() const; + +protected: + void *audioThreadCb(); + static void *_audioThreadCb(void *arg); + +private: + pthread_t audioThread; + + /* Audio */ + bool openAudio(); + void stopAudio(); + + int handle; + int maxbuffersize; + int buffersize; + int channels; + + union { + /* Samples to be sent to soundcard */ + short int *ps16; + int *ps32; + } smps; + + bool en; + bool is32bit; +}; + +#endif