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 }