|
| 1 | +/* |
| 2 | + SuperCollider real time audio synthesis system |
| 3 | + Copyright (c) 2002 James McCartney. All rights reserved. |
| 4 | + http://www.audiosynth.com |
| 5 | +
|
| 6 | + This program is free software; you can redistribute it and/or modify |
| 7 | + it under the terms of the GNU General Public License as published by |
| 8 | + the Free Software Foundation; either version 2 of the License, or |
| 9 | + (at your option) any later version. |
| 10 | +
|
| 11 | + This program is distributed in the hope that it will be useful, |
| 12 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | + GNU General Public License for more details. |
| 15 | +
|
| 16 | + You should have received a copy of the GNU General Public License |
| 17 | + along with this program; if not, write to the Free Software |
| 18 | + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 19 | +*/ |
| 20 | +#include <stdarg.h> |
| 21 | +#include <math.h> |
| 22 | +#include <stdlib.h> |
| 23 | +#include <rtaudio/RtAudio.h> |
| 24 | + |
| 25 | +#include "SC_CoreAudio.h" |
| 26 | +#include "SC_Prototypes.h" |
| 27 | +#include "SC_HiddenWorld.h" |
| 28 | +#include "SC_WorldOptions.h" |
| 29 | +#include "SC_Time.hpp" |
| 30 | + |
| 31 | + |
| 32 | +int32 server_timeseed() { return timeSeed(); } |
| 33 | + |
| 34 | +#include "SC_TimeDLL.hpp" |
| 35 | +// ===================================================================== |
| 36 | +// Timing |
| 37 | + |
| 38 | +static inline int64 sc_PAOSCTime() { return OSCTime(getTime()); } |
| 39 | + |
| 40 | +static inline double sc_PAOSCTimeSeconds() { return (uint64)sc_PAOSCTime() * kOSCtoSecs; } |
| 41 | + |
| 42 | +int64 oscTimeNow() { return sc_PAOSCTime(); } |
| 43 | + |
| 44 | +void initializeScheduler() {} |
| 45 | + |
| 46 | + |
| 47 | +class SC_PulseAudioDriver : public SC_AudioDriver { |
| 48 | + int m_inputChannelCount, m_outputChannelCount; |
| 49 | + int64 m_paStreamStartupTime; |
| 50 | + int64 m_paStreamStartupTimeOSC; |
| 51 | + RtAudio* m_audio; |
| 52 | + double m_maxOutputLatency; |
| 53 | + SC_TimeDLL m_DLL; |
| 54 | + |
| 55 | + int rtCallback(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, |
| 56 | + RtAudioStreamStatus status); |
| 57 | + static int rtCallbackStatic(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, |
| 58 | + RtAudioStreamStatus status, void* userData); |
| 59 | + |
| 60 | +protected: |
| 61 | + // Driver interface methods |
| 62 | + virtual bool DriverSetup(int* outNumSamplesPerCallback, double* outSampleRate) override; |
| 63 | + virtual bool DriverStart() override; |
| 64 | + virtual bool DriverStop() override; |
| 65 | + |
| 66 | +public: |
| 67 | + SC_PulseAudioDriver(struct World* inWorld); |
| 68 | + virtual ~SC_PulseAudioDriver(); |
| 69 | +}; |
| 70 | + |
| 71 | +// This is the "entry point" for our code. This will be called by the server to initialize the audio backend |
| 72 | +SC_AudioDriver* SC_NewAudioDriver(struct World* inWorld) { return new SC_PulseAudioDriver(inWorld); } |
| 73 | + |
| 74 | +SC_PulseAudioDriver::SC_PulseAudioDriver(struct World* inWorld): SC_AudioDriver(inWorld), m_maxOutputLatency(0.) { |
| 75 | + scprintf("In SC_PulseAudioDriver::SC_PulseAudioDriver\n"); |
| 76 | + // We ask RtAudio to use the PulseAudio backend |
| 77 | + m_audio = new RtAudio(RtAudio::LINUX_PULSE); |
| 78 | +} |
| 79 | + |
| 80 | +SC_PulseAudioDriver::~SC_PulseAudioDriver() { |
| 81 | + scprintf("In SC_PulseAudioDriver::~SC_PulseAudioDriver\n"); |
| 82 | + m_audio->closeStream(); |
| 83 | + delete m_audio; |
| 84 | +} |
| 85 | + |
| 86 | +// This is the callback static entry point. It will just relay the call to the object's proper method, |
| 87 | +// using the userData pointer |
| 88 | +int SC_PulseAudioDriver::rtCallbackStatic(void* outputBuffer, void* inputBuffer, unsigned int nFrames, |
| 89 | + double streamTime, RtAudioStreamStatus status, void* userData) { |
| 90 | + SC_PulseAudioDriver* driver = (SC_PulseAudioDriver*)userData; |
| 91 | + |
| 92 | + return driver->rtCallback(outputBuffer, inputBuffer, nFrames, streamTime, status); |
| 93 | +} |
| 94 | + |
| 95 | +void sc_SetDenormalFlags(); |
| 96 | + |
| 97 | +// This is where all the data movement between SuperCollider and the sound server happens. |
| 98 | +// Since PulseAudio uses interleaved samples, that's what we use here |
| 99 | +int SC_PulseAudioDriver::rtCallback(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, |
| 100 | + RtAudioStreamStatus status) { |
| 101 | + sc_SetDenormalFlags(); |
| 102 | + World* world = mWorld; |
| 103 | + |
| 104 | + m_DLL.Update(sc_PAOSCTimeSeconds()); |
| 105 | + |
| 106 | + // This is where all the data movement takes place |
| 107 | + try { |
| 108 | + mFromEngine.Free(); |
| 109 | + mToEngine.Perform(); |
| 110 | + mOscPacketsToEngine.Perform(); |
| 111 | + |
| 112 | + int numSamples = NumSamplesPerCallback(); |
| 113 | + int bufFrames = mWorld->mBufLength; |
| 114 | + int numBufs = numSamples / bufFrames; |
| 115 | + |
| 116 | + if (nFrames != bufFrames) { |
| 117 | + scprintf("warning: nFrames in callback != mWorld->mBufLength\n"); |
| 118 | + } |
| 119 | + |
| 120 | + float* inBuses = mWorld->mAudioBus + mWorld->mNumOutputs * bufFrames; |
| 121 | + float* outBuses = mWorld->mAudioBus; |
| 122 | + int32* inTouched = mWorld->mAudioBusTouched + mWorld->mNumOutputs; |
| 123 | + int32* outTouched = mWorld->mAudioBusTouched; |
| 124 | + |
| 125 | + int bufFramePos = 0; |
| 126 | + int64 oscTime = mOSCbuftime = (uint64)((m_DLL.PeriodTime() + m_maxOutputLatency) * kSecondsToOSCunits + .5); |
| 127 | + int64 oscInc = mOSCincrement = (uint64)((m_DLL.Period() / numBufs) * kSecondsToOSCunits + .5); |
| 128 | + mSmoothSampleRate = m_DLL.SampleRate(); |
| 129 | + double oscToSamples = mOSCtoSamples = mSmoothSampleRate * kOSCtoSecs /* 1/pow(2,32) */; |
| 130 | + |
| 131 | + // main loop |
| 132 | + for (int i = 0; i < numBufs; ++i, mWorld->mBufCounter++, bufFramePos += bufFrames) { |
| 133 | + int32 bufCounter = mWorld->mBufCounter; |
| 134 | + int32* tch; |
| 135 | + |
| 136 | + // Process inputs, marking them as touched |
| 137 | + tch = inTouched; |
| 138 | + for (int k = 0; k < m_inputChannelCount; ++k) { |
| 139 | + *tch++ = bufCounter; |
| 140 | + float* dst = inBuses + k * bufFrames; |
| 141 | + const float* src = (float*)inputBuffer + k + bufFramePos; |
| 142 | + for (int n = 0; n < bufFrames; n++) { |
| 143 | + *dst = *src; |
| 144 | + src += m_outputChannelCount; |
| 145 | + dst++; |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + // run engine |
| 150 | + int64 schedTime; |
| 151 | + int64 nextTime = oscTime + oscInc; |
| 152 | + while ((schedTime = mScheduler.NextTime()) <= nextTime) { |
| 153 | + float diffTime = (float)(schedTime - oscTime) * oscToSamples + 0.5; |
| 154 | + float diffTimeFloor = floor(diffTime); |
| 155 | + world->mSampleOffset = (int)diffTimeFloor; |
| 156 | + world->mSubsampleOffset = diffTime - diffTimeFloor; |
| 157 | + |
| 158 | + if (world->mSampleOffset < 0) |
| 159 | + world->mSampleOffset = 0; |
| 160 | + else if (world->mSampleOffset >= world->mBufLength) |
| 161 | + world->mSampleOffset = world->mBufLength - 1; |
| 162 | + |
| 163 | + SC_ScheduledEvent event = mScheduler.Remove(); |
| 164 | + event.Perform(); |
| 165 | + } |
| 166 | + world->mSampleOffset = 0; |
| 167 | + world->mSubsampleOffset = 0.0f; |
| 168 | + |
| 169 | + World_Run(world); |
| 170 | + |
| 171 | + // Process outputs (considering which ones have been touched) |
| 172 | + tch = outTouched; |
| 173 | + for (int k = 0; k < m_outputChannelCount; ++k) { |
| 174 | + float* dst = (float*)outputBuffer + k + bufFramePos; |
| 175 | + if (tch[k] == bufCounter) { |
| 176 | + const float* src = outBuses + k * bufFrames; |
| 177 | + for (int n = 0; n < bufFrames; n++) { |
| 178 | + *dst = *src; |
| 179 | + src++; |
| 180 | + dst += m_outputChannelCount; |
| 181 | + } |
| 182 | + } else { |
| 183 | + *dst = 0; |
| 184 | + dst += m_outputChannelCount; |
| 185 | + } |
| 186 | + } |
| 187 | + // update buffer time |
| 188 | + oscTime = mOSCbuftime = nextTime; |
| 189 | + } |
| 190 | + } catch (std::exception& exc) { |
| 191 | + scprintf("SC_PulseAudioDriver: exception in real time: %s\n", exc.what()); |
| 192 | + } catch (...) { |
| 193 | + scprintf("SC_PulseAudioDriver: unknown exception in real time\n"); |
| 194 | + } |
| 195 | +#if 0 |
| 196 | + double cpuUsage = Pa_GetStreamCpuLoad(mStream) * 100.0; |
| 197 | + mAvgCPU = mAvgCPU + 0.1 * (cpuUsage - mAvgCPU); |
| 198 | + if (cpuUsage > mPeakCPU || --mPeakCounter <= 0) { |
| 199 | + mPeakCPU = cpuUsage; |
| 200 | + mPeakCounter = mMaxPeakCounter; |
| 201 | + } |
| 202 | + |
| 203 | +#endif |
| 204 | + mAudioSync.Signal(); |
| 205 | + return 0; |
| 206 | +} |
| 207 | + |
| 208 | + |
| 209 | +// ======================================================================== |
| 210 | +// The SC_AudioDriver interface methods that we need to implement are below |
| 211 | + |
| 212 | +bool SC_PulseAudioDriver::DriverSetup(int* outNumSamples, double* outSampleRate) { |
| 213 | + int rc; |
| 214 | + int device; |
| 215 | + scprintf("In SC_PulseAudioDriver::DriverSetup\n"); |
| 216 | + |
| 217 | + // Show the devices |
| 218 | + int numDevices = m_audio->getDeviceCount(); |
| 219 | + if (numDevices == 0) { |
| 220 | + scprintf("No audio devices found\n"); |
| 221 | + return false; |
| 222 | + } |
| 223 | + |
| 224 | + scprintf("Found %d device(s):\n", numDevices); |
| 225 | + std::vector<RtAudio::DeviceInfo> infos; |
| 226 | + for (int i = 0; i < numDevices; i++) { |
| 227 | + try { |
| 228 | + RtAudio::DeviceInfo info; |
| 229 | + info = m_audio->getDeviceInfo(i); |
| 230 | + scprintf(" - %s (device #%d with %d ins %d outs)\n", info.name.c_str(), i, info.inputChannels, |
| 231 | + info.outputChannels); |
| 232 | + |
| 233 | + infos.push_back(info); |
| 234 | + } catch (RtAudioError& e) { |
| 235 | + e.printMessage(); |
| 236 | + break; |
| 237 | + } |
| 238 | + } |
| 239 | + |
| 240 | + // Use the default devices |
| 241 | + unsigned int outputDevice = m_audio->getDefaultOutputDevice(); |
| 242 | + unsigned int inputDevice = m_audio->getDefaultInputDevice(); |
| 243 | + |
| 244 | + // Number of input and output channels to use |
| 245 | + m_outputChannelCount = std::min<size_t>(mWorld->mNumOutputs, infos[outputDevice].duplexChannels); |
| 246 | + m_inputChannelCount = std::min<size_t>(mWorld->mNumInputs, infos[inputDevice].duplexChannels); |
| 247 | + |
| 248 | + // What sample rate to use? If we get one, make sure it is supported, and fall back to the default if not |
| 249 | + *outNumSamples = mWorld->mBufLength; |
| 250 | + if (mPreferredSampleRate) { |
| 251 | + std::vector<unsigned int> supportedSampleRates { infos[outputDevice].sampleRates }; |
| 252 | + if (std::find(supportedSampleRates.begin(), supportedSampleRates.end(), (unsigned int)mPreferredSampleRate) |
| 253 | + != supportedSampleRates.end()) { |
| 254 | + *outSampleRate = mPreferredSampleRate; |
| 255 | + } else { |
| 256 | + scprintf("Requested sample rate NOT supported. Setting to 44.1 kHz\n"); |
| 257 | + *outSampleRate = 44100; |
| 258 | + } |
| 259 | + } else { |
| 260 | + *outSampleRate = 44100; |
| 261 | + } |
| 262 | + |
| 263 | + // Configure the stream parameters |
| 264 | + RtAudio::StreamParameters outParameters; |
| 265 | + outParameters.deviceId = outputDevice; |
| 266 | + outParameters.nChannels = m_outputChannelCount; |
| 267 | + RtAudio::StreamParameters inParameters; |
| 268 | + inParameters.deviceId = inputDevice; |
| 269 | + inParameters.nChannels = m_inputChannelCount; |
| 270 | + // use a separate variable for the number of frames, so that we can compare with the actual given value |
| 271 | + // and warn if they are different |
| 272 | + unsigned int bufferFrames = *outNumSamples; |
| 273 | + RtAudio::StreamOptions options; |
| 274 | + options.flags = RTAUDIO_MINIMIZE_LATENCY | RTAUDIO_SCHEDULE_REALTIME; |
| 275 | + options.streamName = "SuperCollider"; |
| 276 | + try { |
| 277 | + scprintf("Opening stream with %d output and %d input channels\n", outParameters.nChannels, |
| 278 | + inParameters.nChannels); |
| 279 | + // Depending on whether we are using inputs, we open the stream with the appropriate parameters |
| 280 | + if (m_inputChannelCount > 0) { |
| 281 | + m_audio->openStream(&outParameters, &inParameters, RTAUDIO_FLOAT32, *outSampleRate, &bufferFrames, |
| 282 | + &SC_PulseAudioDriver::rtCallbackStatic, this, &options); |
| 283 | + } else { |
| 284 | + m_audio->openStream(&outParameters, NULL, RTAUDIO_FLOAT32, *outSampleRate, &bufferFrames, |
| 285 | + &SC_PulseAudioDriver::rtCallbackStatic, this, &options); |
| 286 | + } |
| 287 | + // Check that the requested number of frames matches what SC asked for and issue a warning if not |
| 288 | + if (*outNumSamples != bufferFrames) { |
| 289 | + scprintf("*outNumSamples != bufferFrames (%d != %d)\n", *outNumSamples, bufferFrames); |
| 290 | + } |
| 291 | + *outNumSamples = bufferFrames; |
| 292 | + } catch (RtAudioError& e) { |
| 293 | + e.printMessage(); |
| 294 | + return false; |
| 295 | + } |
| 296 | + return true; |
| 297 | +} |
| 298 | + |
| 299 | +bool SC_PulseAudioDriver::DriverStart() { |
| 300 | + // sync times |
| 301 | + m_paStreamStartupTimeOSC = 0; |
| 302 | + m_paStreamStartupTime = 0; |
| 303 | + |
| 304 | + // Start the streaming |
| 305 | + try { |
| 306 | + m_audio->startStream(); |
| 307 | + } catch (RtAudioError& e) { |
| 308 | + e.printMessage(); |
| 309 | + return false; |
| 310 | + } |
| 311 | + |
| 312 | + m_DLL.Reset(mSampleRate, mNumSamplesPerCallback, SC_TIME_DLL_BW, sc_PAOSCTimeSeconds()); |
| 313 | + return true; |
| 314 | +} |
| 315 | + |
| 316 | +bool SC_PulseAudioDriver::DriverStop() { |
| 317 | + // We just stop the stream |
| 318 | + m_audio->stopStream(); |
| 319 | + return true; |
| 320 | +} |
0 commit comments