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