The OpenD Programming Language

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 		static if(__traits(hasMember, event, "setIfInitialized"))
1781 			event.setIfInitialized();
1782 		else
1783 			event.set();
1784 	}
1785 
1786 	/// ditto
1787 	public bool suspended() {
1788 		return suspended_;
1789 	}
1790 
1791 	/++
1792 		Stops playback and unsupends if necessary and exits.
1793 
1794 		Call this instead of join.
1795 
1796 		Please note: you should never call this from inside an audio
1797 		callback, as it will crash or deadlock. Instead, just return false
1798 		from your buffer fill function to indicate that you are done.
1799 
1800 		History:
1801 			Added December 30, 2021 (dub v10.5)
1802 	+/
1803 	public Throwable exit(bool rethrow = false) {
1804 		exiting = true;
1805 		unsuspend();
1806 		if(ao)
1807 			ao.stop();
1808 
1809 		return join(rethrow);
1810 	}
1811 
1812 
1813 	private void run() {
1814 		version(linux) {
1815 			// this thread has no business intercepting signals from the main thread,
1816 			// so gonna block a couple of them
1817 			import core.sys.posix.signal;
1818 			sigset_t sigset;
1819 			auto err = sigemptyset(&sigset);
1820 			assert(!err);
1821 
1822 			err = sigaddset(&sigset, SIGINT); assert(!err);
1823 			err = sigaddset(&sigset, SIGCHLD); assert(!err);
1824 
1825 			err = sigprocmask(SIG_BLOCK, &sigset, null);
1826 			assert(!err);
1827 		}
1828 
1829 		AudioOutput ao = AudioOutput(device, SampleRate, channels);
1830 
1831 		this.ao = &ao;
1832 		scope(exit) this.ao = null;
1833 		auto omg = this;
1834 		ao.fillData = (short[] buffer) {
1835 			short[BUFFER_SIZE_SHORT] bfr;
1836 			bool first = true;
1837 			if(fillDatasLength) {
1838 				for(int idx = 0; idx < fillDatasLength; idx++) {
1839 					auto dg = fillDatas[idx];
1840 					auto ret = dg(bfr[0 .. buffer.length][]);
1841 					foreach(i, v; bfr[0 .. buffer.length][]) {
1842 						int val;
1843 						if(first)
1844 							val = 0;
1845 						else
1846 							val = buffer[i];
1847 
1848 						int a = val;
1849 						int b = v;
1850 						int cap = a + b;
1851 						if(cap > short.max) cap = short.max;
1852 						else if(cap < short.min) cap = short.min;
1853 						val = cast(short) cap;
1854 						buffer[i] = cast(short) val;
1855 					}
1856 					if(!ret) {
1857 						// it returned false meaning this one is finished...
1858 						synchronized(omg) {
1859 							fillDatas[idx] = fillDatas[fillDatasLength - 1];
1860 							fillDatasLength--;
1861 						}
1862 						idx--;
1863 					}
1864 
1865 					first = false;
1866 				}
1867 			} else {
1868 				buffer[] = 0;
1869 			}
1870 		};
1871 		//try
1872 		resume_from_suspend:
1873 		ao.play();
1874 		/+
1875 		catch(Throwable t) {
1876 			import std.stdio;
1877 			writeln(t);
1878 		}
1879 		+/
1880 
1881 		if(suspendWanted) {
1882 			ao.close();
1883 
1884 			event.initialize(true, false);
1885 			if(event.wait() && !exiting) {
1886 				event.reset();
1887 
1888 				ao.open();
1889 				goto resume_from_suspend;
1890 			}
1891 		}
1892 
1893 		event.terminate();
1894 	}
1895 
1896 	static if(__VERSION__ > 2080) {
1897 		import core.sync.event;
1898 	} else {
1899 		// bad emulation of the Event but meh
1900 		static struct Event {
1901 			void terminate() {}
1902 			void initialize(bool, bool) {}
1903 
1904 			bool isSet;
1905 
1906 			void set() { isSet = true; }
1907 			void reset() { isSet = false; }
1908 			bool wait() {
1909 				while(!isSet) {
1910 					Thread.sleep(500.msecs);
1911 				}
1912 				isSet = false;
1913 				return true;
1914 			}
1915 
1916 		}
1917 	}
1918 
1919 	Event event;
1920 }
1921 
1922 
1923 import core.stdc.config;
1924 
1925 version(linux) version=ALSA;
1926 version(Windows) version=WinMM;
1927 
1928 version(ALSA) {
1929 	// this is the virtual rawmidi device on my computer at least
1930 	// maybe later i'll make it probe
1931 	//
1932 	// Getting midi to actually play on Linux is a bit of a pain.
1933 	// Here's what I did:
1934 	/*
1935 		# load the kernel driver, if amidi -l gives ioctl error,
1936 		# you haven't done this yet!
1937 		modprobe snd-virmidi
1938 
1939 		# start a software synth. timidity -iA is also an option
1940 		fluidsynth soundfont.sf2
1941 
1942 		# connect the virtual hardware port to the synthesizer
1943 		aconnect 24:0 128:0
1944 
1945 
1946 		I might also add a snd_seq client here which is a bit
1947 		easier to setup but for now I'm using the rawmidi so you
1948 		gotta get them connected somehow.
1949 	*/
1950 
1951 	// fyi raw midi dump:  amidi -d --port hw:4,0
1952 	// connect my midi out to fluidsynth: aconnect 28:0 128:0
1953 	// and my keyboard to it: aconnect 32:0 128:0
1954 }
1955 
1956 /// Thrown on audio failures.
1957 /// Subclass this to provide OS-specific exceptions
1958 class AudioException : Exception {
1959 	this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
1960 		super(message, file, line, next);
1961 	}
1962 }
1963 
1964 /++
1965 	Gives PCM input access (such as a microphone).
1966 
1967 	History:
1968 		Windows support added May 10, 2020 and the API got overhauled too.
1969 +/
1970 struct AudioInput {
1971 	version(ALSA) {
1972 		snd_pcm_t* handle;
1973 	} else version(WinMM) {
1974 		HWAVEIN handle;
1975 		HANDLE event;
1976 	} else static assert(0);
1977 
1978 	@disable this();
1979 	@disable this(this);
1980 
1981 	int channels;
1982 	int SampleRate;
1983 
1984 	/// Always pass card == 0.
1985 	this(int card, int SampleRate = 44100, int channels = 2) {
1986 		assert(card == 0);
1987 		this("default", SampleRate, channels);
1988 	}
1989 
1990 	/++
1991 		`device` is a device name. On Linux, it is the ALSA string.
1992 		On Windows, it is currently ignored, so you should pass "default"
1993 		or null so when it does get implemented your code won't break.
1994 
1995 		History:
1996 			Added Nov 8, 2020.
1997 	+/
1998 	this(string device, int SampleRate = 44100, int channels = 2) {
1999 		assert(channels == 1 || channels == 2);
2000 
2001 		this.channels = channels;
2002 		this.SampleRate = SampleRate;
2003 
2004 		version(ALSA) {
2005 			handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_CAPTURE, SampleRate, channels, device);
2006 		} else version(WinMM) {
2007 			event = CreateEvent(null, false /* manual reset */, false /* initially triggered */, null);
2008 
2009 			WAVEFORMATEX format;
2010 			format.wFormatTag = WAVE_FORMAT_PCM;
2011 			format.nChannels = 2;
2012 			format.nSamplesPerSec = SampleRate;
2013 			format.nAvgBytesPerSec = SampleRate * channels * 2; // two channels, two bytes per sample
2014 			format.nBlockAlign = 4;
2015 			format.wBitsPerSample = 16;
2016 			format.cbSize = 0;
2017 			if(auto err = waveInOpen(&handle, WAVE_MAPPER, &format, cast(DWORD_PTR) event, cast(DWORD_PTR) &this, CALLBACK_EVENT))
2018 				throw new WinMMException("wave in open", err);
2019 
2020 		} else static assert(0);
2021 	}
2022 
2023 	/// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz
2024 	/// Each item in the array thus alternates between left and right channel
2025 	/// and it takes a total of 88,200 items to make one second of sound.
2026 	///
2027 	/// Returns the slice of the buffer actually read into
2028 	///
2029 	/// LINUX ONLY. You should prolly use [record] instead
2030 	version(ALSA)
2031 	short[] read(short[] buffer) {
2032 		snd_pcm_sframes_t read;
2033 
2034 		read = snd_pcm_readi(handle, buffer.ptr, buffer.length / channels /* div number of channels apparently */);
2035 		if(read < 0) {
2036 			read = snd_pcm_recover(handle, cast(int) read, 0);
2037 			if(read < 0)
2038 				throw new AlsaException("pcm read", cast(int)read);
2039 			return null;
2040 		}
2041 
2042 		return buffer[0 .. read * channels];
2043 	}
2044 
2045 	/// passes a buffer of data to fill
2046 	///
2047 	/// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz
2048 	/// Each item in the array thus alternates between left and right channel
2049 	/// and it takes a total of 88,200 items to make one second of sound.
2050 	void delegate(short[]) receiveData;
2051 
2052 	///
2053 	void stop() {
2054 		recording = false;
2055 	}
2056 
2057 	/// First, set [receiveData], then call this.
2058 	void record() {
2059 		assert(receiveData !is null);
2060 		recording = true;
2061 
2062 		version(ALSA) {
2063 			short[BUFFER_SIZE_SHORT] buffer;
2064 			while(recording) {
2065 				auto got = read(buffer);
2066 				receiveData(got);
2067 			}
2068 		} else version(WinMM) {
2069 
2070 			enum numBuffers = 2; // use a lot of buffers to get smooth output with Sleep, see below
2071 			short[BUFFER_SIZE_SHORT][numBuffers] buffers;
2072 
2073 			WAVEHDR[numBuffers] headers;
2074 
2075 			foreach(i, ref header; headers) {
2076 				auto buffer = buffers[i][];
2077 				header.lpData = cast(char*) buffer.ptr;
2078 				header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof;
2079 				header.dwFlags = 0;// WHDR_BEGINLOOP | WHDR_ENDLOOP;
2080 				header.dwLoops = 0;
2081 
2082 				if(auto err = waveInPrepareHeader(handle, &header, header.sizeof))
2083 					throw new WinMMException("prepare header", err);
2084 
2085 				header.dwUser = 1; // mark that the driver is using it
2086 				if(auto err = waveInAddBuffer(handle, &header, header.sizeof))
2087 					throw new WinMMException("wave in read", err);
2088 			}
2089 
2090 			waveInStart(handle);
2091 			scope(failure) waveInReset(handle);
2092 
2093 			while(recording) {
2094 				if(auto err = WaitForSingleObject(event, INFINITE))
2095 					throw new Exception("WaitForSingleObject");
2096 				if(!recording)
2097 					break;
2098 
2099 				foreach(ref header; headers) {
2100 					if(!(header.dwFlags & WHDR_DONE)) continue;
2101 
2102 					receiveData((cast(short*) header.lpData)[0 .. header.dwBytesRecorded / short.sizeof]);
2103 					if(!recording) break;
2104 					header.dwUser = 1; // mark that the driver is using it
2105 					if(auto err = waveInAddBuffer(handle, &header, header.sizeof)) {
2106                                                 throw new WinMMException("waveInAddBuffer", err);
2107                                         }
2108 				}
2109 			}
2110 
2111 			/*
2112 			if(auto err = waveInStop(handle))
2113 				throw new WinMMException("wave in stop", err);
2114 			*/
2115 
2116 			if(auto err = waveInReset(handle)) {
2117 				throw new WinMMException("wave in reset", err);
2118 			}
2119 
2120 			still_in_use:
2121 			foreach(idx, header; headers)
2122 				if(!(header.dwFlags & WHDR_DONE)) {
2123 					Sleep(1);
2124 					goto still_in_use;
2125 				}
2126 
2127 			foreach(ref header; headers)
2128 				if(auto err = waveInUnprepareHeader(handle, &header, header.sizeof)) {
2129 					throw new WinMMException("unprepare header", err);
2130 				}
2131 
2132 			ResetEvent(event);
2133 		} else static assert(0);
2134 	}
2135 
2136 	private bool recording;
2137 
2138 	~this() {
2139 		receiveData = null;
2140 		version(ALSA) {
2141 			snd_pcm_close(handle);
2142 		} else version(WinMM) {
2143 			if(auto err = waveInClose(handle))
2144 				throw new WinMMException("close", err);
2145 
2146 			CloseHandle(event);
2147 			// in wine (though not Windows nor winedbg as far as I can tell)
2148 			// this randomly segfaults. the sleep prevents it. idk why.
2149 			Sleep(5);
2150 		} else static assert(0);
2151 	}
2152 }
2153 
2154 ///
2155 enum SampleRateFull = 44100;
2156 
2157 /// Gives PCM output access (such as the speakers).
2158 struct AudioOutput {
2159 	version(ALSA) {
2160 		snd_pcm_t* handle;
2161 	} else version(WinMM) {
2162 		HWAVEOUT handle;
2163 	}
2164 
2165 	@disable this();
2166 	// This struct must NEVER be moved or copied, a pointer to it may
2167 	// be passed to a device driver and stored!
2168 	@disable this(this);
2169 
2170 	private int SampleRate;
2171 	private int channels;
2172 	private string device;
2173 
2174 	/++
2175 		`device` is a device name. On Linux, it is the ALSA string.
2176 		On Windows, it is currently ignored, so you should pass "default"
2177 		or null so when it does get implemented your code won't break.
2178 
2179 		History:
2180 			Added Nov 8, 2020.
2181 	+/
2182 	this(string device, int SampleRate = 44100, int channels = 2) {
2183 		assert(channels == 1 || channels == 2);
2184 
2185 		this.SampleRate = SampleRate;
2186 		this.channels = channels;
2187 		this.device = device;
2188 
2189 		open();
2190 	}
2191 
2192 	/// Always pass card == 0.
2193 	this(int card, int SampleRate = 44100, int channels = 2) {
2194 		assert(card == 0);
2195 
2196 		this("default", SampleRate, channels);
2197 	}
2198 
2199 	/// passes a buffer of data to fill
2200 	///
2201 	/// Data is assumed to be interleaved stereo, LE 16 bit, 44.1 kHz (unless you change that in the ctor)
2202 	/// Each item in the array thus alternates between left and right channel (unless you change that in the ctor)
2203 	/// and it takes a total of 88,200 items to make one second of sound.
2204 	void delegate(short[]) fillData;
2205 
2206 	shared(bool) playing = false; // considered to be volatile
2207 
2208 	/// Starts playing, loops until stop is called
2209 	void play() {
2210 		if(handle is null)
2211 			open();
2212 
2213 		assert(fillData !is null);
2214 		playing = true;
2215 
2216 		version(ALSA) {
2217 			short[BUFFER_SIZE_SHORT] buffer;
2218 			while(playing) {
2219 				auto err = snd_pcm_wait(handle, 500);
2220 				if(err < 0) {
2221 					// see: https://stackoverflow.com/a/59400592/1457000
2222 					err = snd_pcm_recover(handle, err, 0);
2223 					if(err)
2224 						throw new AlsaException("pcm recover failed after pcm_wait did ", err);
2225 					//throw new AlsaException("uh oh", err);
2226 					continue;
2227 				}
2228 				if(err == 0)
2229 					continue;
2230 				// err == 0 means timeout
2231 				// err == 1 means ready
2232 
2233 				auto ready = snd_pcm_avail_update(handle);
2234 				if(ready < 0) {
2235 					//import std.stdio; writeln("recover");
2236 
2237 					// actually it seems ok to just try again..
2238 
2239 					// err = snd_pcm_recover(handle, err, 0);
2240 					//if(err)
2241 						//throw new AlsaException("avail", cast(int)ready);
2242 					continue;
2243 				}
2244 				if(ready > BUFFER_SIZE_FRAMES)
2245 					ready = BUFFER_SIZE_FRAMES;
2246 				//import std.stdio; writeln("filling ", ready);
2247 				fillData(buffer[0 .. ready * channels]);
2248 				if(playing) {
2249 					snd_pcm_sframes_t written;
2250 					auto data = buffer[0 .. ready * channels];
2251 
2252 					while(data.length) {
2253 						written = snd_pcm_writei(handle, data.ptr, data.length / channels);
2254 						if(written < 0) {
2255 						//import std.stdio; writeln(written);
2256 							written = snd_pcm_recover(handle, cast(int)written, 0);
2257 						//import std.stdio; writeln("recover ", written);
2258 							if (written < 0) throw new AlsaException("pcm write", cast(int)written);
2259 						}
2260 						data = data[written * channels .. $];
2261 					}
2262 				}
2263 			}
2264 		} else version(WinMM) {
2265 
2266 			enum numBuffers = 4; // use a lot of buffers to get smooth output with Sleep, see below
2267 			short[BUFFER_SIZE_SHORT][numBuffers] buffers;
2268 
2269 			WAVEHDR[numBuffers] headers;
2270 
2271 			foreach(i, ref header; headers) {
2272 				// since this is wave out, it promises not to write...
2273 				auto buffer = buffers[i][];
2274 				header.lpData = cast(char*) buffer.ptr;
2275 				header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof;
2276 				header.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
2277 				header.dwLoops = 1;
2278 
2279 				if(auto err = waveOutPrepareHeader(handle, &header, header.sizeof))
2280 					throw new WinMMException("prepare header", err);
2281 
2282 				// prime it
2283 				fillData(buffer[]);
2284 
2285 				// indicate that they are filled and good to go
2286 				header.dwUser = 1;
2287 			}
2288 
2289 			while(playing) {
2290 				// and queue both to be played, if they are ready
2291 				foreach(ref header; headers)
2292 					if(header.dwUser) {
2293 						if(auto err = waveOutWrite(handle, &header, header.sizeof))
2294 							throw new WinMMException("wave out write", err);
2295 						header.dwUser = 0;
2296 					}
2297 				Sleep(1);
2298 				// the system resolution may be lower than this sleep. To avoid gaps
2299 				// in output, we use multiple buffers. Might introduce latency, not
2300 				// sure how best to fix. I don't want to busy loop...
2301 			}
2302 
2303 			// wait for the system to finish with our buffers
2304 			bool anyInUse = true;
2305 
2306 			while(anyInUse) {
2307 				anyInUse = false;
2308 				foreach(header; headers) {
2309 					if(!header.dwUser) {
2310 						anyInUse = true;
2311 						break;
2312 					}
2313 				}
2314 				if(anyInUse)
2315 					Sleep(1);
2316 			}
2317 
2318 			foreach(ref header; headers)
2319 				if(auto err = waveOutUnprepareHeader(handle, &header, header.sizeof))
2320 					throw new WinMMException("unprepare", err);
2321 		} else static assert(0);
2322 
2323 		close();
2324 	}
2325 
2326 	/// Breaks the play loop
2327 	void stop() {
2328 		playing = false;
2329 	}
2330 
2331 	///
2332 	void pause() {
2333 		version(WinMM)
2334 			waveOutPause(handle);
2335 		else version(ALSA)
2336 			snd_pcm_pause(handle, 1);
2337 	}
2338 
2339 	///
2340 	void unpause() {
2341 		version(WinMM)
2342 			waveOutRestart(handle);
2343 		else version(ALSA)
2344 			snd_pcm_pause(handle, 0);
2345 
2346 	}
2347 
2348 	version(WinMM) {
2349 		extern(Windows)
2350 		static void mmCallback(HWAVEOUT handle, UINT msg, void* userData, WAVEHDR* header, DWORD_PTR param2) {
2351 			AudioOutput* ao = cast(AudioOutput*) userData;
2352 			if(msg == WOM_DONE) {
2353 				// we want to bounce back and forth between two buffers
2354 				// to keep the sound going all the time
2355 				if(ao.playing) {
2356 					ao.fillData((cast(short*) header.lpData)[0 .. header.dwBufferLength / short.sizeof]);
2357 				}
2358 				header.dwUser = 1;
2359 			}
2360 		}
2361 	}
2362 
2363 
2364 	/++
2365 		Re-opens the audio device that you have previously [close]d.
2366 
2367 		History:
2368 			Added December 30, 2021
2369 	+/
2370 	void open() {
2371 		assert(handle is null);
2372 		assert(!playing);
2373 		version(ALSA) {
2374 			handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_PLAYBACK, SampleRate, channels, device);
2375 		} else version(WinMM) {
2376 			WAVEFORMATEX format;
2377 			format.wFormatTag = WAVE_FORMAT_PCM;
2378 			format.nChannels = cast(ushort) channels;
2379 			format.nSamplesPerSec = SampleRate;
2380 			format.nAvgBytesPerSec = SampleRate * channels * 2; // two channels, two bytes per sample
2381 			format.nBlockAlign = cast(short)(channels * 2);
2382 			format.wBitsPerSample = 16;
2383 			format.cbSize = 0;
2384 			if(auto err = waveOutOpen(&handle, WAVE_MAPPER, &format, cast(DWORD_PTR) &mmCallback, cast(DWORD_PTR) &this, CALLBACK_FUNCTION))
2385 				throw new WinMMException("wave out open", err);
2386 		} else static assert(0);
2387 	}
2388 
2389 	/++
2390 		Closes the audio device. You MUST call [stop] before calling this.
2391 
2392 		History:
2393 			Added December 30, 2021
2394 	+/
2395 	void close() {
2396 		if(!handle)
2397 			return;
2398 		assert(!playing);
2399 		version(ALSA) {
2400 			snd_pcm_close(handle);
2401 			handle = null;
2402 		} else version(WinMM) {
2403 			waveOutClose(handle);
2404 			handle = null;
2405 		} else static assert(0);
2406 	}
2407 
2408 	// FIXME: add async function hooks
2409 
2410 	~this() {
2411 		close();
2412 	}
2413 }
2414 
2415 /++
2416 	For reading midi events from hardware, for example, an electronic piano keyboard
2417 	attached to the computer.
2418 +/
2419 struct MidiInput {
2420 	// reading midi devices...
2421 	version(ALSA) {
2422 		snd_rawmidi_t* handle;
2423 	} else version(WinMM) {
2424 		HMIDIIN handle;
2425 	}
2426 
2427 	@disable this();
2428 	@disable this(this);
2429 
2430 	/+
2431 B0 40 7F # pedal on
2432 B0 40 00 # sustain pedal off
2433 	+/
2434 
2435 	/// Always pass card == 0.
2436 	this(int card) {
2437 		assert(card == 0);
2438 
2439 		this("default"); // "hw:4,0"
2440 	}
2441 
2442 	/++
2443 		`device` is a device name. On Linux, it is the ALSA string.
2444 		On Windows, it is currently ignored, so you should pass "default"
2445 		or null so when it does get implemented your code won't break.
2446 
2447 		History:
2448 			Added Nov 8, 2020.
2449 	+/
2450 	this(string device) {
2451 		version(ALSA) {
2452 			if(auto err = snd_rawmidi_open(&handle, null, device.toStringz, 0))
2453 				throw new AlsaException("rawmidi open", err);
2454 		} else version(WinMM) {
2455 			if(auto err = midiInOpen(&handle, 0, cast(DWORD_PTR) &mmCallback, cast(DWORD_PTR) &this, CALLBACK_FUNCTION))
2456 				throw new WinMMException("midi in open", err);
2457 		} else static assert(0);
2458 	}
2459 
2460 	private bool recording = false;
2461 
2462 	///
2463 	void stop() {
2464 		recording = false;
2465 	}
2466 
2467 	/++
2468 		Records raw midi input data from the device.
2469 
2470 		The timestamp is given in milliseconds since recording
2471 		began (if you keep this program running for 23ish days
2472 		it might overflow! so... don't do that.). The other bytes
2473 		are the midi messages.
2474 
2475 		$(PITFALL Do not call any other multimedia functions from the callback!)
2476 	+/
2477 	void record(void delegate(uint timestamp, ubyte b1, ubyte b2, ubyte b3) dg) {
2478 		version(ALSA) {
2479 			recording = true;
2480 			ubyte[1024] data;
2481 			import core.time;
2482 			auto start = MonoTime.currTime;
2483 			while(recording) {
2484 				auto read = snd_rawmidi_read(handle, data.ptr, data.length);
2485 				if(read < 0)
2486 					throw new AlsaException("midi read", cast(int) read);
2487 
2488 				auto got = data[0 .. read];
2489 				while(got.length) {
2490 					// FIXME some messages are fewer bytes....
2491 					dg(cast(uint) (MonoTime.currTime - start).total!"msecs", got[0], got[1], got[2]);
2492 					got = got[3 .. $];
2493 				}
2494 			}
2495 		} else version(WinMM) {
2496 			recording = true;
2497 			this.dg = dg;
2498 			scope(exit)
2499 				this.dg = null;
2500 			midiInStart(handle);
2501 			scope(exit)
2502 				midiInReset(handle);
2503 
2504 			while(recording) {
2505 				Sleep(1);
2506 			}
2507 		} else static assert(0);
2508 	}
2509 
2510 	version(WinMM)
2511 	private void delegate(uint timestamp, ubyte b1, ubyte b2, ubyte b3) dg;
2512 
2513 
2514 	version(WinMM)
2515 	extern(Windows)
2516 	static
2517 	void mmCallback(HMIDIIN handle, UINT msg, DWORD_PTR user, DWORD_PTR param1, DWORD_PTR param2) {
2518 		MidiInput* mi = cast(MidiInput*) user;
2519 		if(msg == MIM_DATA) {
2520 			mi.dg(
2521 				cast(uint) param2,
2522 				param1 & 0xff,
2523 				(param1 >> 8) & 0xff,
2524 				(param1 >> 16) & 0xff
2525 			);
2526 		}
2527 	}
2528 
2529 	~this() {
2530 		version(ALSA) {
2531 			snd_rawmidi_close(handle);
2532 		} else version(WinMM) {
2533 			midiInClose(handle);
2534 		} else static assert(0);
2535 	}
2536 }
2537 
2538 /// Gives MIDI output access.
2539 struct MidiOutput {
2540 	version(ALSA) {
2541 		snd_rawmidi_t* handle;
2542 	} else version(WinMM) {
2543 		HMIDIOUT handle;
2544 	}
2545 
2546 	@disable this();
2547 	@disable this(this);
2548 
2549 	/// Always pass card == 0.
2550 	this(int card) {
2551 		assert(card == 0);
2552 
2553 		this("default"); // "hw:3,0"
2554 	}
2555 
2556 	/++
2557 		`device` is a device name. On Linux, it is the ALSA string.
2558 		On Windows, it is currently ignored, so you should pass "default"
2559 		or null so when it does get implemented your code won't break.
2560 
2561 		If you pass the string "DUMMY", it will not actually open a device
2562 		and simply be a do-nothing mock object;
2563 
2564 		History:
2565 			Added Nov 8, 2020.
2566 
2567 			Support for the "DUMMY" device was added on January 2, 2022.
2568 	+/
2569 	this(string device) {
2570 		if(device == "DUMMY")
2571 			return;
2572 
2573 		version(ALSA) {
2574 			if(auto err = snd_rawmidi_open(null, &handle, device.toStringz, 0))
2575 				throw new AlsaException("rawmidi open", err);
2576 		} else version(WinMM) {
2577 			if(auto err = midiOutOpen(&handle, 0, 0, 0, CALLBACK_NULL))
2578 				throw new WinMMException("midi out open", err);
2579 		} else static assert(0);
2580 	}
2581 
2582 	void silenceAllNotes() {
2583 		foreach(a; 0 .. 16)
2584 			writeMidiMessage((0x0b << 4)|a /*MIDI_EVENT_CONTROLLER*/, 123, 0);
2585 	}
2586 
2587 	/// Send a reset message, silencing all notes
2588 	void reset() {
2589 		if(!handle) return;
2590 
2591 		version(ALSA) {
2592 			silenceAllNotes();
2593 			static immutable ubyte[1] resetCmd = [0xff];
2594 			writeRawMessageData(resetCmd[]);
2595 			// and flush it immediately
2596 			snd_rawmidi_drain(handle);
2597 		} else version(WinMM) {
2598 			if(auto error = midiOutReset(handle))
2599 				throw new WinMMException("midi reset", error);
2600 		} else static assert(0);
2601 	}
2602 
2603 	/// Writes a single low-level midi message
2604 	/// Timing and sending sane data is your responsibility!
2605 	void writeMidiMessage(int status, int param1, int param2) {
2606 		if(!handle) return;
2607 		version(ALSA) {
2608 			ubyte[3] dataBuffer;
2609 
2610 			dataBuffer[0] = cast(ubyte) status;
2611 			dataBuffer[1] = cast(ubyte) param1;
2612 			dataBuffer[2] = cast(ubyte) param2;
2613 
2614 			auto msg = status >> 4;
2615 			ubyte[] data;
2616 			if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch)
2617 				data = dataBuffer[0 .. 2];
2618 			else
2619 				data = dataBuffer[];
2620 
2621 			writeRawMessageData(data);
2622 		} else version(WinMM) {
2623 			DWORD word = (param2 << 16) | (param1 << 8) | status;
2624 			if(auto error = midiOutShortMsg(handle, word))
2625 				throw new WinMMException("midi out", error);
2626 		} else static assert(0);
2627 
2628 	}
2629 
2630 	/// Writes a series of individual raw messages.
2631 	/// Timing and sending sane data is your responsibility!
2632 	/// The data should NOT include any timestamp bytes - each midi message should be 2 or 3 bytes.
2633 	void writeRawMessageData(scope const(ubyte)[] data) {
2634 		if(!handle) return;
2635 		if(data.length == 0)
2636 			return;
2637 		version(ALSA) {
2638 			ssize_t written;
2639 
2640 			while(data.length) {
2641 				written = snd_rawmidi_write(handle, data.ptr, data.length);
2642 				if(written < 0)
2643 					throw new AlsaException("midi write", cast(int) written);
2644 				data = data[cast(int) written .. $];
2645 			}
2646 		} else version(WinMM) {
2647 			while(data.length) {
2648 				auto msg = data[0] >> 4;
2649 				if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) {
2650 					writeMidiMessage(data[0], data[1], 0);
2651 					data = data[2 .. $];
2652 				} else {
2653 					writeMidiMessage(data[0], data[1], data[2]);
2654 					data = data[3 .. $];
2655 				}
2656 			}
2657 		} else static assert(0);
2658 	}
2659 
2660 	~this() {
2661 		if(!handle) return;
2662 		version(ALSA) {
2663 			snd_rawmidi_close(handle);
2664 		} else version(WinMM) {
2665 			midiOutClose(handle);
2666 		} else static assert(0);
2667 	}
2668 }
2669 
2670 
2671 // FIXME: maybe add a PC speaker beep function for completeness
2672 
2673 /// Interfaces with the default sound card. You should only have a single instance of this and it should
2674 /// be stack allocated, so its destructor cleans up after it.
2675 ///
2676 /// A mixer gives access to things like volume controls and mute buttons. It should also give a
2677 /// callback feature to alert you of when the settings are changed by another program.
2678 version(ALSA) // FIXME
2679 struct AudioMixer {
2680 	// To port to a new OS: put the data in the right version blocks
2681 	// then implement each function. Leave else static assert(0) at the
2682 	// end of each version group in a function so it is easier to implement elsewhere later.
2683 	//
2684 	// If a function is only relevant on your OS, put the whole function in a version block
2685 	// and give it an OS specific name of some sort.
2686 	//
2687 	// Feel free to do that btw without worrying about lowest common denominator: we want low level access when we want it.
2688 	//
2689 	// Put necessary bindings at the end of the file, or use an import if you like, but I prefer these files to be standalone.
2690 	version(ALSA) {
2691 		snd_mixer_t* handle;
2692 		snd_mixer_selem_id_t* sid;
2693 		snd_mixer_elem_t* selem;
2694 
2695 		c_long maxVolume, minVolume; // these are ok to use if you are writing ALSA specific code i guess
2696 
2697 		enum selemName = "Master";
2698 	}
2699 
2700 	@disable this();
2701 	@disable this(this);
2702 
2703 	/// Only cardId == 0 is supported
2704 	this(int cardId) {
2705 		assert(cardId == 0, "Pass 0 to use default sound card.");
2706 
2707 		this("default");
2708 	}
2709 
2710 	/++
2711 		`device` is a device name. On Linux, it is the ALSA string.
2712 		On Windows, it is currently ignored, so you should pass "default"
2713 		or null so when it does get implemented your code won't break.
2714 
2715 		History:
2716 			Added Nov 8, 2020.
2717 	+/
2718 	this(string device) {
2719 		version(ALSA) {
2720 			if(auto err = snd_mixer_open(&handle, 0))
2721 				throw new AlsaException("open sound", err);
2722 			scope(failure)
2723 				snd_mixer_close(handle);
2724 			if(auto err = snd_mixer_attach(handle, device.toStringz))
2725 				throw new AlsaException("attach to sound card", err);
2726 			if(auto err = snd_mixer_selem_register(handle, null, null))
2727 				throw new AlsaException("register mixer", err);
2728 			if(auto err = snd_mixer_load(handle))
2729 				throw new AlsaException("load mixer", err);
2730 
2731 			if(auto err = snd_mixer_selem_id_malloc(&sid))
2732 				throw new AlsaException("master channel open", err);
2733 			scope(failure)
2734 				snd_mixer_selem_id_free(sid);
2735 			snd_mixer_selem_id_set_index(sid, 0);
2736 			snd_mixer_selem_id_set_name(sid, selemName);
2737 			selem = snd_mixer_find_selem(handle, sid);
2738 			if(selem is null)
2739 				throw new AlsaException("find master element", 0);
2740 
2741 			if(auto err = snd_mixer_selem_get_playback_volume_range(selem, &minVolume, &maxVolume))
2742 				throw new AlsaException("get volume range", err);
2743 
2744 			version(with_eventloop) {
2745 				import arsd.eventloop;
2746 				addFileEventListeners(getAlsaFileDescriptors()[0], &eventListener, null, null);
2747 				setAlsaElemCallback(&alsaCallback);
2748 			}
2749 		} else static assert(0);
2750 	}
2751 
2752 	~this() {
2753 		version(ALSA) {
2754 			version(with_eventloop) {
2755 				import arsd.eventloop;
2756 				removeFileEventListeners(getAlsaFileDescriptors()[0]);
2757 			}
2758 			snd_mixer_selem_id_free(sid);
2759 			snd_mixer_close(handle);
2760 		} else static assert(0);
2761 	}
2762 
2763 	version(ALSA)
2764 	version(with_eventloop) {
2765 		static struct MixerEvent {}
2766 		nothrow @nogc
2767 		extern(C) static int alsaCallback(snd_mixer_elem_t*, uint) {
2768 			import arsd.eventloop;
2769 			try
2770 				send(MixerEvent());
2771 			catch(Exception)
2772 				return 1;
2773 
2774 			return 0;
2775 		}
2776 
2777 		void eventListener(int fd) {
2778 			handleAlsaEvents();
2779 		}
2780 	}
2781 
2782 	/// Gets the master channel's mute state
2783 	/// Note: this affects shared system state and you should not use it unless the end user wants you to.
2784 	@property bool muteMaster() {
2785 		version(ALSA) {
2786 			int result;
2787 			if(auto err = snd_mixer_selem_get_playback_switch(selem, 0, &result))
2788 				throw new AlsaException("get mute state", err);
2789 			return result == 0;
2790 		} else static assert(0);
2791 	}
2792 
2793 	/// Mutes or unmutes the master channel
2794 	/// Note: this affects shared system state and you should not use it unless the end user wants you to.
2795 	@property void muteMaster(bool mute) {
2796 		version(ALSA) {
2797 			if(auto err = snd_mixer_selem_set_playback_switch_all(selem, mute ? 0 : 1))
2798 				throw new AlsaException("set mute state", err);
2799 		} else static assert(0);
2800 	}
2801 
2802 	/// returns a percentage, between 0 and 100 (inclusive)
2803 	int getMasterVolume() {
2804 		version(ALSA) {
2805 			auto volume = getMasterVolumeExact();
2806 			return cast(int)(volume * 100 / (maxVolume - minVolume));
2807 		} else static assert(0);
2808 	}
2809 
2810 	/// Gets the exact value returned from the operating system. The range may vary.
2811 	int getMasterVolumeExact() {
2812 		version(ALSA) {
2813 			c_long volume;
2814 			snd_mixer_selem_get_playback_volume(selem, 0, &volume);
2815 			return cast(int)volume;
2816 		} else static assert(0);
2817 	}
2818 
2819 	/// sets a percentage on the volume, so it must be 0 <= volume <= 100
2820 	/// Note: this affects shared system state and you should not use it unless the end user wants you to.
2821 	void setMasterVolume(int volume) {
2822 		version(ALSA) {
2823 			assert(volume >= 0 && volume <= 100);
2824 			setMasterVolumeExact(cast(int)(volume * (maxVolume - minVolume) / 100));
2825 		} else static assert(0);
2826 	}
2827 
2828 	/// Sets an exact volume. Must be in range of the OS provided min and max.
2829 	void setMasterVolumeExact(int volume) {
2830 		version(ALSA) {
2831 			if(auto err = snd_mixer_selem_set_playback_volume_all(selem, volume))
2832 				throw new AlsaException("set volume", err);
2833 		} else static assert(0);
2834 	}
2835 
2836 	version(ALSA) {
2837 		/// Gets the ALSA descriptors which you can watch for events
2838 		/// on using regular select, poll, epoll, etc.
2839 		int[] getAlsaFileDescriptors() {
2840 			import core.sys.posix.poll;
2841 			pollfd[32] descriptors = void;
2842 			int got = snd_mixer_poll_descriptors(handle, descriptors.ptr, descriptors.length);
2843 			int[] result;
2844 			result.length = got;
2845 			foreach(i, desc; descriptors[0 .. got])
2846 				result[i] = desc.fd;
2847 			return result;
2848 		}
2849 
2850 		/// When the FD is ready, call this to let ALSA do its thing.
2851 		void handleAlsaEvents() {
2852 			snd_mixer_handle_events(handle);
2853 		}
2854 
2855 		/// Set a callback for the master volume change events.
2856 		void setAlsaElemCallback(snd_mixer_elem_callback_t dg) {
2857 			snd_mixer_elem_set_callback(selem, dg);
2858 		}
2859 	}
2860 }
2861 
2862 // ****************
2863 // Midi helpers
2864 // ****************
2865 
2866 // FIXME: code the .mid file format, read and write
2867 
2868 enum MidiEvent {
2869 	NoteOff           = 0x08,
2870 	NoteOn            = 0x09,
2871 	NoteAftertouch    = 0x0a,
2872 	Controller        = 0x0b,
2873 	ProgramChange     = 0x0c, // one param
2874 	ChannelAftertouch = 0x0d, // one param
2875 	PitchBend         = 0x0e,
2876 }
2877 
2878 enum MidiNote : ubyte {
2879 	middleC = 60,
2880 	A =  69, // 440 Hz
2881 	As = 70,
2882 	B =  71,
2883 	C =  72,
2884 	Cs = 73,
2885 	D =  74,
2886 	Ds = 75,
2887 	E =  76,
2888 	F =  77,
2889 	Fs = 78,
2890 	G =  79,
2891 	Gs = 80,
2892 }
2893 
2894 /// Puts a note on at the beginning of the passed slice, advancing it by the amount of the message size.
2895 /// Returns the message slice.
2896 ///
2897 /// See: http://www.midi.org/techspecs/midimessages.php
2898 ubyte[] midiNoteOn(ref ubyte[] where, ubyte channel, byte note, byte velocity) {
2899 	where[0] = (MidiEvent.NoteOn << 4) | (channel&0x0f);
2900 	where[1] = note;
2901 	where[2] = velocity;
2902 	auto it = where[0 .. 3];
2903 	where = where[3 .. $];
2904 	return it;
2905 }
2906 
2907 /// Note off.
2908 ubyte[] midiNoteOff(ref ubyte[] where, ubyte channel, byte note, byte velocity) {
2909 	where[0] = (MidiEvent.NoteOff << 4) | (channel&0x0f);
2910 	where[1] = note;
2911 	where[2] = velocity;
2912 	auto it = where[0 .. 3];
2913 	where = where[3 .. $];
2914 	return it;
2915 }
2916 
2917 /// Aftertouch.
2918 ubyte[] midiNoteAftertouch(ref ubyte[] where, ubyte channel, byte note, byte pressure) {
2919 	where[0] = (MidiEvent.NoteAftertouch << 4) | (channel&0x0f);
2920 	where[1] = note;
2921 	where[2] = pressure;
2922 	auto it = where[0 .. 3];
2923 	where = where[3 .. $];
2924 	return it;
2925 }
2926 
2927 /// Controller.
2928 ubyte[] midiNoteController(ref ubyte[] where, ubyte channel, byte controllerNumber, byte controllerValue) {
2929 	where[0] = (MidiEvent.Controller << 4) | (channel&0x0f);
2930 	where[1] = controllerNumber;
2931 	where[2] = controllerValue;
2932 	auto it = where[0 .. 3];
2933 	where = where[3 .. $];
2934 	return it;
2935 }
2936 
2937 /// Program change.
2938 ubyte[] midiProgramChange(ref ubyte[] where, ubyte channel, byte program) {
2939 	where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f);
2940 	where[1] = program;
2941 	auto it = where[0 .. 2];
2942 	where = where[2 .. $];
2943 	return it;
2944 }
2945 
2946 /// Channel aftertouch.
2947 ubyte[] midiChannelAftertouch(ref ubyte[] where, ubyte channel, byte amount) {
2948 	where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f);
2949 	where[1] = amount;
2950 	auto it = where[0 .. 2];
2951 	where = where[2 .. $];
2952 	return it;
2953 }
2954 
2955 /// Pitch bend. FIXME doesn't work right
2956 ubyte[] midiNotePitchBend(ref ubyte[] where, ubyte channel, short change) {
2957 /*
2958 first byte is llllll
2959 second byte is mmmmmm
2960 
2961 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.
2962 */
2963 	where[0] = (MidiEvent.PitchBend << 4) | (channel&0x0f);
2964 	// FIXME
2965 	where[1] = 0;
2966 	where[2] = 0;
2967 	auto it = where[0 .. 3];
2968 	where = where[3 .. $];
2969 	return it;
2970 }
2971 
2972 
2973 // ****************
2974 // Wav helpers
2975 // ****************
2976 
2977 // FIXME: the .wav file format should be here, read and write (at least basics)
2978 // as well as some kind helpers to generate some sounds.
2979 
2980 // ****************
2981 // OS specific helper stuff follows
2982 // ****************
2983 
2984 private const(char)* toStringz(string s) {
2985 	return s.ptr; // FIXME jic
2986 }
2987 
2988 version(ALSA)
2989 // Opens the PCM device with default settings: stereo, 16 bit, 44.1 kHz, interleaved R/W.
2990 snd_pcm_t* openAlsaPcm(snd_pcm_stream_t direction, int SampleRate, int channels, string cardName = "default") {
2991 	snd_pcm_t* handle;
2992 	snd_pcm_hw_params_t* hwParams;
2993 
2994 	/* Open PCM and initialize hardware */
2995 
2996 	// import arsd.core;
2997 	// writeln("before");
2998 	if (auto err = snd_pcm_open(&handle, cardName.toStringz, direction, 0))
2999 		throw new AlsaException("open device", err);
3000 	// writeln("after");
3001 	scope(failure)
3002 		snd_pcm_close(handle);
3003 
3004 
3005 	if (auto err = snd_pcm_hw_params_malloc(&hwParams))
3006 		throw new AlsaException("params malloc", err);
3007 	scope(exit)
3008 		snd_pcm_hw_params_free(hwParams);
3009 
3010 	if (auto err = snd_pcm_hw_params_any(handle, hwParams))
3011 		// can actually survive a failure here, we will just move forward
3012 		{} // throw new AlsaException("params init", err);
3013 
3014 	if (auto err = snd_pcm_hw_params_set_access(handle, hwParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED))
3015 		throw new AlsaException("params access", err);
3016 
3017 	if (auto err = snd_pcm_hw_params_set_format(handle, hwParams, snd_pcm_format.SND_PCM_FORMAT_S16_LE))
3018 		throw new AlsaException("params format", err);
3019 
3020 	uint rate = SampleRate;
3021 	int dir = 0;
3022 	if (auto err = snd_pcm_hw_params_set_rate_near(handle, hwParams, &rate, &dir))
3023 		throw new AlsaException("params rate", err);
3024 
3025 	assert(rate == SampleRate); // cheap me
3026 
3027 	if (auto err = snd_pcm_hw_params_set_channels(handle, hwParams, channels))
3028 		throw new AlsaException("params channels", err);
3029 
3030 	uint periods = 4;
3031 	{
3032 	auto err = snd_pcm_hw_params_set_periods_near(handle, hwParams, &periods, 0);
3033 	if(err < 0)
3034 		throw new AlsaException("periods", err);
3035 
3036 	// import std.stdio; writeln(periods);
3037 	snd_pcm_uframes_t sz = (BUFFER_SIZE_FRAMES * periods);
3038 	err = snd_pcm_hw_params_set_buffer_size_near(handle, hwParams, &sz);
3039 	if(err < 0)
3040 		throw new AlsaException("buffer size", err);
3041 	}
3042 
3043 	if (auto err = snd_pcm_hw_params(handle, hwParams))
3044 		throw new AlsaException("params install", err);
3045 
3046 	/* Setting up the callbacks */
3047 
3048 	snd_pcm_sw_params_t* swparams;
3049 	if(auto err = snd_pcm_sw_params_malloc(&swparams))
3050 		throw new AlsaException("sw malloc", err);
3051 	scope(exit)
3052 		snd_pcm_sw_params_free(swparams);
3053 	if(auto err = snd_pcm_sw_params_current(handle, swparams))
3054 		throw new AlsaException("sw set", err);
3055 	if(auto err = snd_pcm_sw_params_set_avail_min(handle, swparams, BUFFER_SIZE_FRAMES))
3056 		throw new AlsaException("sw min", err);
3057 	if(auto err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0))
3058 		throw new AlsaException("sw threshold", err);
3059 	if(auto err = snd_pcm_sw_params(handle, swparams))
3060 		throw new AlsaException("sw params", err);
3061 
3062 	/* finish setup */
3063 
3064 	// writeln("prepare");
3065 	if (auto err = snd_pcm_prepare(handle))
3066 		throw new AlsaException("prepare", err);
3067 	// writeln("done");
3068 
3069 	assert(handle !is null);
3070 	return handle;
3071 }
3072 
3073 version(ALSA)
3074 class AlsaException : AudioException {
3075 	this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
3076 		auto msg = snd_strerror(error);
3077 		import core.stdc.string;
3078 		super(cast(string) (message ~ ": " ~ msg[0 .. strlen(msg)]), file, line, next);
3079 	}
3080 }
3081 
3082 version(WinMM)
3083 class WinMMException : AudioException {
3084 	this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
3085 		// FIXME: format the error
3086 		// midiOutGetErrorText, etc.
3087 		super(message, file, line, next);
3088 	}
3089 }
3090 
3091 // ****************
3092 // Bindings follow
3093 // ****************
3094 
3095 version(ALSA) {
3096 extern(C):
3097 @nogc nothrow:
3098 	pragma(lib, "asound");
3099 	private import core.sys.posix.poll;
3100 
3101 	const(char)* snd_strerror(int);
3102 
3103 	// pcm
3104 	enum snd_pcm_stream_t {
3105 		SND_PCM_STREAM_PLAYBACK,
3106 		SND_PCM_STREAM_CAPTURE
3107 	}
3108 
3109 	enum snd_pcm_access_t {
3110 		/** mmap access with simple interleaved channels */
3111 		SND_PCM_ACCESS_MMAP_INTERLEAVED = 0,
3112 		/** mmap access with simple non interleaved channels */
3113 		SND_PCM_ACCESS_MMAP_NONINTERLEAVED,
3114 		/** mmap access with complex placement */
3115 		SND_PCM_ACCESS_MMAP_COMPLEX,
3116 		/** snd_pcm_readi/snd_pcm_writei access */
3117 		SND_PCM_ACCESS_RW_INTERLEAVED,
3118 		/** snd_pcm_readn/snd_pcm_writen access */
3119 		SND_PCM_ACCESS_RW_NONINTERLEAVED,
3120 		SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED
3121 	}
3122 
3123 	enum snd_pcm_format {
3124 		/** Unknown */
3125 		SND_PCM_FORMAT_UNKNOWN = -1,
3126 		/** Signed 8 bit */
3127 		SND_PCM_FORMAT_S8 = 0,
3128 		/** Unsigned 8 bit */
3129 		SND_PCM_FORMAT_U8,
3130 		/** Signed 16 bit Little Endian */
3131 		SND_PCM_FORMAT_S16_LE,
3132 		/** Signed 16 bit Big Endian */
3133 		SND_PCM_FORMAT_S16_BE,
3134 		/** Unsigned 16 bit Little Endian */
3135 		SND_PCM_FORMAT_U16_LE,
3136 		/** Unsigned 16 bit Big Endian */
3137 		SND_PCM_FORMAT_U16_BE,
3138 		/** Signed 24 bit Little Endian using low three bytes in 32-bit word */
3139 		SND_PCM_FORMAT_S24_LE,
3140 		/** Signed 24 bit Big Endian using low three bytes in 32-bit word */
3141 		SND_PCM_FORMAT_S24_BE,
3142 		/** Unsigned 24 bit Little Endian using low three bytes in 32-bit word */
3143 		SND_PCM_FORMAT_U24_LE,
3144 		/** Unsigned 24 bit Big Endian using low three bytes in 32-bit word */
3145 		SND_PCM_FORMAT_U24_BE,
3146 		/** Signed 32 bit Little Endian */
3147 		SND_PCM_FORMAT_S32_LE,
3148 		/** Signed 32 bit Big Endian */
3149 		SND_PCM_FORMAT_S32_BE,
3150 		/** Unsigned 32 bit Little Endian */
3151 		SND_PCM_FORMAT_U32_LE,
3152 		/** Unsigned 32 bit Big Endian */
3153 		SND_PCM_FORMAT_U32_BE,
3154 		/** Float 32 bit Little Endian, Range -1.0 to 1.0 */
3155 		SND_PCM_FORMAT_FLOAT_LE,
3156 		/** Float 32 bit Big Endian, Range -1.0 to 1.0 */
3157 		SND_PCM_FORMAT_FLOAT_BE,
3158 		/** Float 64 bit Little Endian, Range -1.0 to 1.0 */
3159 		SND_PCM_FORMAT_FLOAT64_LE,
3160 		/** Float 64 bit Big Endian, Range -1.0 to 1.0 */
3161 		SND_PCM_FORMAT_FLOAT64_BE,
3162 		/** IEC-958 Little Endian */
3163 		SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
3164 		/** IEC-958 Big Endian */
3165 		SND_PCM_FORMAT_IEC958_SUBFRAME_BE,
3166 		/** Mu-Law */
3167 		SND_PCM_FORMAT_MU_LAW,
3168 		/** A-Law */
3169 		SND_PCM_FORMAT_A_LAW,
3170 		/** Ima-ADPCM */
3171 		SND_PCM_FORMAT_IMA_ADPCM,
3172 		/** MPEG */
3173 		SND_PCM_FORMAT_MPEG,
3174 		/** GSM */
3175 		SND_PCM_FORMAT_GSM,
3176 		/** Special */
3177 		SND_PCM_FORMAT_SPECIAL = 31,
3178 		/** Signed 24bit Little Endian in 3bytes format */
3179 		SND_PCM_FORMAT_S24_3LE = 32,
3180 		/** Signed 24bit Big Endian in 3bytes format */
3181 		SND_PCM_FORMAT_S24_3BE,
3182 		/** Unsigned 24bit Little Endian in 3bytes format */
3183 		SND_PCM_FORMAT_U24_3LE,
3184 		/** Unsigned 24bit Big Endian in 3bytes format */
3185 		SND_PCM_FORMAT_U24_3BE,
3186 		/** Signed 20bit Little Endian in 3bytes format */
3187 		SND_PCM_FORMAT_S20_3LE,
3188 		/** Signed 20bit Big Endian in 3bytes format */
3189 		SND_PCM_FORMAT_S20_3BE,
3190 		/** Unsigned 20bit Little Endian in 3bytes format */
3191 		SND_PCM_FORMAT_U20_3LE,
3192 		/** Unsigned 20bit Big Endian in 3bytes format */
3193 		SND_PCM_FORMAT_U20_3BE,
3194 		/** Signed 18bit Little Endian in 3bytes format */
3195 		SND_PCM_FORMAT_S18_3LE,
3196 		/** Signed 18bit Big Endian in 3bytes format */
3197 		SND_PCM_FORMAT_S18_3BE,
3198 		/** Unsigned 18bit Little Endian in 3bytes format */
3199 		SND_PCM_FORMAT_U18_3LE,
3200 		/** Unsigned 18bit Big Endian in 3bytes format */
3201 		SND_PCM_FORMAT_U18_3BE,
3202 		/* G.723 (ADPCM) 24 kbit/s, 8 samples in 3 bytes */
3203 		SND_PCM_FORMAT_G723_24,
3204 		/* G.723 (ADPCM) 24 kbit/s, 1 sample in 1 byte */
3205 		SND_PCM_FORMAT_G723_24_1B,
3206 		/* G.723 (ADPCM) 40 kbit/s, 8 samples in 3 bytes */
3207 		SND_PCM_FORMAT_G723_40,
3208 		/* G.723 (ADPCM) 40 kbit/s, 1 sample in 1 byte */
3209 		SND_PCM_FORMAT_G723_40_1B,
3210 		/* Direct Stream Digital (DSD) in 1-byte samples (x8) */
3211 		SND_PCM_FORMAT_DSD_U8,
3212 		/* Direct Stream Digital (DSD) in 2-byte samples (x16) */
3213 		SND_PCM_FORMAT_DSD_U16_LE,
3214 		SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_DSD_U16_LE,
3215 
3216 		// I snipped a bunch of endian-specific ones!
3217 	}
3218 
3219 	struct snd_pcm_t {}
3220 	struct snd_pcm_hw_params_t {}
3221 	struct snd_pcm_sw_params_t {}
3222 
3223 	int snd_pcm_open(snd_pcm_t**, const char*, snd_pcm_stream_t, int);
3224 	int snd_pcm_close(snd_pcm_t*);
3225 	int snd_pcm_pause(snd_pcm_t*, int);
3226 	int snd_pcm_prepare(snd_pcm_t*);
3227 	int snd_pcm_hw_params(snd_pcm_t*, snd_pcm_hw_params_t*);
3228 	int snd_pcm_hw_params_set_periods(snd_pcm_t*, snd_pcm_hw_params_t*, uint, int);
3229 	int snd_pcm_hw_params_set_periods_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int);
3230 	int snd_pcm_hw_params_set_buffer_size(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t);
3231 	int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t*);
3232 	int snd_pcm_hw_params_set_channels(snd_pcm_t*, snd_pcm_hw_params_t*, uint);
3233 	int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t**);
3234 	void snd_pcm_hw_params_free(snd_pcm_hw_params_t*);
3235 	int snd_pcm_hw_params_any(snd_pcm_t*, snd_pcm_hw_params_t*);
3236 	int snd_pcm_hw_params_set_access(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_access_t);
3237 	int snd_pcm_hw_params_set_format(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_format);
3238 	int snd_pcm_hw_params_set_rate_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int*);
3239 
3240 	int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t**);
3241 	void snd_pcm_sw_params_free(snd_pcm_sw_params_t*);
3242 
3243 	int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
3244 	int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
3245 	int snd_pcm_sw_params_set_avail_min(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
3246 	int snd_pcm_sw_params_set_start_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
3247 	int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
3248 
3249 	alias snd_pcm_sframes_t = c_long;
3250 	alias snd_pcm_uframes_t = c_ulong;
3251 	snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t*, const void*, snd_pcm_uframes_t size);
3252 	snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t*, void*, snd_pcm_uframes_t size);
3253 
3254 	int snd_pcm_wait(snd_pcm_t *pcm, int timeout);
3255 	snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm);
3256 	snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm);
3257 
3258 	int snd_pcm_recover (snd_pcm_t* pcm, int err, int silent);
3259 
3260 	alias snd_lib_error_handler_t = void function (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...);
3261 	int snd_lib_error_set_handler (snd_lib_error_handler_t handler);
3262 
3263 	import core.stdc.stdarg;
3264 	private void alsa_message_silencer (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...) @system {}
3265 	//k8: ALSAlib loves to trash stderr; shut it up
3266 	void silence_alsa_messages () { snd_lib_error_set_handler(&alsa_message_silencer); }
3267 	extern(D) shared static this () { silence_alsa_messages(); }
3268 
3269 	// raw midi
3270 
3271 	static if(is(size_t == uint))
3272 		alias ssize_t = int;
3273 	else
3274 		alias ssize_t = long;
3275 
3276 
3277 	struct snd_rawmidi_t {}
3278 	int snd_rawmidi_open(snd_rawmidi_t**, snd_rawmidi_t**, const char*, int);
3279 	int snd_rawmidi_close(snd_rawmidi_t*);
3280 	int snd_rawmidi_drain(snd_rawmidi_t*);
3281 	ssize_t snd_rawmidi_write(snd_rawmidi_t*, const void*, size_t);
3282 	ssize_t snd_rawmidi_read(snd_rawmidi_t*, void*, size_t);
3283 
3284 	// mixer
3285 
3286 	struct snd_mixer_t {}
3287 	struct snd_mixer_elem_t {}
3288 	struct snd_mixer_selem_id_t {}
3289 
3290 	alias snd_mixer_elem_callback_t = int function(snd_mixer_elem_t*, uint);
3291 
3292 	int snd_mixer_open(snd_mixer_t**, int mode);
3293 	int snd_mixer_close(snd_mixer_t*);
3294 	int snd_mixer_attach(snd_mixer_t*, const char*);
3295 	int snd_mixer_load(snd_mixer_t*);
3296 
3297 	// FIXME: those aren't actually void*
3298 	int snd_mixer_selem_register(snd_mixer_t*, void*, void*);
3299 	int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t**);
3300 	void snd_mixer_selem_id_free(snd_mixer_selem_id_t*);
3301 	void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t*, uint);
3302 	void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t*, const char*);
3303 	snd_mixer_elem_t* snd_mixer_find_selem(snd_mixer_t*, const scope snd_mixer_selem_id_t*);
3304 
3305 	// FIXME: the int should be an enum for channel identifier
3306 	int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t*, int, c_long*);
3307 
3308 	int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t*, c_long*, c_long*);
3309 
3310 	int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t*, c_long);
3311 
3312 	void snd_mixer_elem_set_callback(snd_mixer_elem_t*, snd_mixer_elem_callback_t);
3313 	int snd_mixer_poll_descriptors(snd_mixer_t*, pollfd*, uint space);
3314 
3315 	int snd_mixer_handle_events(snd_mixer_t*);
3316 
3317 	// FIXME: the first int should be an enum for channel identifier
3318 	int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t*, int, int* value);
3319 	int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t*, int);
3320 }
3321 
3322 version(WinMM) {
3323 extern(Windows):
3324 @nogc nothrow:
3325 	pragma(lib, "winmm");
3326 	import core.sys.windows.windows;
3327 
3328 /*
3329 	Windows functions include:
3330 	http://msdn.microsoft.com/en-us/library/ms713762%28VS.85%29.aspx
3331 	http://msdn.microsoft.com/en-us/library/ms713504%28v=vs.85%29.aspx
3332 	http://msdn.microsoft.com/en-us/library/windows/desktop/dd798480%28v=vs.85%29.aspx#
3333 	http://msdn.microsoft.com/en-US/subscriptions/ms712109.aspx
3334 */
3335 
3336 	// pcm
3337 
3338 	// midi
3339 /+
3340 	alias HMIDIOUT = HANDLE;
3341 	alias MMRESULT = UINT;
3342 
3343 	MMRESULT midiOutOpen(HMIDIOUT*, UINT, DWORD, DWORD, DWORD);
3344 	MMRESULT midiOutClose(HMIDIOUT);
3345 	MMRESULT midiOutReset(HMIDIOUT);
3346 	MMRESULT midiOutShortMsg(HMIDIOUT, DWORD);
3347 
3348 	alias HWAVEOUT = HANDLE;
3349 
3350 	struct WAVEFORMATEX {
3351 		WORD wFormatTag;
3352 		WORD nChannels;
3353 		DWORD nSamplesPerSec;
3354 		DWORD nAvgBytesPerSec;
3355 		WORD nBlockAlign;
3356 		WORD wBitsPerSample;
3357 		WORD cbSize;
3358 	}
3359 
3360 	struct WAVEHDR {
3361 		void* lpData;
3362 		DWORD dwBufferLength;
3363 		DWORD dwBytesRecorded;
3364 		DWORD dwUser;
3365 		DWORD dwFlags;
3366 		DWORD dwLoops;
3367 		WAVEHDR *lpNext;
3368 		DWORD reserved;
3369 	}
3370 
3371 	enum UINT WAVE_MAPPER= -1;
3372 
3373 	MMRESULT waveOutOpen(HWAVEOUT*, UINT_PTR, WAVEFORMATEX*, void* callback, void*, DWORD);
3374 	MMRESULT waveOutClose(HWAVEOUT);
3375 	MMRESULT waveOutPrepareHeader(HWAVEOUT, WAVEHDR*, UINT);
3376 	MMRESULT waveOutUnprepareHeader(HWAVEOUT, WAVEHDR*, UINT);
3377 	MMRESULT waveOutWrite(HWAVEOUT, WAVEHDR*, UINT);
3378 
3379 	MMRESULT waveOutGetVolume(HWAVEOUT, PDWORD);
3380 	MMRESULT waveOutSetVolume(HWAVEOUT, DWORD);
3381 
3382 	enum CALLBACK_TYPEMASK = 0x70000;
3383 	enum CALLBACK_NULL     = 0;
3384 	enum CALLBACK_WINDOW   = 0x10000;
3385 	enum CALLBACK_TASK     = 0x20000;
3386 	enum CALLBACK_FUNCTION = 0x30000;
3387 	enum CALLBACK_THREAD   = CALLBACK_TASK;
3388 	enum CALLBACK_EVENT    = 0x50000;
3389 
3390 	enum WAVE_FORMAT_PCM = 1;
3391 
3392 	enum WHDR_PREPARED = 2;
3393 	enum WHDR_BEGINLOOP = 4;
3394 	enum WHDR_ENDLOOP = 8;
3395 	enum WHDR_INQUEUE = 16;
3396 
3397 	enum WinMMMessage : UINT {
3398 		MM_JOY1MOVE            = 0x3A0,
3399 		MM_JOY2MOVE,
3400 		MM_JOY1ZMOVE,
3401 		MM_JOY2ZMOVE,       // = 0x3A3
3402 		MM_JOY1BUTTONDOWN      = 0x3B5,
3403 		MM_JOY2BUTTONDOWN,
3404 		MM_JOY1BUTTONUP,
3405 		MM_JOY2BUTTONUP,
3406 		MM_MCINOTIFY,       // = 0x3B9
3407 		MM_WOM_OPEN            = 0x3BB,
3408 		MM_WOM_CLOSE,
3409 		MM_WOM_DONE,
3410 		MM_WIM_OPEN,
3411 		MM_WIM_CLOSE,
3412 		MM_WIM_DATA,
3413 		MM_MIM_OPEN,
3414 		MM_MIM_CLOSE,
3415 		MM_MIM_DATA,
3416 		MM_MIM_LONGDATA,
3417 		MM_MIM_ERROR,
3418 		MM_MIM_LONGERROR,
3419 		MM_MOM_OPEN,
3420 		MM_MOM_CLOSE,
3421 		MM_MOM_DONE,        // = 0x3C9
3422 		MM_DRVM_OPEN           = 0x3D0,
3423 		MM_DRVM_CLOSE,
3424 		MM_DRVM_DATA,
3425 		MM_DRVM_ERROR,
3426 		MM_STREAM_OPEN,
3427 		MM_STREAM_CLOSE,
3428 		MM_STREAM_DONE,
3429 		MM_STREAM_ERROR,    // = 0x3D7
3430 		MM_MOM_POSITIONCB      = 0x3CA,
3431 		MM_MCISIGNAL,
3432 		MM_MIM_MOREDATA,    // = 0x3CC
3433 		MM_MIXM_LINE_CHANGE    = 0x3D0,
3434 		MM_MIXM_CONTROL_CHANGE = 0x3D1
3435 	}
3436 
3437 
3438 	enum WOM_OPEN  = WinMMMessage.MM_WOM_OPEN;
3439 	enum WOM_CLOSE = WinMMMessage.MM_WOM_CLOSE;
3440 	enum WOM_DONE  = WinMMMessage.MM_WOM_DONE;
3441 	enum WIM_OPEN  = WinMMMessage.MM_WIM_OPEN;
3442 	enum WIM_CLOSE = WinMMMessage.MM_WIM_CLOSE;
3443 	enum WIM_DATA  = WinMMMessage.MM_WIM_DATA;
3444 
3445 
3446 	uint mciSendStringA(const scope char*,char*,uint,void*);
3447 
3448 +/
3449 }
3450 
3451 version(with_resampler) {
3452 	/* Copyright (C) 2007-2008 Jean-Marc Valin
3453 	 * Copyright (C) 2008      Thorvald Natvig
3454 	 * D port by Ketmar // Invisible Vector
3455 	 *
3456 	 * Arbitrary resampling code
3457 	 *
3458 	 * Redistribution and use in source and binary forms, with or without
3459 	 * modification, are permitted provided that the following conditions are
3460 	 * met:
3461 	 *
3462 	 * 1. Redistributions of source code must retain the above copyright notice,
3463 	 * this list of conditions and the following disclaimer.
3464 	 *
3465 	 * 2. Redistributions in binary form must reproduce the above copyright
3466 	 * notice, this list of conditions and the following disclaimer in the
3467 	 * documentation and/or other materials provided with the distribution.
3468 	 *
3469 	 * 3. The name of the author may not be used to endorse or promote products
3470 	 * derived from this software without specific prior written permission.
3471 	 *
3472 	 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
3473 	 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
3474 	 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3475 	 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
3476 	 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
3477 	 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3478 	 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3479 	 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
3480 	 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
3481 	 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3482 	 * POSSIBILITY OF SUCH DAMAGE.
3483 	 */
3484 
3485 	/* A-a-a-and now... D port is covered by the following license!
3486 	 *
3487 	 * This program is free software: you can redistribute it and/or modify
3488 	 * it under the terms of the GNU General Public License as published by
3489 	 * the Free Software Foundation, either version 3 of the License, or
3490 	 * (at your option) any later version.
3491 	 *
3492 	 * This program is distributed in the hope that it will be useful,
3493 	 * but WITHOUT ANY WARRANTY; without even the implied warranty of
3494 	 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3495 	 * GNU General Public License for more details.
3496 	 *
3497 	 * You should have received a copy of the GNU General Public License
3498 	 * along with this program. If not, see <http://www.gnu.org/licenses/>.
3499 	 */
3500 	//module iv.follin.resampler /*is aliced*/;
3501 	//import iv.alice;
3502 
3503 	/*
3504 	   The design goals of this code are:
3505 	      - Very fast algorithm
3506 	      - SIMD-friendly algorithm
3507 	      - Low memory requirement
3508 	      - Good *perceptual* quality (and not best SNR)
3509 
3510 	   Warning: This resampler is relatively new. Although I think I got rid of
3511 	   all the major bugs and I don't expect the API to change anymore, there
3512 	   may be something I've missed. So use with caution.
3513 
3514 	   This algorithm is based on this original resampling algorithm:
3515 	   Smith, Julius O. Digital Audio Resampling Home Page
3516 	   Center for Computer Research in Music and Acoustics (CCRMA),
3517 	   Stanford University, 2007.
3518 	   Web published at http://www-ccrma.stanford.edu/~jos/resample/.
3519 
3520 	   There is one main difference, though. This resampler uses cubic
3521 	   interpolation instead of linear interpolation in the above paper. This
3522 	   makes the table much smaller and makes it possible to compute that table
3523 	   on a per-stream basis. In turn, being able to tweak the table for each
3524 	   stream makes it possible to both reduce complexity on simple ratios
3525 	   (e.g. 2/3), and get rid of the rounding operations in the inner loop.
3526 	   The latter both reduces CPU time and makes the algorithm more SIMD-friendly.
3527 	*/
3528 	version = sincresample_use_full_table;
3529 	version(X86) {
3530 	  version(sincresample_disable_sse) {
3531 	  } else {
3532 	    version(D_PIC) {} else version = sincresample_use_sse;
3533 	  }
3534 	}
3535 
3536 
3537 	// ////////////////////////////////////////////////////////////////////////// //
3538 	public struct SpeexResampler {
3539 	public:
3540 	  alias Quality = int;
3541 	  enum : uint {
3542 	    Fastest = 0,
3543 	    Voip = 3,
3544 	    Default = 4,
3545 	    Desktop = 5,
3546 	    Music = 8,
3547 	    Best = 10,
3548 	  }
3549 
3550 	  enum Error {
3551 	    OK = 0,
3552 	    NoMemory,
3553 	    BadState,
3554 	    BadArgument,
3555 	    BadData,
3556 	  }
3557 
3558 	private:
3559 	nothrow @trusted @nogc:
3560 	  alias ResamplerFn = int function (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint *indataLen, float *outdata, uint *outdataLen);
3561 
3562 	private:
3563 	  uint inRate;
3564 	  uint outRate;
3565 	  uint numRate; // from
3566 	  uint denRate; // to
3567 
3568 	  Quality srQuality;
3569 	  uint chanCount;
3570 	  uint filterLen;
3571 	  uint memAllocSize;
3572 	  uint bufferSize;
3573 	  int intAdvance;
3574 	  int fracAdvance;
3575 	  float cutoff;
3576 	  uint oversample;
3577 	  bool started;
3578 
3579 	  // these are per-channel
3580 	  int[64] lastSample;
3581 	  uint[64] sampFracNum;
3582 	  uint[64] magicSamples;
3583 
3584 	  float* mem;
3585 	  uint realMemLen; // how much memory really allocated
3586 	  float* sincTable;
3587 	  uint sincTableLen;
3588 	  uint realSincTableLen; // how much memory really allocated
3589 	  ResamplerFn resampler;
3590 
3591 	  int inStride;
3592 	  int outStride;
3593 
3594 	public:
3595 	  static string errorStr (int err) {
3596 	    switch (err) with (Error) {
3597 	      case OK: return "success";
3598 	      case NoMemory: return "memory allocation failed";
3599 	      case BadState: return "bad resampler state";
3600 	      case BadArgument: return "invalid argument";
3601 	      case BadData: return "bad data passed";
3602 	      default:
3603 	    }
3604 	    return "unknown error";
3605 	  }
3606 
3607 	public:
3608 	  @disable this (this);
3609 	  ~this () { deinit(); }
3610 
3611 	  @property bool inited () const pure { return (resampler !is null); }
3612 
3613 	  void deinit () {
3614 	    import core.stdc.stdlib : free;
3615 	    if (mem !is null) { free(mem); mem = null; }
3616 	    if (sincTable !is null) { free(sincTable); sincTable = null; }
3617 	    /*
3618 	    memAllocSize = realMemLen = 0;
3619 	    sincTableLen = realSincTableLen = 0;
3620 	    resampler = null;
3621 	    started = false;
3622 	    */
3623 	    inRate = outRate = numRate = denRate = 0;
3624 	    srQuality = cast(Quality)666;
3625 	    chanCount = 0;
3626 	    filterLen = 0;
3627 	    memAllocSize = 0;
3628 	    bufferSize = 0;
3629 	    intAdvance = 0;
3630 	    fracAdvance = 0;
3631 	    cutoff = 0;
3632 	    oversample = 0;
3633 	    started = 0;
3634 
3635 	    mem = null;
3636 	    realMemLen = 0; // how much memory really allocated
3637 	    sincTable = null;
3638 	    sincTableLen = 0;
3639 	    realSincTableLen = 0; // how much memory really allocated
3640 	    resampler = null;
3641 
3642 	    inStride = outStride = 0;
3643 	  }
3644 
3645 	  /** Create a new resampler with integer input and output rates.
3646 	   *
3647 	   * Params:
3648 	   *  chans = Number of channels to be processed
3649 	   *  inRate = Input sampling rate (integer number of Hz).
3650 	   *  outRate = Output sampling rate (integer number of Hz).
3651 	   *  aquality = Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
3652 	   *
3653 	   * Returns:
3654 	   *  0 or error code
3655 	   */
3656 	  Error setup (uint chans, uint ainRate, uint aoutRate, Quality aquality/*, usize line=__LINE__*/) {
3657 	    //{ import core.stdc.stdio; printf("init: %u -> %u at %u\n", ainRate, aoutRate, cast(uint)line); }
3658 	    import core.stdc.stdlib : malloc, free;
3659 
3660 	    deinit();
3661 	    if (aquality < 0) aquality = 0;
3662 	    if (aquality > SpeexResampler.Best) aquality = SpeexResampler.Best;
3663 	    if (chans < 1 || chans > 16) return Error.BadArgument;
3664 
3665 	    started = false;
3666 	    inRate = 0;
3667 	    outRate = 0;
3668 	    numRate = 0;
3669 	    denRate = 0;
3670 	    srQuality = cast(Quality)666; // it's ok
3671 	    sincTableLen = 0;
3672 	    memAllocSize = 0;
3673 	    filterLen = 0;
3674 	    mem = null;
3675 	    resampler = null;
3676 
3677 	    cutoff = 1.0f;
3678 	    chanCount = chans;
3679 	    inStride = 1;
3680 	    outStride = 1;
3681 
3682 	    bufferSize = 160;
3683 
3684 	    // per channel data
3685 	    lastSample[] = 0;
3686 	    magicSamples[] = 0;
3687 	    sampFracNum[] = 0;
3688 
3689 	    setQuality(aquality);
3690 	    setRate(ainRate, aoutRate);
3691 
3692 	    if (auto filterErr = updateFilter()) { deinit(); return filterErr; }
3693 	    skipZeros(); // make sure that the first samples to go out of the resamplers don't have leading zeros
3694 
3695 	    return Error.OK;
3696 	  }
3697 
3698 	  /** Set (change) the input/output sampling rates (integer value).
3699 	   *
3700 	   * Params:
3701 	   *  ainRate = Input sampling rate (integer number of Hz).
3702 	   *  aoutRate = Output sampling rate (integer number of Hz).
3703 	   *
3704 	   * Returns:
3705 	   *  0 or error code
3706 	   */
3707 	  Error setRate (uint ainRate, uint aoutRate/*, usize line=__LINE__*/) {
3708 	    //{ import core.stdc.stdio; printf("changing rate: %u -> %u at %u\n", ainRate, aoutRate, cast(uint)line); }
3709 	    if (inRate == ainRate && outRate == aoutRate) return Error.OK;
3710 	    //{ import core.stdc.stdio; printf("changing rate: %u -> %u at %u\n", ratioNum, ratioDen, cast(uint)line); }
3711 
3712 	    uint oldDen = denRate;
3713 	    inRate = ainRate;
3714 	    outRate = aoutRate;
3715 	    auto div = gcd(ainRate, aoutRate);
3716 	    numRate = ainRate/div;
3717 	    denRate = aoutRate/div;
3718 
3719 	    if (oldDen > 0) {
3720 	      foreach (ref v; sampFracNum.ptr[0..chanCount]) {
3721 		v = v*denRate/oldDen;
3722 		// safety net
3723 		if (v >= denRate) v = denRate-1;
3724 	      }
3725 	    }
3726 
3727 	    return (inited ? updateFilter() : Error.OK);
3728 	  }
3729 
3730 	  /** Get the current input/output sampling rates (integer value).
3731 	   *
3732 	   * Params:
3733 	   *  ainRate = Input sampling rate (integer number of Hz) copied.
3734 	   *  aoutRate = Output sampling rate (integer number of Hz) copied.
3735 	   */
3736 	  void getRate (out uint ainRate, out uint aoutRate) {
3737 	    ainRate = inRate;
3738 	    aoutRate = outRate;
3739 	  }
3740 
3741 	  @property uint getInRate () { return inRate; }
3742 	  @property uint getOutRate () { return outRate; }
3743 
3744 	  @property uint getChans () { return chanCount; }
3745 
3746 	  /** Get the current resampling ratio. This will be reduced to the least common denominator.
3747 	   *
3748 	   * Params:
3749 	   *  ratioNum = Numerator of the sampling rate ratio copied
3750 	   *  ratioDen = Denominator of the sampling rate ratio copied
3751 	   */
3752 	  void getRatio (out uint ratioNum, out uint ratioDen) {
3753 	    ratioNum = numRate;
3754 	    ratioDen = denRate;
3755 	  }
3756 
3757 	  /** Set (change) the conversion quality.
3758 	   *
3759 	   * Params:
3760 	   *  quality = Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
3761 	   *
3762 	   * Returns:
3763 	   *  0 or error code
3764 	   */
3765 	  Error setQuality (Quality aquality) {
3766 	    if (aquality < 0) aquality = 0;
3767 	    if (aquality > SpeexResampler.Best) aquality = SpeexResampler.Best;
3768 	    if (srQuality == aquality) return Error.OK;
3769 	    srQuality = aquality;
3770 	    return (inited ? updateFilter() : Error.OK);
3771 	  }
3772 
3773 	  /** Get the conversion quality.
3774 	   *
3775 	   * Returns:
3776 	   *  Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
3777 	   */
3778 	  int getQuality () { return srQuality; }
3779 
3780 	  /** Get the latency introduced by the resampler measured in input samples.
3781 	   *
3782 	   * Returns:
3783 	   *  Input latency;
3784 	   */
3785 	  int inputLatency () { return filterLen/2; }
3786 
3787 	  /** Get the latency introduced by the resampler measured in output samples.
3788 	   *
3789 	   * Returns:
3790 	   *  Output latency.
3791 	   */
3792 	  int outputLatency () { return ((filterLen/2)*denRate+(numRate>>1))/numRate; }
3793 
3794 	  /* Make sure that the first samples to go out of the resamplers don't have
3795 	   * leading zeros. This is only useful before starting to use a newly created
3796 	   * resampler. It is recommended to use that when resampling an audio file, as
3797 	   * it will generate a file with the same length. For real-time processing,
3798 	   * it is probably easier not to use this call (so that the output duration
3799 	   * is the same for the first frame).
3800 	   *
3801 	   * Setup/reset sequence will automatically call this, so it is private.
3802 	   */
3803 	  private void skipZeros () { foreach (immutable i; 0..chanCount) lastSample.ptr[i] = filterLen/2; }
3804 
3805 	  static struct Data {
3806 	    const(float)[] dataIn;
3807 	    float[] dataOut;
3808 	    uint inputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
3809 	    uint outputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
3810 	  }
3811 
3812 	  /** Resample (an interleaved) float array. The input and output buffers must *not* overlap.
3813 	   * `data.dataIn` can be empty, but `data.dataOut` can't.
3814 	   * Function will return number of consumed samples (*not* *frames*!) in `data.inputSamplesUsed`,
3815 	   * and number of produced samples in `data.outputSamplesUsed`.
3816 	   * You should provide enough samples for all channels, and all channels will be processed.
3817 	   *
3818 	   * Params:
3819 	   *  data = input and output buffers, number of frames consumed and produced
3820 	   *
3821 	   * Returns:
3822 	   *  0 or error code
3823 	   */
3824 	  Error process(string mode="interleaved") (ref Data data) {
3825 	    static assert(mode == "interleaved" || mode == "sequential");
3826 
3827 	    data.inputSamplesUsed = data.outputSamplesUsed = 0;
3828 	    if (!inited) return Error.BadState;
3829 
3830 	    if (data.dataIn.length%chanCount || data.dataOut.length < 1 || data.dataOut.length%chanCount) return Error.BadData;
3831 	    if (data.dataIn.length > uint.max/4 || data.dataOut.length > uint.max/4) return Error.BadData;
3832 
3833 	    static if (mode == "interleaved") {
3834 	      inStride = outStride = chanCount;
3835 	    } else {
3836 	      inStride = outStride = 1;
3837 	    }
3838 	    uint iofs = 0, oofs = 0;
3839 	    immutable uint idclen = cast(uint)(data.dataIn.length/chanCount);
3840 	    immutable uint odclen = cast(uint)(data.dataOut.length/chanCount);
3841 	    foreach (immutable i; 0..chanCount) {
3842 	      data.inputSamplesUsed = idclen;
3843 	      data.outputSamplesUsed = odclen;
3844 	      if (data.dataIn.length) {
3845 		processOneChannel(i, data.dataIn.ptr+iofs, &data.inputSamplesUsed, data.dataOut.ptr+oofs, &data.outputSamplesUsed);
3846 	      } else {
3847 		processOneChannel(i, null, &data.inputSamplesUsed, data.dataOut.ptr+oofs, &data.outputSamplesUsed);
3848 	      }
3849 	      static if (mode == "interleaved") {
3850 		++iofs;
3851 		++oofs;
3852 	      } else {
3853 		iofs += idclen;
3854 		oofs += odclen;
3855 	      }
3856 	    }
3857 	    data.inputSamplesUsed *= chanCount;
3858 	    data.outputSamplesUsed *= chanCount;
3859 	    return Error.OK;
3860 	  }
3861 
3862 
3863 	  //HACK for libswresample
3864 	  // return -1 or number of outframes
3865 	  int swrconvert (float** outbuf, int outframes, const(float)**inbuf, int inframes) {
3866 	    if (!inited || outframes < 1 || inframes < 0) return -1;
3867 	    inStride = outStride = 1;
3868 	    Data data;
3869 	    foreach (immutable i; 0..chanCount) {
3870 	      data.dataIn = (inframes ? inbuf[i][0..inframes] : null);
3871 	      data.dataOut = (outframes ? outbuf[i][0..outframes] : null);
3872 	      data.inputSamplesUsed = inframes;
3873 	      data.outputSamplesUsed = outframes;
3874 	      if (inframes > 0) {
3875 		processOneChannel(i, data.dataIn.ptr, &data.inputSamplesUsed, data.dataOut.ptr, &data.outputSamplesUsed);
3876 	      } else {
3877 		processOneChannel(i, null, &data.inputSamplesUsed, data.dataOut.ptr, &data.outputSamplesUsed);
3878 	      }
3879 	    }
3880 	    return data.outputSamplesUsed;
3881 	  }
3882 
3883 	  /// Reset a resampler so a new (unrelated) stream can be processed.
3884 	  void reset () {
3885 	    lastSample[] = 0;
3886 	    magicSamples[] = 0;
3887 	    sampFracNum[] = 0;
3888 	    //foreach (immutable i; 0..chanCount*(filterLen-1)) mem[i] = 0;
3889 	    if (mem !is null) mem[0..chanCount*(filterLen-1)] = 0;
3890 	    skipZeros(); // make sure that the first samples to go out of the resamplers don't have leading zeros
3891 	  }
3892 
3893 	private:
3894 	  Error processOneChannel (uint chanIdx, const(float)* indata, uint* indataLen, float* outdata, uint* outdataLen) {
3895 	    uint ilen = *indataLen;
3896 	    uint olen = *outdataLen;
3897 	    float* x = mem+chanIdx*memAllocSize;
3898 	    immutable int filterOfs = filterLen-1;
3899 	    immutable uint xlen = memAllocSize-filterOfs;
3900 	    immutable int istride = inStride;
3901 	    if (magicSamples.ptr[chanIdx]) olen -= magic(chanIdx, &outdata, olen);
3902 	    if (!magicSamples.ptr[chanIdx]) {
3903 	      while (ilen && olen) {
3904 		uint ichunk = (ilen > xlen ? xlen : ilen);
3905 		uint ochunk = olen;
3906 		if (indata !is null) {
3907 		  //foreach (immutable j; 0..ichunk) x[j+filterOfs] = indata[j*istride];
3908 		  if (istride == 1) {
3909 		    x[filterOfs..filterOfs+ichunk] = indata[0..ichunk];
3910 		  } else {
3911 		    auto sp = indata;
3912 		    auto dp = x+filterOfs;
3913 		    foreach (immutable j; 0..ichunk) { *dp++ = *sp; sp += istride; }
3914 		  }
3915 		} else {
3916 		  //foreach (immutable j; 0..ichunk) x[j+filterOfs] = 0;
3917 		  x[filterOfs..filterOfs+ichunk] = 0;
3918 		}
3919 		processNative(chanIdx, &ichunk, outdata, &ochunk);
3920 		ilen -= ichunk;
3921 		olen -= ochunk;
3922 		outdata += ochunk*outStride;
3923 		if (indata !is null) indata += ichunk*istride;
3924 	      }
3925 	    }
3926 	    *indataLen -= ilen;
3927 	    *outdataLen -= olen;
3928 	    return Error.OK;
3929 	  }
3930 
3931 	  Error processNative (uint chanIdx, uint* indataLen, float* outdata, uint* outdataLen) {
3932 	    immutable N = filterLen;
3933 	    int outSample = 0;
3934 	    float* x = mem+chanIdx*memAllocSize;
3935 	    uint ilen;
3936 	    started = true;
3937 	    // call the right resampler through the function ptr
3938 	    outSample = resampler(this, chanIdx, x, indataLen, outdata, outdataLen);
3939 	    if (lastSample.ptr[chanIdx] < cast(int)*indataLen) *indataLen = lastSample.ptr[chanIdx];
3940 	    *outdataLen = outSample;
3941 	    lastSample.ptr[chanIdx] -= *indataLen;
3942 	    ilen = *indataLen;
3943 	    foreach (immutable j; 0..N-1) x[j] = x[j+ilen];
3944 	    return Error.OK;
3945 	  }
3946 
3947 	  int magic (uint chanIdx, float **outdata, uint outdataLen) {
3948 	    uint tempInLen = magicSamples.ptr[chanIdx];
3949 	    float* x = mem+chanIdx*memAllocSize;
3950 	    processNative(chanIdx, &tempInLen, *outdata, &outdataLen);
3951 	    magicSamples.ptr[chanIdx] -= tempInLen;
3952 	    // if we couldn't process all "magic" input samples, save the rest for next time
3953 	    if (magicSamples.ptr[chanIdx]) {
3954 	      immutable N = filterLen;
3955 	      foreach (immutable i; 0..magicSamples.ptr[chanIdx]) x[N-1+i] = x[N-1+i+tempInLen];
3956 	    }
3957 	    *outdata += outdataLen*outStride;
3958 	    return outdataLen;
3959 	  }
3960 
3961 	  Error updateFilter () {
3962 	    uint oldFilterLen = filterLen;
3963 	    uint oldAllocSize = memAllocSize;
3964 	    bool useDirect;
3965 	    uint minSincTableLen;
3966 	    uint minAllocSize;
3967 
3968 	    intAdvance = numRate/denRate;
3969 	    fracAdvance = numRate%denRate;
3970 	    oversample = qualityMap.ptr[srQuality].oversample;
3971 	    filterLen = qualityMap.ptr[srQuality].baseLength;
3972 
3973 	    if (numRate > denRate) {
3974 	      // down-sampling
3975 	      cutoff = qualityMap.ptr[srQuality].downsampleBandwidth*denRate/numRate;
3976 	      // FIXME: divide the numerator and denominator by a certain amount if they're too large
3977 	      filterLen = filterLen*numRate/denRate;
3978 	      // round up to make sure we have a multiple of 8 for SSE
3979 	      filterLen = ((filterLen-1)&(~0x7))+8;
3980 	      if (2*denRate < numRate) oversample >>= 1;
3981 	      if (4*denRate < numRate) oversample >>= 1;
3982 	      if (8*denRate < numRate) oversample >>= 1;
3983 	      if (16*denRate < numRate) oversample >>= 1;
3984 	      if (oversample < 1) oversample = 1;
3985 	    } else {
3986 	      // up-sampling
3987 	      cutoff = qualityMap.ptr[srQuality].upsampleBandwidth;
3988 	    }
3989 
3990 	    // choose the resampling type that requires the least amount of memory
3991 	    version(sincresample_use_full_table) {
3992 	      useDirect = true;
3993 	      if (int.max/float.sizeof/denRate < filterLen) goto fail;
3994 	    } else {
3995 	      useDirect = (filterLen*denRate <= filterLen*oversample+8 && int.max/float.sizeof/denRate >= filterLen);
3996 	    }
3997 
3998 	    if (useDirect) {
3999 	      minSincTableLen = filterLen*denRate;
4000 	    } else {
4001 	      if ((int.max/float.sizeof-8)/oversample < filterLen) goto fail;
4002 	      minSincTableLen = filterLen*oversample+8;
4003 	    }
4004 
4005 	    if (sincTableLen < minSincTableLen) {
4006 	      import core.stdc.stdlib : realloc;
4007 	      auto nslen = cast(uint)(minSincTableLen*float.sizeof);
4008 	      if (nslen > realSincTableLen) {
4009 		if (nslen < 512*1024) nslen = 512*1024; // inc to 3 mb?
4010 		auto x = cast(float*)realloc(sincTable, nslen);
4011 		if (!x) goto fail;
4012 		sincTable = x;
4013 		realSincTableLen = nslen;
4014 	      }
4015 	      sincTableLen = minSincTableLen;
4016 	    }
4017 
4018 	    if (useDirect) {
4019 	      foreach (int i; 0..denRate) {
4020 		foreach (int j; 0..filterLen) {
4021 		  sincTable[i*filterLen+j] = sinc(cutoff, ((j-cast(int)filterLen/2+1)-(cast(float)i)/denRate), filterLen, qualityMap.ptr[srQuality].windowFunc);
4022 		}
4023 	      }
4024 	      if (srQuality > 8) {
4025 		resampler = &resamplerBasicDirect!double;
4026 	      } else {
4027 		resampler = &resamplerBasicDirect!float;
4028 	      }
4029 	    } else {
4030 	      foreach (immutable int i; -4..cast(int)(oversample*filterLen+4)) {
4031 		sincTable[i+4] = sinc(cutoff, (i/cast(float)oversample-filterLen/2), filterLen, qualityMap.ptr[srQuality].windowFunc);
4032 	      }
4033 	      if (srQuality > 8) {
4034 		resampler = &resamplerBasicInterpolate!double;
4035 	      } else {
4036 		resampler = &resamplerBasicInterpolate!float;
4037 	      }
4038 	    }
4039 
4040 	    /* Here's the place where we update the filter memory to take into account
4041 	       the change in filter length. It's probably the messiest part of the code
4042 	       due to handling of lots of corner cases. */
4043 
4044 	    // adding bufferSize to filterLen won't overflow here because filterLen could be multiplied by float.sizeof above
4045 	    minAllocSize = filterLen-1+bufferSize;
4046 	    if (minAllocSize > memAllocSize) {
4047 	      import core.stdc.stdlib : realloc;
4048 	      if (int.max/float.sizeof/chanCount < minAllocSize) goto fail;
4049 	      auto nslen = cast(uint)(chanCount*minAllocSize*mem[0].sizeof);
4050 	      if (nslen > realMemLen) {
4051 		if (nslen < 16384) nslen = 16384;
4052 		auto x = cast(float*)realloc(mem, nslen);
4053 		if (x is null) goto fail;
4054 		mem = x;
4055 		realMemLen = nslen;
4056 	      }
4057 	      memAllocSize = minAllocSize;
4058 	    }
4059 	    if (!started) {
4060 	      //foreach (i=0;i<chanCount*memAllocSize;i++) mem[i] = 0;
4061 	      mem[0..chanCount*memAllocSize] = 0;
4062 	    } else if (filterLen > oldFilterLen) {
4063 	      // increase the filter length
4064 	      foreach_reverse (uint i; 0..chanCount) {
4065 		uint j;
4066 		uint olen = oldFilterLen;
4067 		{
4068 		  // try and remove the magic samples as if nothing had happened
4069 		  //FIXME: this is wrong but for now we need it to avoid going over the array bounds
4070 		  olen = oldFilterLen+2*magicSamples.ptr[i];
4071 		  for (j = oldFilterLen-1+magicSamples.ptr[i]; j--; ) mem[i*memAllocSize+j+magicSamples.ptr[i]] = mem[i*oldAllocSize+j];
4072 		  //for (j = 0; j < magicSamples.ptr[i]; ++j) mem[i*memAllocSize+j] = 0;
4073 		  mem[i*memAllocSize..i*memAllocSize+magicSamples.ptr[i]] = 0;
4074 		  magicSamples.ptr[i] = 0;
4075 		}
4076 		if (filterLen > olen) {
4077 		  // if the new filter length is still bigger than the "augmented" length
4078 		  // copy data going backward
4079 		  for (j = 0; j < olen-1; ++j) mem[i*memAllocSize+(filterLen-2-j)] = mem[i*memAllocSize+(olen-2-j)];
4080 		  // then put zeros for lack of anything better
4081 		  for (; j < filterLen-1; ++j) mem[i*memAllocSize+(filterLen-2-j)] = 0;
4082 		  // adjust lastSample
4083 		  lastSample.ptr[i] += (filterLen-olen)/2;
4084 		} else {
4085 		  // put back some of the magic!
4086 		  magicSamples.ptr[i] = (olen-filterLen)/2;
4087 		  for (j = 0; j < filterLen-1+magicSamples.ptr[i]; ++j) mem[i*memAllocSize+j] = mem[i*memAllocSize+j+magicSamples.ptr[i]];
4088 		}
4089 	      }
4090 	    } else if (filterLen < oldFilterLen) {
4091 	      // reduce filter length, this a bit tricky
4092 	      // we need to store some of the memory as "magic" samples so they can be used directly as input the next time(s)
4093 	      foreach (immutable i; 0..chanCount) {
4094 		uint j;
4095 		uint oldMagic = magicSamples.ptr[i];
4096 		magicSamples.ptr[i] = (oldFilterLen-filterLen)/2;
4097 		// we must copy some of the memory that's no longer used
4098 		// copy data going backward
4099 		for (j = 0; j < filterLen-1+magicSamples.ptr[i]+oldMagic; ++j) {
4100 		  mem[i*memAllocSize+j] = mem[i*memAllocSize+j+magicSamples.ptr[i]];
4101 		}
4102 		magicSamples.ptr[i] += oldMagic;
4103 	      }
4104 	    }
4105 	    return Error.OK;
4106 
4107 	  fail:
4108 	    resampler = null;
4109 	    /* mem may still contain consumed input samples for the filter.
4110 	       Restore filterLen so that filterLen-1 still points to the position after
4111 	       the last of these samples. */
4112 	    filterLen = oldFilterLen;
4113 	    return Error.NoMemory;
4114 	  }
4115 	}
4116 
4117 
4118 	// ////////////////////////////////////////////////////////////////////////// //
4119 	static immutable double[68] kaiser12Table = [
4120 	  0.99859849, 1.00000000, 0.99859849, 0.99440475, 0.98745105, 0.97779076,
4121 	  0.96549770, 0.95066529, 0.93340547, 0.91384741, 0.89213598, 0.86843014,
4122 	  0.84290116, 0.81573067, 0.78710866, 0.75723148, 0.72629970, 0.69451601,
4123 	  0.66208321, 0.62920216, 0.59606986, 0.56287762, 0.52980938, 0.49704014,
4124 	  0.46473455, 0.43304576, 0.40211431, 0.37206735, 0.34301800, 0.31506490,
4125 	  0.28829195, 0.26276832, 0.23854851, 0.21567274, 0.19416736, 0.17404546,
4126 	  0.15530766, 0.13794294, 0.12192957, 0.10723616, 0.09382272, 0.08164178,
4127 	  0.07063950, 0.06075685, 0.05193064, 0.04409466, 0.03718069, 0.03111947,
4128 	  0.02584161, 0.02127838, 0.01736250, 0.01402878, 0.01121463, 0.00886058,
4129 	  0.00691064, 0.00531256, 0.00401805, 0.00298291, 0.00216702, 0.00153438,
4130 	  0.00105297, 0.00069463, 0.00043489, 0.00025272, 0.00013031, 0.0000527734,
4131 	  0.00001000, 0.00000000];
4132 
4133 	static immutable double[36] kaiser10Table = [
4134 	  0.99537781, 1.00000000, 0.99537781, 0.98162644, 0.95908712, 0.92831446,
4135 	  0.89005583, 0.84522401, 0.79486424, 0.74011713, 0.68217934, 0.62226347,
4136 	  0.56155915, 0.50119680, 0.44221549, 0.38553619, 0.33194107, 0.28205962,
4137 	  0.23636152, 0.19515633, 0.15859932, 0.12670280, 0.09935205, 0.07632451,
4138 	  0.05731132, 0.04193980, 0.02979584, 0.02044510, 0.01345224, 0.00839739,
4139 	  0.00488951, 0.00257636, 0.00115101, 0.00035515, 0.00000000, 0.00000000];
4140 
4141 	static immutable double[36] kaiser8Table = [
4142 	  0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200,
4143 	  0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126,
4144 	  0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272,
4145 	  0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758,
4146 	  0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490,
4147 	  0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000];
4148 
4149 	static immutable double[36] kaiser6Table = [
4150 	  0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003,
4151 	  0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565,
4152 	  0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561,
4153 	  0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058,
4154 	  0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600,
4155 	  0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000];
4156 
4157 	struct FuncDef {
4158 	  immutable(double)* table;
4159 	  int oversample;
4160 	}
4161 
4162 	static immutable FuncDef Kaiser12 = FuncDef(kaiser12Table.ptr, 64);
4163 	static immutable FuncDef Kaiser10 = FuncDef(kaiser10Table.ptr, 32);
4164 	static immutable FuncDef Kaiser8 = FuncDef(kaiser8Table.ptr, 32);
4165 	static immutable FuncDef Kaiser6 = FuncDef(kaiser6Table.ptr, 32);
4166 
4167 
4168 	struct QualityMapping {
4169 	  int baseLength;
4170 	  int oversample;
4171 	  float downsampleBandwidth;
4172 	  float upsampleBandwidth;
4173 	  immutable FuncDef* windowFunc;
4174 	}
4175 
4176 
4177 	/* This table maps conversion quality to internal parameters. There are two
4178 	   reasons that explain why the up-sampling bandwidth is larger than the
4179 	   down-sampling bandwidth:
4180 	   1) When up-sampling, we can assume that the spectrum is already attenuated
4181 	      close to the Nyquist rate (from an A/D or a previous resampling filter)
4182 	   2) Any aliasing that occurs very close to the Nyquist rate will be masked
4183 	      by the sinusoids/noise just below the Nyquist rate (guaranteed only for
4184 	      up-sampling).
4185 	*/
4186 	static immutable QualityMapping[11] qualityMap = [
4187 	  QualityMapping(  8,  4, 0.830f, 0.860f, &Kaiser6 ), /* Q0 */
4188 	  QualityMapping( 16,  4, 0.850f, 0.880f, &Kaiser6 ), /* Q1 */
4189 	  QualityMapping( 32,  4, 0.882f, 0.910f, &Kaiser6 ), /* Q2 */  /* 82.3% cutoff ( ~60 dB stop) 6  */
4190 	  QualityMapping( 48,  8, 0.895f, 0.917f, &Kaiser8 ), /* Q3 */  /* 84.9% cutoff ( ~80 dB stop) 8  */
4191 	  QualityMapping( 64,  8, 0.921f, 0.940f, &Kaiser8 ), /* Q4 */  /* 88.7% cutoff ( ~80 dB stop) 8  */
4192 	  QualityMapping( 80, 16, 0.922f, 0.940f, &Kaiser10), /* Q5 */  /* 89.1% cutoff (~100 dB stop) 10 */
4193 	  QualityMapping( 96, 16, 0.940f, 0.945f, &Kaiser10), /* Q6 */  /* 91.5% cutoff (~100 dB stop) 10 */
4194 	  QualityMapping(128, 16, 0.950f, 0.950f, &Kaiser10), /* Q7 */  /* 93.1% cutoff (~100 dB stop) 10 */
4195 	  QualityMapping(160, 16, 0.960f, 0.960f, &Kaiser10), /* Q8 */  /* 94.5% cutoff (~100 dB stop) 10 */
4196 	  QualityMapping(192, 32, 0.968f, 0.968f, &Kaiser12), /* Q9 */  /* 95.5% cutoff (~100 dB stop) 10 */
4197 	  QualityMapping(256, 32, 0.975f, 0.975f, &Kaiser12), /* Q10 */ /* 96.6% cutoff (~100 dB stop) 10 */
4198 	];
4199 
4200 
4201 	nothrow @trusted @nogc:
4202 	/*8, 24, 40, 56, 80, 104, 128, 160, 200, 256, 320*/
4203 	double computeFunc (float x, immutable FuncDef* func) {
4204 	  version(Posix) import core.stdc.math : lrintf;
4205 	  import std.math : floor;
4206 	  //double[4] interp;
4207 	  float y = x*func.oversample;
4208 	  version(Posix) {
4209 	    int ind = cast(int)lrintf(floor(y));
4210 	  } else {
4211 	    int ind = cast(int)(floor(y));
4212 	  }
4213 	  float frac = (y-ind);
4214 	  immutable f2 = frac*frac;
4215 	  immutable f3 = f2*frac;
4216 	  double interp3 = -0.1666666667*frac+0.1666666667*(f3);
4217 	  double interp2 = frac+0.5*(f2)-0.5*(f3);
4218 	  //double interp2 = 1.0f-0.5f*frac-f2+0.5f*f3;
4219 	  double interp0 = -0.3333333333*frac+0.5*(f2)-0.1666666667*(f3);
4220 	  // just to make sure we don't have rounding problems
4221 	  double interp1 = 1.0f-interp3-interp2-interp0;
4222 	  //sum = frac*accum[1]+(1-frac)*accum[2];
4223 	  return interp0*func.table[ind]+interp1*func.table[ind+1]+interp2*func.table[ind+2]+interp3*func.table[ind+3];
4224 	}
4225 
4226 
4227 	// the slow way of computing a sinc for the table; should improve that some day
4228 	float sinc (float cutoff, float x, int N, immutable FuncDef *windowFunc) {
4229 	  version(LittleEndian) {
4230 	    align(1) union temp_float { align(1): float f; uint n; }
4231 	  } else {
4232 	    static T fabs(T) (T n) pure { static if (__VERSION__ > 2067) pragma(inline, true); return (n < 0 ? -n : n); }
4233 	  }
4234 	  import std.math : sin, PI;
4235 	  version(LittleEndian) {
4236 	    temp_float txx = void;
4237 	    txx.f = x;
4238 	    txx.n &= 0x7fff_ffff; // abs
4239 	    if (txx.f < 1.0e-6f) return cutoff;
4240 	    if (txx.f > 0.5f*N) return 0;
4241 	  } else {
4242 	    if (fabs(x) < 1.0e-6f) return cutoff;
4243 	    if (fabs(x) > 0.5f*N) return 0;
4244 	  }
4245 	  //FIXME: can it really be any slower than this?
4246 	  immutable float xx = x*cutoff;
4247 	  immutable pixx = PI*xx;
4248 	  version(LittleEndian) {
4249 	    return cutoff*sin(pixx)/pixx*computeFunc(2.0*txx.f/N, windowFunc);
4250 	  } else {
4251 	    return cutoff*sin(pixx)/pixx*computeFunc(fabs(2.0*x/N), windowFunc);
4252 	  }
4253 	}
4254 
4255 
4256 	void cubicCoef (in float frac, float* interp) {
4257 	  immutable f2 = frac*frac;
4258 	  immutable f3 = f2*frac;
4259 	  // compute interpolation coefficients; i'm not sure whether this corresponds to cubic interpolation but I know it's MMSE-optimal on a sinc
4260 	  interp[0] =  -0.16667f*frac+0.16667f*f3;
4261 	  interp[1] = frac+0.5f*f2-0.5f*f3;
4262 	  //interp[2] = 1.0f-0.5f*frac-f2+0.5f*f3;
4263 	  interp[3] = -0.33333f*frac+0.5f*f2-0.16667f*f3;
4264 	  // just to make sure we don't have rounding problems
4265 	  interp[2] = 1.0-interp[0]-interp[1]-interp[3];
4266 	}
4267 
4268 
4269 	// ////////////////////////////////////////////////////////////////////////// //
4270 	int resamplerBasicDirect(T) (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint* indataLen, float* outdata, uint* outdataLen)
4271 	if (is(T == float) || is(T == double))
4272 	{
4273 	  auto N = st.filterLen;
4274 	  static if (is(T == double)) assert(N%4 == 0);
4275 	  int outSample = 0;
4276 	  int lastSample = st.lastSample.ptr[chanIdx];
4277 	  uint sampFracNum = st.sampFracNum.ptr[chanIdx];
4278 	  const(float)* sincTable = st.sincTable;
4279 	  immutable outStride = st.outStride;
4280 	  immutable intAdvance = st.intAdvance;
4281 	  immutable fracAdvance = st.fracAdvance;
4282 	  immutable denRate = st.denRate;
4283 	  T sum = void;
4284 	  while (!(lastSample >= cast(int)(*indataLen) || outSample >= cast(int)(*outdataLen))) {
4285 	    const(float)* sinct = &sincTable[sampFracNum*N];
4286 	    const(float)* iptr = &indata[lastSample];
4287 	    static if (is(T == float)) {
4288 	      // at least 2x speedup with SSE here (but for unrolled loop)
4289 	      if (N%4 == 0) {
4290 		version(sincresample_use_sse) {
4291 		  //align(64) __gshared float[4] zero = 0;
4292 		  align(64) __gshared float[4+128] zeroesBuf = 0; // dmd cannot into such aligns, alas
4293 		  __gshared uint zeroesptr = 0;
4294 		  if (zeroesptr == 0) {
4295 		    zeroesptr = cast(uint)zeroesBuf.ptr;
4296 		    if (zeroesptr&0x3f) zeroesptr = (zeroesptr|0x3f)+1;
4297 		  }
4298 		  //assert((zeroesptr&0x3f) == 0, "wtf?!");
4299 		  asm nothrow @safe @nogc {
4300 		    mov       ECX,[N];
4301 		    shr       ECX,2;
4302 		    mov       EAX,[zeroesptr];
4303 		    movaps    XMM0,[EAX];
4304 		    mov       EAX,[sinct];
4305 		    mov       EBX,[iptr];
4306 		    mov       EDX,16;
4307 		    align 8;
4308 		   rbdseeloop:
4309 		    movups    XMM1,[EAX];
4310 		    movups    XMM2,[EBX];
4311 		    mulps     XMM1,XMM2;
4312 		    addps     XMM0,XMM1;
4313 		    add       EAX,EDX;
4314 		    add       EBX,EDX;
4315 		    dec       ECX;
4316 		    jnz       rbdseeloop;
4317 		    // store result in sum
4318 		    movhlps   XMM1,XMM0; // now low part of XMM1 contains high part of XMM0
4319 		    addps     XMM0,XMM1; // low part of XMM0 is ok
4320 		    movaps    XMM1,XMM0;
4321 		    shufps    XMM1,XMM0,0b_01_01_01_01; // 2nd float of XMM0 goes to the 1st float of XMM1
4322 		    addss     XMM0,XMM1;
4323 		    movss     [sum],XMM0;
4324 		  }
4325 		  /*
4326 		  float sum1 = 0;
4327 		  foreach (immutable j; 0..N) sum1 += sinct[j]*iptr[j];
4328 		  import std.math;
4329 		  if (fabs(sum-sum1) > 0.000001f) {
4330 		    import core.stdc.stdio;
4331 		    printf("sum=%f; sum1=%f\n", sum, sum1);
4332 		    assert(0);
4333 		  }
4334 		  */
4335 		} else {
4336 		  // no SSE; for my i3 unrolled loop is almost of the speed of SSE code
4337 		  T[4] accum = 0;
4338 		  foreach (immutable j; 0..N/4) {
4339 		    accum.ptr[0] += *sinct++ * *iptr++;
4340 		    accum.ptr[1] += *sinct++ * *iptr++;
4341 		    accum.ptr[2] += *sinct++ * *iptr++;
4342 		    accum.ptr[3] += *sinct++ * *iptr++;
4343 		  }
4344 		  sum = accum.ptr[0]+accum.ptr[1]+accum.ptr[2]+accum.ptr[3];
4345 		}
4346 	      } else {
4347 		sum = 0;
4348 		foreach (immutable j; 0..N) sum += *sinct++ * *iptr++;
4349 	      }
4350 	      outdata[outStride*outSample++] = sum;
4351 	    } else {
4352 	      if (N%4 == 0) {
4353 		//TODO: write SSE code here!
4354 		// for my i3 unrolled loop is ~2 times faster
4355 		T[4] accum = 0;
4356 		foreach (immutable j; 0..N/4) {
4357 		  accum.ptr[0] += cast(double)*sinct++ * cast(double)*iptr++;
4358 		  accum.ptr[1] += cast(double)*sinct++ * cast(double)*iptr++;
4359 		  accum.ptr[2] += cast(double)*sinct++ * cast(double)*iptr++;
4360 		  accum.ptr[3] += cast(double)*sinct++ * cast(double)*iptr++;
4361 		}
4362 		sum = accum.ptr[0]+accum.ptr[1]+accum.ptr[2]+accum.ptr[3];
4363 	      } else {
4364 		sum = 0;
4365 		foreach (immutable j; 0..N) sum += cast(double)*sinct++ * cast(double)*iptr++;
4366 	      }
4367 	      outdata[outStride*outSample++] = cast(float)sum;
4368 	    }
4369 	    lastSample += intAdvance;
4370 	    sampFracNum += fracAdvance;
4371 	    if (sampFracNum >= denRate) {
4372 	      sampFracNum -= denRate;
4373 	      ++lastSample;
4374 	    }
4375 	  }
4376 	  st.lastSample.ptr[chanIdx] = lastSample;
4377 	  st.sampFracNum.ptr[chanIdx] = sampFracNum;
4378 	  return outSample;
4379 	}
4380 
4381 
4382 	int resamplerBasicInterpolate(T) (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint *indataLen, float *outdata, uint *outdataLen)
4383 	if (is(T == float) || is(T == double))
4384 	{
4385 	  immutable N = st.filterLen;
4386 	  assert(N%4 == 0);
4387 	  int outSample = 0;
4388 	  int lastSample = st.lastSample.ptr[chanIdx];
4389 	  uint sampFracNum = st.sampFracNum.ptr[chanIdx];
4390 	  immutable outStride = st.outStride;
4391 	  immutable intAdvance = st.intAdvance;
4392 	  immutable fracAdvance = st.fracAdvance;
4393 	  immutable denRate = st.denRate;
4394 	  float sum;
4395 
4396 	  float[4] interp = void;
4397 	  T[4] accum = void;
4398 	  while (!(lastSample >= cast(int)(*indataLen) || outSample >= cast(int)(*outdataLen))) {
4399 	    const(float)* iptr = &indata[lastSample];
4400 	    const int offset = sampFracNum*st.oversample/st.denRate;
4401 	    const float frac = (cast(float)((sampFracNum*st.oversample)%st.denRate))/st.denRate;
4402 	    accum[] = 0;
4403 	    //TODO: optimize!
4404 	    foreach (immutable j; 0..N) {
4405 	      immutable T currIn = iptr[j];
4406 	      accum.ptr[0] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset-2]);
4407 	      accum.ptr[1] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset-1]);
4408 	      accum.ptr[2] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset]);
4409 	      accum.ptr[3] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset+1]);
4410 	    }
4411 
4412 	    cubicCoef(frac, interp.ptr);
4413 	    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]);
4414 
4415 	    outdata[outStride*outSample++] = sum;
4416 	    lastSample += intAdvance;
4417 	    sampFracNum += fracAdvance;
4418 	    if (sampFracNum >= denRate) {
4419 	      sampFracNum -= denRate;
4420 	      ++lastSample;
4421 	    }
4422 	  }
4423 
4424 	  st.lastSample.ptr[chanIdx] = lastSample;
4425 	  st.sampFracNum.ptr[chanIdx] = sampFracNum;
4426 	  return outSample;
4427 	}
4428 
4429 
4430 	// ////////////////////////////////////////////////////////////////////////// //
4431 	uint gcd (uint a, uint b) pure {
4432 	  if (a == 0) return b;
4433 	  if (b == 0) return a;
4434 	  for (;;) {
4435 	    if (a > b) {
4436 	      a %= b;
4437 	      if (a == 0) return b;
4438 	      if (a == 1) return 1;
4439 	    } else {
4440 	      b %= a;
4441 	      if (b == 0) return a;
4442 	      if (b == 1) return 1;
4443 	    }
4444 	  }
4445 	}
4446 
4447 
4448 	// ////////////////////////////////////////////////////////////////////////// //
4449 	// very simple and cheap cubic upsampler
4450 	struct CubicUpsampler {
4451 	public:
4452 	nothrow @trusted @nogc:
4453 	  float[2] curposfrac; // current position offset [0..1)
4454 	  float step; // how long we should move on one step?
4455 	  float[4][2] data; // -1..3
4456 	  uint[2] drain;
4457 
4458 	  void reset () {
4459 	    curposfrac[] = 0.0f;
4460 	    foreach (ref d; data) d[] = 0.0f;
4461 	    drain[] = 0;
4462 	  }
4463 
4464 	  bool setup (float astep) {
4465 	    if (astep >= 1.0f) return false;
4466 	    step = astep;
4467 	    return true;
4468 	  }
4469 
4470 	  /*
4471 	  static struct Data {
4472 	    const(float)[] dataIn;
4473 	    float[] dataOut;
4474 	    uint inputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
4475 	    uint outputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
4476 	  }
4477 	  */
4478 
4479 	  SpeexResampler.Error process (ref SpeexResampler.Data d) {
4480 	    d.inputSamplesUsed = d.outputSamplesUsed = 0;
4481 	    if (d.dataOut.length < 2) return SpeexResampler.Error.OK;
4482 	    foreach (uint cidx; 0..2) {
4483 	      uint inleft = cast(uint)d.dataIn.length/2;
4484 	      uint outleft = cast(uint)d.dataOut.length/2;
4485 	      processChannel(inleft, outleft, (d.dataIn.length ? d.dataIn.ptr+cidx : null), (d.dataOut.length ? d.dataOut.ptr+cidx : null), cidx);
4486 	      d.outputSamplesUsed += cast(uint)(d.dataOut.length/2)-outleft;
4487 	      d.inputSamplesUsed += cast(uint)(d.dataIn.length/2)-inleft;
4488 	    }
4489 	    return SpeexResampler.Error.OK;
4490 	  }
4491 
4492 	  private void processChannel (ref uint inleft, ref uint outleft, const(float)* dataIn, float* dataOut, uint cidx) {
4493 	    if (outleft == 0) return;
4494 	    if (inleft == 0 && drain.ptr[cidx] <= 1) return;
4495 	    auto dt = data.ptr[cidx].ptr;
4496 	    auto drn = drain.ptr+cidx;
4497 	    auto cpf = curposfrac.ptr+cidx;
4498 	    immutable float st = step;
4499 	    for (;;) {
4500 	      // fill buffer
4501 	      while ((*drn) < 4) {
4502 		if (inleft == 0) return;
4503 		dt[(*drn)++] = *dataIn;
4504 		dataIn += 2;
4505 		--inleft;
4506 	      }
4507 	      if (outleft == 0) return;
4508 	      --outleft;
4509 	      // cubic interpolation
4510 	      /*version(none)*/ {
4511 		// interpolate between y1 and y2
4512 		immutable float mu = (*cpf); // how far we are moved from y1 to y2
4513 		immutable float mu2 = mu*mu; // wow
4514 		immutable float y0 = dt[0], y1 = dt[1], y2 = dt[2], y3 = dt[3];
4515 		version(complex_cubic) {
4516 		  immutable float z0 = 0.5*y3;
4517 		  immutable float z1 = 0.5*y0;
4518 		  immutable float a0 = 1.5*y1-z1-1.5*y2+z0;
4519 		  immutable float a1 = y0-2.5*y1+2*y2-z0;
4520 		  immutable float a2 = 0.5*y2-z1;
4521 		} else {
4522 		  immutable float a0 = y3-y2-y0+y1;
4523 		  immutable float a1 = y0-y1-a0;
4524 		  immutable float a2 = y2-y0;
4525 		}
4526 		*dataOut = a0*mu*mu2+a1*mu2+a2*mu+y1;
4527 	      }// else *dataOut = dt[1];
4528 	      dataOut += 2;
4529 	      if (((*cpf) += st) >= 1.0f) {
4530 		(*cpf) -= 1.0f;
4531 		dt[0] = dt[1];
4532 		dt[1] = dt[2];
4533 		dt[2] = dt[3];
4534 		dt[3] = 0.0f;
4535 		--(*drn); // will request more input bytes
4536 	      }
4537 	    }
4538 	  }
4539 	}
4540 }
4541 
4542 version(with_resampler)
4543 abstract class ResamplingContext {
4544 	int inputSampleRate;
4545 	int outputSampleRate;
4546 
4547 	int inputChannels;
4548 	int outputChannels;
4549 
4550 	SpeexResampler resamplerLeft;
4551 	SpeexResampler resamplerRight;
4552 
4553 	SpeexResampler.Data resamplerDataLeft;
4554 	SpeexResampler.Data resamplerDataRight;
4555 
4556 	float[][2] buffersIn;
4557 	float[][2] buffersOut;
4558 
4559 	uint rateNum;
4560 	uint rateDem;
4561 
4562 	float[][2] dataReady;
4563 
4564 	SampleControlFlags scflags;
4565 
4566 	this(SampleControlFlags scflags, int inputSampleRate, int outputSampleRate, int inputChannels, int outputChannels) {
4567 		this.scflags = scflags;
4568 		this.inputSampleRate = inputSampleRate;
4569 		this.outputSampleRate = outputSampleRate;
4570 		this.inputChannels = inputChannels;
4571 		this.outputChannels = outputChannels;
4572 
4573 
4574 		if(auto err = resamplerLeft.setup(1, inputSampleRate, outputSampleRate, 5))
4575 			throw new Exception("ugh");
4576 		resamplerRight.setup(1, inputSampleRate, outputSampleRate, 5);
4577 
4578 		processNewRate();
4579 	}
4580 
4581 	void changePlaybackSpeed(float newMultiplier) {
4582 		resamplerLeft.setRate(cast(uint) (inputSampleRate * newMultiplier), outputSampleRate);
4583 		resamplerRight.setRate(cast(uint) (inputSampleRate * newMultiplier), outputSampleRate);
4584 
4585 		processNewRate();
4586 	}
4587 
4588 	void processNewRate() {
4589 		resamplerLeft.getRatio(rateNum, rateDem);
4590 
4591 		int add = (rateNum % rateDem) ? 1 : 0;
4592 
4593 		buffersIn[0] = new float[](BUFFER_SIZE_FRAMES * rateNum / rateDem + add);
4594 		buffersOut[0] = new float[](BUFFER_SIZE_FRAMES);
4595 		if(inputChannels > 1) {
4596 			buffersIn[1] = new float[](BUFFER_SIZE_FRAMES * rateNum / rateDem + add);
4597 			buffersOut[1] = new float[](BUFFER_SIZE_FRAMES);
4598 		}
4599 
4600 	}
4601 
4602 	/+
4603 		float*[2] tmp;
4604 		tmp[0] = buffersIn[0].ptr;
4605 		tmp[1] = buffersIn[1].ptr;
4606 
4607 		auto actuallyGot = v.getSamplesFloat(v.chans, tmp.ptr, cast(int) buffersIn[0].length);
4608 
4609 		resamplerDataLeft.dataIn should be a slice of buffersIn[0] that is filled up
4610 		ditto for resamplerDataRight if the source has two channels
4611 	+/
4612 	abstract void loadMoreSamples();
4613 
4614 	bool loadMore() {
4615 		resamplerDataLeft.dataIn = buffersIn[0];
4616 		resamplerDataLeft.dataOut = buffersOut[0];
4617 
4618 		resamplerDataRight.dataIn = buffersIn[1];
4619 		resamplerDataRight.dataOut = buffersOut[1];
4620 
4621 		loadMoreSamples();
4622 
4623 		//resamplerLeft.reset();
4624 
4625 		if(auto err = resamplerLeft.process(resamplerDataLeft))
4626 			throw new Exception("ugh");
4627 		if(inputChannels > 1)
4628 			//resamplerRight.reset();
4629 			resamplerRight.process(resamplerDataRight);
4630 
4631 		resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[0 .. resamplerDataLeft.outputSamplesUsed];
4632 		resamplerDataRight.dataOut = resamplerDataRight.dataOut[0 .. resamplerDataRight.outputSamplesUsed];
4633 
4634 		if(resamplerDataLeft.dataOut.length == 0) {
4635 			return true;
4636 		}
4637 		return false;
4638 	}
4639 
4640 
4641 	bool fillBuffer(short[] buffer) {
4642 		if(cast(int) buffer.length != buffer.length)
4643 			throw new Exception("eeeek");
4644 
4645 		if(scflags.paused) {
4646 			buffer[] = 0;
4647 			return true;
4648 		}
4649 
4650 		if(outputChannels == 1) {
4651 			foreach(ref s; buffer) {
4652 				if(resamplerDataLeft.dataOut.length == 0) {
4653 					if(loadMore()) {
4654 						scflags.finished_ = true;
4655 						return false;
4656 					}
4657 				}
4658 
4659 				if(inputChannels == 1) {
4660 					s = cast(short) (resamplerDataLeft.dataOut[0] * short.max);
4661 					resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
4662 				} else {
4663 					s = cast(short) ((resamplerDataLeft.dataOut[0] + resamplerDataRight.dataOut[0]) * short.max / 2);
4664 
4665 					resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
4666 					resamplerDataRight.dataOut = resamplerDataRight.dataOut[1 .. $];
4667 				}
4668 			}
4669 
4670 			scflags.currentPosition += cast(float) buffer.length / outputSampleRate / outputChannels * scflags.playbackSpeed;
4671 		} else if(outputChannels == 2) {
4672 			foreach(idx, ref s; buffer) {
4673 				if(resamplerDataLeft.dataOut.length == 0) {
4674 					if(loadMore()) {
4675 						scflags.finished_ = true;
4676 						return false;
4677 					}
4678 				}
4679 
4680 				if(inputChannels == 1) {
4681 					s = cast(short) (resamplerDataLeft.dataOut[0] * short.max);
4682 					if(idx & 1)
4683 						resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
4684 				} else {
4685 					if(idx & 1) {
4686 						s = cast(short) (resamplerDataRight.dataOut[0] * short.max);
4687 						resamplerDataRight.dataOut = resamplerDataRight.dataOut[1 .. $];
4688 					} else {
4689 						s = cast(short) (resamplerDataLeft.dataOut[0] * short.max);
4690 						resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
4691 					}
4692 				}
4693 			}
4694 
4695 			scflags.currentPosition += cast(float) buffer.length / outputSampleRate / outputChannels * scflags.playbackSpeed;
4696 		} else assert(0);
4697 
4698 		if(scflags.stopped)
4699 			scflags.finished_ = true;
4700 		return !scflags.stopped;
4701 	}
4702 }
4703 
4704 private enum scriptable = "arsd_jsvar_compatible";