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