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 specs = device.requestSpecs(specs); 73 // Recalculate block sizes if buffer size changed 74 if (specs.bufferSize_slmp != blockSize * nOfBlocks) { 75 if (!(specs.bufferSize_slmp % 4)) { 76 blockSize = specs.bufferSize_slmp / 4; 77 nOfBlocks = 4; 78 } else { 79 blockSize = specs.bufferSize_slmp; 80 nOfBlocks = 1; 81 } 82 } 83 } 84 /** 85 * Return an array with the names of the available audio devices. 86 */ 87 public string[] getDevices() { 88 return getOutputDeviceNames(); 89 } 90 /** 91 * Returns the available sampling frequency. 92 */ 93 public int getSamplingFrequency() @nogc @safe pure nothrow const { 94 return specs.sampleRate; 95 } 96 /** 97 * Returns the number of audio channels. 98 */ 99 public int getChannels() @nogc @safe pure nothrow const { 100 return specs.outputChannels; 101 } 102 /** 103 * Returns the buffer size in units. 104 */ 105 public size_t getBufferSize() @nogc @safe pure nothrow const { 106 return blockSize * nOfBlocks; 107 } 108 /** 109 * Returns the buffer length in time, in `Duration` format. 110 */ 111 public Duration getBufferDelay() @nogc @safe pure nothrow const { 112 return specs.bufferSize_time; 113 } 114 } 115 /** 116 * Manages all audio modules complete with routing, MIDI2.0, etc. 117 */ 118 public class ModuleManager { 119 protected int blockSize; ///Rendering buffer size in samples, also the length of a single frame. 120 protected int nOfBlocks; ///Number of maximum frames that can be put into the output buffer. 121 protected int currBlock; ///Current audio frame. 122 ///Pointer to the audio device handler. 123 public AudioDeviceHandler devHandler; 124 ///Pointer to a MIDI sequencer, for synchronizing it with the audio stream. 125 public Sequencer midiSeq; 126 protected OutputStream outStrm; ///Output stream handling. 127 /** 128 * List of modules. 129 * 130 * Ran in order, should be ordered in such way to ensure that routing is correct, and the modules that need the 131 * input will get some. 132 */ 133 public AudioModule[] moduleList; 134 /** 135 * List of pointers to input buffers. 136 * 137 * Order of first dimension must match the modules. Pointers can be shared between multiple inputs or outputs. 138 * If a specific plugin doesn't have any inputs, then an array with zero elements must be added. 139 */ 140 protected float*[][] inBufferList; 141 /** 142 * List of pointers to output buffers. 143 * 144 * Order of first dimension must match the modules. Pointers can be shared between multiple inputs or outputs. 145 * If a specific plugin doesn't have any outputs, then an array with zero elements must be added. 146 */ 147 protected float*[][] outBufferList; 148 /** 149 * List of the buffers themselves. 150 * 151 * One buffer can be shared between multiple input and/or output for mixing, etc. 152 * All buffers must have the same size, defined by the variable `blockSize` 153 * The first buffers are used for output rendering. 154 */ 155 protected float[][] buffers; 156 /** 157 * Final output buffer. 158 * 159 * Words are in LRLRLR... order, or similar, depending on number of channels. 160 */ 161 protected float[] finalBuffer; 162 protected int channels; 163 /** 164 * Creates an instance of a module handler. 165 *Params: 166 * handler = The AudioDeviceHandler that contains the data about the audio device. 167 * blockSize = The size of the buffer in samples. 168 * nOfBlocks = The number of frames before they get queued to the audio device. 169 */ 170 public this(AudioDeviceHandler handler) { 171 import pixelperfectengine.audio.base.func : resetBuffer; 172 devHandler = handler; 173 this.blockSize = handler.blockSize; 174 outStrm = handler.device.createOutputStream(); 175 if (outStrm is null) throw new AudioInitException("Audio stream couldn't be opened."); 176 this.nOfBlocks = handler.nOfBlocks; 177 finalBuffer.length = handler.getChannels() * blockSize * nOfBlocks; 178 this.channels = handler.getChannels(); 179 resetBuffer(finalBuffer); 180 181 buffers.length = handler.getChannels(); 182 for (int i ; i < buffers.length ; i++) { 183 buffers[i].length = blockSize; 184 resetBuffer(buffers[i]); 185 } 186 outStrm.callback_buffer = &audioCallback; 187 //super(&render); 188 } 189 /** 190 * Audio callback function. 191 * Renders the audio, then it copies to the destination buffer. If needed, it'll do floating point to integer conversion. 192 */ 193 protected void audioCallback(ubyte[] destbuffer) @nogc nothrow { 194 import pixelperfectengine.audio.base.func : interleave, resetBuffer; 195 import core.stdc.string : memcpy; 196 197 while (currBlock < nOfBlocks) { 198 foreach (ref key; buffers) { 199 resetBuffer(key); 200 } 201 foreach (size_t i, AudioModule am; moduleList) { 202 am.renderFrame(inBufferList[i], outBufferList[i]); 203 } 204 if (midiSeq !is null) 205 midiSeq.lapseTime(devHandler.getBufferDelay); 206 const size_t offset = currBlock * blockSize * channels; 207 interleave(blockSize, buffers[0].ptr, buffers[1].ptr, finalBuffer.ptr + offset); 208 currBlock++; 209 } 210 currBlock = 0; 211 212 memcpy(destbuffer.ptr, finalBuffer.ptr, destbuffer.length); 213 214 } 215 /** 216 * MIDI commands are received here from modules. 217 * 218 * data: up to 128 bits of MIDI 2.0 commands. Any packets that are shorter should be padded with zeros. 219 * offset: time offset of the command. This can reduce jitter caused by the asynchronous operation of the 220 * sequencer and the audio plugin system. 221 */ 222 public void midiReceive(uint[4] data, uint offset) @nogc nothrow { 223 224 } 225 /** 226 * Sets up a specific number of buffers. 227 */ 228 public void setBuffers(size_t num) @safe nothrow { 229 import pixelperfectengine.audio.base.func : resetBuffer; 230 buffers.length = num; 231 for (size_t i ; i < buffers.length ; i++) { 232 buffers[i].length = blockSize; 233 resetBuffer(buffers[i]); 234 } 235 } 236 public int getChannels() const @nogc @safe pure nothrow { 237 return channels; 238 } 239 /** 240 * Adds a plugin to the list. 241 *Params: 242 * md = The audio module to be added. Automatic set-up is done upon addition. 243 * inBuffs = list of the audio inputs to be added, or null if none. 244 * inCfg = list of audio input IDs to be used. Must be matched with `inBuffs` 245 * outBuffs - list of the audio outputs to be added, of null if none. 246 * outCfg = list of audio output IDs to be used. Must be matched with `outBuffs` 247 */ 248 public void addModule(AudioModule md, size_t[] inBuffs, ubyte[] inCfg, size_t[] outBuffs, ubyte[] outCfg) nothrow { 249 md.moduleSetup(inCfg, outCfg, devHandler.getSamplingFrequency, blockSize, this); 250 moduleList ~= md; 251 float*[] buffList0, buffList1; 252 buffList0.length = inBuffs.length; 253 for (size_t i ; i < inBuffs.length ; i++) { 254 buffList0[i] = buffers[inBuffs[i]].ptr; 255 } 256 buffList1.length = outBuffs.length; 257 for (size_t i ; i < outBuffs.length ; i++) { 258 buffList1[i] = buffers[outBuffs[i]].ptr; 259 } 260 inBufferList ~= buffList0; 261 outBufferList ~= buffList1; 262 } 263 public void reset() { 264 moduleList.length = 0; 265 inBufferList.length = 0; 266 outBufferList.length = 0; 267 setBuffers(0); 268 } 269 /** 270 * Runs the audio thread and starts the audio output. 271 */ 272 public int runAudioThread() @nogc nothrow { 273 return outStrm.runAudioThread(); 274 } 275 /** 276 * Stops all audio output. 277 */ 278 public int suspendAudioThread() { 279 return outStrm.suspendAudioThread(); 280 } 281 } 282 283 /** 284 * Thrown on audio initialization errors. 285 */ 286 public class AudioInitException : PPEException { 287 /// 288 @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) 289 { 290 super(msg, file, line, nextInChain); 291 } 292 /// 293 @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) 294 { 295 super(msg, file, line, nextInChain); 296 } 297 }