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