1 module pixelperfectengine.audio.base.handler; 2 3 /* 4 * Copyright (C) 2015-2021, by Laszlo Szeremi under the Boost license. 5 * 6 * Pixel Perfect Engine, audio.base.handler module. 7 */ 8 9 import core.thread; 10 import core.time; 11 12 import std.conv : to; 13 import std.string : fromStringz; 14 import std.bitmanip : bitfields; 15 16 import pixelperfectengine.system.exc; 17 //import pixelperfectengine.system.etc : isPowerOf2; 18 import pixelperfectengine.audio.base.modulebase; 19 import pixelperfectengine.audio.base.midiseq; 20 21 import iota.audio.output; 22 import iota.audio.device; 23 public import iota.audio.types; 24 25 /** 26 * Manages and initializes audio devices. 27 * 28 * Important: Only one instance should be made. 29 */ 30 public class AudioDeviceHandler { 31 package AudioDevice device; ///Contains the initialized audio device 32 protected AudioSpecs specs; ///Contains the requested/given audio specs 33 package int blockSize; ///Requested/given buffer base size / block length (in samples) 34 package int nOfBlocks; ///Requested/given number of blocks before they get sent to the output 35 /** 36 * Creates an instance, and detects all drivers. 37 *Params: 38 * specs: Requested audio specifications. If not available, a nearby will be used instead. 39 * channels: Number of channels 40 * buffSize: The size of the buffer in samples 41 * 42 * Throws an AudioInitException if audio is failed to be initialized. 43 */ 44 public this(AudioSpecs specs, int blockSize, int nOfBlocks) { 45 //context = soundio_create(); 46 this.specs = specs; 47 this.blockSize = blockSize; 48 this.nOfBlocks = nOfBlocks; 49 //req.callback = &callbacksFromSDL; 50 } 51 ///Destructor 52 ~this() { 53 54 } 55 /** 56 * Initializes an audio driver by ID. 57 * 58 * Throws an AudioInitException if audio failed to be initialized. 59 */ 60 public static void initAudioDriver(DriverType backend) { 61 int errCode = initDriver(backend); 62 if (errCode) throw new AudioInitException("Failed to initialize audio driver. Error code: " ~ errCode.to!string); 63 } 64 /** 65 * Opens a specific audio device for audio playback by ID, then sets the values for buffer sizes etc. 66 * 67 * Throws an AudioInitException if audio failed to be initialized 68 */ 69 public void initAudioDevice(int id = -1) { 70 int errCode = openDevice(id, device); 71 if (errCode) throw new AudioInitException("Failed to initialize audio device. Error code: " ~ errCode.to!string); 72 int recSlmpRate = device.getRecommendedSampleRate(); 73 if (recSlmpRate > 0) { 74 specs.sampleRate = recSlmpRate; 75 } 76 specs = device.requestSpecs(specs); 77 // Recalculate block sizes if buffer size changed 78 if (specs.bufferSize_slmp != blockSize * nOfBlocks) { 79 if (!(specs.bufferSize_slmp % 4)) { 80 blockSize = specs.bufferSize_slmp / 4; 81 nOfBlocks = 4; 82 } else { 83 blockSize = specs.bufferSize_slmp; 84 nOfBlocks = 1; 85 } 86 } 87 } 88 /** 89 * Return an array with the names of the available audio devices. 90 */ 91 public string[] getDevices() { 92 return getOutputDeviceNames(); 93 } 94 /** 95 * Returns the available sampling frequency. 96 */ 97 public int getSamplingFrequency() @nogc @safe pure nothrow const { 98 return specs.sampleRate; 99 } 100 /** 101 * Returns the number of audio channels. 102 */ 103 public int getChannels() @nogc @safe pure nothrow const { 104 return specs.outputChannels; 105 } 106 /** 107 * Returns the buffer size in units. 108 */ 109 public size_t getBufferSize() @nogc @safe pure nothrow const { 110 return blockSize * nOfBlocks; 111 } 112 /** 113 * Returns the buffer length in time, in `Duration` format. 114 */ 115 public Duration getBufferDelay() @nogc @safe pure nothrow const { 116 return specs.bufferSize_time; 117 } 118 } 119 /** 120 * Manages all audio modules complete with routing, MIDI2.0, etc. 121 */ 122 public class ModuleManager { 123 protected int blockSize; ///Rendering buffer size in samples, also the length of a single frame. 124 protected int nOfBlocks; ///Number of maximum frames that can be put into the output buffer. 125 protected int currBlock; ///Current audio frame. 126 ///Pointer to the audio device handler. 127 public AudioDeviceHandler devHandler; 128 ///Pointer to a MIDI sequencer, for synchronizing it with the audio stream. 129 public Sequencer midiSeq; 130 protected OutputStream outStrm; ///Output stream handling. 131 /** 132 * List of modules. 133 * 134 * Ran in order, should be ordered in such way to ensure that routing is correct, and the modules that need the 135 * input will get some. 136 */ 137 public AudioModule[] moduleList; 138 /** 139 * List of pointers to input buffers. 140 * 141 * Order of first dimension must match the modules. Pointers can be shared between multiple inputs or outputs. 142 * If a specific plugin doesn't have any inputs, then an array with zero elements must be added. 143 */ 144 protected float*[][] inBufferList; 145 /** 146 * List of pointers to output buffers. 147 * 148 * Order of first dimension must match the modules. Pointers can be shared between multiple inputs or outputs. 149 * If a specific plugin doesn't have any outputs, then an array with zero elements must be added. 150 */ 151 protected float*[][] outBufferList; 152 /** 153 * List of the buffers themselves. 154 * 155 * One buffer can be shared between multiple input and/or output for mixing, etc. 156 * All buffers must have the same size, defined by the variable `blockSize` 157 * The first buffers are used for output rendering. 158 */ 159 protected float[][] buffers; 160 /** 161 * Final output buffer. 162 * 163 * Words are in LRLRLR... order, or similar, depending on number of channels. 164 */ 165 protected float[] finalBuffer; 166 protected int channels; 167 /** 168 * Creates an instance of a module handler. 169 *Params: 170 * handler = The AudioDeviceHandler that contains the data about the audio device. 171 * blockSize = The size of the buffer in samples. 172 * nOfBlocks = The number of frames before they get queued to the audio device. 173 */ 174 public this(AudioDeviceHandler handler) { 175 import pixelperfectengine.audio.base.func : resetBuffer; 176 devHandler = handler; 177 this.blockSize = handler.blockSize; 178 outStrm = handler.device.createOutputStream(); 179 if (outStrm is null) throw new AudioInitException("Audio stream couldn't be opened."); 180 this.nOfBlocks = handler.nOfBlocks; 181 finalBuffer.length = handler.getChannels() * blockSize * nOfBlocks; 182 this.channels = handler.getChannels(); 183 resetBuffer(finalBuffer); 184 185 buffers.length = handler.getChannels(); 186 for (int i ; i < buffers.length ; i++) { 187 buffers[i].length = blockSize; 188 resetBuffer(buffers[i]); 189 } 190 outStrm.callback_buffer = &audioCallback; 191 //super(&render); 192 } 193 /** 194 * Audio callback function. 195 * Renders the audio, then it copies to the destination buffer. If needed, it'll do floating point to integer conversion. 196 */ 197 protected void audioCallback(ubyte[] destbuffer) @nogc nothrow { 198 import pixelperfectengine.audio.base.func : interleave, resetBuffer; 199 import core.stdc.string : memcpy; 200 201 while (currBlock < nOfBlocks) { 202 foreach (ref key; buffers) { 203 resetBuffer(key); 204 } 205 foreach (size_t i, AudioModule am; moduleList) { 206 am.renderFrame(inBufferList[i], outBufferList[i]); 207 } 208 if (midiSeq !is null) 209 midiSeq.lapseTime(devHandler.getBufferDelay); 210 const size_t offset = currBlock * blockSize * channels; 211 interleave(blockSize, buffers[0].ptr, buffers[1].ptr, finalBuffer.ptr + offset); 212 currBlock++; 213 } 214 currBlock = 0; 215 216 memcpy(destbuffer.ptr, finalBuffer.ptr, destbuffer.length); 217 218 } 219 /** 220 * MIDI commands are received here from modules. 221 * 222 * data: up to 128 bits of MIDI 2.0 commands. Any packets that are shorter should be padded with zeros. 223 * offset: time offset of the command. This can reduce jitter caused by the asynchronous operation of the 224 * sequencer and the audio plugin system. 225 */ 226 public void midiReceive(uint[4] data, uint offset) @nogc nothrow { 227 228 } 229 /** 230 * Sets up a specific number of buffers. 231 */ 232 public void setBuffers(size_t num) @safe nothrow { 233 import pixelperfectengine.audio.base.func : resetBuffer; 234 buffers.length = num; 235 for (size_t i ; i < buffers.length ; i++) { 236 buffers[i].length = blockSize; 237 resetBuffer(buffers[i]); 238 } 239 } 240 public int getChannels() const @nogc @safe pure nothrow { 241 return channels; 242 } 243 /** 244 * Adds a plugin to the list. 245 *Params: 246 * md = The audio module to be added. Automatic set-up is done upon addition. 247 * inBuffs = list of the audio inputs to be added, or null if none. 248 * inCfg = list of audio input IDs to be used. Must be matched with `inBuffs` 249 * outBuffs - list of the audio outputs to be added, of null if none. 250 * outCfg = list of audio output IDs to be used. Must be matched with `outBuffs` 251 */ 252 public void addModule(AudioModule md, size_t[] inBuffs, ubyte[] inCfg, size_t[] outBuffs, ubyte[] outCfg) nothrow { 253 md.moduleSetup(inCfg, outCfg, devHandler.getSamplingFrequency, blockSize, this); 254 moduleList ~= md; 255 float*[] buffList0, buffList1; 256 buffList0.length = inBuffs.length; 257 for (size_t i ; i < inBuffs.length ; i++) { 258 buffList0[i] = buffers[inBuffs[i]].ptr; 259 } 260 buffList1.length = outBuffs.length; 261 for (size_t i ; i < outBuffs.length ; i++) { 262 buffList1[i] = buffers[outBuffs[i]].ptr; 263 } 264 inBufferList ~= buffList0; 265 outBufferList ~= buffList1; 266 } 267 public void reset() { 268 moduleList.length = 0; 269 inBufferList.length = 0; 270 outBufferList.length = 0; 271 setBuffers(0); 272 } 273 /** 274 * Runs the audio thread and starts the audio output. 275 */ 276 public int runAudioThread() @nogc nothrow { 277 return outStrm.runAudioThread(); 278 } 279 /** 280 * Stops all audio output. 281 */ 282 public int suspendAudioThread() { 283 return outStrm.suspendAudioThread(); 284 } 285 } 286 287 /** 288 * Thrown on audio initialization errors. 289 */ 290 public class AudioInitException : PPEException { 291 /// 292 @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) 293 { 294 super(msg, file, line, nextInChain); 295 } 296 /// 297 @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) 298 { 299 super(msg, file, line, nextInChain); 300 } 301 }