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