1 // FIXME: add a query devices thing
2 // FIXME: add the alsa sequencer interface cuz then i don't need the virtual raw midi sigh. or at elast load "virtual" and auto connect it somehow
3 // FIXME: 3d sound samples - basically you can assign a position to a thing you are playing in terms of a angle and distance from teh observe and do a bit of lag and left/right balance adjustments, then tell it its own speed for doppler shifts
4 /**
5 	The purpose of this module is to provide audio functions for
6 	things like playback, capture, and volume on both Windows
7 	(via the mmsystem calls) and Linux (through ALSA).
8 
9 	It is only aimed at the basics, and will be filled in as I want
10 	a particular feature. I don't generally need super configurability
11 	and see it as a minus, since I don't generally care either, so I'm
12 	going to be going for defaults that just work. If you need more though,
13 	you can hack the source or maybe just use it for the operating system
14 	bindings.
15 
16 	For example, I'm starting this because I want to write a volume
17 	control program for my linux box, so that's what is going first.
18 	That will consist of a listening callback for volume changes and
19 	being able to get/set the volume.
20 
21 	TODO:
22 		* pre-resampler that loads a clip and prepares it for repeated fast use
23 		* controls so you can tell a particular thing to keep looping until you tell it to stop, or stop after the next loop, etc (think a phaser sound as long as you hold the button down)
24 		* playFile function that detects automatically. basically:
25 			        if(args[1].endsWith("ogg"))
26 					a.playOgg(args[1]);
27 				else if(args[1].endsWith("wav"))
28 					a.playWav(args[1]);
29 				else if(mp3)
30 					a.playMp3(args[1]);
31 
32 
33 		* play audio high level with options to wait until completion or return immediately
34 		* midi mid-level stuff but see [arsd.midi]!
35 
36 		* some kind of encoder???????
37 
38 	I will probably NOT do OSS anymore, since my computer doesn't even work with it now.
39 	Ditto for Macintosh, as I don't have one and don't really care about them.
40 
41 	License:
42 		GPL3 unless you compile with `-version=without_resampler` and do *not* use
43 		the mp3 functions, in which case it is BSL-1.0.
44 */
45 module arsd.simpleaudio;
46 
47 // http://webcache.googleusercontent.com/search?q=cache:NqveBqL0AOUJ:https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html&hl=en&gl=us&strip=1&vwsrc=0
48 
49 version(without_resampler) {
50 
51 } else {
52 	version(X86)
53 		version=with_resampler;
54 	version(X86_64)
55 		version=with_resampler;
56 }
57 
58 enum BUFFER_SIZE_FRAMES = 1024;//512;//2048;
59 enum BUFFER_SIZE_SHORT = BUFFER_SIZE_FRAMES * 2;
60 
61 /// A reasonable default volume for an individual sample. It doesn't need to be large; in fact it needs to not be large so mixing doesn't clip too much.
62 enum DEFAULT_VOLUME = 20;
63 
64 version(Demo_simpleaudio)
65 void main() {
66 /+
67 
68 	version(none) {
69 	import iv.stb.vorbis;
70 
71 	int channels;
72 	short* decoded;
73 	auto v = new VorbisDecoder("test.ogg");
74 
75 	auto ao = AudioOutput(0);
76 	ao.fillData = (short[] buffer) {
77 		auto got = v.getSamplesShortInterleaved(2, buffer.ptr, buffer.length);
78 		if(got == 0) {
79 			ao.stop();
80 		}
81 	};
82 
83 	ao.play();
84 	return;
85 	}
86 
87 
88 
89 
90 	auto thread = new AudioPcmOutThread();
91 	thread.start();
92 
93 	thread.playOgg("test.ogg");
94 
95 	Thread.sleep(5.seconds);
96 
97 	//Thread.sleep(150.msecs);
98 	thread.beep();
99 	Thread.sleep(250.msecs);
100 	thread.blip();
101 	Thread.sleep(250.msecs);
102 	thread.boop();
103 	Thread.sleep(1000.msecs);
104 	/*
105 	thread.beep(800, 500);
106 	Thread.sleep(500.msecs);
107 	thread.beep(366, 500);
108 	Thread.sleep(600.msecs);
109 	thread.beep(800, 500);
110 	thread.beep(366, 500);
111 	Thread.sleep(500.msecs);
112 	Thread.sleep(150.msecs);
113 	thread.beep(200);
114 	Thread.sleep(150.msecs);
115 	thread.beep(100);
116 	Thread.sleep(150.msecs);
117 	thread.noise();
118 	Thread.sleep(150.msecs);
119 	*/
120 
121 
122 	thread.stop();
123 
124 	thread.join();
125 
126 	return;
127 
128 	/*
129 	auto aio = AudioMixer(0);
130 
131 	import std.stdio;
132 	writeln(aio.muteMaster);
133 	*/
134 
135 	/*
136 	mciSendStringA("play test.wav", null, 0, null);
137 	Sleep(3000);
138 	import std.stdio;
139 	if(auto err = mciSendStringA("play test2.wav", null, 0, null))
140 		writeln(err);
141 	Sleep(6000);
142 	return;
143 	*/
144 
145 	// output about a second of random noise to demo PCM
146 	auto ao = AudioOutput(0);
147 	short[BUFFER_SIZE_SHORT] randomSpam = void;
148 	import core.stdc.stdlib;
149 	foreach(ref s; randomSpam)
150 		s = cast(short)((cast(short) rand()) - short.max / 2);
151 
152 	int loopCount = 40;
153 
154 	//import std.stdio;
155 	//writeln("Should be about ", loopCount * BUFFER_SIZE_FRAMES * 1000 / SampleRate, " microseconds");
156 
157 	int loops = 0;
158 	// only do simple stuff in here like fill the data, set simple
159 	// variables, or call stop anything else might cause deadlock
160 	ao.fillData = (short[] buffer) {
161 		buffer[] = randomSpam[0 .. buffer.length];
162 		loops++;
163 		if(loops == loopCount)
164 			ao.stop();
165 	};
166 
167 	ao.play();
168 
169 	return;
170 +/
171 	// Play a C major scale on the piano to demonstrate midi
172 	auto midi = MidiOutput(0);
173 
174 	ubyte[16] buffer = void;
175 	ubyte[] where = buffer[];
176 	midi.writeRawMessageData(where.midiProgramChange(1, 1));
177 	for(ubyte note = MidiNote.C; note <= MidiNote.C + 12; note++) {
178 		where = buffer[];
179 		midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
180 		import core.thread;
181 		Thread.sleep(dur!"msecs"(500));
182 		midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
183 
184 		if(note != 76 && note != 83)
185 			note++;
186 	}
187 	import core.thread;
188 	Thread.sleep(dur!"msecs"(500)); // give the last note a chance to finish
189 }
190 
191 /++
192 	Provides an interface to control a sound.
193 
194 	History:
195 		Added December 23, 2020
196 +/
197 interface SampleController {
198 	/++
199 		Pauses playback, keeping its position. Use [resume] to pick up where it left off.
200 	+/
201 	void pause();
202 	/++
203 		Resumes playback after a call to [pause].
204 	+/
205 	void resume();
206 	/++
207 		Stops playback. Once stopped, it cannot be restarted
208 		except by creating a new sample from the [AudioOutputThread]
209 		object.
210 	+/
211 	void stop();
212 	/++
213 		Reports the current stream position, in seconds, if available (NaN if not).
214 	+/
215 	float position();
216 
217 	/++
218 		If the sample has finished playing. Happens when it runs out or if it is stopped.
219 	+/
220 	bool finished();
221 
222 	/++
223 		If the sample has been paused.
224 
225 		History:
226 			Added May 26, 2021 (dub v10.0)
227 	+/
228 	bool paused();
229 
230 	/++
231 		Seeks to a point in the sample, if possible. If impossible, this function does nothing.
232 
233 		Params:
234 			where = point to seek to, in seconds
235 
236 		History:
237 			Added November 20, 2022 (dub v10.10)
238 	+/
239 	void seek(float where);
240 
241 	/++
242 		Sets a delegate that will be called on the audio thread when the sample is finished
243 		playing; immediately after [finished] becomes `true`.
244 
245 		$(PITFALL
246 			Very important: your callback is called on the audio thread. The safest thing
247 			to do in it is to simply send a message back to your main thread where it deals
248 			with whatever you want to do.
249 		)
250 	+/
251 	//void onfinished(void delegate() shared callback);
252 }
253 
254 private class DummySample : SampleController {
255 	void pause() {}
256 	void resume() {}
257 	void stop() {}
258 	float position() { return float.init; }
259 	bool finished() { return true; }
260 	bool paused() { return true; }
261 
262 	void seek(float where) {}
263 }
264 
265 private class SampleControlFlags : SampleController {
266 	void pause() { paused_ = true; }
267 	void resume() { paused_ = false; }
268 	void stop() { paused_ = false; stopped = true; }
269 
270 	bool paused_;
271 	bool stopped;
272 	bool finished_;
273 
274 	float position() { return currentPosition; }
275 	bool finished() { return finished_; }
276 	bool paused() { return paused_; }
277 
278 	void seek(float where) { synchronized(this) {requestedSeek = where;} }
279 
280 	float currentPosition = 0.0;
281 	float requestedSeek = float.init;
282 }
283 
284 /++
285 	Wraps [AudioPcmOutThreadImplementation] with RAII semantics for better
286 	error handling and disposal than the old way.
287 
288 	DO NOT USE THE `new` OPERATOR ON THIS! Just construct it inline:
289 
290 	---
291 		auto audio = AudioOutputThread(true);
292 		audio.beep();
293 	---
294 
295 	History:
296 		Added May 9, 2020 to replace the old [AudioPcmOutThread] class
297 		that proved pretty difficult to use correctly.
298 +/
299 struct AudioOutputThread {
300 	@disable this();
301 
302 	static if(__VERSION__ < 2098)
303 		mixin(q{ @disable new(size_t); }); // gdc9 requires the arg fyi, but i mix it in because dmd deprecates before semantic so it can't be versioned out ugh
304 	else
305 		@disable new(); // but new dmd is strict about not allowing it
306 
307 	@disable void start() {} // you aren't supposed to control the thread yourself!
308 	/++
309 		You should call `exit` instead of join. It will signal the thread to exit and then call join for you.
310 
311 		If you absolutely must call join, use [rawJoin] instead.
312 
313 		History:
314 			Disabled on December 30, 2021
315 	+/
316 	@disable void join(bool a = false) {} // you aren't supposed to control the thread yourself!
317 
318 	/++
319 		Don't call this unless you're sure you know what you're doing.
320 
321 		You should use `audioOutputThread.exit();` instead.
322 	+/
323 	Throwable rawJoin(bool rethrow = true) {
324 		if(impl is null)
325 			return null;
326 		return impl.join(rethrow);
327 	}
328 
329 	/++
330 		Pass `true` to enable the audio thread. Otherwise, it will
331 		just live as a dummy mock object that you should not actually
332 		try to use.
333 
334 		History:
335 			Parameter `default` added on Nov 8, 2020.
336 
337 			The sample rate parameter was not correctly applied to the device on Linux until December 24, 2020.
338 	+/
339 	this(bool enable, int SampleRate = 44100, int channels = 2, string device = "default") {
340 		if(enable) {
341 			impl = new AudioPcmOutThreadImplementation(SampleRate, channels, device);
342 			impl.refcount++;
343 			impl.start();
344 			impl.waitForInitialization();
345 			impl.priority = Thread.PRIORITY_MAX;
346 		}
347 	}
348 
349 	/// ditto
350 	this(bool enable, string device, int SampleRate = 44100, int channels = 2) {
351 		this(enable, SampleRate, channels, device);
352 	}
353 
354 	/// Keeps an internal refcount.
355 	this(this) {
356 		if(impl)
357 			impl.refcount++;
358 	}
359 
360 	/// When the internal refcount reaches zero, it stops the audio and rejoins the thread, throwing any pending exception (yes the dtor can throw! extremely unlikely though).
361 	~this() {
362 		if(impl) {
363 			impl.refcount--;
364 			if(impl.refcount == 0) {
365 				impl.exit(true);
366 			}
367 		}
368 	}
369 
370 	/++
371 		Returns true if the output is suspended. Use `suspend` and `unsuspend` to change this.
372 
373 		History:
374 			Added December 21, 2021 (dub v10.5)
375 	+/
376 	bool suspended() {
377 		if(impl)
378 			return impl.suspended();
379 		return true;
380 	}
381 
382 	/++
383 		This allows you to check `if(audio)` to see if it is enabled.
384 	+/
385 	bool opCast(T : bool)() {
386 		return impl !is null;
387 	}
388 
389 	/++
390 		Other methods are forwarded to the implementation of type
391 		[AudioPcmOutThreadImplementation]. See that for more information
392 		on what you can do.
393 
394 		This opDispatch template will forward all other methods directly
395 		to that [AudioPcmOutThreadImplementation] if this is live, otherwise
396 		it does nothing.
397 	+/
398 	template opDispatch(string name) {
399 		static if(is(typeof(__traits(getMember, impl, name)) Params == __parameters))
400 		auto opDispatch(Params params) {
401 			if(impl)
402 				return __traits(getMember, impl, name)(params);
403 			static if(!is(typeof(return) == void))
404 				return typeof(return).init;
405 		}
406 		else static assert(0);
407 	}
408 
409 	// manual forward of thse since the opDispatch doesn't do the variadic
410 	alias Sample = AudioPcmOutThreadImplementation.Sample;
411 	void addSample(Sample[] samples...) {
412 		if(impl !is null)
413 			impl.addSample(samples);
414 	}
415 
416 	// since these are templates, the opDispatch won't trigger them, so I have to do it differently.
417 	// the dummysample is good anyway.
418 
419 	SampleController playEmulatedOpl3Midi()(string filename) {
420 		if(impl)
421 			return impl.playEmulatedOpl3Midi(filename);
422 		return new DummySample;
423 	}
424 	SampleController playEmulatedOpl3Midi()(immutable(ubyte)[] data) {
425 		if(impl)
426 			return impl.playEmulatedOpl3Midi(data);
427 		return new DummySample;
428 	}
429 	SampleController playOgg()(string filename, bool loop = false) {
430 		if(impl)
431 			return impl.playOgg(filename, loop);
432 		return new DummySample;
433 	}
434 	SampleController playOgg()(immutable(ubyte)[] data, bool loop = false) {
435 		if(impl)
436 			return impl.playOgg(data, loop);
437 		return new DummySample;
438 	}
439 	SampleController playMp3()(string filename) {
440 		if(impl)
441 			return impl.playMp3(filename);
442 		return new DummySample;
443 	}
444 	SampleController playMp3()(immutable(ubyte)[] data) {
445 		if(impl)
446 			return impl.playMp3(data);
447 		return new DummySample;
448 	}
449 	SampleController playWav()(string filename) {
450 		if(impl)
451 			return impl.playWav(filename);
452 		return new DummySample;
453 	}
454 	SampleController playWav()(immutable(ubyte)[] data) {
455 		if(impl)
456 			return impl.playWav(data);
457 		return new DummySample;
458 	}
459 
460 
461 	/// provides automatic [arsd.jsvar] script wrapping capability. Make sure the
462 	/// script also finishes before this goes out of scope or it may end up talking
463 	/// to a dead object....
464 	auto toArsdJsvar() {
465 		return impl;
466 	}
467 
468 	/+
469 	alias getImpl this;
470 	AudioPcmOutThreadImplementation getImpl() {
471 		assert(impl !is null);
472 		return impl;
473 	}
474 	+/
475 	private AudioPcmOutThreadImplementation impl;
476 }
477 
478 /++
479 	Old thread implementation. I decided to deprecate it in favor of [AudioOutputThread] because
480 	RAII semantics make it easier to get right at the usage point. See that to go forward.
481 
482 	History:
483 		Deprecated on May 9, 2020.
484 +/
485 deprecated("Use AudioOutputThread instead.") class AudioPcmOutThread {}
486 
487 /+
488 /++
489 
490 +/
491 void mmsleep(Duration time) {
492 	version(Windows) {
493 		static HANDLE timerQueue;
494 
495 		static HANDLE event;
496 		if(event is null)
497 			event = CreateEvent(null, false, false, null);
498 
499 		extern(Windows)
500 		static void cb(PVOID ev, BOOLEAN) {
501 			HANDLE e = cast(HANDLE) ev;
502 			SetEvent(e);
503 		}
504 
505 		//if(timerQueue is null)
506 			//timerQueue = CreateTimerQueue();
507 
508 		// DeleteTimerQueueEx(timerQueue, null);
509 
510 		HANDLE nt;
511 		auto ret = CreateTimerQueueTimer(&nt, timerQueue, &cb, event /+ param +/, cast(DWORD) time.total!"msecs", 0 /* period */, WT_EXECUTEDEFAULT);
512 		if(!ret)
513 			throw new Exception("fail");
514 		//DeleteTimerQueueTimer(timerQueue, nt, INVALID_HANDLE_VALUE);
515 
516 		WaitForSingleObject(event, 1000);
517 	}
518 }
519 +/
520 
521 /++
522 	A clock you can use for multimedia applications. It compares time elapsed against
523 	a position variable you pass in to figure out how long to wait to get to that point.
524 	Very similar to Phobos' [std.datetime.stopwatch.StopWatch|StopWatch] but with built-in
525 	wait capabilities.
526 
527 
528 	For example, suppose you want something to happen 60 frames per second:
529 
530 	---
531 	MMClock clock;
532 	Duration frame;
533 	clock.restart();
534 	while(running) {
535 		frame += 1.seconds / 60;
536 		bool onSchedule = clock.waitUntil(frame);
537 
538 		do_essential_frame_work();
539 
540 		if(onSchedule) {
541 			// if we're on time, do other work too.
542 			// but if we weren't on time, skipping this
543 			// might help catch back up to where we're
544 			// supposed to be.
545 
546 			do_would_be_nice_frame_work();
547 		}
548 	}
549 	---
550 +/
551 struct MMClock {
552 	import core.time;
553 
554 	private Duration position;
555 	private MonoTime lastPositionUpdate;
556 	private bool paused;
557 	int speed = 1000; /// 1000 = 1.0, 2000 = 2.0, 500 = 0.5, etc.
558 
559 	private void updatePosition() {
560 		auto now = MonoTime.currTime;
561 		position += (now - lastPositionUpdate) * speed / 1000;
562 		lastPositionUpdate = now;
563 	}
564 
565 	/++
566 		Restarts the clock from position zero.
567 	+/
568 	void restart() {
569 		position = Duration.init;
570 		lastPositionUpdate = MonoTime.currTime;
571 	}
572 
573 	/++
574 		Pauses the clock.
575 	+/
576 	void pause() {
577 		if(paused) return;
578 		updatePosition();
579 		paused = true;
580 	}
581 	void unpause() {
582 		if(!paused) return;
583 		lastPositionUpdate = MonoTime.currTime;
584 		paused = false;
585 	}
586 	/++
587 		Goes to sleep until the real clock catches up to the given
588 		`position`.
589 
590 		Returns: `true` if you're on schedule, returns false if the
591 		given `position` is already in the past. In that case,
592 		you might want to consider skipping some work to get back
593 		on time.
594 	+/
595 	bool waitUntil(Duration position) {
596 		auto diff = timeUntil(position);
597 		if(diff < 0.msecs)
598 			return false;
599 
600 		if(diff == 0.msecs)
601 			return true;
602 
603 		import core.thread;
604 		Thread.sleep(diff);
605 		return true;
606 	}
607 
608 	/++
609 
610 	+/
611 	Duration timeUntil(Duration position) {
612 		updatePosition();
613 		return (position - this.position) * 1000 / speed;
614 	}
615 
616 	/++
617 		Returns the current time on the clock since the
618 		last call to [restart], excluding times when the
619 		clock was paused.
620 	+/
621 	Duration currentPosition() {
622 		updatePosition();
623 		return position;
624 	}
625 }
626 
627 import core.thread;
628 /++
629 	Makes an audio thread for you that you can make
630 	various sounds on and it will mix them with good
631 	enough latency for simple games.
632 
633 	DO NOT USE THIS DIRECTLY. Instead, access it through
634 	[AudioOutputThread].
635 
636 	---
637 		auto audio = AudioOutputThread(true);
638 		audio.beep();
639 
640 		// you need to keep the main program alive long enough
641 		// to keep this thread going to hear anything
642 		Thread.sleep(1.seconds);
643 	---
644 +/
645 final class AudioPcmOutThreadImplementation : Thread {
646 	private this(int SampleRate, int channels, string device = "default") {
647 		this.isDaemon = true;
648 
649 		this.SampleRate = SampleRate;
650 		this.channels = channels;
651 		this.device = device;
652 
653 		super(&run);
654 	}
655 
656 	private int SampleRate;
657 	private int channels;
658 	private int refcount;
659 	private string device;
660 
661 	private void waitForInitialization() {
662 		shared(AudioOutput*)* ao = cast(shared(AudioOutput*)*) &this.ao;
663 		//int wait = 0;
664 		while(isRunning && *ao is null) {
665 			Thread.sleep(5.msecs);
666 			//wait += 5;
667 		}
668 
669 		//import std.stdio; writeln(wait);
670 
671 		if(*ao is null) {
672 			exit(true);
673 		}
674 	}
675 
676 	/++
677 		Asks the device to pause/unpause. This may not actually do anything on some systems.
678 		You should probably use [suspend] and [unsuspend] instead.
679 	+/
680 	@scriptable
681 	void pause() {
682 		if(ao) {
683 			ao.pause();
684 		}
685 	}
686 
687 	/// ditto
688 	@scriptable
689 	void unpause() {
690 		if(ao) {
691 			ao.unpause();
692 		}
693 	}
694 
695 	/++
696 		Stops the output thread. Using the object after it is stopped is not recommended which is why
697 		this is now deprecated.
698 
699 		You probably want [suspend] or [exit] instead. Use [suspend] if you want to stop playing, and
700 		close the output device, but keep the thread alive so you can [unsuspend] later. After calling
701 		[suspend], you can call [unsuspend] and then continue using the other method normally again.
702 
703 		Use [exit] if you want to stop playing, close the output device, and terminate the worker thread.
704 		After calling [exit], you may not call any methods on the thread again.
705 
706 		The one exception is if you are inside an audio callback and want to stop the thread and prepare
707 		it to be [AudioOutputThread.rawJoin]ed. Preferably, you'd avoid doing this - the channels can
708 		simply return false to indicate that they are done. But if you must do that, call [rawStop] instead.
709 
710 		History:
711 			`stop` was deprecated and `rawStop` added on December 30, 2021 (dub v10.5)
712 	+/
713 	deprecated("You want to use either suspend or exit instead, or rawStop if you must but see the docs.")
714 	void stop() {
715 		if(ao) {
716 			ao.stop();
717 		}
718 	}
719 
720 	/// ditto
721 	void rawStop() {
722 		if(ao) { ao.stop(); }
723 	}
724 
725 	/++
726 		Makes some old-school style sound effects. Play with them to see what they actually sound like.
727 
728 		Params:
729 			freq = frequency of the wave in hertz
730 			dur = duration in milliseconds
731 			volume = amplitude of the wave, between 0 and 100
732 			balance = stereo balance. 50 = both speakers equally, 0 = all to the left, none to the right, 100 = all to the right, none to the left.
733 			attack = a parameter to the change of frequency
734 			freqBase = the base frequency in the sound effect algorithm
735 
736 		History:
737 			The `balance` argument was added on December 13, 2021 (dub v10.5)
738 
739 	+/
740 	@scriptable
741 	void beep(int freq = 900, int dur = 150, int volume = DEFAULT_VOLUME, int balance = 50) {
742 		Sample s;
743 		s.operation = 0; // square wave
744 		s.frequency = SampleRate / freq;
745 		s.duration = dur * SampleRate / 1000;
746 		s.volume = volume;
747 		s.balance = balance;
748 		addSample(s);
749 	}
750 
751 	/// ditto
752 	@scriptable
753 	void noise(int dur = 150, int volume = DEFAULT_VOLUME, int balance = 50) {
754 		Sample s;
755 		s.operation = 1; // noise
756 		s.frequency = 0;
757 		s.volume = volume;
758 		s.duration = dur * SampleRate / 1000;
759 		s.balance = balance;
760 		addSample(s);
761 	}
762 
763 	/// ditto
764 	@scriptable
765 	void boop(float attack = 8, int freqBase = 500, int dur = 150, int volume = DEFAULT_VOLUME, int balance = 50) {
766 		Sample s;
767 		s.operation = 5; // custom
768 		s.volume = volume;
769 		s.duration = dur * SampleRate / 1000;
770 		s.balance = balance;
771 		s.f = delegate short(int x) {
772 			auto currentFrequency = cast(float) freqBase / (1 + cast(float) x / (cast(float) SampleRate / attack));
773 			import std.math;
774 			auto freq = 2 * PI /  (cast(float) SampleRate / currentFrequency);
775 			return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100);
776 		};
777 		addSample(s);
778 	}
779 
780 	/// ditto
781 	@scriptable
782 	void blip(float attack = 6, int freqBase = 800, int dur = 150, int volume = DEFAULT_VOLUME, int balance = 50) {
783 		Sample s;
784 		s.operation = 5; // custom
785 		s.volume = volume;
786 		s.duration = dur * SampleRate / 1000;
787 		s.balance = balance;
788 		s.f = delegate short(int x) {
789 			auto currentFrequency = cast(float) freqBase * (1 + cast(float) x / (cast(float) SampleRate / attack));
790 			import std.math;
791 			auto freq = 2 * PI /  (cast(float) SampleRate / currentFrequency);
792 			return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100);
793 		};
794 		addSample(s);
795 	}
796 
797 	version(none)
798 	void custom(int dur = 150, int volume = DEFAULT_VOLUME) {
799 		Sample s;
800 		s.operation = 5; // custom
801 		s.volume = volume;
802 		s.duration = dur * SampleRate / 1000;
803 		s.f = delegate short(int x) {
804 			auto currentFrequency = 500.0 / (1 + cast(float) x / (cast(float) SampleRate / 8));
805 			import std.math;
806 			auto freq = 2 * PI /  (cast(float) SampleRate / currentFrequency);
807 			return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100);
808 		};
809 		addSample(s);
810 	}
811 
812 	/++
813 		Plays the given midi files with the nuked opl3 emulator.
814 
815 		Requires nukedopl3.d (module [arsd.nukedopl3]) to be compiled in, which is GPL.
816 
817 		History:
818 			Added December 24, 2020.
819 		License:
820 			If you use this function, you are opting into the GPL version 2 or later.
821 		Authors:
822 			Based on ketmar's code.
823 	+/
824 	SampleController playEmulatedOpl3Midi()(string filename, bool loop = false) {
825 		import std.file;
826 		auto bytes = cast(immutable(ubyte)[]) std.file.read(filename);
827 
828 		return playEmulatedOpl3Midi(bytes);
829 	}
830 
831 	/// ditto
832 	SampleController playEmulatedOpl3Midi()(immutable(ubyte)[] data, bool loop = false) {
833 		import arsd.nukedopl3;
834 		auto scf = new SampleControlFlags;
835 
836 		auto player = new OPLPlayer(this.SampleRate, true, channels == 2);
837 		player.looped = loop;
838 		player.load(data);
839 		player.play();
840 
841 		addChannel(
842 			delegate bool(short[] buffer) {
843 				if(scf.paused) {
844 					buffer[] = 0;
845 					return true;
846 				}
847 
848 				if(!player.playing) {
849 					scf.finished_ = true;
850 					return false;
851 				}
852 
853 				auto pos = player.generate(buffer[]);
854 				scf.currentPosition += cast(float) buffer.length / SampleRate/ channels;
855 				if(pos == 0 || scf.stopped) {
856 					scf.finished_ = true;
857 					return false;
858 				}
859 				return !scf.stopped;
860 			}
861 		);
862 
863 		return scf;
864 	}
865 
866 	/++
867 		Requires vorbis.d to be compiled in (module arsd.vorbis)
868 
869 		Returns:
870 			An implementation of [SampleController] which lets you pause, etc., the file.
871 
872 			Please note that the static type may change in the future.  It will always be a subtype of [SampleController], but it may be more specialized as I add more features and this will not necessarily match its sister functions, [playMp3] and [playWav], though all three will share an ancestor in [SampleController].  Therefore, if you use `auto`, there's no guarantee the static type won't change in future versions and I will NOT consider that a breaking change since the base interface will remain compatible.  
873 		History:
874 			Automatic resampling support added Nov 7, 2020.
875 
876 			Return value changed from `void` to a sample control object on December 23, 2020.
877 	+/
878 	SampleController playOgg()(string filename, bool loop = false) {
879 		import arsd.vorbis;
880 		auto v = new VorbisDecoder(filename);
881 		return playOgg(v, loop);
882 	}
883 
884 	/// ditto
885 	SampleController playOgg()(immutable(ubyte)[] data, bool loop = false) {
886 		import arsd.vorbis;
887 		auto v = new VorbisDecoder(cast(int) data.length, delegate int(void[] buffer, uint ofs, VorbisDecoder vb) nothrow @nogc {
888 			if(buffer is null)
889 				return 0;
890 			ubyte[] buf = cast(ubyte[]) buffer;
891 
892 			if(ofs + buf.length <= data.length) {
893 				buf[] = data[ofs .. ofs + buf.length];
894 				return cast(int) buf.length;
895 			} else {
896 				buf[0 .. data.length - ofs] = data[ofs .. $];
897 				return cast(int) data.length - ofs;
898 			}
899 		});
900 		return playOgg(v, loop);
901 	}
902 
903 	// no compatibility guarantees, I can change this overload at any time!
904 	/* private */ SampleController playOgg(VorbisDecoder)(VorbisDecoder v, bool loop = false) {
905 
906 		auto scf = new SampleControlFlags;
907 
908 		/+
909 			If you want 2 channels:
910 				if the file has 2+, use them.
911 				If the file has 1, duplicate it for the two outputs.
912 			If you want 1 channel:
913 				if the file has 1, use it
914 				if the file has 2, average them.
915 		+/
916 
917 		if(v.sampleRate == SampleRate && v.chans == channels) {
918 			plain_fallback:
919 			addChannel(
920 				delegate bool(short[] buffer) {
921 					if(scf.paused) {
922 						buffer[] = 0;
923 						return true;
924 					}
925 					if(cast(int) buffer.length != buffer.length)
926 						throw new Exception("eeeek");
927 
928 					synchronized(scf)
929 					if(scf.requestedSeek !is float.init) {
930 						if(v.seek(cast(uint) (scf.requestedSeek * v.sampleRate))) {
931 							scf.currentPosition = scf.requestedSeek;
932 						}
933 
934 						scf.requestedSeek = float.init;
935 					}
936 
937 					plain:
938 					auto got = v.getSamplesShortInterleaved(2, buffer.ptr, cast(int) buffer.length);
939 					if(got == 0) {
940 						if(loop) {
941 							v.seekStart();
942 							scf.currentPosition = 0;
943 							return true;
944 						}
945 
946 						scf.finished_ = true;
947 						return false;
948 					} else {
949 						scf.currentPosition += cast(float) got / v.sampleRate;
950 					}
951 					if(scf.stopped)
952 						scf.finished_ = true;
953 					return !scf.stopped;
954 				}
955 			);
956 		} else {
957 			version(with_resampler) {
958 				auto resampleContext = new class ResamplingContext {
959 					this() {
960 						super(scf, v.sampleRate, SampleRate, v.chans, channels);
961 					}
962 
963 					override void loadMoreSamples() {
964 						float*[2] tmp;
965 						tmp[0] = buffersIn[0].ptr;
966 						tmp[1] = buffersIn[1].ptr;
967 
968 						synchronized(scf)
969 						if(scf.requestedSeek !is float.init) {
970 							if(v.seekFrame(cast(uint) (scf.requestedSeek * v.sampleRate))) {
971 								scf.currentPosition = scf.requestedSeek;
972 							}
973 
974 							scf.requestedSeek = float.init;
975 						}
976 
977 						loop:
978 						auto actuallyGot = v.getSamplesFloat(v.chans, tmp.ptr, cast(int) buffersIn[0].length);
979 						if(actuallyGot == 0 && loop) {
980 							v.seekStart();
981 							scf.currentPosition = 0;
982 							goto loop;
983 						}
984 
985 						resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot];
986 						if(v.chans > 1)
987 							resamplerDataRight.dataIn = buffersIn[1][0 .. actuallyGot];
988 					}
989 				};
990 
991 				addChannel(&resampleContext.fillBuffer);
992 			} else goto plain_fallback;
993 		}
994 
995 		return scf;
996 	}
997 
998 	/++
999 		Requires mp3.d to be compiled in (module [arsd.mp3]) which is LGPL licensed.
1000 		That LGPL license will extend to your code.
1001 
1002 		Returns:
1003 			An implementation of [SampleController] which lets you pause, etc., the file.
1004 
1005 			Please note that the static type may change in the future. It will always be a subtype of [SampleController], but it may be more specialized as I add more features and this will not necessarily match its sister functions, [playOgg] and [playWav], though all three will share an ancestor in [SampleController].  Therefore, if you use `auto`, there's no guarantee the static type won't change in future versions and I will NOT consider that a breaking change since the base interface will remain compatible.  
1006 
1007 		Bugs:
1008 			Mp3s cannot be seeked or looped in the current implementation.
1009 
1010 		History:
1011 			Automatic resampling support added Nov 7, 2020.
1012 
1013 			Return value changed from `void` to a sample control object on December 23, 2020.
1014 
1015 			The `immutable(ubyte)[]` overload was added December 30, 2020.
1016 	+/
1017 	SampleController playMp3()(string filename) {
1018 		import std.stdio;
1019 		auto fi = new File(filename); // just let the GC close it... otherwise random segfaults happen... blargh
1020 		auto reader = delegate(void[] buf) {
1021 			return cast(int) fi.rawRead(buf[]).length;
1022 		};
1023 
1024 		return playMp3(reader);
1025 	}
1026 
1027 	/// ditto
1028 	SampleController playMp3()(immutable(ubyte)[] data) {
1029 		return playMp3( (void[] buffer) {
1030 			ubyte[] buf = cast(ubyte[]) buffer;
1031 			if(data.length >= buf.length) {
1032 				buf[] = data[0 .. buf.length];
1033 				data = data[buf.length .. $];
1034 				return cast(int) buf.length;
1035 			} else {
1036 				auto it = data.length;
1037 				buf[0 .. data.length] = data[];
1038 				buf[data.length .. $] = 0;
1039 				data = data[$ .. $];
1040 				return cast(int) it;
1041 			}
1042 		});
1043 	}
1044 
1045 	// no compatibility guarantees, I can change this overload at any time!
1046 	/* private */ SampleController playMp3()(int delegate(void[]) reader) {
1047 		import arsd.mp3;
1048 
1049 		auto mp3 = new MP3Decoder(reader);
1050 		if(!mp3.valid)
1051 			throw new Exception("file not valid");
1052 
1053 		auto scf = new SampleControlFlags;
1054 
1055 		if(mp3.sampleRate == SampleRate && mp3.channels == channels) {
1056 			plain_fallback:
1057 
1058 			auto next = mp3.frameSamples;
1059 
1060 			addChannel(
1061 				delegate bool(short[] buffer) {
1062 					if(scf.paused) {
1063 						buffer[] = 0;
1064 						return true;
1065 					}
1066 
1067 					if(cast(int) buffer.length != buffer.length)
1068 						throw new Exception("eeeek");
1069 
1070 					synchronized(scf)
1071 					if(scf.requestedSeek !is float.init) {
1072 						if(mp3.seek(cast(uint) (scf.requestedSeek * v.sampleRate))) {
1073 							scf.currentPosition = scf.requestedSeek;
1074 						}
1075 
1076 						scf.requestedSeek = float.init;
1077 					}
1078 
1079 					more:
1080 					if(next.length >= buffer.length) {
1081 						buffer[] = next[0 .. buffer.length];
1082 						next = next[buffer.length .. $];
1083 
1084 						scf.currentPosition += cast(float) buffer.length / mp3.sampleRate / mp3.channels;
1085 					} else {
1086 						buffer[0 .. next.length] = next[];
1087 						buffer = buffer[next.length .. $];
1088 
1089 						scf.currentPosition += cast(float) next.length / mp3.sampleRate / mp3.channels;
1090 
1091 						next = next[$..$];
1092 
1093 						if(buffer.length) {
1094 							if(mp3.valid) {
1095 								mp3.decodeNextFrame(reader);
1096 								next = mp3.frameSamples;
1097 								goto more;
1098 							} else {
1099 								buffer[] = 0;
1100 								scf.finished_ = true;
1101 								return false;
1102 							}
1103 						}
1104 					}
1105 
1106 					if(scf.stopped) {
1107 						scf.finished_ = true;
1108 					}
1109 					return !scf.stopped;
1110 				}
1111 			);
1112 		} else {
1113 			version(with_resampler) {
1114 				auto next = mp3.frameSamples;
1115 
1116 				auto resampleContext = new class ResamplingContext {
1117 					this() {
1118 						super(scf, mp3.sampleRate, SampleRate, mp3.channels, channels);
1119 					}
1120 
1121 					override void loadMoreSamples() {
1122 						if(mp3.channels == 1) {
1123 							int actuallyGot;
1124 
1125 							foreach(ref b; buffersIn[0]) {
1126 								if(next.length == 0) break;
1127 								b = cast(float) next[0] / short.max;
1128 								next = next[1 .. $];
1129 								if(next.length == 0) {
1130 									mp3.decodeNextFrame(reader);
1131 									next = mp3.frameSamples;
1132 								}
1133 								actuallyGot++;
1134 							}
1135 							resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot];
1136 						} else {
1137 							int actuallyGot;
1138 
1139 							foreach(idx, ref b; buffersIn[0]) {
1140 								if(next.length == 0) break;
1141 								b = cast(float) next[0] / short.max;
1142 								next = next[1 .. $];
1143 								if(next.length == 0) {
1144 									mp3.decodeNextFrame(reader);
1145 									next = mp3.frameSamples;
1146 								}
1147 								buffersIn[1][idx] = cast(float) next[0] / short.max;
1148 								next = next[1 .. $];
1149 								if(next.length == 0) {
1150 									mp3.decodeNextFrame(reader);
1151 									next = mp3.frameSamples;
1152 								}
1153 								actuallyGot++;
1154 							}
1155 							resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot];
1156 							resamplerDataRight.dataIn = buffersIn[1][0 .. actuallyGot];
1157 						}
1158 					}
1159 				};
1160 
1161 				addChannel(&resampleContext.fillBuffer);
1162 
1163 			} else goto plain_fallback;
1164 		}
1165 
1166 		return scf;
1167 	}
1168 
1169 	/++
1170 		Requires [arsd.wav]. Only supports simple 8 or 16 bit wav files, no extensible or float formats at this time.
1171 
1172 		Also requires the resampler to be compiled in at this time, but that may change in the future, I was just lazy.
1173 
1174 		Returns:
1175 			An implementation of [SampleController] which lets you pause, etc., the file.
1176 
1177 			Please note that the static type may change in the future.  It will always be a subtype of [SampleController], but it may be more specialized as I add more features and this will not necessarily match its sister functions, [playMp3] and [playOgg], though all three will share an ancestor in [SampleController].  Therefore, if you use `auto`, there's no guarantee the static type won't change in future versions and I will NOT consider that a breaking change since the base interface will remain compatible.  
1178 		History:
1179 			Added Nov 8, 2020.
1180 
1181 			Return value changed from `void` to a sample control object on December 23, 2020.
1182 	+/
1183 	SampleController playWav(R)(R filename_or_data) if(is(R == string) /* filename */ || is(R == immutable(ubyte)[]) /* data */ ) {
1184 		auto scf = new SampleControlFlags;
1185 		version(with_resampler) {
1186 			auto resampleContext = new class ResamplingContext {
1187 				import arsd.wav;
1188 
1189 				this() {
1190 					reader = wavReader(filename_or_data);
1191 					next = reader.front;
1192 
1193 					super(scf, reader.sampleRate, SampleRate, reader.numberOfChannels, channels);
1194 				}
1195 
1196 				typeof(wavReader(filename_or_data)) reader;
1197 				const(ubyte)[] next;
1198 
1199 				override void loadMoreSamples() {
1200 
1201 					bool moar() {
1202 						if(next.length == 0) {
1203 							if(reader.empty)
1204 								return false;
1205 							reader.popFront;
1206 							next = reader.front;
1207 							if(next.length == 0)
1208 								return false;
1209 						}
1210 						return true;
1211 					}
1212 
1213 					if(reader.numberOfChannels == 1) {
1214 						int actuallyGot;
1215 
1216 						foreach(ref b; buffersIn[0]) {
1217 							if(!moar) break;
1218 							if(reader.bitsPerSample == 8) {
1219 								b = (cast(float) next[0] - 128.0f) / 127.0f;
1220 								next = next[1 .. $];
1221 							} else if(reader.bitsPerSample == 16) {
1222 								short n = next[0];
1223 								next = next[1 .. $];
1224 								if(!moar) break;
1225 								n |= cast(ushort)(next[0]) << 8;
1226 								next = next[1 .. $];
1227 
1228 								b = (cast(float) n) / short.max;
1229 							} else assert(0);
1230 
1231 							actuallyGot++;
1232 						}
1233 						resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot];
1234 					} else {
1235 						int actuallyGot;
1236 
1237 						foreach(idx, ref b; buffersIn[0]) {
1238 							if(!moar) break;
1239 							if(reader.bitsPerSample == 8) {
1240 								b = (cast(float) next[0] - 128.0f) / 127.0f;
1241 								next = next[1 .. $];
1242 
1243 								if(!moar) break;
1244 								buffersIn[1][idx] = (cast(float) next[0] - 128.0f) / 127.0f;
1245 								next = next[1 .. $];
1246 							} else if(reader.bitsPerSample == 16) {
1247 								short n = next[0];
1248 								next = next[1 .. $];
1249 								if(!moar) break;
1250 								n |= cast(ushort)(next[0]) << 8;
1251 								next = next[1 .. $];
1252 
1253 								b = (cast(float) n) / short.max;
1254 
1255 								if(!moar) break;
1256 								n = next[0];
1257 								next = next[1 .. $];
1258 								if(!moar) break;
1259 								n |= cast(ushort)(next[0]) << 8;
1260 								next = next[1 .. $];
1261 
1262 								buffersIn[1][idx] = (cast(float) n) / short.max;
1263 							} else assert(0);
1264 
1265 
1266 							actuallyGot++;
1267 						}
1268 						resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot];
1269 						resamplerDataRight.dataIn = buffersIn[1][0 .. actuallyGot];
1270 					}
1271 				}
1272 			};
1273 
1274 			addChannel(&resampleContext.fillBuffer);
1275 
1276 		} else static assert(0, "I was lazy and didn't implement straight-through playing");
1277 
1278 		return scf;
1279 	}
1280 
1281 	/++
1282 		A helper object to create synthesized sound samples.
1283 
1284 		Construct it with the [synth] function.
1285 
1286 		History:
1287 			Added October 29, 2022 (dub v10.10)
1288 
1289 		Examples:
1290 			---
1291 			AudioOutputThread ao = AudioOutputThread(true);
1292 			with(ao.synth) ao.playSynth(beep, boop, blip);
1293 			---
1294 	+/
1295 	static struct SynthBuilder {
1296 		private this(AudioPcmOutThreadImplementation ao) {
1297 			this.ao = ao;
1298 		}
1299 		private AudioPcmOutThreadImplementation ao;
1300 	}
1301 
1302 	/// ditto
1303 	SynthBuilder synth() {
1304 		return SynthBuilder(this);
1305 	}
1306 
1307 	static struct Sample {
1308 		enum Operation {
1309 			squareWave = 0,
1310 			noise = 1,
1311 			triangleWave = 2,
1312 			sawtoothWave = 3,
1313 			sineWave = 4,
1314 			customFunction = 5
1315 		}
1316 
1317 		/+
1318 		static Sample opDispatch(string operation)(int frequency) if(__traits(hasMember, Operation, operation)) {
1319 			Sample s;
1320 			s.operation = cast(int) __traits(getMember, Operation, operation);
1321 			s.frequency = frequency;
1322 			return s;
1323 		}
1324 		+/
1325 
1326 		int operation;
1327 		int frequency; /* in samples */
1328 		int duration; /* in samples */
1329 		int volume = DEFAULT_VOLUME; /* between 1 and 100. You should generally shoot for something lowish, like 20. */
1330 		int delay; /* in samples */
1331 		int balance = 50; /* between 0 and 100 */
1332 
1333 		/+
1334 		// volume envelope
1335 		int attack;
1336 		int decay;
1337 		int sustainLevel;
1338 		int release;
1339 
1340 		// change in frequency
1341 		int frequencyAttack;
1342 
1343 		int vibratoRange; // change of frequency as it sustains
1344 		int vibratoSpeed; // how fast it cycles through the vibratoRange
1345 		+/
1346 
1347 		int x;
1348 		short delegate(int x) f;
1349 	}
1350 
1351 	// FIXME: go ahead and make this return a SampleController too
1352 	final void addSample(Sample[] samples...) {
1353 		if(samples.length == 0)
1354 			return;
1355 
1356 		Sample currentSample = samples[0];
1357 		samples = samples[1 .. $];
1358 		if(samples.length)
1359 			samples = samples.dup; // ensure it isn't in stack memory that might get smashed when the delegate is passed to the other thread
1360 
1361 		int frequencyCounter;
1362 		short val = cast(short) (cast(int) short.max * currentSample.volume / 100);
1363 
1364 		enum divisor = 50;
1365 		int leftMultiplier  = 50 + (50 - currentSample.balance);
1366 		int rightMultiplier = 50 + (currentSample.balance - 50);
1367 		bool left = true;
1368 
1369 		addChannel(
1370 			delegate bool (short[] buffer) {
1371 				newsample:
1372 				if(currentSample.duration) {
1373 					size_t i = 0;
1374 					if(currentSample.delay) {
1375 						if(buffer.length <= currentSample.delay * 2) {
1376 							// whole buffer consumed by delay
1377 							buffer[] = 0;
1378 							currentSample.delay -= buffer.length / 2;
1379 						} else {
1380 							i = currentSample.delay * 2;
1381 							buffer[0 .. i] = 0;
1382 							currentSample.delay = 0;
1383 						}
1384 					}
1385 					if(currentSample.delay > 0)
1386 						return true;
1387 
1388 					size_t sampleFinish;
1389 					if(currentSample.duration * 2 <= buffer.length) {
1390 						sampleFinish = currentSample.duration * 2;
1391 						currentSample.duration = 0;
1392 					} else {
1393 						sampleFinish = buffer.length;
1394 						currentSample.duration -= buffer.length / 2;
1395 					}
1396 
1397 					switch(currentSample.operation) {
1398 						case 0: // square wave
1399 							for(; i < sampleFinish; i++) {
1400 								buffer[i] = cast(short)((val * (left ? leftMultiplier : rightMultiplier)) / divisor);
1401 								left = !left;
1402 								// left and right do the same thing so we only count
1403 								// every other sample
1404 								if(i & 1) {
1405 									if(frequencyCounter)
1406 										frequencyCounter--;
1407 									if(frequencyCounter == 0) {
1408 										// are you kidding me dmd? random casts suck
1409 										val = cast(short) -cast(int)(val);
1410 										frequencyCounter = currentSample.frequency / 2;
1411 									}
1412 								}
1413 							}
1414 						break;
1415 						case 1: // noise
1416 							for(; i < sampleFinish; i++) {
1417 								import std.random;
1418 								buffer[i] = cast(short)((left ? leftMultiplier : rightMultiplier) * uniform(cast(short) -cast(int)val, val) / divisor);
1419 								left = !left;
1420 							}
1421 						break;
1422 						/+
1423 						case 2: // triangle wave
1424 
1425 		short[] tone;
1426 		tone.length = 22050 * len / 1000;
1427 
1428 		short valmax = cast(short) (cast(int) volume * short.max / 100);
1429 		int wavelength = 22050 / freq;
1430 		wavelength /= 2;
1431 		int da = valmax / wavelength;
1432 		int val = 0;
1433 
1434 		for(int a = 0; a < tone.length; a++){
1435 			tone[a] = cast(short) val;
1436 			val+= da;
1437 			if(da > 0 && val >= valmax)
1438 				da *= -1;
1439 			if(da < 0 && val <= -valmax)
1440 				da *= -1;
1441 		}
1442 
1443 		data ~= tone;
1444 
1445 
1446 							for(; i < sampleFinish; i++) {
1447 								buffer[i] = val;
1448 								// left and right do the same thing so we only count
1449 								// every other sample
1450 								if(i & 1) {
1451 									if(frequencyCounter)
1452 										frequencyCounter--;
1453 									if(frequencyCounter == 0) {
1454 										val = 0;
1455 										frequencyCounter = currentSample.frequency / 2;
1456 									}
1457 								}
1458 							}
1459 
1460 						break;
1461 						case 3: // sawtooth wave
1462 		short[] tone;
1463 		tone.length = 22050 * len / 1000;
1464 
1465 		int valmax = volume * short.max / 100;
1466 		int wavelength = 22050 / freq;
1467 		int da = valmax / wavelength;
1468 		short val = 0;
1469 
1470 		for(int a = 0; a < tone.length; a++){
1471 			tone[a] = val;
1472 			val+= da;
1473 			if(val >= valmax)
1474 				val = 0;
1475 		}
1476 
1477 		data ~= tone;
1478 						case 4: // sine wave
1479 		short[] tone;
1480 		tone.length = 22050 * len / 1000;
1481 
1482 		int valmax = volume * short.max / 100;
1483 		int val = 0;
1484 
1485 		float i = 2*PI / (22050/freq);
1486 
1487 		float f = 0;
1488 		for(int a = 0; a < tone.length; a++){
1489 			tone[a] = cast(short) (valmax * sin(f));
1490 			f += i;
1491 			if(f>= 2*PI)
1492 				f -= 2*PI;
1493 		}
1494 
1495 		data ~= tone;
1496 
1497 						+/
1498 						case 5: // custom function
1499 							val = currentSample.f(currentSample.x);
1500 							for(; i < sampleFinish; i++) {
1501 								buffer[i] = cast(short)(val * (left ? leftMultiplier : rightMultiplier) / divisor);
1502 								left = !left;
1503 								if(i & 1) {
1504 									currentSample.x++;
1505 									val = currentSample.f(currentSample.x);
1506 								}
1507 							}
1508 						break;
1509 						default: // unknown; use silence
1510 							currentSample.duration = 0;
1511 					}
1512 
1513 					if(i < buffer.length)
1514 						buffer[i .. $] = 0;
1515 
1516 					return currentSample.duration > 0 || samples.length;
1517 				} else if(samples.length) {
1518 					currentSample = samples[0];
1519 					samples = samples[1 .. $];
1520 
1521 					frequencyCounter = 0;
1522 					val = cast(short) (cast(int) short.max * currentSample.volume / 100);
1523 
1524 					leftMultiplier  = 50 + (50 - currentSample.balance);
1525 					rightMultiplier = 50 + (currentSample.balance - 50);
1526 					left = true;
1527 
1528 					goto newsample;
1529 				} else {
1530 					return false;
1531 				}
1532 			}
1533 		);
1534 	}
1535 
1536 	/++
1537 		The delegate returns false when it is finished (true means keep going).
1538 		It must fill the buffer with waveform data on demand and must be latency
1539 		sensitive; as fast as possible.
1540 	+/
1541 	public void addChannel(bool delegate(short[] buffer) dg) {
1542 		synchronized(this) {
1543 			// silently drops info if we don't have room in the buffer...
1544 			// don't do a lot of long running things lol
1545 			if(fillDatasLength < fillDatas.length)
1546 				fillDatas[fillDatasLength++] = dg;
1547 		}
1548 	}
1549 
1550 	private {
1551 		AudioOutput* ao;
1552 
1553 		bool delegate(short[] buffer)[32] fillDatas;
1554 		int fillDatasLength = 0;
1555 	}
1556 
1557 
1558 	private bool suspendWanted;
1559 	private bool exiting;
1560 
1561 	private bool suspended_;
1562 
1563 	/++
1564 		Stops playing and closes the audio device, but keeps the worker thread
1565 		alive and waiting for a call to [unsuspend], which will re-open everything
1566 		and pick up (close to; a couple buffers may be discarded) where it left off.
1567 
1568 		This is more reliable than [pause] and [unpause] since it doesn't require
1569 		the system/hardware to cooperate.
1570 
1571 		History:
1572 			Added December 30, 2021 (dub v10.5)
1573 	+/
1574 	public void suspend() {
1575 		suspended_ = true;
1576 		suspendWanted = true;
1577 		if(ao)
1578 			ao.stop();
1579 	}
1580 
1581 	/// ditto
1582 	public void unsuspend() {
1583 		suspended_ = false;
1584 		suspendWanted = false;
1585 		event.set();
1586 	}
1587 
1588 	/// ditto
1589 	public bool suspended() {
1590 		return suspended_;
1591 	}
1592 
1593 	/++
1594 		Stops playback and unsupends if necessary and exits.
1595 
1596 		Call this instead of join.
1597 
1598 		Please note: you should never call this from inside an audio
1599 		callback, as it will crash or deadlock. Instead, just return false
1600 		from your buffer fill function to indicate that you are done.
1601 
1602 		History:
1603 			Added December 30, 2021 (dub v10.5)
1604 	+/
1605 	public Throwable exit(bool rethrow = false) {
1606 		exiting = true;
1607 		unsuspend();
1608 		if(ao)
1609 			ao.stop();
1610 
1611 		return join(rethrow);
1612 	}
1613 
1614 
1615 	private void run() {
1616 		version(linux) {
1617 			// this thread has no business intercepting signals from the main thread,
1618 			// so gonna block a couple of them
1619 			import core.sys.posix.signal;
1620 			sigset_t sigset;
1621 			auto err = sigemptyset(&sigset);
1622 			assert(!err);
1623 
1624 			err = sigaddset(&sigset, SIGINT); assert(!err);
1625 			err = sigaddset(&sigset, SIGCHLD); assert(!err);
1626 
1627 			err = sigprocmask(SIG_BLOCK, &sigset, null);
1628 			assert(!err);
1629 		}
1630 
1631 		AudioOutput ao = AudioOutput(device, SampleRate, channels);
1632 
1633 		this.ao = &ao;
1634 		scope(exit) this.ao = null;
1635 		auto omg = this;
1636 		ao.fillData = (short[] buffer) {
1637 			short[BUFFER_SIZE_SHORT] bfr;
1638 			bool first = true;
1639 			if(fillDatasLength) {
1640 				for(int idx = 0; idx < fillDatasLength; idx++) {
1641 					auto dg = fillDatas[idx];
1642 					auto ret = dg(bfr[0 .. buffer.length][]);
1643 					foreach(i, v; bfr[0 .. buffer.length][]) {
1644 						int val;
1645 						if(first)
1646 							val = 0;
1647 						else
1648 							val = buffer[i];
1649 
1650 						int a = val;
1651 						int b = v;
1652 						int cap = a + b;
1653 						if(cap > short.max) cap = short.max;
1654 						else if(cap < short.min) cap = short.min;
1655 						val = cast(short) cap;
1656 						buffer[i] = cast(short) val;
1657 					}
1658 					if(!ret) {
1659 						// it returned false meaning this one is finished...
1660 						synchronized(omg) {
1661 							fillDatas[idx] = fillDatas[fillDatasLength - 1];
1662 							fillDatasLength--;
1663 						}
1664 						idx--;
1665 					}
1666 
1667 					first = false;
1668 				}
1669 			} else {
1670 				buffer[] = 0;
1671 			}
1672 		};
1673 		//try
1674 		resume_from_suspend:
1675 		ao.play();
1676 		/+
1677 		catch(Throwable t) {
1678 			import std.stdio;
1679 			writeln(t);
1680 		}
1681 		+/
1682 
1683 		if(suspendWanted) {
1684 			ao.close();
1685 
1686 			event.initialize(true, false);
1687 			if(event.wait() && !exiting) {
1688 				event.reset();
1689 
1690 				ao.open();
1691 				goto resume_from_suspend;
1692 			}
1693 		}
1694 
1695 		event.terminate();
1696 	}
1697 
1698 	static if(__VERSION__ > 2080) {
1699 		import core.sync.event;
1700 	} else {
1701 		// bad emulation of the Event but meh
1702 		static struct Event {
1703 			void terminate() {}
1704 			void initialize(bool, bool) {}
1705 
1706 			bool isSet;
1707 
1708 			void set() { isSet = true; }
1709 			void reset() { isSet = false; }
1710 			bool wait() {
1711 				while(!isSet) {
1712 					Thread.sleep(500.msecs);
1713 				}
1714 				isSet = false;
1715 				return true;
1716 			}
1717 
1718 		}
1719 	}
1720 
1721 	Event event;
1722 }
1723 
1724 
1725 import core.stdc.config;
1726 
1727 version(linux) version=ALSA;
1728 version(Windows) version=WinMM;
1729 
1730 version(ALSA) {
1731 	// this is the virtual rawmidi device on my computer at least
1732 	// maybe later i'll make it probe
1733 	//
1734 	// Getting midi to actually play on Linux is a bit of a pain.
1735 	// Here's what I did:
1736 	/*
1737 		# load the kernel driver, if amidi -l gives ioctl error,
1738 		# you haven't done this yet!
1739 		modprobe snd-virmidi
1740 
1741 		# start a software synth. timidity -iA is also an option
1742 		fluidsynth soundfont.sf2
1743 
1744 		# connect the virtual hardware port to the synthesizer
1745 		aconnect 24:0 128:0
1746 
1747 
1748 		I might also add a snd_seq client here which is a bit
1749 		easier to setup but for now I'm using the rawmidi so you
1750 		gotta get them connected somehow.
1751 	*/
1752 
1753 	// fyi raw midi dump:  amidi -d --port hw:4,0
1754 	// connect my midi out to fluidsynth: aconnect 28:0 128:0
1755 	// and my keyboard to it: aconnect 32:0 128:0
1756 }
1757 
1758 /// Thrown on audio failures.
1759 /// Subclass this to provide OS-specific exceptions
1760 class AudioException : Exception {
1761 	this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
1762 		super(message, file, line, next);
1763 	}
1764 }
1765 
1766 /++
1767 	Gives PCM input access (such as a microphone).
1768 
1769 	History:
1770 		Windows support added May 10, 2020 and the API got overhauled too.
1771 +/
1772 struct AudioInput {
1773 	version(ALSA) {
1774 		snd_pcm_t* handle;
1775 	} else version(WinMM) {
1776 		HWAVEIN handle;
1777 		HANDLE event;
1778 	} else static assert(0);
1779 
1780 	@disable this();
1781 	@disable this(this);
1782 
1783 	int channels;
1784 	int SampleRate;
1785 
1786 	/// Always pass card == 0.
1787 	this(int card, int SampleRate = 44100, int channels = 2) {
1788 		assert(card == 0);
1789 		this("default", SampleRate, channels);
1790 	}
1791 
1792 	/++
1793 		`device` is a device name. On Linux, it is the ALSA string.
1794 		On Windows, it is currently ignored, so you should pass "default"
1795 		or null so when it does get implemented your code won't break.
1796 
1797 		History:
1798 			Added Nov 8, 2020.
1799 	+/
1800 	this(string device, int SampleRate = 44100, int channels = 2) {
1801 		assert(channels == 1 || channels == 2);
1802 
1803 		this.channels = channels;
1804 		this.SampleRate = SampleRate;
1805 
1806 		version(ALSA) {
1807 			handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_CAPTURE, SampleRate, channels, device);
1808 		} else version(WinMM) {
1809 			event = CreateEvent(null, false /* manual reset */, false /* initially triggered */, null);
1810 
1811 			WAVEFORMATEX format;
1812 			format.wFormatTag = WAVE_FORMAT_PCM;
1813 			format.nChannels = 2;
1814 			format.nSamplesPerSec = SampleRate;
1815 			format.nAvgBytesPerSec = SampleRate * channels * 2; // two channels, two bytes per sample
1816 			format.nBlockAlign = 4;
1817 			format.wBitsPerSample = 16;
1818 			format.cbSize = 0;
1819 			if(auto err = waveInOpen(&handle, WAVE_MAPPER, &format, cast(DWORD_PTR) event, cast(DWORD_PTR) &this, CALLBACK_EVENT))
1820 				throw new WinMMException("wave in open", err);
1821 
1822 		} else static assert(0);
1823 	}
1824 
1825 	/// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz
1826 	/// Each item in the array thus alternates between left and right channel
1827 	/// and it takes a total of 88,200 items to make one second of sound.
1828 	///
1829 	/// Returns the slice of the buffer actually read into
1830 	///
1831 	/// LINUX ONLY. You should prolly use [record] instead
1832 	version(ALSA)
1833 	short[] read(short[] buffer) {
1834 		snd_pcm_sframes_t read;
1835 
1836 		read = snd_pcm_readi(handle, buffer.ptr, buffer.length / channels /* div number of channels apparently */);
1837 		if(read < 0) {
1838 			read = snd_pcm_recover(handle, cast(int) read, 0);
1839 			if(read < 0)
1840 				throw new AlsaException("pcm read", cast(int)read);
1841 			return null;
1842 		}
1843 
1844 		return buffer[0 .. read * channels];
1845 	}
1846 
1847 	/// passes a buffer of data to fill
1848 	///
1849 	/// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz
1850 	/// Each item in the array thus alternates between left and right channel
1851 	/// and it takes a total of 88,200 items to make one second of sound.
1852 	void delegate(short[]) receiveData;
1853 
1854 	///
1855 	void stop() {
1856 		recording = false;
1857 	}
1858 
1859 	/// First, set [receiveData], then call this.
1860 	void record() {
1861 		assert(receiveData !is null);
1862 		recording = true;
1863 
1864 		version(ALSA) {
1865 			short[BUFFER_SIZE_SHORT] buffer;
1866 			while(recording) {
1867 				auto got = read(buffer);
1868 				receiveData(got);
1869 			}
1870 		} else version(WinMM) {
1871 
1872 			enum numBuffers = 2; // use a lot of buffers to get smooth output with Sleep, see below
1873 			short[BUFFER_SIZE_SHORT][numBuffers] buffers;
1874 
1875 			WAVEHDR[numBuffers] headers;
1876 
1877 			foreach(i, ref header; headers) {
1878 				auto buffer = buffers[i][];
1879 				header.lpData = cast(char*) buffer.ptr;
1880 				header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof;
1881 				header.dwFlags = 0;// WHDR_BEGINLOOP | WHDR_ENDLOOP;
1882 				header.dwLoops = 0;
1883 
1884 				if(auto err = waveInPrepareHeader(handle, &header, header.sizeof))
1885 					throw new WinMMException("prepare header", err);
1886 
1887 				header.dwUser = 1; // mark that the driver is using it
1888 				if(auto err = waveInAddBuffer(handle, &header, header.sizeof))
1889 					throw new WinMMException("wave in read", err);
1890 			}
1891 
1892 			waveInStart(handle);
1893 			scope(failure) waveInReset(handle);
1894 
1895 			while(recording) {
1896 				if(auto err = WaitForSingleObject(event, INFINITE))
1897 					throw new Exception("WaitForSingleObject");
1898 				if(!recording)
1899 					break;
1900 
1901 				foreach(ref header; headers) {
1902 					if(!(header.dwFlags & WHDR_DONE)) continue;
1903 
1904 					receiveData((cast(short*) header.lpData)[0 .. header.dwBytesRecorded / short.sizeof]);
1905 					if(!recording) break;
1906 					header.dwUser = 1; // mark that the driver is using it
1907 					if(auto err = waveInAddBuffer(handle, &header, header.sizeof)) {
1908                                                 throw new WinMMException("waveInAddBuffer", err);
1909                                         }
1910 				}
1911 			}
1912 
1913 			/*
1914 			if(auto err = waveInStop(handle))
1915 				throw new WinMMException("wave in stop", err);
1916 			*/
1917 
1918 			if(auto err = waveInReset(handle)) {
1919 				throw new WinMMException("wave in reset", err);
1920 			}
1921 
1922 			still_in_use:
1923 			foreach(idx, header; headers)
1924 				if(!(header.dwFlags & WHDR_DONE)) {
1925 					Sleep(1);
1926 					goto still_in_use;
1927 				}
1928 
1929 			foreach(ref header; headers)
1930 				if(auto err = waveInUnprepareHeader(handle, &header, header.sizeof)) {
1931 					throw new WinMMException("unprepare header", err);
1932 				}
1933 
1934 			ResetEvent(event);
1935 		} else static assert(0);
1936 	}
1937 
1938 	private bool recording;
1939 
1940 	~this() {
1941 		receiveData = null;
1942 		version(ALSA) {
1943 			snd_pcm_close(handle);
1944 		} else version(WinMM) {
1945 			if(auto err = waveInClose(handle))
1946 				throw new WinMMException("close", err);
1947 
1948 			CloseHandle(event);
1949 			// in wine (though not Windows nor winedbg as far as I can tell)
1950 			// this randomly segfaults. the sleep prevents it. idk why.
1951 			Sleep(5);
1952 		} else static assert(0);
1953 	}
1954 }
1955 
1956 ///
1957 enum SampleRateFull = 44100;
1958 
1959 /// Gives PCM output access (such as the speakers).
1960 struct AudioOutput {
1961 	version(ALSA) {
1962 		snd_pcm_t* handle;
1963 	} else version(WinMM) {
1964 		HWAVEOUT handle;
1965 	}
1966 
1967 	@disable this();
1968 	// This struct must NEVER be moved or copied, a pointer to it may
1969 	// be passed to a device driver and stored!
1970 	@disable this(this);
1971 
1972 	private int SampleRate;
1973 	private int channels;
1974 	private string device;
1975 
1976 	/++
1977 		`device` is a device name. On Linux, it is the ALSA string.
1978 		On Windows, it is currently ignored, so you should pass "default"
1979 		or null so when it does get implemented your code won't break.
1980 
1981 		History:
1982 			Added Nov 8, 2020.
1983 	+/
1984 	this(string device, int SampleRate = 44100, int channels = 2) {
1985 		assert(channels == 1 || channels == 2);
1986 
1987 		this.SampleRate = SampleRate;
1988 		this.channels = channels;
1989 		this.device = device;
1990 
1991 		open();
1992 	}
1993 
1994 	/// Always pass card == 0.
1995 	this(int card, int SampleRate = 44100, int channels = 2) {
1996 		assert(card == 0);
1997 
1998 		this("default", SampleRate, channels);
1999 	}
2000 
2001 	/// passes a buffer of data to fill
2002 	///
2003 	/// Data is assumed to be interleaved stereo, LE 16 bit, 44.1 kHz (unless you change that in the ctor)
2004 	/// Each item in the array thus alternates between left and right channel (unless you change that in the ctor)
2005 	/// and it takes a total of 88,200 items to make one second of sound.
2006 	void delegate(short[]) fillData;
2007 
2008 	shared(bool) playing = false; // considered to be volatile
2009 
2010 	/// Starts playing, loops until stop is called
2011 	void play() {
2012 		if(handle is null)
2013 			open();
2014 
2015 		assert(fillData !is null);
2016 		playing = true;
2017 
2018 		version(ALSA) {
2019 			short[BUFFER_SIZE_SHORT] buffer;
2020 			while(playing) {
2021 				auto err = snd_pcm_wait(handle, 500);
2022 				if(err < 0) {
2023 					// see: https://stackoverflow.com/a/59400592/1457000
2024 					err = snd_pcm_recover(handle, err, 0);
2025 					if(err)
2026 						throw new AlsaException("pcm recover failed after pcm_wait did ", err);
2027 					//throw new AlsaException("uh oh", err);
2028 					continue;
2029 				}
2030 				if(err == 0)
2031 					continue;
2032 				// err == 0 means timeout
2033 				// err == 1 means ready
2034 
2035 				auto ready = snd_pcm_avail_update(handle);
2036 				if(ready < 0) {
2037 					//import std.stdio; writeln("recover");
2038 
2039 					// actually it seems ok to just try again..
2040 
2041 					// err = snd_pcm_recover(handle, err, 0);
2042 					//if(err)
2043 						//throw new AlsaException("avail", cast(int)ready);
2044 					continue;
2045 				}
2046 				if(ready > BUFFER_SIZE_FRAMES)
2047 					ready = BUFFER_SIZE_FRAMES;
2048 				//import std.stdio; writeln("filling ", ready);
2049 				fillData(buffer[0 .. ready * channels]);
2050 				if(playing) {
2051 					snd_pcm_sframes_t written;
2052 					auto data = buffer[0 .. ready * channels];
2053 
2054 					while(data.length) {
2055 						written = snd_pcm_writei(handle, data.ptr, data.length / channels);
2056 						if(written < 0) {
2057 						//import std.stdio; writeln(written);
2058 							written = snd_pcm_recover(handle, cast(int)written, 0);
2059 						//import std.stdio; writeln("recover ", written);
2060 							if (written < 0) throw new AlsaException("pcm write", cast(int)written);
2061 						}
2062 						data = data[written * channels .. $];
2063 					}
2064 				}
2065 			}
2066 		} else version(WinMM) {
2067 
2068 			enum numBuffers = 4; // use a lot of buffers to get smooth output with Sleep, see below
2069 			short[BUFFER_SIZE_SHORT][numBuffers] buffers;
2070 
2071 			WAVEHDR[numBuffers] headers;
2072 
2073 			foreach(i, ref header; headers) {
2074 				// since this is wave out, it promises not to write...
2075 				auto buffer = buffers[i][];
2076 				header.lpData = cast(char*) buffer.ptr;
2077 				header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof;
2078 				header.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
2079 				header.dwLoops = 1;
2080 
2081 				if(auto err = waveOutPrepareHeader(handle, &header, header.sizeof))
2082 					throw new WinMMException("prepare header", err);
2083 
2084 				// prime it
2085 				fillData(buffer[]);
2086 
2087 				// indicate that they are filled and good to go
2088 				header.dwUser = 1;
2089 			}
2090 
2091 			while(playing) {
2092 				// and queue both to be played, if they are ready
2093 				foreach(ref header; headers)
2094 					if(header.dwUser) {
2095 						if(auto err = waveOutWrite(handle, &header, header.sizeof))
2096 							throw new WinMMException("wave out write", err);
2097 						header.dwUser = 0;
2098 					}
2099 				Sleep(1);
2100 				// the system resolution may be lower than this sleep. To avoid gaps
2101 				// in output, we use multiple buffers. Might introduce latency, not
2102 				// sure how best to fix. I don't want to busy loop...
2103 			}
2104 
2105 			// wait for the system to finish with our buffers
2106 			bool anyInUse = true;
2107 
2108 			while(anyInUse) {
2109 				anyInUse = false;
2110 				foreach(header; headers) {
2111 					if(!header.dwUser) {
2112 						anyInUse = true;
2113 						break;
2114 					}
2115 				}
2116 				if(anyInUse)
2117 					Sleep(1);
2118 			}
2119 
2120 			foreach(ref header; headers) 
2121 				if(auto err = waveOutUnprepareHeader(handle, &header, header.sizeof))
2122 					throw new WinMMException("unprepare", err);
2123 		} else static assert(0);
2124 
2125 		close();
2126 	}
2127 
2128 	/// Breaks the play loop
2129 	void stop() {
2130 		playing = false;
2131 	}
2132 
2133 	///
2134 	void pause() {
2135 		version(WinMM)
2136 			waveOutPause(handle);
2137 		else version(ALSA)
2138 			snd_pcm_pause(handle, 1);
2139 	}
2140 
2141 	///
2142 	void unpause() {
2143 		version(WinMM)
2144 			waveOutRestart(handle);
2145 		else version(ALSA)
2146 			snd_pcm_pause(handle, 0);
2147 
2148 	}
2149 
2150 	version(WinMM) {
2151 		extern(Windows)
2152 		static void mmCallback(HWAVEOUT handle, UINT msg, void* userData, WAVEHDR* header, DWORD_PTR param2) {
2153 			AudioOutput* ao = cast(AudioOutput*) userData;
2154 			if(msg == WOM_DONE) {
2155 				// we want to bounce back and forth between two buffers
2156 				// to keep the sound going all the time
2157 				if(ao.playing) {
2158 					ao.fillData((cast(short*) header.lpData)[0 .. header.dwBufferLength / short.sizeof]);
2159 				}
2160 				header.dwUser = 1;
2161 			}
2162 		}
2163 	}
2164 
2165 
2166 	/++
2167 		Re-opens the audio device that you have previously [close]d.
2168 
2169 		History:
2170 			Added December 30, 2021
2171 	+/
2172 	void open() {
2173 		assert(handle is null);
2174 		assert(!playing);
2175 		version(ALSA) {
2176 			handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_PLAYBACK, SampleRate, channels, device);
2177 		} else version(WinMM) {
2178 			WAVEFORMATEX format;
2179 			format.wFormatTag = WAVE_FORMAT_PCM;
2180 			format.nChannels = cast(ushort) channels;
2181 			format.nSamplesPerSec = SampleRate;
2182 			format.nAvgBytesPerSec = SampleRate * channels * 2; // two channels, two bytes per sample
2183 			format.nBlockAlign = 4;
2184 			format.wBitsPerSample = 16;
2185 			format.cbSize = 0;
2186 			if(auto err = waveOutOpen(&handle, WAVE_MAPPER, &format, cast(DWORD_PTR) &mmCallback, cast(DWORD_PTR) &this, CALLBACK_FUNCTION))
2187 				throw new WinMMException("wave out open", err);
2188 		} else static assert(0);
2189 	}
2190 
2191 	/++
2192 		Closes the audio device. You MUST call [stop] before calling this.
2193 
2194 		History:
2195 			Added December 30, 2021
2196 	+/
2197 	void close() {
2198 		if(!handle)
2199 			return;
2200 		assert(!playing);
2201 		version(ALSA) {
2202 			snd_pcm_close(handle);
2203 			handle = null;
2204 		} else version(WinMM) {
2205 			waveOutClose(handle);
2206 			handle = null;
2207 		} else static assert(0);
2208 	}
2209 
2210 	// FIXME: add async function hooks
2211 
2212 	~this() {
2213 		close();
2214 	}
2215 }
2216 
2217 /++
2218 	For reading midi events from hardware, for example, an electronic piano keyboard
2219 	attached to the computer.
2220 +/
2221 struct MidiInput {
2222 	// reading midi devices...
2223 	version(ALSA) {
2224 		snd_rawmidi_t* handle;
2225 	} else version(WinMM) {
2226 		HMIDIIN handle;
2227 	}
2228 
2229 	@disable this();
2230 	@disable this(this);
2231 
2232 	/+
2233 B0 40 7F # pedal on
2234 B0 40 00 # sustain pedal off
2235 	+/
2236 
2237 	/// Always pass card == 0.
2238 	this(int card) {
2239 		assert(card == 0);
2240 
2241 		this("default"); // "hw:4,0"
2242 	}
2243 
2244 	/++
2245 		`device` is a device name. On Linux, it is the ALSA string.
2246 		On Windows, it is currently ignored, so you should pass "default"
2247 		or null so when it does get implemented your code won't break.
2248 
2249 		History:
2250 			Added Nov 8, 2020.
2251 	+/
2252 	this(string device) {
2253 		version(ALSA) {
2254 			if(auto err = snd_rawmidi_open(&handle, null, device.toStringz, 0))
2255 				throw new AlsaException("rawmidi open", err);
2256 		} else version(WinMM) {
2257 			if(auto err = midiInOpen(&handle, 0, cast(DWORD_PTR) &mmCallback, cast(DWORD_PTR) &this, CALLBACK_FUNCTION))
2258 				throw new WinMMException("midi in open", err);
2259 		} else static assert(0);
2260 	}
2261 
2262 	private bool recording = false;
2263 
2264 	///
2265 	void stop() {
2266 		recording = false;
2267 	}
2268 
2269 	/++
2270 		Records raw midi input data from the device.
2271 
2272 		The timestamp is given in milliseconds since recording
2273 		began (if you keep this program running for 23ish days
2274 		it might overflow! so... don't do that.). The other bytes
2275 		are the midi messages.
2276 
2277 		$(PITFALL Do not call any other multimedia functions from the callback!)
2278 	+/
2279 	void record(void delegate(uint timestamp, ubyte b1, ubyte b2, ubyte b3) dg) {
2280 		version(ALSA) {
2281 			recording = true;
2282 			ubyte[1024] data;
2283 			import core.time;
2284 			auto start = MonoTime.currTime;
2285 			while(recording) {
2286 				auto read = snd_rawmidi_read(handle, data.ptr, data.length);
2287 				if(read < 0)
2288 					throw new AlsaException("midi read", cast(int) read);
2289 
2290 				auto got = data[0 .. read];
2291 				while(got.length) {
2292 					// FIXME some messages are fewer bytes....
2293 					dg(cast(uint) (MonoTime.currTime - start).total!"msecs", got[0], got[1], got[2]);
2294 					got = got[3 .. $];
2295 				}
2296 			}
2297 		} else version(WinMM) {
2298 			recording = true;
2299 			this.dg = dg;
2300 			scope(exit)
2301 				this.dg = null;
2302 			midiInStart(handle);
2303 			scope(exit)
2304 				midiInReset(handle);
2305 
2306 			while(recording) {
2307 				Sleep(1);
2308 			}
2309 		} else static assert(0);
2310 	}
2311 
2312 	version(WinMM)
2313 	private void delegate(uint timestamp, ubyte b1, ubyte b2, ubyte b3) dg;
2314 
2315 
2316 	version(WinMM)
2317 	extern(Windows)
2318 	static
2319 	void mmCallback(HMIDIIN handle, UINT msg, DWORD_PTR user, DWORD_PTR param1, DWORD_PTR param2) {
2320 		MidiInput* mi = cast(MidiInput*) user;
2321 		if(msg == MIM_DATA) {
2322 			mi.dg(
2323 				cast(uint) param2,
2324 				param1 & 0xff,
2325 				(param1 >> 8) & 0xff,
2326 				(param1 >> 16) & 0xff
2327 			);
2328 		}
2329 	}
2330 
2331 	~this() {
2332 		version(ALSA) {
2333 			snd_rawmidi_close(handle);
2334 		} else version(WinMM) {
2335 			midiInClose(handle);
2336 		} else static assert(0);
2337 	}
2338 }
2339 
2340 version(Posix) {
2341 	import core.sys.posix.signal;
2342 	private sigaction_t oldSigIntr;
2343 	void setSigIntHandler() {
2344 		sigaction_t n;
2345 		n.sa_handler = &interruptSignalHandlerSAudio;
2346 		n.sa_mask = cast(sigset_t) 0;
2347 		n.sa_flags = 0;
2348 		sigaction(SIGINT, &n, &oldSigIntr);
2349 	}
2350 	void restoreSigIntHandler() {
2351 		sigaction(SIGINT, &oldSigIntr, null);
2352 	}
2353 
2354 	__gshared bool interrupted;
2355 
2356 	private
2357 	extern(C)
2358 	void interruptSignalHandlerSAudio(int sigNumber) nothrow {
2359 		interrupted = true;
2360 	}
2361 }
2362 
2363 /// Gives MIDI output access.
2364 struct MidiOutput {
2365 	version(ALSA) {
2366 		snd_rawmidi_t* handle;
2367 	} else version(WinMM) {
2368 		HMIDIOUT handle;
2369 	}
2370 
2371 	@disable this();
2372 	@disable this(this);
2373 
2374 	/// Always pass card == 0.
2375 	this(int card) {
2376 		assert(card == 0);
2377 
2378 		this("default"); // "hw:3,0"
2379 	}
2380 
2381 	/++
2382 		`device` is a device name. On Linux, it is the ALSA string.
2383 		On Windows, it is currently ignored, so you should pass "default"
2384 		or null so when it does get implemented your code won't break.
2385 
2386 		If you pass the string "DUMMY", it will not actually open a device
2387 		and simply be a do-nothing mock object;
2388 
2389 		History:
2390 			Added Nov 8, 2020.
2391 
2392 			Support for the "DUMMY" device was added on January 2, 2022.
2393 	+/
2394 	this(string device) {
2395 		if(device == "DUMMY")
2396 			return;
2397 
2398 		version(ALSA) {
2399 			if(auto err = snd_rawmidi_open(null, &handle, device.toStringz, 0))
2400 				throw new AlsaException("rawmidi open", err);
2401 		} else version(WinMM) {
2402 			if(auto err = midiOutOpen(&handle, 0, 0, 0, CALLBACK_NULL))
2403 				throw new WinMMException("midi out open", err);
2404 		} else static assert(0);
2405 	}
2406 
2407 	void silenceAllNotes() {
2408 		foreach(a; 0 .. 16)
2409 			writeMidiMessage((0x0b << 4)|a /*MIDI_EVENT_CONTROLLER*/, 123, 0);
2410 	}
2411 
2412 	/// Send a reset message, silencing all notes
2413 	void reset() {
2414 		if(!handle) return;
2415 
2416 		version(ALSA) {
2417 			silenceAllNotes();
2418 			static immutable ubyte[1] resetCmd = [0xff];
2419 			writeRawMessageData(resetCmd[]);
2420 			// and flush it immediately
2421 			snd_rawmidi_drain(handle);
2422 		} else version(WinMM) {
2423 			if(auto error = midiOutReset(handle))
2424 				throw new WinMMException("midi reset", error);
2425 		} else static assert(0);
2426 	}
2427 
2428 	/// Writes a single low-level midi message
2429 	/// Timing and sending sane data is your responsibility!
2430 	void writeMidiMessage(int status, int param1, int param2) {
2431 		if(!handle) return;
2432 		version(ALSA) {
2433 			ubyte[3] dataBuffer;
2434 
2435 			dataBuffer[0] = cast(ubyte) status;
2436 			dataBuffer[1] = cast(ubyte) param1;
2437 			dataBuffer[2] = cast(ubyte) param2;
2438 
2439 			auto msg = status >> 4;
2440 			ubyte[] data;
2441 			if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch)
2442 				data = dataBuffer[0 .. 2];
2443 			else
2444 				data = dataBuffer[];
2445 
2446 			writeRawMessageData(data);
2447 		} else version(WinMM) {
2448 			DWORD word = (param2 << 16) | (param1 << 8) | status;
2449 			if(auto error = midiOutShortMsg(handle, word))
2450 				throw new WinMMException("midi out", error);
2451 		} else static assert(0);
2452 
2453 	}
2454 
2455 	/// Writes a series of individual raw messages.
2456 	/// Timing and sending sane data is your responsibility!
2457 	/// The data should NOT include any timestamp bytes - each midi message should be 2 or 3 bytes.
2458 	void writeRawMessageData(scope const(ubyte)[] data) {
2459 		if(!handle) return;
2460 		if(data.length == 0)
2461 			return;
2462 		version(ALSA) {
2463 			ssize_t written;
2464 
2465 			while(data.length) {
2466 				written = snd_rawmidi_write(handle, data.ptr, data.length);
2467 				if(written < 0)
2468 					throw new AlsaException("midi write", cast(int) written);
2469 				data = data[cast(int) written .. $];
2470 			}
2471 		} else version(WinMM) {
2472 			while(data.length) {
2473 				auto msg = data[0] >> 4;
2474 				if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) {
2475 					writeMidiMessage(data[0], data[1], 0);
2476 					data = data[2 .. $];
2477 				} else {
2478 					writeMidiMessage(data[0], data[1], data[2]);
2479 					data = data[3 .. $];
2480 				}
2481 			}
2482 		} else static assert(0);
2483 	}
2484 
2485 	~this() {
2486 		if(!handle) return;
2487 		version(ALSA) {
2488 			snd_rawmidi_close(handle);
2489 		} else version(WinMM) {
2490 			midiOutClose(handle);
2491 		} else static assert(0);
2492 	}
2493 }
2494 
2495 
2496 // FIXME: maybe add a PC speaker beep function for completeness
2497 
2498 /// Interfaces with the default sound card. You should only have a single instance of this and it should
2499 /// be stack allocated, so its destructor cleans up after it.
2500 ///
2501 /// A mixer gives access to things like volume controls and mute buttons. It should also give a
2502 /// callback feature to alert you of when the settings are changed by another program.
2503 version(ALSA) // FIXME
2504 struct AudioMixer {
2505 	// To port to a new OS: put the data in the right version blocks
2506 	// then implement each function. Leave else static assert(0) at the
2507 	// end of each version group in a function so it is easier to implement elsewhere later.
2508 	//
2509 	// If a function is only relevant on your OS, put the whole function in a version block
2510 	// and give it an OS specific name of some sort.
2511 	//
2512 	// Feel free to do that btw without worrying about lowest common denominator: we want low level access when we want it.
2513 	//
2514 	// Put necessary bindings at the end of the file, or use an import if you like, but I prefer these files to be standalone.
2515 	version(ALSA) {
2516 		snd_mixer_t* handle;
2517 		snd_mixer_selem_id_t* sid;
2518 		snd_mixer_elem_t* selem;
2519 
2520 		c_long maxVolume, minVolume; // these are ok to use if you are writing ALSA specific code i guess
2521 
2522 		enum selemName = "Master";
2523 	}
2524 
2525 	@disable this();
2526 	@disable this(this);
2527 
2528 	/// Only cardId == 0 is supported
2529 	this(int cardId) {
2530 		assert(cardId == 0, "Pass 0 to use default sound card.");
2531 
2532 		this("default");
2533 	}
2534 
2535 	/++
2536 		`device` is a device name. On Linux, it is the ALSA string.
2537 		On Windows, it is currently ignored, so you should pass "default"
2538 		or null so when it does get implemented your code won't break.
2539 
2540 		History:
2541 			Added Nov 8, 2020.
2542 	+/
2543 	this(string device) {
2544 		version(ALSA) {
2545 			if(auto err = snd_mixer_open(&handle, 0))
2546 				throw new AlsaException("open sound", err);
2547 			scope(failure)
2548 				snd_mixer_close(handle);
2549 			if(auto err = snd_mixer_attach(handle, device.toStringz))
2550 				throw new AlsaException("attach to sound card", err);
2551 			if(auto err = snd_mixer_selem_register(handle, null, null))
2552 				throw new AlsaException("register mixer", err);
2553 			if(auto err = snd_mixer_load(handle))
2554 				throw new AlsaException("load mixer", err);
2555 
2556 			if(auto err = snd_mixer_selem_id_malloc(&sid))
2557 				throw new AlsaException("master channel open", err);
2558 			scope(failure)
2559 				snd_mixer_selem_id_free(sid);
2560 			snd_mixer_selem_id_set_index(sid, 0);
2561 			snd_mixer_selem_id_set_name(sid, selemName);
2562 			selem = snd_mixer_find_selem(handle, sid);
2563 			if(selem is null)
2564 				throw new AlsaException("find master element", 0);
2565 
2566 			if(auto err = snd_mixer_selem_get_playback_volume_range(selem, &minVolume, &maxVolume))
2567 				throw new AlsaException("get volume range", err);
2568 
2569 			version(with_eventloop) {
2570 				import arsd.eventloop;
2571 				addFileEventListeners(getAlsaFileDescriptors()[0], &eventListener, null, null);
2572 				setAlsaElemCallback(&alsaCallback);
2573 			}
2574 		} else static assert(0);
2575 	}
2576 
2577 	~this() {
2578 		version(ALSA) {
2579 			version(with_eventloop) {
2580 				import arsd.eventloop;
2581 				removeFileEventListeners(getAlsaFileDescriptors()[0]);
2582 			}
2583 			snd_mixer_selem_id_free(sid);
2584 			snd_mixer_close(handle);
2585 		} else static assert(0);
2586 	}
2587 
2588 	version(ALSA)
2589 	version(with_eventloop) {
2590 		static struct MixerEvent {}
2591 		nothrow @nogc
2592 		extern(C) static int alsaCallback(snd_mixer_elem_t*, uint) {
2593 			import arsd.eventloop;
2594 			try
2595 				send(MixerEvent());
2596 			catch(Exception)
2597 				return 1;
2598 
2599 			return 0;
2600 		}
2601 
2602 		void eventListener(int fd) {
2603 			handleAlsaEvents();
2604 		}
2605 	}
2606 
2607 	/// Gets the master channel's mute state
2608 	/// Note: this affects shared system state and you should not use it unless the end user wants you to.
2609 	@property bool muteMaster() {
2610 		version(ALSA) {
2611 			int result;
2612 			if(auto err = snd_mixer_selem_get_playback_switch(selem, 0, &result))
2613 				throw new AlsaException("get mute state", err);
2614 			return result == 0;
2615 		} else static assert(0);
2616 	}
2617 
2618 	/// Mutes or unmutes the master channel
2619 	/// Note: this affects shared system state and you should not use it unless the end user wants you to.
2620 	@property void muteMaster(bool mute) {
2621 		version(ALSA) {
2622 			if(auto err = snd_mixer_selem_set_playback_switch_all(selem, mute ? 0 : 1))
2623 				throw new AlsaException("set mute state", err);
2624 		} else static assert(0);
2625 	}
2626 
2627 	/// returns a percentage, between 0 and 100 (inclusive)
2628 	int getMasterVolume() {
2629 		version(ALSA) {
2630 			auto volume = getMasterVolumeExact();
2631 			return cast(int)(volume * 100 / (maxVolume - minVolume));
2632 		} else static assert(0);
2633 	}
2634 
2635 	/// Gets the exact value returned from the operating system. The range may vary.
2636 	int getMasterVolumeExact() {
2637 		version(ALSA) {
2638 			c_long volume;
2639 			snd_mixer_selem_get_playback_volume(selem, 0, &volume);
2640 			return cast(int)volume;
2641 		} else static assert(0);
2642 	}
2643 
2644 	/// sets a percentage on the volume, so it must be 0 <= volume <= 100
2645 	/// Note: this affects shared system state and you should not use it unless the end user wants you to.
2646 	void setMasterVolume(int volume) {
2647 		version(ALSA) {
2648 			assert(volume >= 0 && volume <= 100);
2649 			setMasterVolumeExact(cast(int)(volume * (maxVolume - minVolume) / 100));
2650 		} else static assert(0);
2651 	}
2652 
2653 	/// Sets an exact volume. Must be in range of the OS provided min and max.
2654 	void setMasterVolumeExact(int volume) {
2655 		version(ALSA) {
2656 			if(auto err = snd_mixer_selem_set_playback_volume_all(selem, volume))
2657 				throw new AlsaException("set volume", err);
2658 		} else static assert(0);
2659 	}
2660 
2661 	version(ALSA) {
2662 		/// Gets the ALSA descriptors which you can watch for events
2663 		/// on using regular select, poll, epoll, etc.
2664 		int[] getAlsaFileDescriptors() {
2665 			import core.sys.posix.poll;
2666 			pollfd[32] descriptors = void;
2667 			int got = snd_mixer_poll_descriptors(handle, descriptors.ptr, descriptors.length);
2668 			int[] result;
2669 			result.length = got;
2670 			foreach(i, desc; descriptors[0 .. got])
2671 				result[i] = desc.fd;
2672 			return result;
2673 		}
2674 
2675 		/// When the FD is ready, call this to let ALSA do its thing.
2676 		void handleAlsaEvents() {
2677 			snd_mixer_handle_events(handle);
2678 		}
2679 
2680 		/// Set a callback for the master volume change events.
2681 		void setAlsaElemCallback(snd_mixer_elem_callback_t dg) {
2682 			snd_mixer_elem_set_callback(selem, dg);
2683 		}
2684 	}
2685 }
2686 
2687 // ****************
2688 // Midi helpers
2689 // ****************
2690 
2691 // FIXME: code the .mid file format, read and write
2692 
2693 enum MidiEvent {
2694 	NoteOff           = 0x08,
2695 	NoteOn            = 0x09,
2696 	NoteAftertouch    = 0x0a,
2697 	Controller        = 0x0b,
2698 	ProgramChange     = 0x0c, // one param
2699 	ChannelAftertouch = 0x0d, // one param
2700 	PitchBend         = 0x0e,
2701 }
2702 
2703 enum MidiNote : ubyte {
2704 	middleC = 60,
2705 	A =  69, // 440 Hz
2706 	As = 70,
2707 	B =  71,
2708 	C =  72,
2709 	Cs = 73,
2710 	D =  74,
2711 	Ds = 75,
2712 	E =  76,
2713 	F =  77,
2714 	Fs = 78,
2715 	G =  79,
2716 	Gs = 80,
2717 }
2718 
2719 /// Puts a note on at the beginning of the passed slice, advancing it by the amount of the message size.
2720 /// Returns the message slice.
2721 ///
2722 /// See: http://www.midi.org/techspecs/midimessages.php
2723 ubyte[] midiNoteOn(ref ubyte[] where, ubyte channel, byte note, byte velocity) {
2724 	where[0] = (MidiEvent.NoteOn << 4) | (channel&0x0f);
2725 	where[1] = note;
2726 	where[2] = velocity;
2727 	auto it = where[0 .. 3];
2728 	where = where[3 .. $];
2729 	return it;
2730 }
2731 
2732 /// Note off.
2733 ubyte[] midiNoteOff(ref ubyte[] where, ubyte channel, byte note, byte velocity) {
2734 	where[0] = (MidiEvent.NoteOff << 4) | (channel&0x0f);
2735 	where[1] = note;
2736 	where[2] = velocity;
2737 	auto it = where[0 .. 3];
2738 	where = where[3 .. $];
2739 	return it;
2740 }
2741 
2742 /// Aftertouch.
2743 ubyte[] midiNoteAftertouch(ref ubyte[] where, ubyte channel, byte note, byte pressure) {
2744 	where[0] = (MidiEvent.NoteAftertouch << 4) | (channel&0x0f);
2745 	where[1] = note;
2746 	where[2] = pressure;
2747 	auto it = where[0 .. 3];
2748 	where = where[3 .. $];
2749 	return it;
2750 }
2751 
2752 /// Controller.
2753 ubyte[] midiNoteController(ref ubyte[] where, ubyte channel, byte controllerNumber, byte controllerValue) {
2754 	where[0] = (MidiEvent.Controller << 4) | (channel&0x0f);
2755 	where[1] = controllerNumber;
2756 	where[2] = controllerValue;
2757 	auto it = where[0 .. 3];
2758 	where = where[3 .. $];
2759 	return it;
2760 }
2761 
2762 /// Program change.
2763 ubyte[] midiProgramChange(ref ubyte[] where, ubyte channel, byte program) {
2764 	where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f);
2765 	where[1] = program;
2766 	auto it = where[0 .. 2];
2767 	where = where[2 .. $];
2768 	return it;
2769 }
2770 
2771 /// Channel aftertouch.
2772 ubyte[] midiChannelAftertouch(ref ubyte[] where, ubyte channel, byte amount) {
2773 	where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f);
2774 	where[1] = amount;
2775 	auto it = where[0 .. 2];
2776 	where = where[2 .. $];
2777 	return it;
2778 }
2779 
2780 /// Pitch bend. FIXME doesn't work right
2781 ubyte[] midiNotePitchBend(ref ubyte[] where, ubyte channel, short change) {
2782 /*
2783 first byte is llllll
2784 second byte is mmmmmm
2785 
2786 Pitch Bend Change. 0mmmmmmm This message is sent to indicate a change in the pitch bender (wheel or lever, typically). The pitch bender is measured by a fourteen bit value. Center (no pitch change) is 2000H. Sensitivity is a function of the transmitter. (llllll) are the least significant 7 bits. (mmmmmm) are the most significant 7 bits.
2787 */
2788 	where[0] = (MidiEvent.PitchBend << 4) | (channel&0x0f);
2789 	// FIXME
2790 	where[1] = 0;
2791 	where[2] = 0;
2792 	auto it = where[0 .. 3];
2793 	where = where[3 .. $];
2794 	return it;
2795 }
2796 
2797 
2798 // ****************
2799 // Wav helpers
2800 // ****************
2801 
2802 // FIXME: the .wav file format should be here, read and write (at least basics)
2803 // as well as some kind helpers to generate some sounds.
2804 
2805 // ****************
2806 // OS specific helper stuff follows
2807 // ****************
2808 
2809 private const(char)* toStringz(string s) {
2810 	return s.ptr; // FIXME jic
2811 }
2812 
2813 version(ALSA)
2814 // Opens the PCM device with default settings: stereo, 16 bit, 44.1 kHz, interleaved R/W.
2815 snd_pcm_t* openAlsaPcm(snd_pcm_stream_t direction, int SampleRate, int channels, string cardName = "default") {
2816 	snd_pcm_t* handle;
2817 	snd_pcm_hw_params_t* hwParams;
2818 
2819 	/* Open PCM and initialize hardware */
2820 
2821 	if (auto err = snd_pcm_open(&handle, cardName.toStringz, direction, 0))
2822 		throw new AlsaException("open device", err);
2823 	scope(failure)
2824 		snd_pcm_close(handle);
2825 
2826 
2827 	if (auto err = snd_pcm_hw_params_malloc(&hwParams))
2828 		throw new AlsaException("params malloc", err);
2829 	scope(exit)
2830 		snd_pcm_hw_params_free(hwParams);
2831 			 
2832 	if (auto err = snd_pcm_hw_params_any(handle, hwParams))
2833 		// can actually survive a failure here, we will just move forward
2834 		{} // throw new AlsaException("params init", err);
2835 
2836 	if (auto err = snd_pcm_hw_params_set_access(handle, hwParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED))
2837 		throw new AlsaException("params access", err);
2838 
2839 	if (auto err = snd_pcm_hw_params_set_format(handle, hwParams, snd_pcm_format.SND_PCM_FORMAT_S16_LE))
2840 		throw new AlsaException("params format", err);
2841 
2842 	uint rate = SampleRate;
2843 	int dir = 0;
2844 	if (auto err = snd_pcm_hw_params_set_rate_near(handle, hwParams, &rate, &dir))
2845 		throw new AlsaException("params rate", err);
2846 
2847 	assert(rate == SampleRate); // cheap me
2848 
2849 	if (auto err = snd_pcm_hw_params_set_channels(handle, hwParams, channels))
2850 		throw new AlsaException("params channels", err);
2851 
2852 	uint periods = 4;
2853 	{
2854 	auto err = snd_pcm_hw_params_set_periods_near(handle, hwParams, &periods, 0);
2855 	if(err < 0)
2856 		throw new AlsaException("periods", err);
2857 
2858 	// import std.stdio; writeln(periods);
2859 	snd_pcm_uframes_t sz = (BUFFER_SIZE_FRAMES * periods);
2860 	err = snd_pcm_hw_params_set_buffer_size_near(handle, hwParams, &sz);
2861 	if(err < 0)
2862 		throw new AlsaException("buffer size", err);
2863 	}
2864 
2865 	if (auto err = snd_pcm_hw_params(handle, hwParams))
2866 		throw new AlsaException("params install", err);
2867 
2868 	/* Setting up the callbacks */
2869 
2870 	snd_pcm_sw_params_t* swparams;
2871 	if(auto err = snd_pcm_sw_params_malloc(&swparams))
2872 		throw new AlsaException("sw malloc", err);
2873 	scope(exit)
2874 		snd_pcm_sw_params_free(swparams);
2875 	if(auto err = snd_pcm_sw_params_current(handle, swparams))
2876 		throw new AlsaException("sw set", err);
2877 	if(auto err = snd_pcm_sw_params_set_avail_min(handle, swparams, BUFFER_SIZE_FRAMES))
2878 		throw new AlsaException("sw min", err);
2879 	if(auto err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0))
2880 		throw new AlsaException("sw threshold", err);
2881 	if(auto err = snd_pcm_sw_params(handle, swparams))
2882 		throw new AlsaException("sw params", err);
2883 
2884 	/* finish setup */
2885 
2886 	if (auto err = snd_pcm_prepare(handle))
2887 		throw new AlsaException("prepare", err);
2888 
2889 	assert(handle !is null);
2890 	return handle;
2891 }
2892 
2893 version(ALSA)
2894 class AlsaException : AudioException {
2895 	this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
2896 		auto msg = snd_strerror(error);
2897 		import core.stdc.string;
2898 		super(cast(string) (message ~ ": " ~ msg[0 .. strlen(msg)]), file, line, next);
2899 	}
2900 }
2901 
2902 version(WinMM)
2903 class WinMMException : AudioException {
2904 	this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
2905 		// FIXME: format the error
2906 		// midiOutGetErrorText, etc.
2907 		super(message, file, line, next);
2908 	}
2909 }
2910 
2911 // ****************
2912 // Bindings follow
2913 // ****************
2914 
2915 version(ALSA) {
2916 extern(C):
2917 @nogc nothrow:
2918 	pragma(lib, "asound");
2919 	private import core.sys.posix.poll;
2920 
2921 	const(char)* snd_strerror(int);
2922 
2923 	// pcm
2924 	enum snd_pcm_stream_t {
2925 		SND_PCM_STREAM_PLAYBACK,
2926 		SND_PCM_STREAM_CAPTURE
2927 	}
2928 
2929 	enum snd_pcm_access_t {
2930 		/** mmap access with simple interleaved channels */ 
2931 		SND_PCM_ACCESS_MMAP_INTERLEAVED = 0, 
2932 		/** mmap access with simple non interleaved channels */ 
2933 		SND_PCM_ACCESS_MMAP_NONINTERLEAVED, 
2934 		/** mmap access with complex placement */ 
2935 		SND_PCM_ACCESS_MMAP_COMPLEX, 
2936 		/** snd_pcm_readi/snd_pcm_writei access */ 
2937 		SND_PCM_ACCESS_RW_INTERLEAVED, 
2938 		/** snd_pcm_readn/snd_pcm_writen access */ 
2939 		SND_PCM_ACCESS_RW_NONINTERLEAVED, 
2940 		SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED
2941 	}
2942 
2943 	enum snd_pcm_format {
2944 		/** Unknown */
2945 		SND_PCM_FORMAT_UNKNOWN = -1,
2946 		/** Signed 8 bit */
2947 		SND_PCM_FORMAT_S8 = 0,
2948 		/** Unsigned 8 bit */
2949 		SND_PCM_FORMAT_U8,
2950 		/** Signed 16 bit Little Endian */
2951 		SND_PCM_FORMAT_S16_LE,
2952 		/** Signed 16 bit Big Endian */
2953 		SND_PCM_FORMAT_S16_BE,
2954 		/** Unsigned 16 bit Little Endian */
2955 		SND_PCM_FORMAT_U16_LE,
2956 		/** Unsigned 16 bit Big Endian */
2957 		SND_PCM_FORMAT_U16_BE,
2958 		/** Signed 24 bit Little Endian using low three bytes in 32-bit word */
2959 		SND_PCM_FORMAT_S24_LE,
2960 		/** Signed 24 bit Big Endian using low three bytes in 32-bit word */
2961 		SND_PCM_FORMAT_S24_BE,
2962 		/** Unsigned 24 bit Little Endian using low three bytes in 32-bit word */
2963 		SND_PCM_FORMAT_U24_LE,
2964 		/** Unsigned 24 bit Big Endian using low three bytes in 32-bit word */
2965 		SND_PCM_FORMAT_U24_BE,
2966 		/** Signed 32 bit Little Endian */
2967 		SND_PCM_FORMAT_S32_LE,
2968 		/** Signed 32 bit Big Endian */
2969 		SND_PCM_FORMAT_S32_BE,
2970 		/** Unsigned 32 bit Little Endian */
2971 		SND_PCM_FORMAT_U32_LE,
2972 		/** Unsigned 32 bit Big Endian */
2973 		SND_PCM_FORMAT_U32_BE,
2974 		/** Float 32 bit Little Endian, Range -1.0 to 1.0 */
2975 		SND_PCM_FORMAT_FLOAT_LE,
2976 		/** Float 32 bit Big Endian, Range -1.0 to 1.0 */
2977 		SND_PCM_FORMAT_FLOAT_BE,
2978 		/** Float 64 bit Little Endian, Range -1.0 to 1.0 */
2979 		SND_PCM_FORMAT_FLOAT64_LE,
2980 		/** Float 64 bit Big Endian, Range -1.0 to 1.0 */
2981 		SND_PCM_FORMAT_FLOAT64_BE,
2982 		/** IEC-958 Little Endian */
2983 		SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
2984 		/** IEC-958 Big Endian */
2985 		SND_PCM_FORMAT_IEC958_SUBFRAME_BE,
2986 		/** Mu-Law */
2987 		SND_PCM_FORMAT_MU_LAW,
2988 		/** A-Law */
2989 		SND_PCM_FORMAT_A_LAW,
2990 		/** Ima-ADPCM */
2991 		SND_PCM_FORMAT_IMA_ADPCM,
2992 		/** MPEG */
2993 		SND_PCM_FORMAT_MPEG,
2994 		/** GSM */
2995 		SND_PCM_FORMAT_GSM,
2996 		/** Special */
2997 		SND_PCM_FORMAT_SPECIAL = 31,
2998 		/** Signed 24bit Little Endian in 3bytes format */
2999 		SND_PCM_FORMAT_S24_3LE = 32,
3000 		/** Signed 24bit Big Endian in 3bytes format */
3001 		SND_PCM_FORMAT_S24_3BE,
3002 		/** Unsigned 24bit Little Endian in 3bytes format */
3003 		SND_PCM_FORMAT_U24_3LE,
3004 		/** Unsigned 24bit Big Endian in 3bytes format */
3005 		SND_PCM_FORMAT_U24_3BE,
3006 		/** Signed 20bit Little Endian in 3bytes format */
3007 		SND_PCM_FORMAT_S20_3LE,
3008 		/** Signed 20bit Big Endian in 3bytes format */
3009 		SND_PCM_FORMAT_S20_3BE,
3010 		/** Unsigned 20bit Little Endian in 3bytes format */
3011 		SND_PCM_FORMAT_U20_3LE,
3012 		/** Unsigned 20bit Big Endian in 3bytes format */
3013 		SND_PCM_FORMAT_U20_3BE,
3014 		/** Signed 18bit Little Endian in 3bytes format */
3015 		SND_PCM_FORMAT_S18_3LE,
3016 		/** Signed 18bit Big Endian in 3bytes format */
3017 		SND_PCM_FORMAT_S18_3BE,
3018 		/** Unsigned 18bit Little Endian in 3bytes format */
3019 		SND_PCM_FORMAT_U18_3LE,
3020 		/** Unsigned 18bit Big Endian in 3bytes format */
3021 		SND_PCM_FORMAT_U18_3BE,
3022 		/* G.723 (ADPCM) 24 kbit/s, 8 samples in 3 bytes */
3023 		SND_PCM_FORMAT_G723_24,
3024 		/* G.723 (ADPCM) 24 kbit/s, 1 sample in 1 byte */
3025 		SND_PCM_FORMAT_G723_24_1B,
3026 		/* G.723 (ADPCM) 40 kbit/s, 8 samples in 3 bytes */
3027 		SND_PCM_FORMAT_G723_40,
3028 		/* G.723 (ADPCM) 40 kbit/s, 1 sample in 1 byte */
3029 		SND_PCM_FORMAT_G723_40_1B,
3030 		/* Direct Stream Digital (DSD) in 1-byte samples (x8) */
3031 		SND_PCM_FORMAT_DSD_U8,
3032 		/* Direct Stream Digital (DSD) in 2-byte samples (x16) */
3033 		SND_PCM_FORMAT_DSD_U16_LE,
3034 		SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_DSD_U16_LE,
3035 
3036 		// I snipped a bunch of endian-specific ones!
3037 	}
3038 
3039 	struct snd_pcm_t {}
3040 	struct snd_pcm_hw_params_t {}
3041 	struct snd_pcm_sw_params_t {}
3042 
3043 	int snd_pcm_open(snd_pcm_t**, const char*, snd_pcm_stream_t, int);
3044 	int snd_pcm_close(snd_pcm_t*);
3045 	int snd_pcm_pause(snd_pcm_t*, int);
3046 	int snd_pcm_prepare(snd_pcm_t*);
3047 	int snd_pcm_hw_params(snd_pcm_t*, snd_pcm_hw_params_t*);
3048 	int snd_pcm_hw_params_set_periods(snd_pcm_t*, snd_pcm_hw_params_t*, uint, int);
3049 	int snd_pcm_hw_params_set_periods_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int);
3050 	int snd_pcm_hw_params_set_buffer_size(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t);
3051 	int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t*);
3052 	int snd_pcm_hw_params_set_channels(snd_pcm_t*, snd_pcm_hw_params_t*, uint);
3053 	int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t**);
3054 	void snd_pcm_hw_params_free(snd_pcm_hw_params_t*);
3055 	int snd_pcm_hw_params_any(snd_pcm_t*, snd_pcm_hw_params_t*);
3056 	int snd_pcm_hw_params_set_access(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_access_t);
3057 	int snd_pcm_hw_params_set_format(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_format);
3058 	int snd_pcm_hw_params_set_rate_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int*);
3059 
3060 	int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t**);
3061 	void snd_pcm_sw_params_free(snd_pcm_sw_params_t*);
3062 
3063 	int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
3064 	int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
3065 	int snd_pcm_sw_params_set_avail_min(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
3066 	int snd_pcm_sw_params_set_start_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
3067 	int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
3068 
3069 	alias snd_pcm_sframes_t = c_long;
3070 	alias snd_pcm_uframes_t = c_ulong;
3071 	snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t*, const void*, snd_pcm_uframes_t size);
3072 	snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t*, void*, snd_pcm_uframes_t size);
3073 
3074 	int snd_pcm_wait(snd_pcm_t *pcm, int timeout);
3075 	snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm);
3076 	snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm);
3077 
3078 	int snd_pcm_recover (snd_pcm_t* pcm, int err, int silent);
3079 
3080 	alias snd_lib_error_handler_t = void function (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...);
3081 	int snd_lib_error_set_handler (snd_lib_error_handler_t handler);
3082 
3083 	import core.stdc.stdarg;
3084 	private void alsa_message_silencer (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...) {}
3085 	//k8: ALSAlib loves to trash stderr; shut it up
3086 	void silence_alsa_messages () { snd_lib_error_set_handler(&alsa_message_silencer); }
3087 	extern(D) shared static this () { silence_alsa_messages(); }
3088 
3089 	// raw midi
3090 
3091 	static if(is(size_t == uint))
3092 		alias ssize_t = int;
3093 	else
3094 		alias ssize_t = long;
3095 
3096 
3097 	struct snd_rawmidi_t {}
3098 	int snd_rawmidi_open(snd_rawmidi_t**, snd_rawmidi_t**, const char*, int);
3099 	int snd_rawmidi_close(snd_rawmidi_t*);
3100 	int snd_rawmidi_drain(snd_rawmidi_t*);
3101 	ssize_t snd_rawmidi_write(snd_rawmidi_t*, const void*, size_t);
3102 	ssize_t snd_rawmidi_read(snd_rawmidi_t*, void*, size_t);
3103 
3104 	// mixer
3105 
3106 	struct snd_mixer_t {}
3107 	struct snd_mixer_elem_t {}
3108 	struct snd_mixer_selem_id_t {}
3109 
3110 	alias snd_mixer_elem_callback_t = int function(snd_mixer_elem_t*, uint);
3111 
3112 	int snd_mixer_open(snd_mixer_t**, int mode);
3113 	int snd_mixer_close(snd_mixer_t*);
3114 	int snd_mixer_attach(snd_mixer_t*, const char*);
3115 	int snd_mixer_load(snd_mixer_t*);
3116 
3117 	// FIXME: those aren't actually void*
3118 	int snd_mixer_selem_register(snd_mixer_t*, void*, void*);
3119 	int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t**);
3120 	void snd_mixer_selem_id_free(snd_mixer_selem_id_t*);
3121 	void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t*, uint);
3122 	void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t*, const char*);
3123 	snd_mixer_elem_t* snd_mixer_find_selem(snd_mixer_t*, in snd_mixer_selem_id_t*);
3124 
3125 	// FIXME: the int should be an enum for channel identifier
3126 	int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t*, int, c_long*);
3127 
3128 	int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t*, c_long*, c_long*);
3129 
3130 	int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t*, c_long);
3131 
3132 	void snd_mixer_elem_set_callback(snd_mixer_elem_t*, snd_mixer_elem_callback_t);
3133 	int snd_mixer_poll_descriptors(snd_mixer_t*, pollfd*, uint space);
3134 
3135 	int snd_mixer_handle_events(snd_mixer_t*);
3136 
3137 	// FIXME: the first int should be an enum for channel identifier
3138 	int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t*, int, int* value);
3139 	int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t*, int);
3140 }
3141 
3142 version(WinMM) {
3143 extern(Windows):
3144 @nogc nothrow:
3145 	pragma(lib, "winmm");
3146 	import core.sys.windows.windows;
3147 
3148 /*
3149 	Windows functions include:
3150 	http://msdn.microsoft.com/en-us/library/ms713762%28VS.85%29.aspx
3151 	http://msdn.microsoft.com/en-us/library/ms713504%28v=vs.85%29.aspx
3152 	http://msdn.microsoft.com/en-us/library/windows/desktop/dd798480%28v=vs.85%29.aspx#
3153 	http://msdn.microsoft.com/en-US/subscriptions/ms712109.aspx
3154 */
3155 
3156 	// pcm
3157 
3158 	// midi
3159 /+
3160 	alias HMIDIOUT = HANDLE;
3161 	alias MMRESULT = UINT;
3162 
3163 	MMRESULT midiOutOpen(HMIDIOUT*, UINT, DWORD, DWORD, DWORD);
3164 	MMRESULT midiOutClose(HMIDIOUT);
3165 	MMRESULT midiOutReset(HMIDIOUT);
3166 	MMRESULT midiOutShortMsg(HMIDIOUT, DWORD);
3167 
3168 	alias HWAVEOUT = HANDLE;
3169 
3170 	struct WAVEFORMATEX {
3171 		WORD wFormatTag;
3172 		WORD nChannels;
3173 		DWORD nSamplesPerSec;
3174 		DWORD nAvgBytesPerSec;
3175 		WORD nBlockAlign;
3176 		WORD wBitsPerSample;
3177 		WORD cbSize;
3178 	}
3179 
3180 	struct WAVEHDR {
3181 		void* lpData;
3182 		DWORD dwBufferLength;
3183 		DWORD dwBytesRecorded;
3184 		DWORD dwUser;
3185 		DWORD dwFlags;
3186 		DWORD dwLoops;
3187 		WAVEHDR *lpNext;
3188 		DWORD reserved;
3189 	}
3190 
3191 	enum UINT WAVE_MAPPER= -1;
3192 
3193 	MMRESULT waveOutOpen(HWAVEOUT*, UINT_PTR, WAVEFORMATEX*, void* callback, void*, DWORD);
3194 	MMRESULT waveOutClose(HWAVEOUT);
3195 	MMRESULT waveOutPrepareHeader(HWAVEOUT, WAVEHDR*, UINT);
3196 	MMRESULT waveOutUnprepareHeader(HWAVEOUT, WAVEHDR*, UINT);
3197 	MMRESULT waveOutWrite(HWAVEOUT, WAVEHDR*, UINT);
3198 
3199 	MMRESULT waveOutGetVolume(HWAVEOUT, PDWORD);
3200 	MMRESULT waveOutSetVolume(HWAVEOUT, DWORD);
3201 
3202 	enum CALLBACK_TYPEMASK = 0x70000;
3203 	enum CALLBACK_NULL     = 0;
3204 	enum CALLBACK_WINDOW   = 0x10000;
3205 	enum CALLBACK_TASK     = 0x20000;
3206 	enum CALLBACK_FUNCTION = 0x30000;
3207 	enum CALLBACK_THREAD   = CALLBACK_TASK;
3208 	enum CALLBACK_EVENT    = 0x50000;
3209 
3210 	enum WAVE_FORMAT_PCM = 1;
3211 
3212 	enum WHDR_PREPARED = 2;
3213 	enum WHDR_BEGINLOOP = 4;
3214 	enum WHDR_ENDLOOP = 8;
3215 	enum WHDR_INQUEUE = 16;
3216 
3217 	enum WinMMMessage : UINT {
3218 		MM_JOY1MOVE            = 0x3A0,
3219 		MM_JOY2MOVE,
3220 		MM_JOY1ZMOVE,
3221 		MM_JOY2ZMOVE,       // = 0x3A3
3222 		MM_JOY1BUTTONDOWN      = 0x3B5,
3223 		MM_JOY2BUTTONDOWN,
3224 		MM_JOY1BUTTONUP,
3225 		MM_JOY2BUTTONUP,
3226 		MM_MCINOTIFY,       // = 0x3B9
3227 		MM_WOM_OPEN            = 0x3BB,
3228 		MM_WOM_CLOSE,
3229 		MM_WOM_DONE,
3230 		MM_WIM_OPEN,
3231 		MM_WIM_CLOSE,
3232 		MM_WIM_DATA,
3233 		MM_MIM_OPEN,
3234 		MM_MIM_CLOSE,
3235 		MM_MIM_DATA,
3236 		MM_MIM_LONGDATA,
3237 		MM_MIM_ERROR,
3238 		MM_MIM_LONGERROR,
3239 		MM_MOM_OPEN,
3240 		MM_MOM_CLOSE,
3241 		MM_MOM_DONE,        // = 0x3C9
3242 		MM_DRVM_OPEN           = 0x3D0,
3243 		MM_DRVM_CLOSE,
3244 		MM_DRVM_DATA,
3245 		MM_DRVM_ERROR,
3246 		MM_STREAM_OPEN,
3247 		MM_STREAM_CLOSE,
3248 		MM_STREAM_DONE,
3249 		MM_STREAM_ERROR,    // = 0x3D7
3250 		MM_MOM_POSITIONCB      = 0x3CA,
3251 		MM_MCISIGNAL,
3252 		MM_MIM_MOREDATA,    // = 0x3CC
3253 		MM_MIXM_LINE_CHANGE    = 0x3D0,
3254 		MM_MIXM_CONTROL_CHANGE = 0x3D1
3255 	}
3256 
3257 
3258 	enum WOM_OPEN  = WinMMMessage.MM_WOM_OPEN;
3259 	enum WOM_CLOSE = WinMMMessage.MM_WOM_CLOSE;
3260 	enum WOM_DONE  = WinMMMessage.MM_WOM_DONE;
3261 	enum WIM_OPEN  = WinMMMessage.MM_WIM_OPEN;
3262 	enum WIM_CLOSE = WinMMMessage.MM_WIM_CLOSE;
3263 	enum WIM_DATA  = WinMMMessage.MM_WIM_DATA;
3264 
3265 
3266 	uint mciSendStringA(in char*,char*,uint,void*);
3267 
3268 +/
3269 }
3270 
3271 version(with_resampler) {
3272 	/* Copyright (C) 2007-2008 Jean-Marc Valin
3273 	 * Copyright (C) 2008      Thorvald Natvig
3274 	 * D port by Ketmar // Invisible Vector
3275 	 *
3276 	 * Arbitrary resampling code
3277 	 *
3278 	 * Redistribution and use in source and binary forms, with or without
3279 	 * modification, are permitted provided that the following conditions are
3280 	 * met:
3281 	 *
3282 	 * 1. Redistributions of source code must retain the above copyright notice,
3283 	 * this list of conditions and the following disclaimer.
3284 	 *
3285 	 * 2. Redistributions in binary form must reproduce the above copyright
3286 	 * notice, this list of conditions and the following disclaimer in the
3287 	 * documentation and/or other materials provided with the distribution.
3288 	 *
3289 	 * 3. The name of the author may not be used to endorse or promote products
3290 	 * derived from this software without specific prior written permission.
3291 	 *
3292 	 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
3293 	 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
3294 	 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3295 	 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
3296 	 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
3297 	 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3298 	 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3299 	 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
3300 	 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
3301 	 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3302 	 * POSSIBILITY OF SUCH DAMAGE.
3303 	 */
3304 
3305 	/* A-a-a-and now... D port is covered by the following license!
3306 	 *
3307 	 * This program is free software: you can redistribute it and/or modify
3308 	 * it under the terms of the GNU General Public License as published by
3309 	 * the Free Software Foundation, either version 3 of the License, or
3310 	 * (at your option) any later version.
3311 	 *
3312 	 * This program is distributed in the hope that it will be useful,
3313 	 * but WITHOUT ANY WARRANTY; without even the implied warranty of
3314 	 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3315 	 * GNU General Public License for more details.
3316 	 *
3317 	 * You should have received a copy of the GNU General Public License
3318 	 * along with this program. If not, see <http://www.gnu.org/licenses/>.
3319 	 */
3320 	//module iv.follin.resampler /*is aliced*/;
3321 	//import iv.alice;
3322 
3323 	/*
3324 	   The design goals of this code are:
3325 	      - Very fast algorithm
3326 	      - SIMD-friendly algorithm
3327 	      - Low memory requirement
3328 	      - Good *perceptual* quality (and not best SNR)
3329 
3330 	   Warning: This resampler is relatively new. Although I think I got rid of
3331 	   all the major bugs and I don't expect the API to change anymore, there
3332 	   may be something I've missed. So use with caution.
3333 
3334 	   This algorithm is based on this original resampling algorithm:
3335 	   Smith, Julius O. Digital Audio Resampling Home Page
3336 	   Center for Computer Research in Music and Acoustics (CCRMA),
3337 	   Stanford University, 2007.
3338 	   Web published at http://www-ccrma.stanford.edu/~jos/resample/.
3339 
3340 	   There is one main difference, though. This resampler uses cubic
3341 	   interpolation instead of linear interpolation in the above paper. This
3342 	   makes the table much smaller and makes it possible to compute that table
3343 	   on a per-stream basis. In turn, being able to tweak the table for each
3344 	   stream makes it possible to both reduce complexity on simple ratios
3345 	   (e.g. 2/3), and get rid of the rounding operations in the inner loop.
3346 	   The latter both reduces CPU time and makes the algorithm more SIMD-friendly.
3347 	*/
3348 	version = sincresample_use_full_table;
3349 	version(X86) {
3350 	  version(sincresample_disable_sse) {
3351 	  } else {
3352 	    version(D_PIC) {} else version = sincresample_use_sse;
3353 	  }
3354 	}
3355 
3356 
3357 	// ////////////////////////////////////////////////////////////////////////// //
3358 	public struct SpeexResampler {
3359 	public:
3360 	  alias Quality = int;
3361 	  enum : uint {
3362 	    Fastest = 0,
3363 	    Voip = 3,
3364 	    Default = 4,
3365 	    Desktop = 5,
3366 	    Music = 8,
3367 	    Best = 10,
3368 	  }
3369 
3370 	  enum Error {
3371 	    OK = 0,
3372 	    NoMemory,
3373 	    BadState,
3374 	    BadArgument,
3375 	    BadData,
3376 	  }
3377 
3378 	private:
3379 	nothrow @trusted @nogc:
3380 	  alias ResamplerFn = int function (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint *indataLen, float *outdata, uint *outdataLen);
3381 
3382 	private:
3383 	  uint inRate;
3384 	  uint outRate;
3385 	  uint numRate; // from
3386 	  uint denRate; // to
3387 
3388 	  Quality srQuality;
3389 	  uint chanCount;
3390 	  uint filterLen;
3391 	  uint memAllocSize;
3392 	  uint bufferSize;
3393 	  int intAdvance;
3394 	  int fracAdvance;
3395 	  float cutoff;
3396 	  uint oversample;
3397 	  bool started;
3398 
3399 	  // these are per-channel
3400 	  int[64] lastSample;
3401 	  uint[64] sampFracNum;
3402 	  uint[64] magicSamples;
3403 
3404 	  float* mem;
3405 	  uint realMemLen; // how much memory really allocated
3406 	  float* sincTable;
3407 	  uint sincTableLen;
3408 	  uint realSincTableLen; // how much memory really allocated
3409 	  ResamplerFn resampler;
3410 
3411 	  int inStride;
3412 	  int outStride;
3413 
3414 	public:
3415 	  static string errorStr (int err) {
3416 	    switch (err) with (Error) {
3417 	      case OK: return "success";
3418 	      case NoMemory: return "memory allocation failed";
3419 	      case BadState: return "bad resampler state";
3420 	      case BadArgument: return "invalid argument";
3421 	      case BadData: return "bad data passed";
3422 	      default:
3423 	    }
3424 	    return "unknown error";
3425 	  }
3426 
3427 	public:
3428 	  @disable this (this);
3429 	  ~this () { deinit(); }
3430 
3431 	  @property bool inited () const pure { return (resampler !is null); }
3432 
3433 	  void deinit () {
3434 	    import core.stdc.stdlib : free;
3435 	    if (mem !is null) { free(mem); mem = null; }
3436 	    if (sincTable !is null) { free(sincTable); sincTable = null; }
3437 	    /*
3438 	    memAllocSize = realMemLen = 0;
3439 	    sincTableLen = realSincTableLen = 0;
3440 	    resampler = null;
3441 	    started = false;
3442 	    */
3443 	    inRate = outRate = numRate = denRate = 0;
3444 	    srQuality = cast(Quality)666;
3445 	    chanCount = 0;
3446 	    filterLen = 0;
3447 	    memAllocSize = 0;
3448 	    bufferSize = 0;
3449 	    intAdvance = 0;
3450 	    fracAdvance = 0;
3451 	    cutoff = 0;
3452 	    oversample = 0;
3453 	    started = 0;
3454 
3455 	    mem = null;
3456 	    realMemLen = 0; // how much memory really allocated
3457 	    sincTable = null;
3458 	    sincTableLen = 0;
3459 	    realSincTableLen = 0; // how much memory really allocated
3460 	    resampler = null;
3461 
3462 	    inStride = outStride = 0;
3463 	  }
3464 
3465 	  /** Create a new resampler with integer input and output rates.
3466 	   *
3467 	   * Params:
3468 	   *  chans = Number of channels to be processed
3469 	   *  inRate = Input sampling rate (integer number of Hz).
3470 	   *  outRate = Output sampling rate (integer number of Hz).
3471 	   *  aquality = Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
3472 	   *
3473 	   * Returns:
3474 	   *  0 or error code
3475 	   */
3476 	  Error setup (uint chans, uint ainRate, uint aoutRate, Quality aquality/*, usize line=__LINE__*/) {
3477 	    //{ import core.stdc.stdio; printf("init: %u -> %u at %u\n", ainRate, aoutRate, cast(uint)line); }
3478 	    import core.stdc.stdlib : malloc, free;
3479 
3480 	    deinit();
3481 	    if (aquality < 0) aquality = 0;
3482 	    if (aquality > SpeexResampler.Best) aquality = SpeexResampler.Best;
3483 	    if (chans < 1 || chans > 16) return Error.BadArgument;
3484 
3485 	    started = false;
3486 	    inRate = 0;
3487 	    outRate = 0;
3488 	    numRate = 0;
3489 	    denRate = 0;
3490 	    srQuality = cast(Quality)666; // it's ok
3491 	    sincTableLen = 0;
3492 	    memAllocSize = 0;
3493 	    filterLen = 0;
3494 	    mem = null;
3495 	    resampler = null;
3496 
3497 	    cutoff = 1.0f;
3498 	    chanCount = chans;
3499 	    inStride = 1;
3500 	    outStride = 1;
3501 
3502 	    bufferSize = 160;
3503 
3504 	    // per channel data
3505 	    lastSample[] = 0;
3506 	    magicSamples[] = 0;
3507 	    sampFracNum[] = 0;
3508 
3509 	    setQuality(aquality);
3510 	    setRate(ainRate, aoutRate);
3511 
3512 	    if (auto filterErr = updateFilter()) { deinit(); return filterErr; }
3513 	    skipZeros(); // make sure that the first samples to go out of the resamplers don't have leading zeros
3514 
3515 	    return Error.OK;
3516 	  }
3517 
3518 	  /** Set (change) the input/output sampling rates (integer value).
3519 	   *
3520 	   * Params:
3521 	   *  ainRate = Input sampling rate (integer number of Hz).
3522 	   *  aoutRate = Output sampling rate (integer number of Hz).
3523 	   *
3524 	   * Returns:
3525 	   *  0 or error code
3526 	   */
3527 	  Error setRate (uint ainRate, uint aoutRate/*, usize line=__LINE__*/) {
3528 	    //{ import core.stdc.stdio; printf("changing rate: %u -> %u at %u\n", ainRate, aoutRate, cast(uint)line); }
3529 	    if (inRate == ainRate && outRate == aoutRate) return Error.OK;
3530 	    //{ import core.stdc.stdio; printf("changing rate: %u -> %u at %u\n", ratioNum, ratioDen, cast(uint)line); }
3531 
3532 	    uint oldDen = denRate;
3533 	    inRate = ainRate;
3534 	    outRate = aoutRate;
3535 	    auto div = gcd(ainRate, aoutRate);
3536 	    numRate = ainRate/div;
3537 	    denRate = aoutRate/div;
3538 
3539 	    if (oldDen > 0) {
3540 	      foreach (ref v; sampFracNum.ptr[0..chanCount]) {
3541 		v = v*denRate/oldDen;
3542 		// safety net
3543 		if (v >= denRate) v = denRate-1;
3544 	      }
3545 	    }
3546 
3547 	    return (inited ? updateFilter() : Error.OK);
3548 	  }
3549 
3550 	  /** Get the current input/output sampling rates (integer value).
3551 	   *
3552 	   * Params:
3553 	   *  ainRate = Input sampling rate (integer number of Hz) copied.
3554 	   *  aoutRate = Output sampling rate (integer number of Hz) copied.
3555 	   */
3556 	  void getRate (out uint ainRate, out uint aoutRate) {
3557 	    ainRate = inRate;
3558 	    aoutRate = outRate;
3559 	  }
3560 
3561 	  @property uint getInRate () { return inRate; }
3562 	  @property uint getOutRate () { return outRate; }
3563 
3564 	  @property uint getChans () { return chanCount; }
3565 
3566 	  /** Get the current resampling ratio. This will be reduced to the least common denominator.
3567 	   *
3568 	   * Params:
3569 	   *  ratioNum = Numerator of the sampling rate ratio copied
3570 	   *  ratioDen = Denominator of the sampling rate ratio copied
3571 	   */
3572 	  void getRatio (out uint ratioNum, out uint ratioDen) {
3573 	    ratioNum = numRate;
3574 	    ratioDen = denRate;
3575 	  }
3576 
3577 	  /** Set (change) the conversion quality.
3578 	   *
3579 	   * Params:
3580 	   *  quality = Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
3581 	   *
3582 	   * Returns:
3583 	   *  0 or error code
3584 	   */
3585 	  Error setQuality (Quality aquality) {
3586 	    if (aquality < 0) aquality = 0;
3587 	    if (aquality > SpeexResampler.Best) aquality = SpeexResampler.Best;
3588 	    if (srQuality == aquality) return Error.OK;
3589 	    srQuality = aquality;
3590 	    return (inited ? updateFilter() : Error.OK);
3591 	  }
3592 
3593 	  /** Get the conversion quality.
3594 	   *
3595 	   * Returns:
3596 	   *  Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
3597 	   */
3598 	  int getQuality () { return srQuality; }
3599 
3600 	  /** Get the latency introduced by the resampler measured in input samples.
3601 	   *
3602 	   * Returns:
3603 	   *  Input latency;
3604 	   */
3605 	  int inputLatency () { return filterLen/2; }
3606 
3607 	  /** Get the latency introduced by the resampler measured in output samples.
3608 	   *
3609 	   * Returns:
3610 	   *  Output latency.
3611 	   */
3612 	  int outputLatency () { return ((filterLen/2)*denRate+(numRate>>1))/numRate; }
3613 
3614 	  /* Make sure that the first samples to go out of the resamplers don't have
3615 	   * leading zeros. This is only useful before starting to use a newly created
3616 	   * resampler. It is recommended to use that when resampling an audio file, as
3617 	   * it will generate a file with the same length. For real-time processing,
3618 	   * it is probably easier not to use this call (so that the output duration
3619 	   * is the same for the first frame).
3620 	   *
3621 	   * Setup/reset sequence will automatically call this, so it is private.
3622 	   */
3623 	  private void skipZeros () { foreach (immutable i; 0..chanCount) lastSample.ptr[i] = filterLen/2; }
3624 
3625 	  static struct Data {
3626 	    const(float)[] dataIn;
3627 	    float[] dataOut;
3628 	    uint inputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
3629 	    uint outputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
3630 	  }
3631 
3632 	  /** Resample (an interleaved) float array. The input and output buffers must *not* overlap.
3633 	   * `data.dataIn` can be empty, but `data.dataOut` can't.
3634 	   * Function will return number of consumed samples (*not* *frames*!) in `data.inputSamplesUsed`,
3635 	   * and number of produced samples in `data.outputSamplesUsed`.
3636 	   * You should provide enough samples for all channels, and all channels will be processed.
3637 	   *
3638 	   * Params:
3639 	   *  data = input and output buffers, number of frames consumed and produced
3640 	   *
3641 	   * Returns:
3642 	   *  0 or error code
3643 	   */
3644 	  Error process(string mode="interleaved") (ref Data data) {
3645 	    static assert(mode == "interleaved" || mode == "sequential");
3646 
3647 	    data.inputSamplesUsed = data.outputSamplesUsed = 0;
3648 	    if (!inited) return Error.BadState;
3649 
3650 	    if (data.dataIn.length%chanCount || data.dataOut.length < 1 || data.dataOut.length%chanCount) return Error.BadData;
3651 	    if (data.dataIn.length > uint.max/4 || data.dataOut.length > uint.max/4) return Error.BadData;
3652 
3653 	    static if (mode == "interleaved") {
3654 	      inStride = outStride = chanCount;
3655 	    } else {
3656 	      inStride = outStride = 1;
3657 	    }
3658 	    uint iofs = 0, oofs = 0;
3659 	    immutable uint idclen = cast(uint)(data.dataIn.length/chanCount);
3660 	    immutable uint odclen = cast(uint)(data.dataOut.length/chanCount);
3661 	    foreach (immutable i; 0..chanCount) {
3662 	      data.inputSamplesUsed = idclen;
3663 	      data.outputSamplesUsed = odclen;
3664 	      if (data.dataIn.length) {
3665 		processOneChannel(i, data.dataIn.ptr+iofs, &data.inputSamplesUsed, data.dataOut.ptr+oofs, &data.outputSamplesUsed);
3666 	      } else {
3667 		processOneChannel(i, null, &data.inputSamplesUsed, data.dataOut.ptr+oofs, &data.outputSamplesUsed);
3668 	      }
3669 	      static if (mode == "interleaved") {
3670 		++iofs;
3671 		++oofs;
3672 	      } else {
3673 		iofs += idclen;
3674 		oofs += odclen;
3675 	      }
3676 	    }
3677 	    data.inputSamplesUsed *= chanCount;
3678 	    data.outputSamplesUsed *= chanCount;
3679 	    return Error.OK;
3680 	  }
3681 
3682 
3683 	  //HACK for libswresample
3684 	  // return -1 or number of outframes
3685 	  int swrconvert (float** outbuf, int outframes, const(float)**inbuf, int inframes) {
3686 	    if (!inited || outframes < 1 || inframes < 0) return -1;
3687 	    inStride = outStride = 1;
3688 	    Data data;
3689 	    foreach (immutable i; 0..chanCount) {
3690 	      data.dataIn = (inframes ? inbuf[i][0..inframes] : null);
3691 	      data.dataOut = (outframes ? outbuf[i][0..outframes] : null);
3692 	      data.inputSamplesUsed = inframes;
3693 	      data.outputSamplesUsed = outframes;
3694 	      if (inframes > 0) {
3695 		processOneChannel(i, data.dataIn.ptr, &data.inputSamplesUsed, data.dataOut.ptr, &data.outputSamplesUsed);
3696 	      } else {
3697 		processOneChannel(i, null, &data.inputSamplesUsed, data.dataOut.ptr, &data.outputSamplesUsed);
3698 	      }
3699 	    }
3700 	    return data.outputSamplesUsed;
3701 	  }
3702 
3703 	  /// Reset a resampler so a new (unrelated) stream can be processed.
3704 	  void reset () {
3705 	    lastSample[] = 0;
3706 	    magicSamples[] = 0;
3707 	    sampFracNum[] = 0;
3708 	    //foreach (immutable i; 0..chanCount*(filterLen-1)) mem[i] = 0;
3709 	    if (mem !is null) mem[0..chanCount*(filterLen-1)] = 0;
3710 	    skipZeros(); // make sure that the first samples to go out of the resamplers don't have leading zeros
3711 	  }
3712 
3713 	private:
3714 	  Error processOneChannel (uint chanIdx, const(float)* indata, uint* indataLen, float* outdata, uint* outdataLen) {
3715 	    uint ilen = *indataLen;
3716 	    uint olen = *outdataLen;
3717 	    float* x = mem+chanIdx*memAllocSize;
3718 	    immutable int filterOfs = filterLen-1;
3719 	    immutable uint xlen = memAllocSize-filterOfs;
3720 	    immutable int istride = inStride;
3721 	    if (magicSamples.ptr[chanIdx]) olen -= magic(chanIdx, &outdata, olen);
3722 	    if (!magicSamples.ptr[chanIdx]) {
3723 	      while (ilen && olen) {
3724 		uint ichunk = (ilen > xlen ? xlen : ilen);
3725 		uint ochunk = olen;
3726 		if (indata !is null) {
3727 		  //foreach (immutable j; 0..ichunk) x[j+filterOfs] = indata[j*istride];
3728 		  if (istride == 1) {
3729 		    x[filterOfs..filterOfs+ichunk] = indata[0..ichunk];
3730 		  } else {
3731 		    auto sp = indata;
3732 		    auto dp = x+filterOfs;
3733 		    foreach (immutable j; 0..ichunk) { *dp++ = *sp; sp += istride; }
3734 		  }
3735 		} else {
3736 		  //foreach (immutable j; 0..ichunk) x[j+filterOfs] = 0;
3737 		  x[filterOfs..filterOfs+ichunk] = 0;
3738 		}
3739 		processNative(chanIdx, &ichunk, outdata, &ochunk);
3740 		ilen -= ichunk;
3741 		olen -= ochunk;
3742 		outdata += ochunk*outStride;
3743 		if (indata !is null) indata += ichunk*istride;
3744 	      }
3745 	    }
3746 	    *indataLen -= ilen;
3747 	    *outdataLen -= olen;
3748 	    return Error.OK;
3749 	  }
3750 
3751 	  Error processNative (uint chanIdx, uint* indataLen, float* outdata, uint* outdataLen) {
3752 	    immutable N = filterLen;
3753 	    int outSample = 0;
3754 	    float* x = mem+chanIdx*memAllocSize;
3755 	    uint ilen;
3756 	    started = true;
3757 	    // call the right resampler through the function ptr
3758 	    outSample = resampler(this, chanIdx, x, indataLen, outdata, outdataLen);
3759 	    if (lastSample.ptr[chanIdx] < cast(int)*indataLen) *indataLen = lastSample.ptr[chanIdx];
3760 	    *outdataLen = outSample;
3761 	    lastSample.ptr[chanIdx] -= *indataLen;
3762 	    ilen = *indataLen;
3763 	    foreach (immutable j; 0..N-1) x[j] = x[j+ilen];
3764 	    return Error.OK;
3765 	  }
3766 
3767 	  int magic (uint chanIdx, float **outdata, uint outdataLen) {
3768 	    uint tempInLen = magicSamples.ptr[chanIdx];
3769 	    float* x = mem+chanIdx*memAllocSize;
3770 	    processNative(chanIdx, &tempInLen, *outdata, &outdataLen);
3771 	    magicSamples.ptr[chanIdx] -= tempInLen;
3772 	    // if we couldn't process all "magic" input samples, save the rest for next time
3773 	    if (magicSamples.ptr[chanIdx]) {
3774 	      immutable N = filterLen;
3775 	      foreach (immutable i; 0..magicSamples.ptr[chanIdx]) x[N-1+i] = x[N-1+i+tempInLen];
3776 	    }
3777 	    *outdata += outdataLen*outStride;
3778 	    return outdataLen;
3779 	  }
3780 
3781 	  Error updateFilter () {
3782 	    uint oldFilterLen = filterLen;
3783 	    uint oldAllocSize = memAllocSize;
3784 	    bool useDirect;
3785 	    uint minSincTableLen;
3786 	    uint minAllocSize;
3787 
3788 	    intAdvance = numRate/denRate;
3789 	    fracAdvance = numRate%denRate;
3790 	    oversample = qualityMap.ptr[srQuality].oversample;
3791 	    filterLen = qualityMap.ptr[srQuality].baseLength;
3792 
3793 	    if (numRate > denRate) {
3794 	      // down-sampling
3795 	      cutoff = qualityMap.ptr[srQuality].downsampleBandwidth*denRate/numRate;
3796 	      // FIXME: divide the numerator and denominator by a certain amount if they're too large
3797 	      filterLen = filterLen*numRate/denRate;
3798 	      // round up to make sure we have a multiple of 8 for SSE
3799 	      filterLen = ((filterLen-1)&(~0x7))+8;
3800 	      if (2*denRate < numRate) oversample >>= 1;
3801 	      if (4*denRate < numRate) oversample >>= 1;
3802 	      if (8*denRate < numRate) oversample >>= 1;
3803 	      if (16*denRate < numRate) oversample >>= 1;
3804 	      if (oversample < 1) oversample = 1;
3805 	    } else {
3806 	      // up-sampling
3807 	      cutoff = qualityMap.ptr[srQuality].upsampleBandwidth;
3808 	    }
3809 
3810 	    // choose the resampling type that requires the least amount of memory
3811 	    version(sincresample_use_full_table) {
3812 	      useDirect = true;
3813 	      if (int.max/float.sizeof/denRate < filterLen) goto fail;
3814 	    } else {
3815 	      useDirect = (filterLen*denRate <= filterLen*oversample+8 && int.max/float.sizeof/denRate >= filterLen);
3816 	    }
3817 
3818 	    if (useDirect) {
3819 	      minSincTableLen = filterLen*denRate;
3820 	    } else {
3821 	      if ((int.max/float.sizeof-8)/oversample < filterLen) goto fail;
3822 	      minSincTableLen = filterLen*oversample+8;
3823 	    }
3824 
3825 	    if (sincTableLen < minSincTableLen) {
3826 	      import core.stdc.stdlib : realloc;
3827 	      auto nslen = cast(uint)(minSincTableLen*float.sizeof);
3828 	      if (nslen > realSincTableLen) {
3829 		if (nslen < 512*1024) nslen = 512*1024; // inc to 3 mb?
3830 		auto x = cast(float*)realloc(sincTable, nslen);
3831 		if (!x) goto fail;
3832 		sincTable = x;
3833 		realSincTableLen = nslen;
3834 	      }
3835 	      sincTableLen = minSincTableLen;
3836 	    }
3837 
3838 	    if (useDirect) {
3839 	      foreach (int i; 0..denRate) {
3840 		foreach (int j; 0..filterLen) {
3841 		  sincTable[i*filterLen+j] = sinc(cutoff, ((j-cast(int)filterLen/2+1)-(cast(float)i)/denRate), filterLen, qualityMap.ptr[srQuality].windowFunc);
3842 		}
3843 	      }
3844 	      if (srQuality > 8) {
3845 		resampler = &resamplerBasicDirect!double;
3846 	      } else {
3847 		resampler = &resamplerBasicDirect!float;
3848 	      }
3849 	    } else {
3850 	      foreach (immutable int i; -4..cast(int)(oversample*filterLen+4)) {
3851 		sincTable[i+4] = sinc(cutoff, (i/cast(float)oversample-filterLen/2), filterLen, qualityMap.ptr[srQuality].windowFunc);
3852 	      }
3853 	      if (srQuality > 8) {
3854 		resampler = &resamplerBasicInterpolate!double;
3855 	      } else {
3856 		resampler = &resamplerBasicInterpolate!float;
3857 	      }
3858 	    }
3859 
3860 	    /* Here's the place where we update the filter memory to take into account
3861 	       the change in filter length. It's probably the messiest part of the code
3862 	       due to handling of lots of corner cases. */
3863 
3864 	    // adding bufferSize to filterLen won't overflow here because filterLen could be multiplied by float.sizeof above
3865 	    minAllocSize = filterLen-1+bufferSize;
3866 	    if (minAllocSize > memAllocSize) {
3867 	      import core.stdc.stdlib : realloc;
3868 	      if (int.max/float.sizeof/chanCount < minAllocSize) goto fail;
3869 	      auto nslen = cast(uint)(chanCount*minAllocSize*mem[0].sizeof);
3870 	      if (nslen > realMemLen) {
3871 		if (nslen < 16384) nslen = 16384;
3872 		auto x = cast(float*)realloc(mem, nslen);
3873 		if (x is null) goto fail;
3874 		mem = x;
3875 		realMemLen = nslen;
3876 	      }
3877 	      memAllocSize = minAllocSize;
3878 	    }
3879 	    if (!started) {
3880 	      //foreach (i=0;i<chanCount*memAllocSize;i++) mem[i] = 0;
3881 	      mem[0..chanCount*memAllocSize] = 0;
3882 	    } else if (filterLen > oldFilterLen) {
3883 	      // increase the filter length
3884 	      foreach_reverse (uint i; 0..chanCount) {
3885 		uint j;
3886 		uint olen = oldFilterLen;
3887 		{
3888 		  // try and remove the magic samples as if nothing had happened
3889 		  //FIXME: this is wrong but for now we need it to avoid going over the array bounds
3890 		  olen = oldFilterLen+2*magicSamples.ptr[i];
3891 		  for (j = oldFilterLen-1+magicSamples.ptr[i]; j--; ) mem[i*memAllocSize+j+magicSamples.ptr[i]] = mem[i*oldAllocSize+j];
3892 		  //for (j = 0; j < magicSamples.ptr[i]; ++j) mem[i*memAllocSize+j] = 0;
3893 		  mem[i*memAllocSize..i*memAllocSize+magicSamples.ptr[i]] = 0;
3894 		  magicSamples.ptr[i] = 0;
3895 		}
3896 		if (filterLen > olen) {
3897 		  // if the new filter length is still bigger than the "augmented" length
3898 		  // copy data going backward
3899 		  for (j = 0; j < olen-1; ++j) mem[i*memAllocSize+(filterLen-2-j)] = mem[i*memAllocSize+(olen-2-j)];
3900 		  // then put zeros for lack of anything better
3901 		  for (; j < filterLen-1; ++j) mem[i*memAllocSize+(filterLen-2-j)] = 0;
3902 		  // adjust lastSample
3903 		  lastSample.ptr[i] += (filterLen-olen)/2;
3904 		} else {
3905 		  // put back some of the magic!
3906 		  magicSamples.ptr[i] = (olen-filterLen)/2;
3907 		  for (j = 0; j < filterLen-1+magicSamples.ptr[i]; ++j) mem[i*memAllocSize+j] = mem[i*memAllocSize+j+magicSamples.ptr[i]];
3908 		}
3909 	      }
3910 	    } else if (filterLen < oldFilterLen) {
3911 	      // reduce filter length, this a bit tricky
3912 	      // we need to store some of the memory as "magic" samples so they can be used directly as input the next time(s)
3913 	      foreach (immutable i; 0..chanCount) {
3914 		uint j;
3915 		uint oldMagic = magicSamples.ptr[i];
3916 		magicSamples.ptr[i] = (oldFilterLen-filterLen)/2;
3917 		// we must copy some of the memory that's no longer used
3918 		// copy data going backward
3919 		for (j = 0; j < filterLen-1+magicSamples.ptr[i]+oldMagic; ++j) {
3920 		  mem[i*memAllocSize+j] = mem[i*memAllocSize+j+magicSamples.ptr[i]];
3921 		}
3922 		magicSamples.ptr[i] += oldMagic;
3923 	      }
3924 	    }
3925 	    return Error.OK;
3926 
3927 	  fail:
3928 	    resampler = null;
3929 	    /* mem may still contain consumed input samples for the filter.
3930 	       Restore filterLen so that filterLen-1 still points to the position after
3931 	       the last of these samples. */
3932 	    filterLen = oldFilterLen;
3933 	    return Error.NoMemory;
3934 	  }
3935 	}
3936 
3937 
3938 	// ////////////////////////////////////////////////////////////////////////// //
3939 	static immutable double[68] kaiser12Table = [
3940 	  0.99859849, 1.00000000, 0.99859849, 0.99440475, 0.98745105, 0.97779076,
3941 	  0.96549770, 0.95066529, 0.93340547, 0.91384741, 0.89213598, 0.86843014,
3942 	  0.84290116, 0.81573067, 0.78710866, 0.75723148, 0.72629970, 0.69451601,
3943 	  0.66208321, 0.62920216, 0.59606986, 0.56287762, 0.52980938, 0.49704014,
3944 	  0.46473455, 0.43304576, 0.40211431, 0.37206735, 0.34301800, 0.31506490,
3945 	  0.28829195, 0.26276832, 0.23854851, 0.21567274, 0.19416736, 0.17404546,
3946 	  0.15530766, 0.13794294, 0.12192957, 0.10723616, 0.09382272, 0.08164178,
3947 	  0.07063950, 0.06075685, 0.05193064, 0.04409466, 0.03718069, 0.03111947,
3948 	  0.02584161, 0.02127838, 0.01736250, 0.01402878, 0.01121463, 0.00886058,
3949 	  0.00691064, 0.00531256, 0.00401805, 0.00298291, 0.00216702, 0.00153438,
3950 	  0.00105297, 0.00069463, 0.00043489, 0.00025272, 0.00013031, 0.0000527734,
3951 	  0.00001000, 0.00000000];
3952 
3953 	static immutable double[36] kaiser10Table = [
3954 	  0.99537781, 1.00000000, 0.99537781, 0.98162644, 0.95908712, 0.92831446,
3955 	  0.89005583, 0.84522401, 0.79486424, 0.74011713, 0.68217934, 0.62226347,
3956 	  0.56155915, 0.50119680, 0.44221549, 0.38553619, 0.33194107, 0.28205962,
3957 	  0.23636152, 0.19515633, 0.15859932, 0.12670280, 0.09935205, 0.07632451,
3958 	  0.05731132, 0.04193980, 0.02979584, 0.02044510, 0.01345224, 0.00839739,
3959 	  0.00488951, 0.00257636, 0.00115101, 0.00035515, 0.00000000, 0.00000000];
3960 
3961 	static immutable double[36] kaiser8Table = [
3962 	  0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200,
3963 	  0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126,
3964 	  0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272,
3965 	  0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758,
3966 	  0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490,
3967 	  0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000];
3968 
3969 	static immutable double[36] kaiser6Table = [
3970 	  0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003,
3971 	  0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565,
3972 	  0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561,
3973 	  0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058,
3974 	  0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600,
3975 	  0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000];
3976 
3977 	struct FuncDef {
3978 	  immutable(double)* table;
3979 	  int oversample;
3980 	}
3981 
3982 	static immutable FuncDef Kaiser12 = FuncDef(kaiser12Table.ptr, 64);
3983 	static immutable FuncDef Kaiser10 = FuncDef(kaiser10Table.ptr, 32);
3984 	static immutable FuncDef Kaiser8 = FuncDef(kaiser8Table.ptr, 32);
3985 	static immutable FuncDef Kaiser6 = FuncDef(kaiser6Table.ptr, 32);
3986 
3987 
3988 	struct QualityMapping {
3989 	  int baseLength;
3990 	  int oversample;
3991 	  float downsampleBandwidth;
3992 	  float upsampleBandwidth;
3993 	  immutable FuncDef* windowFunc;
3994 	}
3995 
3996 
3997 	/* This table maps conversion quality to internal parameters. There are two
3998 	   reasons that explain why the up-sampling bandwidth is larger than the
3999 	   down-sampling bandwidth:
4000 	   1) When up-sampling, we can assume that the spectrum is already attenuated
4001 	      close to the Nyquist rate (from an A/D or a previous resampling filter)
4002 	   2) Any aliasing that occurs very close to the Nyquist rate will be masked
4003 	      by the sinusoids/noise just below the Nyquist rate (guaranteed only for
4004 	      up-sampling).
4005 	*/
4006 	static immutable QualityMapping[11] qualityMap = [
4007 	  QualityMapping(  8,  4, 0.830f, 0.860f, &Kaiser6 ), /* Q0 */
4008 	  QualityMapping( 16,  4, 0.850f, 0.880f, &Kaiser6 ), /* Q1 */
4009 	  QualityMapping( 32,  4, 0.882f, 0.910f, &Kaiser6 ), /* Q2 */  /* 82.3% cutoff ( ~60 dB stop) 6  */
4010 	  QualityMapping( 48,  8, 0.895f, 0.917f, &Kaiser8 ), /* Q3 */  /* 84.9% cutoff ( ~80 dB stop) 8  */
4011 	  QualityMapping( 64,  8, 0.921f, 0.940f, &Kaiser8 ), /* Q4 */  /* 88.7% cutoff ( ~80 dB stop) 8  */
4012 	  QualityMapping( 80, 16, 0.922f, 0.940f, &Kaiser10), /* Q5 */  /* 89.1% cutoff (~100 dB stop) 10 */
4013 	  QualityMapping( 96, 16, 0.940f, 0.945f, &Kaiser10), /* Q6 */  /* 91.5% cutoff (~100 dB stop) 10 */
4014 	  QualityMapping(128, 16, 0.950f, 0.950f, &Kaiser10), /* Q7 */  /* 93.1% cutoff (~100 dB stop) 10 */
4015 	  QualityMapping(160, 16, 0.960f, 0.960f, &Kaiser10), /* Q8 */  /* 94.5% cutoff (~100 dB stop) 10 */
4016 	  QualityMapping(192, 32, 0.968f, 0.968f, &Kaiser12), /* Q9 */  /* 95.5% cutoff (~100 dB stop) 10 */
4017 	  QualityMapping(256, 32, 0.975f, 0.975f, &Kaiser12), /* Q10 */ /* 96.6% cutoff (~100 dB stop) 10 */
4018 	];
4019 
4020 
4021 	nothrow @trusted @nogc:
4022 	/*8, 24, 40, 56, 80, 104, 128, 160, 200, 256, 320*/
4023 	double computeFunc (float x, immutable FuncDef* func) {
4024 	  version(Posix) import core.stdc.math : lrintf;
4025 	  import std.math : floor;
4026 	  //double[4] interp;
4027 	  float y = x*func.oversample;
4028 	  version(Posix) {
4029 	    int ind = cast(int)lrintf(floor(y));
4030 	  } else {
4031 	    int ind = cast(int)(floor(y));
4032 	  }
4033 	  float frac = (y-ind);
4034 	  immutable f2 = frac*frac;
4035 	  immutable f3 = f2*frac;
4036 	  double interp3 = -0.1666666667*frac+0.1666666667*(f3);
4037 	  double interp2 = frac+0.5*(f2)-0.5*(f3);
4038 	  //double interp2 = 1.0f-0.5f*frac-f2+0.5f*f3;
4039 	  double interp0 = -0.3333333333*frac+0.5*(f2)-0.1666666667*(f3);
4040 	  // just to make sure we don't have rounding problems
4041 	  double interp1 = 1.0f-interp3-interp2-interp0;
4042 	  //sum = frac*accum[1]+(1-frac)*accum[2];
4043 	  return interp0*func.table[ind]+interp1*func.table[ind+1]+interp2*func.table[ind+2]+interp3*func.table[ind+3];
4044 	}
4045 
4046 
4047 	// the slow way of computing a sinc for the table; should improve that some day
4048 	float sinc (float cutoff, float x, int N, immutable FuncDef *windowFunc) {
4049 	  version(LittleEndian) {
4050 	    align(1) union temp_float { align(1): float f; uint n; }
4051 	  } else {
4052 	    static T fabs(T) (T n) pure { static if (__VERSION__ > 2067) pragma(inline, true); return (n < 0 ? -n : n); }
4053 	  }
4054 	  import std.math : sin, PI;
4055 	  version(LittleEndian) {
4056 	    temp_float txx = void;
4057 	    txx.f = x;
4058 	    txx.n &= 0x7fff_ffff; // abs
4059 	    if (txx.f < 1.0e-6f) return cutoff;
4060 	    if (txx.f > 0.5f*N) return 0;
4061 	  } else {
4062 	    if (fabs(x) < 1.0e-6f) return cutoff;
4063 	    if (fabs(x) > 0.5f*N) return 0;
4064 	  }
4065 	  //FIXME: can it really be any slower than this?
4066 	  immutable float xx = x*cutoff;
4067 	  immutable pixx = PI*xx;
4068 	  version(LittleEndian) {
4069 	    return cutoff*sin(pixx)/pixx*computeFunc(2.0*txx.f/N, windowFunc);
4070 	  } else {
4071 	    return cutoff*sin(pixx)/pixx*computeFunc(fabs(2.0*x/N), windowFunc);
4072 	  }
4073 	}
4074 
4075 
4076 	void cubicCoef (in float frac, float* interp) {
4077 	  immutable f2 = frac*frac;
4078 	  immutable f3 = f2*frac;
4079 	  // compute interpolation coefficients; i'm not sure whether this corresponds to cubic interpolation but I know it's MMSE-optimal on a sinc
4080 	  interp[0] =  -0.16667f*frac+0.16667f*f3;
4081 	  interp[1] = frac+0.5f*f2-0.5f*f3;
4082 	  //interp[2] = 1.0f-0.5f*frac-f2+0.5f*f3;
4083 	  interp[3] = -0.33333f*frac+0.5f*f2-0.16667f*f3;
4084 	  // just to make sure we don't have rounding problems
4085 	  interp[2] = 1.0-interp[0]-interp[1]-interp[3];
4086 	}
4087 
4088 
4089 	// ////////////////////////////////////////////////////////////////////////// //
4090 	int resamplerBasicDirect(T) (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint* indataLen, float* outdata, uint* outdataLen)
4091 	if (is(T == float) || is(T == double))
4092 	{
4093 	  auto N = st.filterLen;
4094 	  static if (is(T == double)) assert(N%4 == 0);
4095 	  int outSample = 0;
4096 	  int lastSample = st.lastSample.ptr[chanIdx];
4097 	  uint sampFracNum = st.sampFracNum.ptr[chanIdx];
4098 	  const(float)* sincTable = st.sincTable;
4099 	  immutable outStride = st.outStride;
4100 	  immutable intAdvance = st.intAdvance;
4101 	  immutable fracAdvance = st.fracAdvance;
4102 	  immutable denRate = st.denRate;
4103 	  T sum = void;
4104 	  while (!(lastSample >= cast(int)(*indataLen) || outSample >= cast(int)(*outdataLen))) {
4105 	    const(float)* sinct = &sincTable[sampFracNum*N];
4106 	    const(float)* iptr = &indata[lastSample];
4107 	    static if (is(T == float)) {
4108 	      // at least 2x speedup with SSE here (but for unrolled loop)
4109 	      if (N%4 == 0) {
4110 		version(sincresample_use_sse) {
4111 		  //align(64) __gshared float[4] zero = 0;
4112 		  align(64) __gshared float[4+128] zeroesBuf = 0; // dmd cannot into such aligns, alas
4113 		  __gshared uint zeroesptr = 0;
4114 		  if (zeroesptr == 0) {
4115 		    zeroesptr = cast(uint)zeroesBuf.ptr;
4116 		    if (zeroesptr&0x3f) zeroesptr = (zeroesptr|0x3f)+1;
4117 		  }
4118 		  //assert((zeroesptr&0x3f) == 0, "wtf?!");
4119 		  asm nothrow @safe @nogc {
4120 		    mov       ECX,[N];
4121 		    shr       ECX,2;
4122 		    mov       EAX,[zeroesptr];
4123 		    movaps    XMM0,[EAX];
4124 		    mov       EAX,[sinct];
4125 		    mov       EBX,[iptr];
4126 		    mov       EDX,16;
4127 		    align 8;
4128 		   rbdseeloop:
4129 		    movups    XMM1,[EAX];
4130 		    movups    XMM2,[EBX];
4131 		    mulps     XMM1,XMM2;
4132 		    addps     XMM0,XMM1;
4133 		    add       EAX,EDX;
4134 		    add       EBX,EDX;
4135 		    dec       ECX;
4136 		    jnz       rbdseeloop;
4137 		    // store result in sum
4138 		    movhlps   XMM1,XMM0; // now low part of XMM1 contains high part of XMM0
4139 		    addps     XMM0,XMM1; // low part of XMM0 is ok
4140 		    movaps    XMM1,XMM0;
4141 		    shufps    XMM1,XMM0,0b_01_01_01_01; // 2nd float of XMM0 goes to the 1st float of XMM1
4142 		    addss     XMM0,XMM1;
4143 		    movss     [sum],XMM0;
4144 		  }
4145 		  /*
4146 		  float sum1 = 0;
4147 		  foreach (immutable j; 0..N) sum1 += sinct[j]*iptr[j];
4148 		  import std.math;
4149 		  if (fabs(sum-sum1) > 0.000001f) {
4150 		    import core.stdc.stdio;
4151 		    printf("sum=%f; sum1=%f\n", sum, sum1);
4152 		    assert(0);
4153 		  }
4154 		  */
4155 		} else {
4156 		  // no SSE; for my i3 unrolled loop is almost of the speed of SSE code
4157 		  T[4] accum = 0;
4158 		  foreach (immutable j; 0..N/4) {
4159 		    accum.ptr[0] += *sinct++ * *iptr++;
4160 		    accum.ptr[1] += *sinct++ * *iptr++;
4161 		    accum.ptr[2] += *sinct++ * *iptr++;
4162 		    accum.ptr[3] += *sinct++ * *iptr++;
4163 		  }
4164 		  sum = accum.ptr[0]+accum.ptr[1]+accum.ptr[2]+accum.ptr[3];
4165 		}
4166 	      } else {
4167 		sum = 0;
4168 		foreach (immutable j; 0..N) sum += *sinct++ * *iptr++;
4169 	      }
4170 	      outdata[outStride*outSample++] = sum;
4171 	    } else {
4172 	      if (N%4 == 0) {
4173 		//TODO: write SSE code here!
4174 		// for my i3 unrolled loop is ~2 times faster
4175 		T[4] accum = 0;
4176 		foreach (immutable j; 0..N/4) {
4177 		  accum.ptr[0] += cast(double)*sinct++ * cast(double)*iptr++;
4178 		  accum.ptr[1] += cast(double)*sinct++ * cast(double)*iptr++;
4179 		  accum.ptr[2] += cast(double)*sinct++ * cast(double)*iptr++;
4180 		  accum.ptr[3] += cast(double)*sinct++ * cast(double)*iptr++;
4181 		}
4182 		sum = accum.ptr[0]+accum.ptr[1]+accum.ptr[2]+accum.ptr[3];
4183 	      } else {
4184 		sum = 0;
4185 		foreach (immutable j; 0..N) sum += cast(double)*sinct++ * cast(double)*iptr++;
4186 	      }
4187 	      outdata[outStride*outSample++] = cast(float)sum;
4188 	    }
4189 	    lastSample += intAdvance;
4190 	    sampFracNum += fracAdvance;
4191 	    if (sampFracNum >= denRate) {
4192 	      sampFracNum -= denRate;
4193 	      ++lastSample;
4194 	    }
4195 	  }
4196 	  st.lastSample.ptr[chanIdx] = lastSample;
4197 	  st.sampFracNum.ptr[chanIdx] = sampFracNum;
4198 	  return outSample;
4199 	}
4200 
4201 
4202 	int resamplerBasicInterpolate(T) (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint *indataLen, float *outdata, uint *outdataLen)
4203 	if (is(T == float) || is(T == double))
4204 	{
4205 	  immutable N = st.filterLen;
4206 	  assert(N%4 == 0);
4207 	  int outSample = 0;
4208 	  int lastSample = st.lastSample.ptr[chanIdx];
4209 	  uint sampFracNum = st.sampFracNum.ptr[chanIdx];
4210 	  immutable outStride = st.outStride;
4211 	  immutable intAdvance = st.intAdvance;
4212 	  immutable fracAdvance = st.fracAdvance;
4213 	  immutable denRate = st.denRate;
4214 	  float sum;
4215 
4216 	  float[4] interp = void;
4217 	  T[4] accum = void;
4218 	  while (!(lastSample >= cast(int)(*indataLen) || outSample >= cast(int)(*outdataLen))) {
4219 	    const(float)* iptr = &indata[lastSample];
4220 	    const int offset = sampFracNum*st.oversample/st.denRate;
4221 	    const float frac = (cast(float)((sampFracNum*st.oversample)%st.denRate))/st.denRate;
4222 	    accum[] = 0;
4223 	    //TODO: optimize!
4224 	    foreach (immutable j; 0..N) {
4225 	      immutable T currIn = iptr[j];
4226 	      accum.ptr[0] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset-2]);
4227 	      accum.ptr[1] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset-1]);
4228 	      accum.ptr[2] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset]);
4229 	      accum.ptr[3] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset+1]);
4230 	    }
4231 
4232 	    cubicCoef(frac, interp.ptr);
4233 	    sum = (interp.ptr[0]*accum.ptr[0])+(interp.ptr[1]*accum.ptr[1])+(interp.ptr[2]*accum.ptr[2])+(interp.ptr[3]*accum.ptr[3]);
4234 
4235 	    outdata[outStride*outSample++] = sum;
4236 	    lastSample += intAdvance;
4237 	    sampFracNum += fracAdvance;
4238 	    if (sampFracNum >= denRate) {
4239 	      sampFracNum -= denRate;
4240 	      ++lastSample;
4241 	    }
4242 	  }
4243 
4244 	  st.lastSample.ptr[chanIdx] = lastSample;
4245 	  st.sampFracNum.ptr[chanIdx] = sampFracNum;
4246 	  return outSample;
4247 	}
4248 
4249 
4250 	// ////////////////////////////////////////////////////////////////////////// //
4251 	uint gcd (uint a, uint b) pure {
4252 	  if (a == 0) return b;
4253 	  if (b == 0) return a;
4254 	  for (;;) {
4255 	    if (a > b) {
4256 	      a %= b;
4257 	      if (a == 0) return b;
4258 	      if (a == 1) return 1;
4259 	    } else {
4260 	      b %= a;
4261 	      if (b == 0) return a;
4262 	      if (b == 1) return 1;
4263 	    }
4264 	  }
4265 	}
4266 
4267 
4268 	// ////////////////////////////////////////////////////////////////////////// //
4269 	// very simple and cheap cubic upsampler
4270 	struct CubicUpsampler {
4271 	public:
4272 	nothrow @trusted @nogc:
4273 	  float[2] curposfrac; // current position offset [0..1)
4274 	  float step; // how long we should move on one step?
4275 	  float[4][2] data; // -1..3
4276 	  uint[2] drain;
4277 
4278 	  void reset () {
4279 	    curposfrac[] = 0.0f;
4280 	    foreach (ref d; data) d[] = 0.0f;
4281 	    drain[] = 0;
4282 	  }
4283 
4284 	  bool setup (float astep) {
4285 	    if (astep >= 1.0f) return false;
4286 	    step = astep;
4287 	    return true;
4288 	  }
4289 
4290 	  /*
4291 	  static struct Data {
4292 	    const(float)[] dataIn;
4293 	    float[] dataOut;
4294 	    uint inputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
4295 	    uint outputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
4296 	  }
4297 	  */
4298 
4299 	  SpeexResampler.Error process (ref SpeexResampler.Data d) {
4300 	    d.inputSamplesUsed = d.outputSamplesUsed = 0;
4301 	    if (d.dataOut.length < 2) return SpeexResampler.Error.OK;
4302 	    foreach (uint cidx; 0..2) {
4303 	      uint inleft = cast(uint)d.dataIn.length/2;
4304 	      uint outleft = cast(uint)d.dataOut.length/2;
4305 	      processChannel(inleft, outleft, (d.dataIn.length ? d.dataIn.ptr+cidx : null), (d.dataOut.length ? d.dataOut.ptr+cidx : null), cidx);
4306 	      d.outputSamplesUsed += cast(uint)(d.dataOut.length/2)-outleft;
4307 	      d.inputSamplesUsed += cast(uint)(d.dataIn.length/2)-inleft;
4308 	    }
4309 	    return SpeexResampler.Error.OK;
4310 	  }
4311 
4312 	  private void processChannel (ref uint inleft, ref uint outleft, const(float)* dataIn, float* dataOut, uint cidx) {
4313 	    if (outleft == 0) return;
4314 	    if (inleft == 0 && drain.ptr[cidx] <= 1) return;
4315 	    auto dt = data.ptr[cidx].ptr;
4316 	    auto drn = drain.ptr+cidx;
4317 	    auto cpf = curposfrac.ptr+cidx;
4318 	    immutable float st = step;
4319 	    for (;;) {
4320 	      // fill buffer
4321 	      while ((*drn) < 4) {
4322 		if (inleft == 0) return;
4323 		dt[(*drn)++] = *dataIn;
4324 		dataIn += 2;
4325 		--inleft;
4326 	      }
4327 	      if (outleft == 0) return;
4328 	      --outleft;
4329 	      // cubic interpolation
4330 	      /*version(none)*/ {
4331 		// interpolate between y1 and y2
4332 		immutable float mu = (*cpf); // how far we are moved from y1 to y2
4333 		immutable float mu2 = mu*mu; // wow
4334 		immutable float y0 = dt[0], y1 = dt[1], y2 = dt[2], y3 = dt[3];
4335 		version(complex_cubic) {
4336 		  immutable float z0 = 0.5*y3;
4337 		  immutable float z1 = 0.5*y0;
4338 		  immutable float a0 = 1.5*y1-z1-1.5*y2+z0;
4339 		  immutable float a1 = y0-2.5*y1+2*y2-z0;
4340 		  immutable float a2 = 0.5*y2-z1;
4341 		} else {
4342 		  immutable float a0 = y3-y2-y0+y1;
4343 		  immutable float a1 = y0-y1-a0;
4344 		  immutable float a2 = y2-y0;
4345 		}
4346 		*dataOut = a0*mu*mu2+a1*mu2+a2*mu+y1;
4347 	      }// else *dataOut = dt[1];
4348 	      dataOut += 2;
4349 	      if (((*cpf) += st) >= 1.0f) {
4350 		(*cpf) -= 1.0f;
4351 		dt[0] = dt[1];
4352 		dt[1] = dt[2];
4353 		dt[2] = dt[3];
4354 		dt[3] = 0.0f;
4355 		--(*drn); // will request more input bytes
4356 	      }
4357 	    }
4358 	  }
4359 	}
4360 }
4361 
4362 version(with_resampler)
4363 abstract class ResamplingContext {
4364 	int inputSampleRate;
4365 	int outputSampleRate;
4366 
4367 	int inputChannels;
4368 	int outputChannels;
4369 
4370 	SpeexResampler resamplerLeft;
4371 	SpeexResampler resamplerRight;
4372 
4373 	SpeexResampler.Data resamplerDataLeft;
4374 	SpeexResampler.Data resamplerDataRight;
4375 
4376 	float[][2] buffersIn;
4377 	float[][2] buffersOut;
4378 
4379 	uint rateNum;
4380 	uint rateDem;
4381 
4382 	float[][2] dataReady;
4383 
4384 	SampleControlFlags scflags;
4385 
4386 	this(SampleControlFlags scflags, int inputSampleRate, int outputSampleRate, int inputChannels, int outputChannels) {
4387 		this.scflags = scflags;
4388 		this.inputSampleRate = inputSampleRate;
4389 		this.outputSampleRate = outputSampleRate;
4390 		this.inputChannels = inputChannels;
4391 		this.outputChannels = outputChannels;
4392 
4393 
4394 		if(auto err = resamplerLeft.setup(1, inputSampleRate, outputSampleRate, 5))
4395 			throw new Exception("ugh");
4396 		resamplerRight.setup(1, inputSampleRate, outputSampleRate, 5);
4397 
4398 		resamplerLeft.getRatio(rateNum, rateDem);
4399 
4400 		int add = (rateNum % rateDem) ? 1 : 0;
4401 
4402 		buffersIn[0] = new float[](BUFFER_SIZE_FRAMES * rateNum / rateDem + add);
4403 		buffersOut[0] = new float[](BUFFER_SIZE_FRAMES);
4404 		if(inputChannels > 1) {
4405 			buffersIn[1] = new float[](BUFFER_SIZE_FRAMES * rateNum / rateDem + add);
4406 			buffersOut[1] = new float[](BUFFER_SIZE_FRAMES);
4407 		}
4408 	}
4409 
4410 	/+
4411 		float*[2] tmp;
4412 		tmp[0] = buffersIn[0].ptr;
4413 		tmp[1] = buffersIn[1].ptr;
4414 
4415 		auto actuallyGot = v.getSamplesFloat(v.chans, tmp.ptr, cast(int) buffersIn[0].length);
4416 
4417 		resamplerDataLeft.dataIn should be a slice of buffersIn[0] that is filled up
4418 		ditto for resamplerDataRight if the source has two channels
4419 	+/
4420 	abstract void loadMoreSamples();
4421 
4422 	bool loadMore() {
4423 		resamplerDataLeft.dataIn = buffersIn[0];
4424 		resamplerDataLeft.dataOut = buffersOut[0];
4425 
4426 		resamplerDataRight.dataIn = buffersIn[1];
4427 		resamplerDataRight.dataOut = buffersOut[1];
4428 
4429 		loadMoreSamples();
4430 
4431 		//resamplerLeft.reset();
4432 
4433 		if(auto err = resamplerLeft.process(resamplerDataLeft))
4434 			throw new Exception("ugh");
4435 		if(inputChannels > 1)
4436 			//resamplerRight.reset();
4437 			resamplerRight.process(resamplerDataRight);
4438 
4439 		resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[0 .. resamplerDataLeft.outputSamplesUsed];
4440 		resamplerDataRight.dataOut = resamplerDataRight.dataOut[0 .. resamplerDataRight.outputSamplesUsed];
4441 
4442 		if(resamplerDataLeft.dataOut.length == 0) {
4443 			return true;
4444 		}
4445 		return false;
4446 	}
4447 
4448 
4449 	bool fillBuffer(short[] buffer) {
4450 		if(cast(int) buffer.length != buffer.length)
4451 			throw new Exception("eeeek");
4452 
4453 		if(scflags.paused) {
4454 			buffer[] = 0;
4455 			return true;
4456 		}
4457 
4458 		if(outputChannels == 1) {
4459 			foreach(ref s; buffer) {
4460 				if(resamplerDataLeft.dataOut.length == 0) {
4461 					if(loadMore()) {
4462 						scflags.finished_ = true;
4463 						return false;
4464 					}
4465 				}
4466 
4467 				if(inputChannels == 1) {
4468 					s = cast(short) (resamplerDataLeft.dataOut[0] * short.max);
4469 					resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
4470 				} else {
4471 					s = cast(short) ((resamplerDataLeft.dataOut[0] + resamplerDataRight.dataOut[0]) * short.max / 2);
4472 
4473 					resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
4474 					resamplerDataRight.dataOut = resamplerDataRight.dataOut[1 .. $];
4475 				}
4476 			}
4477 
4478 			scflags.currentPosition += cast(float) buffer.length / outputSampleRate / outputChannels;
4479 		} else if(outputChannels == 2) {
4480 			foreach(idx, ref s; buffer) {
4481 				if(resamplerDataLeft.dataOut.length == 0) {
4482 					if(loadMore()) {
4483 						scflags.finished_ = true;
4484 						return false;
4485 					}
4486 				}
4487 
4488 				if(inputChannels == 1) {
4489 					s = cast(short) (resamplerDataLeft.dataOut[0] * short.max);
4490 					if(idx & 1)
4491 						resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
4492 				} else {
4493 					if(idx & 1) {
4494 						s = cast(short) (resamplerDataRight.dataOut[0] * short.max);
4495 						resamplerDataRight.dataOut = resamplerDataRight.dataOut[1 .. $];
4496 					} else {
4497 						s = cast(short) (resamplerDataLeft.dataOut[0] * short.max);
4498 						resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
4499 					}
4500 				}
4501 			}
4502 
4503 			scflags.currentPosition += cast(float) buffer.length / outputSampleRate / outputChannels;
4504 		} else assert(0);
4505 
4506 		if(scflags.stopped)
4507 			scflags.finished_ = true;
4508 		return !scflags.stopped;
4509 	}
4510 }
4511 
4512 private enum scriptable = "arsd_jsvar_compatible";