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