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 	if (auto err = snd_pcm_open(&handle, cardName.toStringz, direction, 0))
2994 		throw new AlsaException("open device", err);
2995 	scope(failure)
2996 		snd_pcm_close(handle);
2997 
2998 
2999 	if (auto err = snd_pcm_hw_params_malloc(&hwParams))
3000 		throw new AlsaException("params malloc", err);
3001 	scope(exit)
3002 		snd_pcm_hw_params_free(hwParams);
3003 
3004 	if (auto err = snd_pcm_hw_params_any(handle, hwParams))
3005 		// can actually survive a failure here, we will just move forward
3006 		{} // throw new AlsaException("params init", err);
3007 
3008 	if (auto err = snd_pcm_hw_params_set_access(handle, hwParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED))
3009 		throw new AlsaException("params access", err);
3010 
3011 	if (auto err = snd_pcm_hw_params_set_format(handle, hwParams, snd_pcm_format.SND_PCM_FORMAT_S16_LE))
3012 		throw new AlsaException("params format", err);
3013 
3014 	uint rate = SampleRate;
3015 	int dir = 0;
3016 	if (auto err = snd_pcm_hw_params_set_rate_near(handle, hwParams, &rate, &dir))
3017 		throw new AlsaException("params rate", err);
3018 
3019 	assert(rate == SampleRate); // cheap me
3020 
3021 	if (auto err = snd_pcm_hw_params_set_channels(handle, hwParams, channels))
3022 		throw new AlsaException("params channels", err);
3023 
3024 	uint periods = 4;
3025 	{
3026 	auto err = snd_pcm_hw_params_set_periods_near(handle, hwParams, &periods, 0);
3027 	if(err < 0)
3028 		throw new AlsaException("periods", err);
3029 
3030 	// import std.stdio; writeln(periods);
3031 	snd_pcm_uframes_t sz = (BUFFER_SIZE_FRAMES * periods);
3032 	err = snd_pcm_hw_params_set_buffer_size_near(handle, hwParams, &sz);
3033 	if(err < 0)
3034 		throw new AlsaException("buffer size", err);
3035 	}
3036 
3037 	if (auto err = snd_pcm_hw_params(handle, hwParams))
3038 		throw new AlsaException("params install", err);
3039 
3040 	/* Setting up the callbacks */
3041 
3042 	snd_pcm_sw_params_t* swparams;
3043 	if(auto err = snd_pcm_sw_params_malloc(&swparams))
3044 		throw new AlsaException("sw malloc", err);
3045 	scope(exit)
3046 		snd_pcm_sw_params_free(swparams);
3047 	if(auto err = snd_pcm_sw_params_current(handle, swparams))
3048 		throw new AlsaException("sw set", err);
3049 	if(auto err = snd_pcm_sw_params_set_avail_min(handle, swparams, BUFFER_SIZE_FRAMES))
3050 		throw new AlsaException("sw min", err);
3051 	if(auto err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0))
3052 		throw new AlsaException("sw threshold", err);
3053 	if(auto err = snd_pcm_sw_params(handle, swparams))
3054 		throw new AlsaException("sw params", err);
3055 
3056 	/* finish setup */
3057 
3058 	if (auto err = snd_pcm_prepare(handle))
3059 		throw new AlsaException("prepare", err);
3060 
3061 	assert(handle !is null);
3062 	return handle;
3063 }
3064 
3065 version(ALSA)
3066 class AlsaException : AudioException {
3067 	this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
3068 		auto msg = snd_strerror(error);
3069 		import core.stdc.string;
3070 		super(cast(string) (message ~ ": " ~ msg[0 .. strlen(msg)]), file, line, next);
3071 	}
3072 }
3073 
3074 version(WinMM)
3075 class WinMMException : AudioException {
3076 	this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
3077 		// FIXME: format the error
3078 		// midiOutGetErrorText, etc.
3079 		super(message, file, line, next);
3080 	}
3081 }
3082 
3083 // ****************
3084 // Bindings follow
3085 // ****************
3086 
3087 version(ALSA) {
3088 extern(C):
3089 @nogc nothrow:
3090 	pragma(lib, "asound");
3091 	private import core.sys.posix.poll;
3092 
3093 	const(char)* snd_strerror(int);
3094 
3095 	// pcm
3096 	enum snd_pcm_stream_t {
3097 		SND_PCM_STREAM_PLAYBACK,
3098 		SND_PCM_STREAM_CAPTURE
3099 	}
3100 
3101 	enum snd_pcm_access_t {
3102 		/** mmap access with simple interleaved channels */
3103 		SND_PCM_ACCESS_MMAP_INTERLEAVED = 0,
3104 		/** mmap access with simple non interleaved channels */
3105 		SND_PCM_ACCESS_MMAP_NONINTERLEAVED,
3106 		/** mmap access with complex placement */
3107 		SND_PCM_ACCESS_MMAP_COMPLEX,
3108 		/** snd_pcm_readi/snd_pcm_writei access */
3109 		SND_PCM_ACCESS_RW_INTERLEAVED,
3110 		/** snd_pcm_readn/snd_pcm_writen access */
3111 		SND_PCM_ACCESS_RW_NONINTERLEAVED,
3112 		SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED
3113 	}
3114 
3115 	enum snd_pcm_format {
3116 		/** Unknown */
3117 		SND_PCM_FORMAT_UNKNOWN = -1,
3118 		/** Signed 8 bit */
3119 		SND_PCM_FORMAT_S8 = 0,
3120 		/** Unsigned 8 bit */
3121 		SND_PCM_FORMAT_U8,
3122 		/** Signed 16 bit Little Endian */
3123 		SND_PCM_FORMAT_S16_LE,
3124 		/** Signed 16 bit Big Endian */
3125 		SND_PCM_FORMAT_S16_BE,
3126 		/** Unsigned 16 bit Little Endian */
3127 		SND_PCM_FORMAT_U16_LE,
3128 		/** Unsigned 16 bit Big Endian */
3129 		SND_PCM_FORMAT_U16_BE,
3130 		/** Signed 24 bit Little Endian using low three bytes in 32-bit word */
3131 		SND_PCM_FORMAT_S24_LE,
3132 		/** Signed 24 bit Big Endian using low three bytes in 32-bit word */
3133 		SND_PCM_FORMAT_S24_BE,
3134 		/** Unsigned 24 bit Little Endian using low three bytes in 32-bit word */
3135 		SND_PCM_FORMAT_U24_LE,
3136 		/** Unsigned 24 bit Big Endian using low three bytes in 32-bit word */
3137 		SND_PCM_FORMAT_U24_BE,
3138 		/** Signed 32 bit Little Endian */
3139 		SND_PCM_FORMAT_S32_LE,
3140 		/** Signed 32 bit Big Endian */
3141 		SND_PCM_FORMAT_S32_BE,
3142 		/** Unsigned 32 bit Little Endian */
3143 		SND_PCM_FORMAT_U32_LE,
3144 		/** Unsigned 32 bit Big Endian */
3145 		SND_PCM_FORMAT_U32_BE,
3146 		/** Float 32 bit Little Endian, Range -1.0 to 1.0 */
3147 		SND_PCM_FORMAT_FLOAT_LE,
3148 		/** Float 32 bit Big Endian, Range -1.0 to 1.0 */
3149 		SND_PCM_FORMAT_FLOAT_BE,
3150 		/** Float 64 bit Little Endian, Range -1.0 to 1.0 */
3151 		SND_PCM_FORMAT_FLOAT64_LE,
3152 		/** Float 64 bit Big Endian, Range -1.0 to 1.0 */
3153 		SND_PCM_FORMAT_FLOAT64_BE,
3154 		/** IEC-958 Little Endian */
3155 		SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
3156 		/** IEC-958 Big Endian */
3157 		SND_PCM_FORMAT_IEC958_SUBFRAME_BE,
3158 		/** Mu-Law */
3159 		SND_PCM_FORMAT_MU_LAW,
3160 		/** A-Law */
3161 		SND_PCM_FORMAT_A_LAW,
3162 		/** Ima-ADPCM */
3163 		SND_PCM_FORMAT_IMA_ADPCM,
3164 		/** MPEG */
3165 		SND_PCM_FORMAT_MPEG,
3166 		/** GSM */
3167 		SND_PCM_FORMAT_GSM,
3168 		/** Special */
3169 		SND_PCM_FORMAT_SPECIAL = 31,
3170 		/** Signed 24bit Little Endian in 3bytes format */
3171 		SND_PCM_FORMAT_S24_3LE = 32,
3172 		/** Signed 24bit Big Endian in 3bytes format */
3173 		SND_PCM_FORMAT_S24_3BE,
3174 		/** Unsigned 24bit Little Endian in 3bytes format */
3175 		SND_PCM_FORMAT_U24_3LE,
3176 		/** Unsigned 24bit Big Endian in 3bytes format */
3177 		SND_PCM_FORMAT_U24_3BE,
3178 		/** Signed 20bit Little Endian in 3bytes format */
3179 		SND_PCM_FORMAT_S20_3LE,
3180 		/** Signed 20bit Big Endian in 3bytes format */
3181 		SND_PCM_FORMAT_S20_3BE,
3182 		/** Unsigned 20bit Little Endian in 3bytes format */
3183 		SND_PCM_FORMAT_U20_3LE,
3184 		/** Unsigned 20bit Big Endian in 3bytes format */
3185 		SND_PCM_FORMAT_U20_3BE,
3186 		/** Signed 18bit Little Endian in 3bytes format */
3187 		SND_PCM_FORMAT_S18_3LE,
3188 		/** Signed 18bit Big Endian in 3bytes format */
3189 		SND_PCM_FORMAT_S18_3BE,
3190 		/** Unsigned 18bit Little Endian in 3bytes format */
3191 		SND_PCM_FORMAT_U18_3LE,
3192 		/** Unsigned 18bit Big Endian in 3bytes format */
3193 		SND_PCM_FORMAT_U18_3BE,
3194 		/* G.723 (ADPCM) 24 kbit/s, 8 samples in 3 bytes */
3195 		SND_PCM_FORMAT_G723_24,
3196 		/* G.723 (ADPCM) 24 kbit/s, 1 sample in 1 byte */
3197 		SND_PCM_FORMAT_G723_24_1B,
3198 		/* G.723 (ADPCM) 40 kbit/s, 8 samples in 3 bytes */
3199 		SND_PCM_FORMAT_G723_40,
3200 		/* G.723 (ADPCM) 40 kbit/s, 1 sample in 1 byte */
3201 		SND_PCM_FORMAT_G723_40_1B,
3202 		/* Direct Stream Digital (DSD) in 1-byte samples (x8) */
3203 		SND_PCM_FORMAT_DSD_U8,
3204 		/* Direct Stream Digital (DSD) in 2-byte samples (x16) */
3205 		SND_PCM_FORMAT_DSD_U16_LE,
3206 		SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_DSD_U16_LE,
3207 
3208 		// I snipped a bunch of endian-specific ones!
3209 	}
3210 
3211 	struct snd_pcm_t {}
3212 	struct snd_pcm_hw_params_t {}
3213 	struct snd_pcm_sw_params_t {}
3214 
3215 	int snd_pcm_open(snd_pcm_t**, const char*, snd_pcm_stream_t, int);
3216 	int snd_pcm_close(snd_pcm_t*);
3217 	int snd_pcm_pause(snd_pcm_t*, int);
3218 	int snd_pcm_prepare(snd_pcm_t*);
3219 	int snd_pcm_hw_params(snd_pcm_t*, snd_pcm_hw_params_t*);
3220 	int snd_pcm_hw_params_set_periods(snd_pcm_t*, snd_pcm_hw_params_t*, uint, int);
3221 	int snd_pcm_hw_params_set_periods_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int);
3222 	int snd_pcm_hw_params_set_buffer_size(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t);
3223 	int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t*);
3224 	int snd_pcm_hw_params_set_channels(snd_pcm_t*, snd_pcm_hw_params_t*, uint);
3225 	int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t**);
3226 	void snd_pcm_hw_params_free(snd_pcm_hw_params_t*);
3227 	int snd_pcm_hw_params_any(snd_pcm_t*, snd_pcm_hw_params_t*);
3228 	int snd_pcm_hw_params_set_access(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_access_t);
3229 	int snd_pcm_hw_params_set_format(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_format);
3230 	int snd_pcm_hw_params_set_rate_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int*);
3231 
3232 	int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t**);
3233 	void snd_pcm_sw_params_free(snd_pcm_sw_params_t*);
3234 
3235 	int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
3236 	int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
3237 	int snd_pcm_sw_params_set_avail_min(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
3238 	int snd_pcm_sw_params_set_start_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
3239 	int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
3240 
3241 	alias snd_pcm_sframes_t = c_long;
3242 	alias snd_pcm_uframes_t = c_ulong;
3243 	snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t*, const void*, snd_pcm_uframes_t size);
3244 	snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t*, void*, snd_pcm_uframes_t size);
3245 
3246 	int snd_pcm_wait(snd_pcm_t *pcm, int timeout);
3247 	snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm);
3248 	snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm);
3249 
3250 	int snd_pcm_recover (snd_pcm_t* pcm, int err, int silent);
3251 
3252 	alias snd_lib_error_handler_t = void function (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...);
3253 	int snd_lib_error_set_handler (snd_lib_error_handler_t handler);
3254 
3255 	import core.stdc.stdarg;
3256 	private void alsa_message_silencer (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...) {}
3257 	//k8: ALSAlib loves to trash stderr; shut it up
3258 	void silence_alsa_messages () { snd_lib_error_set_handler(&alsa_message_silencer); }
3259 	extern(D) shared static this () { silence_alsa_messages(); }
3260 
3261 	// raw midi
3262 
3263 	static if(is(size_t == uint))
3264 		alias ssize_t = int;
3265 	else
3266 		alias ssize_t = long;
3267 
3268 
3269 	struct snd_rawmidi_t {}
3270 	int snd_rawmidi_open(snd_rawmidi_t**, snd_rawmidi_t**, const char*, int);
3271 	int snd_rawmidi_close(snd_rawmidi_t*);
3272 	int snd_rawmidi_drain(snd_rawmidi_t*);
3273 	ssize_t snd_rawmidi_write(snd_rawmidi_t*, const void*, size_t);
3274 	ssize_t snd_rawmidi_read(snd_rawmidi_t*, void*, size_t);
3275 
3276 	// mixer
3277 
3278 	struct snd_mixer_t {}
3279 	struct snd_mixer_elem_t {}
3280 	struct snd_mixer_selem_id_t {}
3281 
3282 	alias snd_mixer_elem_callback_t = int function(snd_mixer_elem_t*, uint);
3283 
3284 	int snd_mixer_open(snd_mixer_t**, int mode);
3285 	int snd_mixer_close(snd_mixer_t*);
3286 	int snd_mixer_attach(snd_mixer_t*, const char*);
3287 	int snd_mixer_load(snd_mixer_t*);
3288 
3289 	// FIXME: those aren't actually void*
3290 	int snd_mixer_selem_register(snd_mixer_t*, void*, void*);
3291 	int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t**);
3292 	void snd_mixer_selem_id_free(snd_mixer_selem_id_t*);
3293 	void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t*, uint);
3294 	void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t*, const char*);
3295 	snd_mixer_elem_t* snd_mixer_find_selem(snd_mixer_t*, const scope snd_mixer_selem_id_t*);
3296 
3297 	// FIXME: the int should be an enum for channel identifier
3298 	int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t*, int, c_long*);
3299 
3300 	int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t*, c_long*, c_long*);
3301 
3302 	int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t*, c_long);
3303 
3304 	void snd_mixer_elem_set_callback(snd_mixer_elem_t*, snd_mixer_elem_callback_t);
3305 	int snd_mixer_poll_descriptors(snd_mixer_t*, pollfd*, uint space);
3306 
3307 	int snd_mixer_handle_events(snd_mixer_t*);
3308 
3309 	// FIXME: the first int should be an enum for channel identifier
3310 	int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t*, int, int* value);
3311 	int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t*, int);
3312 }
3313 
3314 version(WinMM) {
3315 extern(Windows):
3316 @nogc nothrow:
3317 	pragma(lib, "winmm");
3318 	import core.sys.windows.windows;
3319 
3320 /*
3321 	Windows functions include:
3322 	http://msdn.microsoft.com/en-us/library/ms713762%28VS.85%29.aspx
3323 	http://msdn.microsoft.com/en-us/library/ms713504%28v=vs.85%29.aspx
3324 	http://msdn.microsoft.com/en-us/library/windows/desktop/dd798480%28v=vs.85%29.aspx#
3325 	http://msdn.microsoft.com/en-US/subscriptions/ms712109.aspx
3326 */
3327 
3328 	// pcm
3329 
3330 	// midi
3331 /+
3332 	alias HMIDIOUT = HANDLE;
3333 	alias MMRESULT = UINT;
3334 
3335 	MMRESULT midiOutOpen(HMIDIOUT*, UINT, DWORD, DWORD, DWORD);
3336 	MMRESULT midiOutClose(HMIDIOUT);
3337 	MMRESULT midiOutReset(HMIDIOUT);
3338 	MMRESULT midiOutShortMsg(HMIDIOUT, DWORD);
3339 
3340 	alias HWAVEOUT = HANDLE;
3341 
3342 	struct WAVEFORMATEX {
3343 		WORD wFormatTag;
3344 		WORD nChannels;
3345 		DWORD nSamplesPerSec;
3346 		DWORD nAvgBytesPerSec;
3347 		WORD nBlockAlign;
3348 		WORD wBitsPerSample;
3349 		WORD cbSize;
3350 	}
3351 
3352 	struct WAVEHDR {
3353 		void* lpData;
3354 		DWORD dwBufferLength;
3355 		DWORD dwBytesRecorded;
3356 		DWORD dwUser;
3357 		DWORD dwFlags;
3358 		DWORD dwLoops;
3359 		WAVEHDR *lpNext;
3360 		DWORD reserved;
3361 	}
3362 
3363 	enum UINT WAVE_MAPPER= -1;
3364 
3365 	MMRESULT waveOutOpen(HWAVEOUT*, UINT_PTR, WAVEFORMATEX*, void* callback, void*, DWORD);
3366 	MMRESULT waveOutClose(HWAVEOUT);
3367 	MMRESULT waveOutPrepareHeader(HWAVEOUT, WAVEHDR*, UINT);
3368 	MMRESULT waveOutUnprepareHeader(HWAVEOUT, WAVEHDR*, UINT);
3369 	MMRESULT waveOutWrite(HWAVEOUT, WAVEHDR*, UINT);
3370 
3371 	MMRESULT waveOutGetVolume(HWAVEOUT, PDWORD);
3372 	MMRESULT waveOutSetVolume(HWAVEOUT, DWORD);
3373 
3374 	enum CALLBACK_TYPEMASK = 0x70000;
3375 	enum CALLBACK_NULL     = 0;
3376 	enum CALLBACK_WINDOW   = 0x10000;
3377 	enum CALLBACK_TASK     = 0x20000;
3378 	enum CALLBACK_FUNCTION = 0x30000;
3379 	enum CALLBACK_THREAD   = CALLBACK_TASK;
3380 	enum CALLBACK_EVENT    = 0x50000;
3381 
3382 	enum WAVE_FORMAT_PCM = 1;
3383 
3384 	enum WHDR_PREPARED = 2;
3385 	enum WHDR_BEGINLOOP = 4;
3386 	enum WHDR_ENDLOOP = 8;
3387 	enum WHDR_INQUEUE = 16;
3388 
3389 	enum WinMMMessage : UINT {
3390 		MM_JOY1MOVE            = 0x3A0,
3391 		MM_JOY2MOVE,
3392 		MM_JOY1ZMOVE,
3393 		MM_JOY2ZMOVE,       // = 0x3A3
3394 		MM_JOY1BUTTONDOWN      = 0x3B5,
3395 		MM_JOY2BUTTONDOWN,
3396 		MM_JOY1BUTTONUP,
3397 		MM_JOY2BUTTONUP,
3398 		MM_MCINOTIFY,       // = 0x3B9
3399 		MM_WOM_OPEN            = 0x3BB,
3400 		MM_WOM_CLOSE,
3401 		MM_WOM_DONE,
3402 		MM_WIM_OPEN,
3403 		MM_WIM_CLOSE,
3404 		MM_WIM_DATA,
3405 		MM_MIM_OPEN,
3406 		MM_MIM_CLOSE,
3407 		MM_MIM_DATA,
3408 		MM_MIM_LONGDATA,
3409 		MM_MIM_ERROR,
3410 		MM_MIM_LONGERROR,
3411 		MM_MOM_OPEN,
3412 		MM_MOM_CLOSE,
3413 		MM_MOM_DONE,        // = 0x3C9
3414 		MM_DRVM_OPEN           = 0x3D0,
3415 		MM_DRVM_CLOSE,
3416 		MM_DRVM_DATA,
3417 		MM_DRVM_ERROR,
3418 		MM_STREAM_OPEN,
3419 		MM_STREAM_CLOSE,
3420 		MM_STREAM_DONE,
3421 		MM_STREAM_ERROR,    // = 0x3D7
3422 		MM_MOM_POSITIONCB      = 0x3CA,
3423 		MM_MCISIGNAL,
3424 		MM_MIM_MOREDATA,    // = 0x3CC
3425 		MM_MIXM_LINE_CHANGE    = 0x3D0,
3426 		MM_MIXM_CONTROL_CHANGE = 0x3D1
3427 	}
3428 
3429 
3430 	enum WOM_OPEN  = WinMMMessage.MM_WOM_OPEN;
3431 	enum WOM_CLOSE = WinMMMessage.MM_WOM_CLOSE;
3432 	enum WOM_DONE  = WinMMMessage.MM_WOM_DONE;
3433 	enum WIM_OPEN  = WinMMMessage.MM_WIM_OPEN;
3434 	enum WIM_CLOSE = WinMMMessage.MM_WIM_CLOSE;
3435 	enum WIM_DATA  = WinMMMessage.MM_WIM_DATA;
3436 
3437 
3438 	uint mciSendStringA(const scope char*,char*,uint,void*);
3439 
3440 +/
3441 }
3442 
3443 version(with_resampler) {
3444 	/* Copyright (C) 2007-2008 Jean-Marc Valin
3445 	 * Copyright (C) 2008      Thorvald Natvig
3446 	 * D port by Ketmar // Invisible Vector
3447 	 *
3448 	 * Arbitrary resampling code
3449 	 *
3450 	 * Redistribution and use in source and binary forms, with or without
3451 	 * modification, are permitted provided that the following conditions are
3452 	 * met:
3453 	 *
3454 	 * 1. Redistributions of source code must retain the above copyright notice,
3455 	 * this list of conditions and the following disclaimer.
3456 	 *
3457 	 * 2. Redistributions in binary form must reproduce the above copyright
3458 	 * notice, this list of conditions and the following disclaimer in the
3459 	 * documentation and/or other materials provided with the distribution.
3460 	 *
3461 	 * 3. The name of the author may not be used to endorse or promote products
3462 	 * derived from this software without specific prior written permission.
3463 	 *
3464 	 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
3465 	 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
3466 	 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3467 	 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
3468 	 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
3469 	 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3470 	 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3471 	 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
3472 	 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
3473 	 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3474 	 * POSSIBILITY OF SUCH DAMAGE.
3475 	 */
3476 
3477 	/* A-a-a-and now... D port is covered by the following license!
3478 	 *
3479 	 * This program is free software: you can redistribute it and/or modify
3480 	 * it under the terms of the GNU General Public License as published by
3481 	 * the Free Software Foundation, either version 3 of the License, or
3482 	 * (at your option) any later version.
3483 	 *
3484 	 * This program is distributed in the hope that it will be useful,
3485 	 * but WITHOUT ANY WARRANTY; without even the implied warranty of
3486 	 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3487 	 * GNU General Public License for more details.
3488 	 *
3489 	 * You should have received a copy of the GNU General Public License
3490 	 * along with this program. If not, see <http://www.gnu.org/licenses/>.
3491 	 */
3492 	//module iv.follin.resampler /*is aliced*/;
3493 	//import iv.alice;
3494 
3495 	/*
3496 	   The design goals of this code are:
3497 	      - Very fast algorithm
3498 	      - SIMD-friendly algorithm
3499 	      - Low memory requirement
3500 	      - Good *perceptual* quality (and not best SNR)
3501 
3502 	   Warning: This resampler is relatively new. Although I think I got rid of
3503 	   all the major bugs and I don't expect the API to change anymore, there
3504 	   may be something I've missed. So use with caution.
3505 
3506 	   This algorithm is based on this original resampling algorithm:
3507 	   Smith, Julius O. Digital Audio Resampling Home Page
3508 	   Center for Computer Research in Music and Acoustics (CCRMA),
3509 	   Stanford University, 2007.
3510 	   Web published at http://www-ccrma.stanford.edu/~jos/resample/.
3511 
3512 	   There is one main difference, though. This resampler uses cubic
3513 	   interpolation instead of linear interpolation in the above paper. This
3514 	   makes the table much smaller and makes it possible to compute that table
3515 	   on a per-stream basis. In turn, being able to tweak the table for each
3516 	   stream makes it possible to both reduce complexity on simple ratios
3517 	   (e.g. 2/3), and get rid of the rounding operations in the inner loop.
3518 	   The latter both reduces CPU time and makes the algorithm more SIMD-friendly.
3519 	*/
3520 	version = sincresample_use_full_table;
3521 	version(X86) {
3522 	  version(sincresample_disable_sse) {
3523 	  } else {
3524 	    version(D_PIC) {} else version = sincresample_use_sse;
3525 	  }
3526 	}
3527 
3528 
3529 	// ////////////////////////////////////////////////////////////////////////// //
3530 	public struct SpeexResampler {
3531 	public:
3532 	  alias Quality = int;
3533 	  enum : uint {
3534 	    Fastest = 0,
3535 	    Voip = 3,
3536 	    Default = 4,
3537 	    Desktop = 5,
3538 	    Music = 8,
3539 	    Best = 10,
3540 	  }
3541 
3542 	  enum Error {
3543 	    OK = 0,
3544 	    NoMemory,
3545 	    BadState,
3546 	    BadArgument,
3547 	    BadData,
3548 	  }
3549 
3550 	private:
3551 	nothrow @trusted @nogc:
3552 	  alias ResamplerFn = int function (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint *indataLen, float *outdata, uint *outdataLen);
3553 
3554 	private:
3555 	  uint inRate;
3556 	  uint outRate;
3557 	  uint numRate; // from
3558 	  uint denRate; // to
3559 
3560 	  Quality srQuality;
3561 	  uint chanCount;
3562 	  uint filterLen;
3563 	  uint memAllocSize;
3564 	  uint bufferSize;
3565 	  int intAdvance;
3566 	  int fracAdvance;
3567 	  float cutoff;
3568 	  uint oversample;
3569 	  bool started;
3570 
3571 	  // these are per-channel
3572 	  int[64] lastSample;
3573 	  uint[64] sampFracNum;
3574 	  uint[64] magicSamples;
3575 
3576 	  float* mem;
3577 	  uint realMemLen; // how much memory really allocated
3578 	  float* sincTable;
3579 	  uint sincTableLen;
3580 	  uint realSincTableLen; // how much memory really allocated
3581 	  ResamplerFn resampler;
3582 
3583 	  int inStride;
3584 	  int outStride;
3585 
3586 	public:
3587 	  static string errorStr (int err) {
3588 	    switch (err) with (Error) {
3589 	      case OK: return "success";
3590 	      case NoMemory: return "memory allocation failed";
3591 	      case BadState: return "bad resampler state";
3592 	      case BadArgument: return "invalid argument";
3593 	      case BadData: return "bad data passed";
3594 	      default:
3595 	    }
3596 	    return "unknown error";
3597 	  }
3598 
3599 	public:
3600 	  @disable this (this);
3601 	  ~this () { deinit(); }
3602 
3603 	  @property bool inited () const pure { return (resampler !is null); }
3604 
3605 	  void deinit () {
3606 	    import core.stdc.stdlib : free;
3607 	    if (mem !is null) { free(mem); mem = null; }
3608 	    if (sincTable !is null) { free(sincTable); sincTable = null; }
3609 	    /*
3610 	    memAllocSize = realMemLen = 0;
3611 	    sincTableLen = realSincTableLen = 0;
3612 	    resampler = null;
3613 	    started = false;
3614 	    */
3615 	    inRate = outRate = numRate = denRate = 0;
3616 	    srQuality = cast(Quality)666;
3617 	    chanCount = 0;
3618 	    filterLen = 0;
3619 	    memAllocSize = 0;
3620 	    bufferSize = 0;
3621 	    intAdvance = 0;
3622 	    fracAdvance = 0;
3623 	    cutoff = 0;
3624 	    oversample = 0;
3625 	    started = 0;
3626 
3627 	    mem = null;
3628 	    realMemLen = 0; // how much memory really allocated
3629 	    sincTable = null;
3630 	    sincTableLen = 0;
3631 	    realSincTableLen = 0; // how much memory really allocated
3632 	    resampler = null;
3633 
3634 	    inStride = outStride = 0;
3635 	  }
3636 
3637 	  /** Create a new resampler with integer input and output rates.
3638 	   *
3639 	   * Params:
3640 	   *  chans = Number of channels to be processed
3641 	   *  inRate = Input sampling rate (integer number of Hz).
3642 	   *  outRate = Output sampling rate (integer number of Hz).
3643 	   *  aquality = Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
3644 	   *
3645 	   * Returns:
3646 	   *  0 or error code
3647 	   */
3648 	  Error setup (uint chans, uint ainRate, uint aoutRate, Quality aquality/*, usize line=__LINE__*/) {
3649 	    //{ import core.stdc.stdio; printf("init: %u -> %u at %u\n", ainRate, aoutRate, cast(uint)line); }
3650 	    import core.stdc.stdlib : malloc, free;
3651 
3652 	    deinit();
3653 	    if (aquality < 0) aquality = 0;
3654 	    if (aquality > SpeexResampler.Best) aquality = SpeexResampler.Best;
3655 	    if (chans < 1 || chans > 16) return Error.BadArgument;
3656 
3657 	    started = false;
3658 	    inRate = 0;
3659 	    outRate = 0;
3660 	    numRate = 0;
3661 	    denRate = 0;
3662 	    srQuality = cast(Quality)666; // it's ok
3663 	    sincTableLen = 0;
3664 	    memAllocSize = 0;
3665 	    filterLen = 0;
3666 	    mem = null;
3667 	    resampler = null;
3668 
3669 	    cutoff = 1.0f;
3670 	    chanCount = chans;
3671 	    inStride = 1;
3672 	    outStride = 1;
3673 
3674 	    bufferSize = 160;
3675 
3676 	    // per channel data
3677 	    lastSample[] = 0;
3678 	    magicSamples[] = 0;
3679 	    sampFracNum[] = 0;
3680 
3681 	    setQuality(aquality);
3682 	    setRate(ainRate, aoutRate);
3683 
3684 	    if (auto filterErr = updateFilter()) { deinit(); return filterErr; }
3685 	    skipZeros(); // make sure that the first samples to go out of the resamplers don't have leading zeros
3686 
3687 	    return Error.OK;
3688 	  }
3689 
3690 	  /** Set (change) the input/output sampling rates (integer value).
3691 	   *
3692 	   * Params:
3693 	   *  ainRate = Input sampling rate (integer number of Hz).
3694 	   *  aoutRate = Output sampling rate (integer number of Hz).
3695 	   *
3696 	   * Returns:
3697 	   *  0 or error code
3698 	   */
3699 	  Error setRate (uint ainRate, uint aoutRate/*, usize line=__LINE__*/) {
3700 	    //{ import core.stdc.stdio; printf("changing rate: %u -> %u at %u\n", ainRate, aoutRate, cast(uint)line); }
3701 	    if (inRate == ainRate && outRate == aoutRate) return Error.OK;
3702 	    //{ import core.stdc.stdio; printf("changing rate: %u -> %u at %u\n", ratioNum, ratioDen, cast(uint)line); }
3703 
3704 	    uint oldDen = denRate;
3705 	    inRate = ainRate;
3706 	    outRate = aoutRate;
3707 	    auto div = gcd(ainRate, aoutRate);
3708 	    numRate = ainRate/div;
3709 	    denRate = aoutRate/div;
3710 
3711 	    if (oldDen > 0) {
3712 	      foreach (ref v; sampFracNum.ptr[0..chanCount]) {
3713 		v = v*denRate/oldDen;
3714 		// safety net
3715 		if (v >= denRate) v = denRate-1;
3716 	      }
3717 	    }
3718 
3719 	    return (inited ? updateFilter() : Error.OK);
3720 	  }
3721 
3722 	  /** Get the current input/output sampling rates (integer value).
3723 	   *
3724 	   * Params:
3725 	   *  ainRate = Input sampling rate (integer number of Hz) copied.
3726 	   *  aoutRate = Output sampling rate (integer number of Hz) copied.
3727 	   */
3728 	  void getRate (out uint ainRate, out uint aoutRate) {
3729 	    ainRate = inRate;
3730 	    aoutRate = outRate;
3731 	  }
3732 
3733 	  @property uint getInRate () { return inRate; }
3734 	  @property uint getOutRate () { return outRate; }
3735 
3736 	  @property uint getChans () { return chanCount; }
3737 
3738 	  /** Get the current resampling ratio. This will be reduced to the least common denominator.
3739 	   *
3740 	   * Params:
3741 	   *  ratioNum = Numerator of the sampling rate ratio copied
3742 	   *  ratioDen = Denominator of the sampling rate ratio copied
3743 	   */
3744 	  void getRatio (out uint ratioNum, out uint ratioDen) {
3745 	    ratioNum = numRate;
3746 	    ratioDen = denRate;
3747 	  }
3748 
3749 	  /** Set (change) the conversion quality.
3750 	   *
3751 	   * Params:
3752 	   *  quality = Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
3753 	   *
3754 	   * Returns:
3755 	   *  0 or error code
3756 	   */
3757 	  Error setQuality (Quality aquality) {
3758 	    if (aquality < 0) aquality = 0;
3759 	    if (aquality > SpeexResampler.Best) aquality = SpeexResampler.Best;
3760 	    if (srQuality == aquality) return Error.OK;
3761 	    srQuality = aquality;
3762 	    return (inited ? updateFilter() : Error.OK);
3763 	  }
3764 
3765 	  /** Get the conversion quality.
3766 	   *
3767 	   * Returns:
3768 	   *  Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
3769 	   */
3770 	  int getQuality () { return srQuality; }
3771 
3772 	  /** Get the latency introduced by the resampler measured in input samples.
3773 	   *
3774 	   * Returns:
3775 	   *  Input latency;
3776 	   */
3777 	  int inputLatency () { return filterLen/2; }
3778 
3779 	  /** Get the latency introduced by the resampler measured in output samples.
3780 	   *
3781 	   * Returns:
3782 	   *  Output latency.
3783 	   */
3784 	  int outputLatency () { return ((filterLen/2)*denRate+(numRate>>1))/numRate; }
3785 
3786 	  /* Make sure that the first samples to go out of the resamplers don't have
3787 	   * leading zeros. This is only useful before starting to use a newly created
3788 	   * resampler. It is recommended to use that when resampling an audio file, as
3789 	   * it will generate a file with the same length. For real-time processing,
3790 	   * it is probably easier not to use this call (so that the output duration
3791 	   * is the same for the first frame).
3792 	   *
3793 	   * Setup/reset sequence will automatically call this, so it is private.
3794 	   */
3795 	  private void skipZeros () { foreach (immutable i; 0..chanCount) lastSample.ptr[i] = filterLen/2; }
3796 
3797 	  static struct Data {
3798 	    const(float)[] dataIn;
3799 	    float[] dataOut;
3800 	    uint inputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
3801 	    uint outputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
3802 	  }
3803 
3804 	  /** Resample (an interleaved) float array. The input and output buffers must *not* overlap.
3805 	   * `data.dataIn` can be empty, but `data.dataOut` can't.
3806 	   * Function will return number of consumed samples (*not* *frames*!) in `data.inputSamplesUsed`,
3807 	   * and number of produced samples in `data.outputSamplesUsed`.
3808 	   * You should provide enough samples for all channels, and all channels will be processed.
3809 	   *
3810 	   * Params:
3811 	   *  data = input and output buffers, number of frames consumed and produced
3812 	   *
3813 	   * Returns:
3814 	   *  0 or error code
3815 	   */
3816 	  Error process(string mode="interleaved") (ref Data data) {
3817 	    static assert(mode == "interleaved" || mode == "sequential");
3818 
3819 	    data.inputSamplesUsed = data.outputSamplesUsed = 0;
3820 	    if (!inited) return Error.BadState;
3821 
3822 	    if (data.dataIn.length%chanCount || data.dataOut.length < 1 || data.dataOut.length%chanCount) return Error.BadData;
3823 	    if (data.dataIn.length > uint.max/4 || data.dataOut.length > uint.max/4) return Error.BadData;
3824 
3825 	    static if (mode == "interleaved") {
3826 	      inStride = outStride = chanCount;
3827 	    } else {
3828 	      inStride = outStride = 1;
3829 	    }
3830 	    uint iofs = 0, oofs = 0;
3831 	    immutable uint idclen = cast(uint)(data.dataIn.length/chanCount);
3832 	    immutable uint odclen = cast(uint)(data.dataOut.length/chanCount);
3833 	    foreach (immutable i; 0..chanCount) {
3834 	      data.inputSamplesUsed = idclen;
3835 	      data.outputSamplesUsed = odclen;
3836 	      if (data.dataIn.length) {
3837 		processOneChannel(i, data.dataIn.ptr+iofs, &data.inputSamplesUsed, data.dataOut.ptr+oofs, &data.outputSamplesUsed);
3838 	      } else {
3839 		processOneChannel(i, null, &data.inputSamplesUsed, data.dataOut.ptr+oofs, &data.outputSamplesUsed);
3840 	      }
3841 	      static if (mode == "interleaved") {
3842 		++iofs;
3843 		++oofs;
3844 	      } else {
3845 		iofs += idclen;
3846 		oofs += odclen;
3847 	      }
3848 	    }
3849 	    data.inputSamplesUsed *= chanCount;
3850 	    data.outputSamplesUsed *= chanCount;
3851 	    return Error.OK;
3852 	  }
3853 
3854 
3855 	  //HACK for libswresample
3856 	  // return -1 or number of outframes
3857 	  int swrconvert (float** outbuf, int outframes, const(float)**inbuf, int inframes) {
3858 	    if (!inited || outframes < 1 || inframes < 0) return -1;
3859 	    inStride = outStride = 1;
3860 	    Data data;
3861 	    foreach (immutable i; 0..chanCount) {
3862 	      data.dataIn = (inframes ? inbuf[i][0..inframes] : null);
3863 	      data.dataOut = (outframes ? outbuf[i][0..outframes] : null);
3864 	      data.inputSamplesUsed = inframes;
3865 	      data.outputSamplesUsed = outframes;
3866 	      if (inframes > 0) {
3867 		processOneChannel(i, data.dataIn.ptr, &data.inputSamplesUsed, data.dataOut.ptr, &data.outputSamplesUsed);
3868 	      } else {
3869 		processOneChannel(i, null, &data.inputSamplesUsed, data.dataOut.ptr, &data.outputSamplesUsed);
3870 	      }
3871 	    }
3872 	    return data.outputSamplesUsed;
3873 	  }
3874 
3875 	  /// Reset a resampler so a new (unrelated) stream can be processed.
3876 	  void reset () {
3877 	    lastSample[] = 0;
3878 	    magicSamples[] = 0;
3879 	    sampFracNum[] = 0;
3880 	    //foreach (immutable i; 0..chanCount*(filterLen-1)) mem[i] = 0;
3881 	    if (mem !is null) mem[0..chanCount*(filterLen-1)] = 0;
3882 	    skipZeros(); // make sure that the first samples to go out of the resamplers don't have leading zeros
3883 	  }
3884 
3885 	private:
3886 	  Error processOneChannel (uint chanIdx, const(float)* indata, uint* indataLen, float* outdata, uint* outdataLen) {
3887 	    uint ilen = *indataLen;
3888 	    uint olen = *outdataLen;
3889 	    float* x = mem+chanIdx*memAllocSize;
3890 	    immutable int filterOfs = filterLen-1;
3891 	    immutable uint xlen = memAllocSize-filterOfs;
3892 	    immutable int istride = inStride;
3893 	    if (magicSamples.ptr[chanIdx]) olen -= magic(chanIdx, &outdata, olen);
3894 	    if (!magicSamples.ptr[chanIdx]) {
3895 	      while (ilen && olen) {
3896 		uint ichunk = (ilen > xlen ? xlen : ilen);
3897 		uint ochunk = olen;
3898 		if (indata !is null) {
3899 		  //foreach (immutable j; 0..ichunk) x[j+filterOfs] = indata[j*istride];
3900 		  if (istride == 1) {
3901 		    x[filterOfs..filterOfs+ichunk] = indata[0..ichunk];
3902 		  } else {
3903 		    auto sp = indata;
3904 		    auto dp = x+filterOfs;
3905 		    foreach (immutable j; 0..ichunk) { *dp++ = *sp; sp += istride; }
3906 		  }
3907 		} else {
3908 		  //foreach (immutable j; 0..ichunk) x[j+filterOfs] = 0;
3909 		  x[filterOfs..filterOfs+ichunk] = 0;
3910 		}
3911 		processNative(chanIdx, &ichunk, outdata, &ochunk);
3912 		ilen -= ichunk;
3913 		olen -= ochunk;
3914 		outdata += ochunk*outStride;
3915 		if (indata !is null) indata += ichunk*istride;
3916 	      }
3917 	    }
3918 	    *indataLen -= ilen;
3919 	    *outdataLen -= olen;
3920 	    return Error.OK;
3921 	  }
3922 
3923 	  Error processNative (uint chanIdx, uint* indataLen, float* outdata, uint* outdataLen) {
3924 	    immutable N = filterLen;
3925 	    int outSample = 0;
3926 	    float* x = mem+chanIdx*memAllocSize;
3927 	    uint ilen;
3928 	    started = true;
3929 	    // call the right resampler through the function ptr
3930 	    outSample = resampler(this, chanIdx, x, indataLen, outdata, outdataLen);
3931 	    if (lastSample.ptr[chanIdx] < cast(int)*indataLen) *indataLen = lastSample.ptr[chanIdx];
3932 	    *outdataLen = outSample;
3933 	    lastSample.ptr[chanIdx] -= *indataLen;
3934 	    ilen = *indataLen;
3935 	    foreach (immutable j; 0..N-1) x[j] = x[j+ilen];
3936 	    return Error.OK;
3937 	  }
3938 
3939 	  int magic (uint chanIdx, float **outdata, uint outdataLen) {
3940 	    uint tempInLen = magicSamples.ptr[chanIdx];
3941 	    float* x = mem+chanIdx*memAllocSize;
3942 	    processNative(chanIdx, &tempInLen, *outdata, &outdataLen);
3943 	    magicSamples.ptr[chanIdx] -= tempInLen;
3944 	    // if we couldn't process all "magic" input samples, save the rest for next time
3945 	    if (magicSamples.ptr[chanIdx]) {
3946 	      immutable N = filterLen;
3947 	      foreach (immutable i; 0..magicSamples.ptr[chanIdx]) x[N-1+i] = x[N-1+i+tempInLen];
3948 	    }
3949 	    *outdata += outdataLen*outStride;
3950 	    return outdataLen;
3951 	  }
3952 
3953 	  Error updateFilter () {
3954 	    uint oldFilterLen = filterLen;
3955 	    uint oldAllocSize = memAllocSize;
3956 	    bool useDirect;
3957 	    uint minSincTableLen;
3958 	    uint minAllocSize;
3959 
3960 	    intAdvance = numRate/denRate;
3961 	    fracAdvance = numRate%denRate;
3962 	    oversample = qualityMap.ptr[srQuality].oversample;
3963 	    filterLen = qualityMap.ptr[srQuality].baseLength;
3964 
3965 	    if (numRate > denRate) {
3966 	      // down-sampling
3967 	      cutoff = qualityMap.ptr[srQuality].downsampleBandwidth*denRate/numRate;
3968 	      // FIXME: divide the numerator and denominator by a certain amount if they're too large
3969 	      filterLen = filterLen*numRate/denRate;
3970 	      // round up to make sure we have a multiple of 8 for SSE
3971 	      filterLen = ((filterLen-1)&(~0x7))+8;
3972 	      if (2*denRate < numRate) oversample >>= 1;
3973 	      if (4*denRate < numRate) oversample >>= 1;
3974 	      if (8*denRate < numRate) oversample >>= 1;
3975 	      if (16*denRate < numRate) oversample >>= 1;
3976 	      if (oversample < 1) oversample = 1;
3977 	    } else {
3978 	      // up-sampling
3979 	      cutoff = qualityMap.ptr[srQuality].upsampleBandwidth;
3980 	    }
3981 
3982 	    // choose the resampling type that requires the least amount of memory
3983 	    version(sincresample_use_full_table) {
3984 	      useDirect = true;
3985 	      if (int.max/float.sizeof/denRate < filterLen) goto fail;
3986 	    } else {
3987 	      useDirect = (filterLen*denRate <= filterLen*oversample+8 && int.max/float.sizeof/denRate >= filterLen);
3988 	    }
3989 
3990 	    if (useDirect) {
3991 	      minSincTableLen = filterLen*denRate;
3992 	    } else {
3993 	      if ((int.max/float.sizeof-8)/oversample < filterLen) goto fail;
3994 	      minSincTableLen = filterLen*oversample+8;
3995 	    }
3996 
3997 	    if (sincTableLen < minSincTableLen) {
3998 	      import core.stdc.stdlib : realloc;
3999 	      auto nslen = cast(uint)(minSincTableLen*float.sizeof);
4000 	      if (nslen > realSincTableLen) {
4001 		if (nslen < 512*1024) nslen = 512*1024; // inc to 3 mb?
4002 		auto x = cast(float*)realloc(sincTable, nslen);
4003 		if (!x) goto fail;
4004 		sincTable = x;
4005 		realSincTableLen = nslen;
4006 	      }
4007 	      sincTableLen = minSincTableLen;
4008 	    }
4009 
4010 	    if (useDirect) {
4011 	      foreach (int i; 0..denRate) {
4012 		foreach (int j; 0..filterLen) {
4013 		  sincTable[i*filterLen+j] = sinc(cutoff, ((j-cast(int)filterLen/2+1)-(cast(float)i)/denRate), filterLen, qualityMap.ptr[srQuality].windowFunc);
4014 		}
4015 	      }
4016 	      if (srQuality > 8) {
4017 		resampler = &resamplerBasicDirect!double;
4018 	      } else {
4019 		resampler = &resamplerBasicDirect!float;
4020 	      }
4021 	    } else {
4022 	      foreach (immutable int i; -4..cast(int)(oversample*filterLen+4)) {
4023 		sincTable[i+4] = sinc(cutoff, (i/cast(float)oversample-filterLen/2), filterLen, qualityMap.ptr[srQuality].windowFunc);
4024 	      }
4025 	      if (srQuality > 8) {
4026 		resampler = &resamplerBasicInterpolate!double;
4027 	      } else {
4028 		resampler = &resamplerBasicInterpolate!float;
4029 	      }
4030 	    }
4031 
4032 	    /* Here's the place where we update the filter memory to take into account
4033 	       the change in filter length. It's probably the messiest part of the code
4034 	       due to handling of lots of corner cases. */
4035 
4036 	    // adding bufferSize to filterLen won't overflow here because filterLen could be multiplied by float.sizeof above
4037 	    minAllocSize = filterLen-1+bufferSize;
4038 	    if (minAllocSize > memAllocSize) {
4039 	      import core.stdc.stdlib : realloc;
4040 	      if (int.max/float.sizeof/chanCount < minAllocSize) goto fail;
4041 	      auto nslen = cast(uint)(chanCount*minAllocSize*mem[0].sizeof);
4042 	      if (nslen > realMemLen) {
4043 		if (nslen < 16384) nslen = 16384;
4044 		auto x = cast(float*)realloc(mem, nslen);
4045 		if (x is null) goto fail;
4046 		mem = x;
4047 		realMemLen = nslen;
4048 	      }
4049 	      memAllocSize = minAllocSize;
4050 	    }
4051 	    if (!started) {
4052 	      //foreach (i=0;i<chanCount*memAllocSize;i++) mem[i] = 0;
4053 	      mem[0..chanCount*memAllocSize] = 0;
4054 	    } else if (filterLen > oldFilterLen) {
4055 	      // increase the filter length
4056 	      foreach_reverse (uint i; 0..chanCount) {
4057 		uint j;
4058 		uint olen = oldFilterLen;
4059 		{
4060 		  // try and remove the magic samples as if nothing had happened
4061 		  //FIXME: this is wrong but for now we need it to avoid going over the array bounds
4062 		  olen = oldFilterLen+2*magicSamples.ptr[i];
4063 		  for (j = oldFilterLen-1+magicSamples.ptr[i]; j--; ) mem[i*memAllocSize+j+magicSamples.ptr[i]] = mem[i*oldAllocSize+j];
4064 		  //for (j = 0; j < magicSamples.ptr[i]; ++j) mem[i*memAllocSize+j] = 0;
4065 		  mem[i*memAllocSize..i*memAllocSize+magicSamples.ptr[i]] = 0;
4066 		  magicSamples.ptr[i] = 0;
4067 		}
4068 		if (filterLen > olen) {
4069 		  // if the new filter length is still bigger than the "augmented" length
4070 		  // copy data going backward
4071 		  for (j = 0; j < olen-1; ++j) mem[i*memAllocSize+(filterLen-2-j)] = mem[i*memAllocSize+(olen-2-j)];
4072 		  // then put zeros for lack of anything better
4073 		  for (; j < filterLen-1; ++j) mem[i*memAllocSize+(filterLen-2-j)] = 0;
4074 		  // adjust lastSample
4075 		  lastSample.ptr[i] += (filterLen-olen)/2;
4076 		} else {
4077 		  // put back some of the magic!
4078 		  magicSamples.ptr[i] = (olen-filterLen)/2;
4079 		  for (j = 0; j < filterLen-1+magicSamples.ptr[i]; ++j) mem[i*memAllocSize+j] = mem[i*memAllocSize+j+magicSamples.ptr[i]];
4080 		}
4081 	      }
4082 	    } else if (filterLen < oldFilterLen) {
4083 	      // reduce filter length, this a bit tricky
4084 	      // we need to store some of the memory as "magic" samples so they can be used directly as input the next time(s)
4085 	      foreach (immutable i; 0..chanCount) {
4086 		uint j;
4087 		uint oldMagic = magicSamples.ptr[i];
4088 		magicSamples.ptr[i] = (oldFilterLen-filterLen)/2;
4089 		// we must copy some of the memory that's no longer used
4090 		// copy data going backward
4091 		for (j = 0; j < filterLen-1+magicSamples.ptr[i]+oldMagic; ++j) {
4092 		  mem[i*memAllocSize+j] = mem[i*memAllocSize+j+magicSamples.ptr[i]];
4093 		}
4094 		magicSamples.ptr[i] += oldMagic;
4095 	      }
4096 	    }
4097 	    return Error.OK;
4098 
4099 	  fail:
4100 	    resampler = null;
4101 	    /* mem may still contain consumed input samples for the filter.
4102 	       Restore filterLen so that filterLen-1 still points to the position after
4103 	       the last of these samples. */
4104 	    filterLen = oldFilterLen;
4105 	    return Error.NoMemory;
4106 	  }
4107 	}
4108 
4109 
4110 	// ////////////////////////////////////////////////////////////////////////// //
4111 	static immutable double[68] kaiser12Table = [
4112 	  0.99859849, 1.00000000, 0.99859849, 0.99440475, 0.98745105, 0.97779076,
4113 	  0.96549770, 0.95066529, 0.93340547, 0.91384741, 0.89213598, 0.86843014,
4114 	  0.84290116, 0.81573067, 0.78710866, 0.75723148, 0.72629970, 0.69451601,
4115 	  0.66208321, 0.62920216, 0.59606986, 0.56287762, 0.52980938, 0.49704014,
4116 	  0.46473455, 0.43304576, 0.40211431, 0.37206735, 0.34301800, 0.31506490,
4117 	  0.28829195, 0.26276832, 0.23854851, 0.21567274, 0.19416736, 0.17404546,
4118 	  0.15530766, 0.13794294, 0.12192957, 0.10723616, 0.09382272, 0.08164178,
4119 	  0.07063950, 0.06075685, 0.05193064, 0.04409466, 0.03718069, 0.03111947,
4120 	  0.02584161, 0.02127838, 0.01736250, 0.01402878, 0.01121463, 0.00886058,
4121 	  0.00691064, 0.00531256, 0.00401805, 0.00298291, 0.00216702, 0.00153438,
4122 	  0.00105297, 0.00069463, 0.00043489, 0.00025272, 0.00013031, 0.0000527734,
4123 	  0.00001000, 0.00000000];
4124 
4125 	static immutable double[36] kaiser10Table = [
4126 	  0.99537781, 1.00000000, 0.99537781, 0.98162644, 0.95908712, 0.92831446,
4127 	  0.89005583, 0.84522401, 0.79486424, 0.74011713, 0.68217934, 0.62226347,
4128 	  0.56155915, 0.50119680, 0.44221549, 0.38553619, 0.33194107, 0.28205962,
4129 	  0.23636152, 0.19515633, 0.15859932, 0.12670280, 0.09935205, 0.07632451,
4130 	  0.05731132, 0.04193980, 0.02979584, 0.02044510, 0.01345224, 0.00839739,
4131 	  0.00488951, 0.00257636, 0.00115101, 0.00035515, 0.00000000, 0.00000000];
4132 
4133 	static immutable double[36] kaiser8Table = [
4134 	  0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200,
4135 	  0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126,
4136 	  0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272,
4137 	  0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758,
4138 	  0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490,
4139 	  0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000];
4140 
4141 	static immutable double[36] kaiser6Table = [
4142 	  0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003,
4143 	  0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565,
4144 	  0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561,
4145 	  0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058,
4146 	  0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600,
4147 	  0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000];
4148 
4149 	struct FuncDef {
4150 	  immutable(double)* table;
4151 	  int oversample;
4152 	}
4153 
4154 	static immutable FuncDef Kaiser12 = FuncDef(kaiser12Table.ptr, 64);
4155 	static immutable FuncDef Kaiser10 = FuncDef(kaiser10Table.ptr, 32);
4156 	static immutable FuncDef Kaiser8 = FuncDef(kaiser8Table.ptr, 32);
4157 	static immutable FuncDef Kaiser6 = FuncDef(kaiser6Table.ptr, 32);
4158 
4159 
4160 	struct QualityMapping {
4161 	  int baseLength;
4162 	  int oversample;
4163 	  float downsampleBandwidth;
4164 	  float upsampleBandwidth;
4165 	  immutable FuncDef* windowFunc;
4166 	}
4167 
4168 
4169 	/* This table maps conversion quality to internal parameters. There are two
4170 	   reasons that explain why the up-sampling bandwidth is larger than the
4171 	   down-sampling bandwidth:
4172 	   1) When up-sampling, we can assume that the spectrum is already attenuated
4173 	      close to the Nyquist rate (from an A/D or a previous resampling filter)
4174 	   2) Any aliasing that occurs very close to the Nyquist rate will be masked
4175 	      by the sinusoids/noise just below the Nyquist rate (guaranteed only for
4176 	      up-sampling).
4177 	*/
4178 	static immutable QualityMapping[11] qualityMap = [
4179 	  QualityMapping(  8,  4, 0.830f, 0.860f, &Kaiser6 ), /* Q0 */
4180 	  QualityMapping( 16,  4, 0.850f, 0.880f, &Kaiser6 ), /* Q1 */
4181 	  QualityMapping( 32,  4, 0.882f, 0.910f, &Kaiser6 ), /* Q2 */  /* 82.3% cutoff ( ~60 dB stop) 6  */
4182 	  QualityMapping( 48,  8, 0.895f, 0.917f, &Kaiser8 ), /* Q3 */  /* 84.9% cutoff ( ~80 dB stop) 8  */
4183 	  QualityMapping( 64,  8, 0.921f, 0.940f, &Kaiser8 ), /* Q4 */  /* 88.7% cutoff ( ~80 dB stop) 8  */
4184 	  QualityMapping( 80, 16, 0.922f, 0.940f, &Kaiser10), /* Q5 */  /* 89.1% cutoff (~100 dB stop) 10 */
4185 	  QualityMapping( 96, 16, 0.940f, 0.945f, &Kaiser10), /* Q6 */  /* 91.5% cutoff (~100 dB stop) 10 */
4186 	  QualityMapping(128, 16, 0.950f, 0.950f, &Kaiser10), /* Q7 */  /* 93.1% cutoff (~100 dB stop) 10 */
4187 	  QualityMapping(160, 16, 0.960f, 0.960f, &Kaiser10), /* Q8 */  /* 94.5% cutoff (~100 dB stop) 10 */
4188 	  QualityMapping(192, 32, 0.968f, 0.968f, &Kaiser12), /* Q9 */  /* 95.5% cutoff (~100 dB stop) 10 */
4189 	  QualityMapping(256, 32, 0.975f, 0.975f, &Kaiser12), /* Q10 */ /* 96.6% cutoff (~100 dB stop) 10 */
4190 	];
4191 
4192 
4193 	nothrow @trusted @nogc:
4194 	/*8, 24, 40, 56, 80, 104, 128, 160, 200, 256, 320*/
4195 	double computeFunc (float x, immutable FuncDef* func) {
4196 	  version(Posix) import core.stdc.math : lrintf;
4197 	  import std.math : floor;
4198 	  //double[4] interp;
4199 	  float y = x*func.oversample;
4200 	  version(Posix) {
4201 	    int ind = cast(int)lrintf(floor(y));
4202 	  } else {
4203 	    int ind = cast(int)(floor(y));
4204 	  }
4205 	  float frac = (y-ind);
4206 	  immutable f2 = frac*frac;
4207 	  immutable f3 = f2*frac;
4208 	  double interp3 = -0.1666666667*frac+0.1666666667*(f3);
4209 	  double interp2 = frac+0.5*(f2)-0.5*(f3);
4210 	  //double interp2 = 1.0f-0.5f*frac-f2+0.5f*f3;
4211 	  double interp0 = -0.3333333333*frac+0.5*(f2)-0.1666666667*(f3);
4212 	  // just to make sure we don't have rounding problems
4213 	  double interp1 = 1.0f-interp3-interp2-interp0;
4214 	  //sum = frac*accum[1]+(1-frac)*accum[2];
4215 	  return interp0*func.table[ind]+interp1*func.table[ind+1]+interp2*func.table[ind+2]+interp3*func.table[ind+3];
4216 	}
4217 
4218 
4219 	// the slow way of computing a sinc for the table; should improve that some day
4220 	float sinc (float cutoff, float x, int N, immutable FuncDef *windowFunc) {
4221 	  version(LittleEndian) {
4222 	    align(1) union temp_float { align(1): float f; uint n; }
4223 	  } else {
4224 	    static T fabs(T) (T n) pure { static if (__VERSION__ > 2067) pragma(inline, true); return (n < 0 ? -n : n); }
4225 	  }
4226 	  import std.math : sin, PI;
4227 	  version(LittleEndian) {
4228 	    temp_float txx = void;
4229 	    txx.f = x;
4230 	    txx.n &= 0x7fff_ffff; // abs
4231 	    if (txx.f < 1.0e-6f) return cutoff;
4232 	    if (txx.f > 0.5f*N) return 0;
4233 	  } else {
4234 	    if (fabs(x) < 1.0e-6f) return cutoff;
4235 	    if (fabs(x) > 0.5f*N) return 0;
4236 	  }
4237 	  //FIXME: can it really be any slower than this?
4238 	  immutable float xx = x*cutoff;
4239 	  immutable pixx = PI*xx;
4240 	  version(LittleEndian) {
4241 	    return cutoff*sin(pixx)/pixx*computeFunc(2.0*txx.f/N, windowFunc);
4242 	  } else {
4243 	    return cutoff*sin(pixx)/pixx*computeFunc(fabs(2.0*x/N), windowFunc);
4244 	  }
4245 	}
4246 
4247 
4248 	void cubicCoef (in float frac, float* interp) {
4249 	  immutable f2 = frac*frac;
4250 	  immutable f3 = f2*frac;
4251 	  // compute interpolation coefficients; i'm not sure whether this corresponds to cubic interpolation but I know it's MMSE-optimal on a sinc
4252 	  interp[0] =  -0.16667f*frac+0.16667f*f3;
4253 	  interp[1] = frac+0.5f*f2-0.5f*f3;
4254 	  //interp[2] = 1.0f-0.5f*frac-f2+0.5f*f3;
4255 	  interp[3] = -0.33333f*frac+0.5f*f2-0.16667f*f3;
4256 	  // just to make sure we don't have rounding problems
4257 	  interp[2] = 1.0-interp[0]-interp[1]-interp[3];
4258 	}
4259 
4260 
4261 	// ////////////////////////////////////////////////////////////////////////// //
4262 	int resamplerBasicDirect(T) (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint* indataLen, float* outdata, uint* outdataLen)
4263 	if (is(T == float) || is(T == double))
4264 	{
4265 	  auto N = st.filterLen;
4266 	  static if (is(T == double)) assert(N%4 == 0);
4267 	  int outSample = 0;
4268 	  int lastSample = st.lastSample.ptr[chanIdx];
4269 	  uint sampFracNum = st.sampFracNum.ptr[chanIdx];
4270 	  const(float)* sincTable = st.sincTable;
4271 	  immutable outStride = st.outStride;
4272 	  immutable intAdvance = st.intAdvance;
4273 	  immutable fracAdvance = st.fracAdvance;
4274 	  immutable denRate = st.denRate;
4275 	  T sum = void;
4276 	  while (!(lastSample >= cast(int)(*indataLen) || outSample >= cast(int)(*outdataLen))) {
4277 	    const(float)* sinct = &sincTable[sampFracNum*N];
4278 	    const(float)* iptr = &indata[lastSample];
4279 	    static if (is(T == float)) {
4280 	      // at least 2x speedup with SSE here (but for unrolled loop)
4281 	      if (N%4 == 0) {
4282 		version(sincresample_use_sse) {
4283 		  //align(64) __gshared float[4] zero = 0;
4284 		  align(64) __gshared float[4+128] zeroesBuf = 0; // dmd cannot into such aligns, alas
4285 		  __gshared uint zeroesptr = 0;
4286 		  if (zeroesptr == 0) {
4287 		    zeroesptr = cast(uint)zeroesBuf.ptr;
4288 		    if (zeroesptr&0x3f) zeroesptr = (zeroesptr|0x3f)+1;
4289 		  }
4290 		  //assert((zeroesptr&0x3f) == 0, "wtf?!");
4291 		  asm nothrow @safe @nogc {
4292 		    mov       ECX,[N];
4293 		    shr       ECX,2;
4294 		    mov       EAX,[zeroesptr];
4295 		    movaps    XMM0,[EAX];
4296 		    mov       EAX,[sinct];
4297 		    mov       EBX,[iptr];
4298 		    mov       EDX,16;
4299 		    align 8;
4300 		   rbdseeloop:
4301 		    movups    XMM1,[EAX];
4302 		    movups    XMM2,[EBX];
4303 		    mulps     XMM1,XMM2;
4304 		    addps     XMM0,XMM1;
4305 		    add       EAX,EDX;
4306 		    add       EBX,EDX;
4307 		    dec       ECX;
4308 		    jnz       rbdseeloop;
4309 		    // store result in sum
4310 		    movhlps   XMM1,XMM0; // now low part of XMM1 contains high part of XMM0
4311 		    addps     XMM0,XMM1; // low part of XMM0 is ok
4312 		    movaps    XMM1,XMM0;
4313 		    shufps    XMM1,XMM0,0b_01_01_01_01; // 2nd float of XMM0 goes to the 1st float of XMM1
4314 		    addss     XMM0,XMM1;
4315 		    movss     [sum],XMM0;
4316 		  }
4317 		  /*
4318 		  float sum1 = 0;
4319 		  foreach (immutable j; 0..N) sum1 += sinct[j]*iptr[j];
4320 		  import std.math;
4321 		  if (fabs(sum-sum1) > 0.000001f) {
4322 		    import core.stdc.stdio;
4323 		    printf("sum=%f; sum1=%f\n", sum, sum1);
4324 		    assert(0);
4325 		  }
4326 		  */
4327 		} else {
4328 		  // no SSE; for my i3 unrolled loop is almost of the speed of SSE code
4329 		  T[4] accum = 0;
4330 		  foreach (immutable j; 0..N/4) {
4331 		    accum.ptr[0] += *sinct++ * *iptr++;
4332 		    accum.ptr[1] += *sinct++ * *iptr++;
4333 		    accum.ptr[2] += *sinct++ * *iptr++;
4334 		    accum.ptr[3] += *sinct++ * *iptr++;
4335 		  }
4336 		  sum = accum.ptr[0]+accum.ptr[1]+accum.ptr[2]+accum.ptr[3];
4337 		}
4338 	      } else {
4339 		sum = 0;
4340 		foreach (immutable j; 0..N) sum += *sinct++ * *iptr++;
4341 	      }
4342 	      outdata[outStride*outSample++] = sum;
4343 	    } else {
4344 	      if (N%4 == 0) {
4345 		//TODO: write SSE code here!
4346 		// for my i3 unrolled loop is ~2 times faster
4347 		T[4] accum = 0;
4348 		foreach (immutable j; 0..N/4) {
4349 		  accum.ptr[0] += cast(double)*sinct++ * cast(double)*iptr++;
4350 		  accum.ptr[1] += cast(double)*sinct++ * cast(double)*iptr++;
4351 		  accum.ptr[2] += cast(double)*sinct++ * cast(double)*iptr++;
4352 		  accum.ptr[3] += cast(double)*sinct++ * cast(double)*iptr++;
4353 		}
4354 		sum = accum.ptr[0]+accum.ptr[1]+accum.ptr[2]+accum.ptr[3];
4355 	      } else {
4356 		sum = 0;
4357 		foreach (immutable j; 0..N) sum += cast(double)*sinct++ * cast(double)*iptr++;
4358 	      }
4359 	      outdata[outStride*outSample++] = cast(float)sum;
4360 	    }
4361 	    lastSample += intAdvance;
4362 	    sampFracNum += fracAdvance;
4363 	    if (sampFracNum >= denRate) {
4364 	      sampFracNum -= denRate;
4365 	      ++lastSample;
4366 	    }
4367 	  }
4368 	  st.lastSample.ptr[chanIdx] = lastSample;
4369 	  st.sampFracNum.ptr[chanIdx] = sampFracNum;
4370 	  return outSample;
4371 	}
4372 
4373 
4374 	int resamplerBasicInterpolate(T) (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint *indataLen, float *outdata, uint *outdataLen)
4375 	if (is(T == float) || is(T == double))
4376 	{
4377 	  immutable N = st.filterLen;
4378 	  assert(N%4 == 0);
4379 	  int outSample = 0;
4380 	  int lastSample = st.lastSample.ptr[chanIdx];
4381 	  uint sampFracNum = st.sampFracNum.ptr[chanIdx];
4382 	  immutable outStride = st.outStride;
4383 	  immutable intAdvance = st.intAdvance;
4384 	  immutable fracAdvance = st.fracAdvance;
4385 	  immutable denRate = st.denRate;
4386 	  float sum;
4387 
4388 	  float[4] interp = void;
4389 	  T[4] accum = void;
4390 	  while (!(lastSample >= cast(int)(*indataLen) || outSample >= cast(int)(*outdataLen))) {
4391 	    const(float)* iptr = &indata[lastSample];
4392 	    const int offset = sampFracNum*st.oversample/st.denRate;
4393 	    const float frac = (cast(float)((sampFracNum*st.oversample)%st.denRate))/st.denRate;
4394 	    accum[] = 0;
4395 	    //TODO: optimize!
4396 	    foreach (immutable j; 0..N) {
4397 	      immutable T currIn = iptr[j];
4398 	      accum.ptr[0] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset-2]);
4399 	      accum.ptr[1] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset-1]);
4400 	      accum.ptr[2] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset]);
4401 	      accum.ptr[3] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset+1]);
4402 	    }
4403 
4404 	    cubicCoef(frac, interp.ptr);
4405 	    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]);
4406 
4407 	    outdata[outStride*outSample++] = sum;
4408 	    lastSample += intAdvance;
4409 	    sampFracNum += fracAdvance;
4410 	    if (sampFracNum >= denRate) {
4411 	      sampFracNum -= denRate;
4412 	      ++lastSample;
4413 	    }
4414 	  }
4415 
4416 	  st.lastSample.ptr[chanIdx] = lastSample;
4417 	  st.sampFracNum.ptr[chanIdx] = sampFracNum;
4418 	  return outSample;
4419 	}
4420 
4421 
4422 	// ////////////////////////////////////////////////////////////////////////// //
4423 	uint gcd (uint a, uint b) pure {
4424 	  if (a == 0) return b;
4425 	  if (b == 0) return a;
4426 	  for (;;) {
4427 	    if (a > b) {
4428 	      a %= b;
4429 	      if (a == 0) return b;
4430 	      if (a == 1) return 1;
4431 	    } else {
4432 	      b %= a;
4433 	      if (b == 0) return a;
4434 	      if (b == 1) return 1;
4435 	    }
4436 	  }
4437 	}
4438 
4439 
4440 	// ////////////////////////////////////////////////////////////////////////// //
4441 	// very simple and cheap cubic upsampler
4442 	struct CubicUpsampler {
4443 	public:
4444 	nothrow @trusted @nogc:
4445 	  float[2] curposfrac; // current position offset [0..1)
4446 	  float step; // how long we should move on one step?
4447 	  float[4][2] data; // -1..3
4448 	  uint[2] drain;
4449 
4450 	  void reset () {
4451 	    curposfrac[] = 0.0f;
4452 	    foreach (ref d; data) d[] = 0.0f;
4453 	    drain[] = 0;
4454 	  }
4455 
4456 	  bool setup (float astep) {
4457 	    if (astep >= 1.0f) return false;
4458 	    step = astep;
4459 	    return true;
4460 	  }
4461 
4462 	  /*
4463 	  static struct Data {
4464 	    const(float)[] dataIn;
4465 	    float[] dataOut;
4466 	    uint inputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
4467 	    uint outputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
4468 	  }
4469 	  */
4470 
4471 	  SpeexResampler.Error process (ref SpeexResampler.Data d) {
4472 	    d.inputSamplesUsed = d.outputSamplesUsed = 0;
4473 	    if (d.dataOut.length < 2) return SpeexResampler.Error.OK;
4474 	    foreach (uint cidx; 0..2) {
4475 	      uint inleft = cast(uint)d.dataIn.length/2;
4476 	      uint outleft = cast(uint)d.dataOut.length/2;
4477 	      processChannel(inleft, outleft, (d.dataIn.length ? d.dataIn.ptr+cidx : null), (d.dataOut.length ? d.dataOut.ptr+cidx : null), cidx);
4478 	      d.outputSamplesUsed += cast(uint)(d.dataOut.length/2)-outleft;
4479 	      d.inputSamplesUsed += cast(uint)(d.dataIn.length/2)-inleft;
4480 	    }
4481 	    return SpeexResampler.Error.OK;
4482 	  }
4483 
4484 	  private void processChannel (ref uint inleft, ref uint outleft, const(float)* dataIn, float* dataOut, uint cidx) {
4485 	    if (outleft == 0) return;
4486 	    if (inleft == 0 && drain.ptr[cidx] <= 1) return;
4487 	    auto dt = data.ptr[cidx].ptr;
4488 	    auto drn = drain.ptr+cidx;
4489 	    auto cpf = curposfrac.ptr+cidx;
4490 	    immutable float st = step;
4491 	    for (;;) {
4492 	      // fill buffer
4493 	      while ((*drn) < 4) {
4494 		if (inleft == 0) return;
4495 		dt[(*drn)++] = *dataIn;
4496 		dataIn += 2;
4497 		--inleft;
4498 	      }
4499 	      if (outleft == 0) return;
4500 	      --outleft;
4501 	      // cubic interpolation
4502 	      /*version(none)*/ {
4503 		// interpolate between y1 and y2
4504 		immutable float mu = (*cpf); // how far we are moved from y1 to y2
4505 		immutable float mu2 = mu*mu; // wow
4506 		immutable float y0 = dt[0], y1 = dt[1], y2 = dt[2], y3 = dt[3];
4507 		version(complex_cubic) {
4508 		  immutable float z0 = 0.5*y3;
4509 		  immutable float z1 = 0.5*y0;
4510 		  immutable float a0 = 1.5*y1-z1-1.5*y2+z0;
4511 		  immutable float a1 = y0-2.5*y1+2*y2-z0;
4512 		  immutable float a2 = 0.5*y2-z1;
4513 		} else {
4514 		  immutable float a0 = y3-y2-y0+y1;
4515 		  immutable float a1 = y0-y1-a0;
4516 		  immutable float a2 = y2-y0;
4517 		}
4518 		*dataOut = a0*mu*mu2+a1*mu2+a2*mu+y1;
4519 	      }// else *dataOut = dt[1];
4520 	      dataOut += 2;
4521 	      if (((*cpf) += st) >= 1.0f) {
4522 		(*cpf) -= 1.0f;
4523 		dt[0] = dt[1];
4524 		dt[1] = dt[2];
4525 		dt[2] = dt[3];
4526 		dt[3] = 0.0f;
4527 		--(*drn); // will request more input bytes
4528 	      }
4529 	    }
4530 	  }
4531 	}
4532 }
4533 
4534 version(with_resampler)
4535 abstract class ResamplingContext {
4536 	int inputSampleRate;
4537 	int outputSampleRate;
4538 
4539 	int inputChannels;
4540 	int outputChannels;
4541 
4542 	SpeexResampler resamplerLeft;
4543 	SpeexResampler resamplerRight;
4544 
4545 	SpeexResampler.Data resamplerDataLeft;
4546 	SpeexResampler.Data resamplerDataRight;
4547 
4548 	float[][2] buffersIn;
4549 	float[][2] buffersOut;
4550 
4551 	uint rateNum;
4552 	uint rateDem;
4553 
4554 	float[][2] dataReady;
4555 
4556 	SampleControlFlags scflags;
4557 
4558 	this(SampleControlFlags scflags, int inputSampleRate, int outputSampleRate, int inputChannels, int outputChannels) {
4559 		this.scflags = scflags;
4560 		this.inputSampleRate = inputSampleRate;
4561 		this.outputSampleRate = outputSampleRate;
4562 		this.inputChannels = inputChannels;
4563 		this.outputChannels = outputChannels;
4564 
4565 
4566 		if(auto err = resamplerLeft.setup(1, inputSampleRate, outputSampleRate, 5))
4567 			throw new Exception("ugh");
4568 		resamplerRight.setup(1, inputSampleRate, outputSampleRate, 5);
4569 
4570 		processNewRate();
4571 	}
4572 
4573 	void changePlaybackSpeed(float newMultiplier) {
4574 		resamplerLeft.setRate(cast(uint) (inputSampleRate * newMultiplier), outputSampleRate);
4575 		resamplerRight.setRate(cast(uint) (inputSampleRate * newMultiplier), outputSampleRate);
4576 
4577 		processNewRate();
4578 	}
4579 
4580 	void processNewRate() {
4581 		resamplerLeft.getRatio(rateNum, rateDem);
4582 
4583 		int add = (rateNum % rateDem) ? 1 : 0;
4584 
4585 		buffersIn[0] = new float[](BUFFER_SIZE_FRAMES * rateNum / rateDem + add);
4586 		buffersOut[0] = new float[](BUFFER_SIZE_FRAMES);
4587 		if(inputChannels > 1) {
4588 			buffersIn[1] = new float[](BUFFER_SIZE_FRAMES * rateNum / rateDem + add);
4589 			buffersOut[1] = new float[](BUFFER_SIZE_FRAMES);
4590 		}
4591 
4592 	}
4593 
4594 	/+
4595 		float*[2] tmp;
4596 		tmp[0] = buffersIn[0].ptr;
4597 		tmp[1] = buffersIn[1].ptr;
4598 
4599 		auto actuallyGot = v.getSamplesFloat(v.chans, tmp.ptr, cast(int) buffersIn[0].length);
4600 
4601 		resamplerDataLeft.dataIn should be a slice of buffersIn[0] that is filled up
4602 		ditto for resamplerDataRight if the source has two channels
4603 	+/
4604 	abstract void loadMoreSamples();
4605 
4606 	bool loadMore() {
4607 		resamplerDataLeft.dataIn = buffersIn[0];
4608 		resamplerDataLeft.dataOut = buffersOut[0];
4609 
4610 		resamplerDataRight.dataIn = buffersIn[1];
4611 		resamplerDataRight.dataOut = buffersOut[1];
4612 
4613 		loadMoreSamples();
4614 
4615 		//resamplerLeft.reset();
4616 
4617 		if(auto err = resamplerLeft.process(resamplerDataLeft))
4618 			throw new Exception("ugh");
4619 		if(inputChannels > 1)
4620 			//resamplerRight.reset();
4621 			resamplerRight.process(resamplerDataRight);
4622 
4623 		resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[0 .. resamplerDataLeft.outputSamplesUsed];
4624 		resamplerDataRight.dataOut = resamplerDataRight.dataOut[0 .. resamplerDataRight.outputSamplesUsed];
4625 
4626 		if(resamplerDataLeft.dataOut.length == 0) {
4627 			return true;
4628 		}
4629 		return false;
4630 	}
4631 
4632 
4633 	bool fillBuffer(short[] buffer) {
4634 		if(cast(int) buffer.length != buffer.length)
4635 			throw new Exception("eeeek");
4636 
4637 		if(scflags.paused) {
4638 			buffer[] = 0;
4639 			return true;
4640 		}
4641 
4642 		if(outputChannels == 1) {
4643 			foreach(ref s; buffer) {
4644 				if(resamplerDataLeft.dataOut.length == 0) {
4645 					if(loadMore()) {
4646 						scflags.finished_ = true;
4647 						return false;
4648 					}
4649 				}
4650 
4651 				if(inputChannels == 1) {
4652 					s = cast(short) (resamplerDataLeft.dataOut[0] * short.max);
4653 					resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
4654 				} else {
4655 					s = cast(short) ((resamplerDataLeft.dataOut[0] + resamplerDataRight.dataOut[0]) * short.max / 2);
4656 
4657 					resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
4658 					resamplerDataRight.dataOut = resamplerDataRight.dataOut[1 .. $];
4659 				}
4660 			}
4661 
4662 			scflags.currentPosition += cast(float) buffer.length / outputSampleRate / outputChannels * scflags.playbackSpeed;
4663 		} else if(outputChannels == 2) {
4664 			foreach(idx, ref s; buffer) {
4665 				if(resamplerDataLeft.dataOut.length == 0) {
4666 					if(loadMore()) {
4667 						scflags.finished_ = true;
4668 						return false;
4669 					}
4670 				}
4671 
4672 				if(inputChannels == 1) {
4673 					s = cast(short) (resamplerDataLeft.dataOut[0] * short.max);
4674 					if(idx & 1)
4675 						resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
4676 				} else {
4677 					if(idx & 1) {
4678 						s = cast(short) (resamplerDataRight.dataOut[0] * short.max);
4679 						resamplerDataRight.dataOut = resamplerDataRight.dataOut[1 .. $];
4680 					} else {
4681 						s = cast(short) (resamplerDataLeft.dataOut[0] * short.max);
4682 						resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
4683 					}
4684 				}
4685 			}
4686 
4687 			scflags.currentPosition += cast(float) buffer.length / outputSampleRate / outputChannels * scflags.playbackSpeed;
4688 		} else assert(0);
4689 
4690 		if(scflags.stopped)
4691 			scflags.finished_ = true;
4692 		return !scflags.stopped;
4693 	}
4694 }
4695 
4696 private enum scriptable = "arsd_jsvar_compatible";