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 }