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