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