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