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.audio.base.modulebase; 18 19 /+import bindbc.sdl.bind.sdlaudio; 20 import bindbc.sdl.bind.sdlerror : SDL_GetError; 21 import bindbc.sdl.bind.sdl : SDL_Init, SDL_INIT_AUDIO;+/ 22 23 /** 24 * Manages and initializes audio devices. 25 * 26 * Important: Only one instance should be made. 27 */ 28 public class AudioDeviceHandler { 29 30 protected int channelLayout; ///Requested channel layout 31 protected int slmpFreq; ///Requested/given sampling frequency 32 protected int frameSize; ///Requested/given buffer base size / frame length (in samples) 33 protected int nOfFrames; ///Requested/given number of frames before they get sent to the output 34 /** 35 * Creates an instance, and detects all drivers. 36 *Params: 37 * slmpFreq: Requested sampling frequency. If not available, a nearby will be used instead. 38 * channels: Number of channels 39 * buffSize: The size of the buffer in samples 40 * 41 * Throws an AudioInitException if audio is failed to be initialized. 42 */ 43 public this(int channelLayout, int slmpFreq, int frameSize, int nOfFrames) { 44 //context = soundio_create(); 45 46 this.channelLayout = channelLayout; 47 this.slmpFreq = slmpFreq; 48 this.frameSize = frameSize; 49 this.nOfFrames = nOfFrames; 50 //req.callback = &callbacksFromSDL; 51 } 52 ///Destructor 53 ~this() { 54 55 }/+ 56 /** 57 * Initializes an audio driver by ID. 58 * 59 * Throws an AudioInitException if audio failed to be initialized. 60 */ 61 public void initAudioDriver(SoundIoBackend backend) { 62 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) { 70 71 } 72 /** 73 * Return an array with the names of the available audio devices. 74 */ 75 public string[] getDevices() @trusted pure nothrow const { 76 string[] result; 77 78 return result; 79 } 80 /** 81 * Returns the available sampling frequency. 82 */ 83 public int getSamplingFrequency() @nogc @safe pure nothrow const { 84 return slmpFreq; 85 } 86 /** 87 * Returns the number of audio channels. 88 */ 89 public int getChannels() @nogc @safe pure nothrow const { 90 return 0; 91 } 92 /** 93 * Returns the buffer size in units. 94 */ 95 public size_t getBufferSize() @nogc @safe pure nothrow const { 96 return frameSize * nOfFrames; 97 } 98 } 99 /** 100 * Manages all audio modules complete with routing, MIDI2.0, etc. 101 */ 102 public class ModuleManager { 103 protected int bufferSize; ///Rendering buffer size in samples, also the length of a single frame. 104 protected int nOfFrames; ///Number of maximum frames that can be put into the output buffer. 105 protected int currFrame; ///Current audio frame. 106 protected uint statusFlags; ///Status flags of the module manager. 107 protected ThreadID threadID; ///Low-level thread ID. 108 /** 109 * Status flags for the module manager. 110 */ 111 public enum Flags { 112 IsRunning = 1<<0, ///Set if thread is running. 113 BufferUnderrunError = 1<<16, ///Set if a buffer underrun error have been occured. 114 AudioQueueError = 1<<17, ///Set if the SDL_QueueAudio function returns with an error code. 115 } 116 ///Pointer to the audio device handler. 117 public AudioDeviceHandler devHandler; 118 /** 119 * List of modules. 120 * 121 * Ran in order, should be ordered in such way to ensure that routing is correct, and the modules that need the 122 * input will get some. 123 */ 124 protected AudioModule[] moduleList; 125 /** 126 * List of pointers to input buffers. 127 * 128 * Order of first dimension must match the modules. Pointers can be shared between multiple inputs or outputs. 129 * If a specific plugin doesn't have any inputs, then an array with zero elements must be added. 130 */ 131 protected float*[][] inBufferList; 132 /** 133 * List of pointers to output buffers. 134 * 135 * Order of first dimension must match the modules. Pointers can be shared between multiple inputs or outputs. 136 * If a specific plugin doesn't have any outputs, then an array with zero elements must be added. 137 */ 138 protected float*[][] outBufferList; 139 /** 140 * List of the buffers themselves. 141 * 142 * One buffer can be shared between multiple input and/or output for mixing, etc. 143 * All buffers must have the same size, defined by the variable `bufferSize` 144 * The first buffers are used for output rendering. 145 */ 146 protected float[][] buffers; 147 /** 148 * Final output buffer. 149 * 150 * Words are in LRLRLR... order, or similar, depending on number of channels. 151 */ 152 protected float[] finalBuffer; 153 /** 154 * Creates an instance of a module handler. 155 *Params: 156 * handler = The AudioDeviceHandler that contains the data about the audio device. 157 * bufferSize = The size of the buffer in samples. 158 * nOfFrames = The number of frames before they get queued to the audio device. 159 */ 160 public this(AudioDeviceHandler handler, int bufferSize, int nOfFrames) { 161 import pixelperfectengine.audio.base.func : resetBuffer; 162 devHandler = handler; 163 this.bufferSize = bufferSize; 164 //assert(handler.getBufferSize % bufferSize == 0, "`bufferSize` is not power of 2!"); 165 //nOfFrames = handler.getBufferSize / bufferSize; 166 this.nOfFrames = nOfFrames; 167 finalBuffer.length = handler.getChannels() * bufferSize * nOfFrames; 168 resetBuffer(finalBuffer); 169 170 buffers.length = handler.getChannels(); 171 for (int i ; i < buffers.length ; i++) { 172 buffers[i].length = bufferSize; 173 resetBuffer(buffers[i]); 174 } 175 //super(&render); 176 } 177 /+ 178 /** 179 * Renders audio to the 180 */ 181 protected void render(SoundIoOutStream* stream, int frameCountMin, int frameCountMax) @nogc nothrow { 182 import pixelperfectengine.system.etc : clamp; 183 184 }+/ 185 /+ 186 /** 187 * Runs the audio thread. 188 */ 189 protected void run() @nogc nothrow { 190 import pixelperfectengine.audio.base.func : interleave, resetBuffer; 191 statusFlags = Flags.IsRunning; //reset status flags 192 while (statusFlags & Flags.IsRunning) { 193 /+if (SDL_QueueAudio(devHandler.getAudioDeviceID(), cast(const void*)finalBuffer.ptr, cast(uint)(finalBuffer.length * 194 float.sizeof))) statusFlags |= Flags.AudioQueueError;+/ 195 while (currFrame < nOfFrames) { 196 foreach (ref key; buffers) { 197 resetBuffer(key); 198 } 199 foreach (size_t i, AudioModule am; moduleList) { 200 am.renderFrame(inBufferList[i], outBufferList[i]); 201 } 202 const size_t offset = currFrame * bufferSize; 203 interleave(bufferSize, buffers[0].ptr, buffers[1].ptr, finalBuffer.ptr + offset); 204 currFrame++; 205 } 206 currFrame = 0; 207 //Put thread to sleep 208 TickDuration newTimeStamp = TickDuration.currSystemTick(); 209 if (newTimeStamp > timeStamp + timeDelta) 210 statusFlags |= Flags.BufferUnderrunError; 211 else 212 Thread.sleep(cast(Duration)(timeDelta - (newTimeStamp - timeStamp))); 213 timeStamp = TickDuration.currSystemTick(); 214 } 215 }+/ 216 /** 217 * MIDI commands are received here from modules. 218 * 219 * data: up to 128 bits of MIDI 2.0 commands. Any packets that are shorter should be padded with zeros. 220 * offset: time offset of the command. This can reduce jitter caused by the asynchronous operation of the 221 * sequencer and the audio plugin system. 222 */ 223 public void midiReceive(uint[4] data, uint offset) @nogc nothrow { 224 225 } 226 /** 227 * Sets up a specific number of buffers. 228 */ 229 public void setBuffers(size_t num) @safe nothrow { 230 import pixelperfectengine.audio.base.func : resetBuffer; 231 buffers.length = num; 232 for (size_t i ; i < buffers.length ; i++) { 233 buffers[i].length = bufferSize; 234 resetBuffer(buffers[i]); 235 } 236 } 237 /** 238 * Adds a plugin to the list. 239 *Params: 240 * md = The audio module to be added. Automatic set-up is done upon addition. 241 * inBuffs = list of the audio inputs to be added, or null if none. 242 * inCfg = list of audio input IDs to be used. Must be matched with `inBuffs` 243 * outBuffs - list of the audio outputs to be added, of null if none. 244 * outCfg = list of audio output IDs to be used. Must be matched with `outBuffs` 245 */ 246 public void addModule(AudioModule md, size_t[] inBuffs, ubyte[] inCfg, size_t[] outBuffs, ubyte[] outCfg) nothrow { 247 md.moduleSetup(inCfg, outCfg, devHandler.getSamplingFrequency, bufferSize, this); 248 moduleList ~= md; 249 float*[] buffList0, buffList1; 250 buffList0.length = inBuffs.length; 251 for (size_t i ; i < inBuffs.length ; i++) { 252 buffList0[i] = buffers[inBuffs[i]].ptr; 253 } 254 buffList1.length = outBuffs.length; 255 for (size_t i ; i < outBuffs.length ; i++) { 256 buffList1[i] = buffers[outBuffs[i]].ptr; 257 } 258 inBufferList ~= buffList0; 259 outBufferList ~= buffList1; 260 } 261 262 /** 263 * 264 */ 265 public void runAudioThread() @nogc nothrow { 266 267 } 268 /** 269 * Stops all audio output. 270 * 271 */ 272 public uint suspendAudioThread() { 273 return uint.init; 274 } 275 } 276 277 /** 278 * Thrown on audio initialization errors. 279 */ 280 public class AudioInitException : PPEException { 281 /// 282 @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) 283 { 284 super(msg, file, line, nextInChain); 285 } 286 /// 287 @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) 288 { 289 super(msg, file, line, nextInChain); 290 } 291 }