1 /**
2 	The purpose of this module is to provide audio functions for
3 	things like playback, capture, and volume on both Windows
4 	(via the mmsystem calls)and Linux (through ALSA).
5 
6 	It is only aimed at the basics, and will be filled in as I want
7 	a particular feature. I don't generally need super configurability
8 	and see it as a minus, since I don't generally care either, so I'm
9 	going to be going for defaults that just work. If you need more though,
10 	you can hack the source or maybe just use it for the operating system
11 	bindings.
12 
13 	For example, I'm starting this because I want to write a volume
14 	control program for my linux box, so that's what is going first.
15 	That will consist of a listening callback for volume changes and
16 	being able to get/set the volume.
17 
18 
19 	HOW IT WORKS:
20 		You make a callback which feeds data to the device. Make an
21 		AudioOutput struct then feed your callback to it. Then play.
22 
23 		Then call loop? Or that could be in play?
24 
25 		Methods:
26 			setCallback
27 			play
28 			pause
29 
30 
31 	TODO:
32 		* play audio high level with options to wait until completion or return immediately
33 		* midi mid-level stuff
34 		* audio callback stuff (it tells us when to fill the buffer)
35 
36 		* Windows support for waveOut and waveIn. Maybe mixer too, but that's lowest priority.
37 
38 		* I'll also write .mid and .wav functions at least eventually. Maybe in separate modules but probably here since they aren't that complex.
39 
40 	I will probably NOT do OSS anymore, since my computer doesn't even work with it now.
41 	Ditto for Macintosh, as I don't have one and don't really care about them.
42 */
43 module arsd.simpleaudio;
44 
45 enum BUFFER_SIZE_FRAMES = 1024;//512;//2048;
46 enum BUFFER_SIZE_SHORT = BUFFER_SIZE_FRAMES * 2;
47 
48 /// 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.
49 enum DEFAULT_VOLUME = 20;
50 
51 version(Demo)
52 void main() {
53 /+
54 
55 	version(none) {
56 	import iv.stb.vorbis;
57 
58 	int channels;
59 	short* decoded;
60 	auto v = new VorbisDecoder("test.ogg");
61 
62 	auto ao = AudioOutput(0);
63 	ao.fillData = (short[] buffer) {
64 		auto got = v.getSamplesShortInterleaved(2, buffer.ptr, buffer.length);
65 		if(got == 0) {
66 			ao.stop();
67 		}
68 	};
69 
70 	ao.play();
71 	return;
72 	}
73 
74 
75 
76 
77 	auto thread = new AudioPcmOutThread();
78 	thread.start();
79 
80 	thread.playOgg("test.ogg");
81 
82 	Thread.sleep(5.seconds);
83 
84 	//Thread.sleep(150.msecs);
85 	thread.beep();
86 	Thread.sleep(250.msecs);
87 	thread.blip();
88 	Thread.sleep(250.msecs);
89 	thread.boop();
90 	Thread.sleep(1000.msecs);
91 	/*
92 	thread.beep(800, 500);
93 	Thread.sleep(500.msecs);
94 	thread.beep(366, 500);
95 	Thread.sleep(600.msecs);
96 	thread.beep(800, 500);
97 	thread.beep(366, 500);
98 	Thread.sleep(500.msecs);
99 	Thread.sleep(150.msecs);
100 	thread.beep(200);
101 	Thread.sleep(150.msecs);
102 	thread.beep(100);
103 	Thread.sleep(150.msecs);
104 	thread.noise();
105 	Thread.sleep(150.msecs);
106 	*/
107 
108 
109 	thread.stop();
110 
111 	thread.join();
112 
113 	return;
114 
115 	/*
116 	auto aio = AudioMixer(0);
117 
118 	import std.stdio;
119 	writeln(aio.muteMaster);
120 	*/
121 
122 	/*
123 	mciSendStringA("play test.wav", null, 0, null);
124 	Sleep(3000);
125 	import std.stdio;
126 	if(auto err = mciSendStringA("play test2.wav", null, 0, null))
127 		writeln(err);
128 	Sleep(6000);
129 	return;
130 	*/
131 
132 	// output about a second of random noise to demo PCM
133 	auto ao = AudioOutput(0);
134 	short[BUFFER_SIZE_SHORT] randomSpam = void;
135 	import core.stdc.stdlib;
136 	foreach(ref s; randomSpam)
137 		s = cast(short)((cast(short) rand()) - short.max / 2);
138 
139 	int loopCount = 40;
140 
141 	//import std.stdio;
142 	//writeln("Should be about ", loopCount * BUFFER_SIZE_FRAMES * 1000 / SampleRate, " microseconds");
143 
144 	int loops = 0;
145 	// only do simple stuff in here like fill the data, set simple
146 	// variables, or call stop anything else might cause deadlock
147 	ao.fillData = (short[] buffer) {
148 		buffer[] = randomSpam[0 .. buffer.length];
149 		loops++;
150 		if(loops == loopCount)
151 			ao.stop();
152 	};
153 
154 	ao.play();
155 
156 	return;
157 +/
158 	// Play a C major scale on the piano to demonstrate midi
159 	auto midi = MidiOutput(0);
160 
161 	ubyte[16] buffer = void;
162 	ubyte[] where = buffer[];
163 	midi.writeRawMessageData(where.midiProgramChange(1, 1));
164 	for(ubyte note = MidiNote.C; note <= MidiNote.C + 12; note++) {
165 		where = buffer[];
166 		midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
167 		import core.thread;
168 		Thread.sleep(dur!"msecs"(500));
169 		midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
170 
171 		if(note != 76 && note != 83)
172 			note++;
173 	}
174 	import core.thread;
175 	Thread.sleep(dur!"msecs"(500)); // give the last note a chance to finish
176 }
177 
178 /++
179 	Wraps [AudioPcmOutThreadImplementation] with RAII semantics for better
180 	error handling and disposal than the old way.
181 
182 	DO NOT USE THE `new` OPERATOR ON THIS! Just construct it inline:
183 
184 	---
185 		auto audio = AudioOutputThread(true);
186 		audio.beep();
187 	---
188 
189 	History:
190 		Added May 9, 2020 to replace the old [AudioPcmOutThread] class
191 		that proved pretty difficult to use correctly.
192 +/
193 struct AudioOutputThread {
194 	@disable this();
195 
196 	/++
197 		Pass `true` to enable the audio thread. Otherwise, it will
198 		just live as a dummy mock object that you should not actually
199 		try to use.
200 	+/
201 	this(bool enable, int SampleRate = 44100, int channels = 2) {
202 		if(enable) {
203 			impl = new AudioPcmOutThreadImplementation(SampleRate, channels);
204 			impl.refcount++;
205 			impl.start();
206 			impl.waitForInitialization();
207 		}
208 	}
209 
210 	/// Keeps an internal refcount.
211 	this(this) {
212 		if(impl)
213 			impl.refcount++;
214 	}
215 
216 	/// 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).
217 	~this() {
218 		if(impl) {
219 			impl.refcount--;
220 			if(impl.refcount == 0) {
221 				impl.stop();
222 				impl.join();
223 			}
224 		}
225 	}
226 
227 	/++
228 		This allows you to check `if(audio)` to see if it is enabled.
229 	+/
230 	bool opCast(T : bool)() {
231 		return impl !is null;
232 	}
233 
234 	/++
235 		Other methods are forwarded to the implementation of type
236 		[AudioPcmOutThreadImplementation]. See that for more information
237 		on what you can do.
238 
239 		This opDispatch template will forward all other methods directly
240 		to that [AudioPcmOutThreadImplementation] if this is live, otherwise
241 		it does nothing.
242 	+/
243 	template opDispatch(string name) {
244 		static if(is(typeof(__traits(getMember, impl, name)) Params == __parameters))
245 		auto opDispatch(Params params) {
246 			if(impl)
247 				return __traits(getMember, impl, name)(params);
248 			static if(!is(typeof(return) == void))
249 				return typeof(return).init;
250 		}
251 		else static assert(0);
252 	}
253 
254 	void playOgg()(string filename, bool loop = false) {
255 		if(impl)
256 		impl.playOgg(filename, loop);
257 	}
258 	void playMp3()(string filename) {
259 		if(impl)
260 		impl.playMp3(filename);
261 	}
262 
263 
264 	/// provides automatic [arsd.jsvar] script wrapping capability. Make sure the
265 	/// script also finishes before this goes out of scope or it may end up talking
266 	/// to a dead object....
267 	auto toArsdJsvar() {
268 		return impl;
269 	}
270 
271 	/+
272 	alias getImpl this;
273 	AudioPcmOutThreadImplementation getImpl() {
274 		assert(impl !is null);
275 		return impl;
276 	}
277 	+/
278 	private AudioPcmOutThreadImplementation impl;
279 }
280 
281 /++
282 	Old thread implementation. I decided to deprecate it in favor of [AudioOutputThread] because
283 	RAII semantics make it easier to get right at the usage point. See that to go forward.
284 
285 	History:
286 		Deprecated on May 9, 2020.
287 +/
288 deprecated("Use AudioOutputThread instead.") class AudioPcmOutThread {}
289 
290 import core.thread;
291 /++
292 	Makes an audio thread for you that you can make
293 	various sounds on and it will mix them with good
294 	enough latency for simple games.
295 
296 	DO NOT USE THIS DIRECTLY. Instead, access it through
297 	[AudioOutputThread].
298 
299 	---
300 		auto audio = AudioOutputThread(true);
301 		audio.beep();
302 
303 		// you need to keep the main program alive long enough
304 		// to keep this thread going to hear anything
305 		Thread.sleep(1.seconds);
306 	---
307 +/
308 final class AudioPcmOutThreadImplementation : Thread {
309 	private this(int SampleRate, int channels) {
310 		this.isDaemon = true;
311 
312 		this.SampleRate = SampleRate;
313 		this.channels = channels;
314 
315 		super(&run);
316 	}
317 
318 	private int SampleRate;
319 	private int channels;
320 	private int refcount;
321 
322 	private void waitForInitialization() {
323 		shared(AudioOutput*)* ao = cast(shared(AudioOutput*)*) &this.ao;
324 		while(isRunning && *ao is null) {
325 			Thread.sleep(5.msecs);
326 		}
327 
328 		if(*ao is null)
329 			join(); // it couldn't initialize, just rethrow the exception
330 	}
331 
332 	///
333 	@scriptable
334 	void pause() {
335 		if(ao) {
336 			ao.pause();
337 		}
338 	}
339 
340 	///
341 	@scriptable
342 	void unpause() {
343 		if(ao) {
344 			ao.unpause();
345 		}
346 	}
347 
348 	///
349 	void stop() {
350 		if(ao) {
351 			ao.stop();
352 		}
353 	}
354 
355 	/// Args in hertz and milliseconds
356 	@scriptable
357 	void beep(int freq = 900, int dur = 150, int volume = DEFAULT_VOLUME) {
358 		Sample s;
359 		s.operation = 0; // square wave
360 		s.frequency = SampleRate / freq;
361 		s.duration = dur * SampleRate / 1000;
362 		s.volume = volume;
363 		addSample(s);
364 	}
365 
366 	///
367 	@scriptable
368 	void noise(int dur = 150, int volume = DEFAULT_VOLUME) {
369 		Sample s;
370 		s.operation = 1; // noise
371 		s.frequency = 0;
372 		s.volume = volume;
373 		s.duration = dur * SampleRate / 1000;
374 		addSample(s);
375 	}
376 
377 	///
378 	@scriptable
379 	void boop(float attack = 8, int freqBase = 500, int dur = 150, int volume = DEFAULT_VOLUME) {
380 		Sample s;
381 		s.operation = 5; // custom
382 		s.volume = volume;
383 		s.duration = dur * SampleRate / 1000;
384 		s.f = delegate short(int x) {
385 			auto currentFrequency = cast(float) freqBase / (1 + cast(float) x / (cast(float) SampleRate / attack));
386 			import std.math;
387 			auto freq = 2 * PI /  (cast(float) SampleRate / currentFrequency);
388 			return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100);
389 		};
390 		addSample(s);
391 	}
392 
393 	///
394 	@scriptable
395 	void blip(float attack = 6, int freqBase = 800, int dur = 150, int volume = DEFAULT_VOLUME) {
396 		Sample s;
397 		s.operation = 5; // custom
398 		s.volume = volume;
399 		s.duration = dur * SampleRate / 1000;
400 		s.f = delegate short(int x) {
401 			auto currentFrequency = cast(float) freqBase * (1 + cast(float) x / (cast(float) SampleRate / attack));
402 			import std.math;
403 			auto freq = 2 * PI /  (cast(float) SampleRate / currentFrequency);
404 			return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100);
405 		};
406 		addSample(s);
407 	}
408 
409 	version(none)
410 	void custom(int dur = 150, int volume = DEFAULT_VOLUME) {
411 		Sample s;
412 		s.operation = 5; // custom
413 		s.volume = volume;
414 		s.duration = dur * SampleRate / 1000;
415 		s.f = delegate short(int x) {
416 			auto currentFrequency = 500.0 / (1 + cast(float) x / (cast(float) SampleRate / 8));
417 			import std.math;
418 			auto freq = 2 * PI /  (cast(float) SampleRate / currentFrequency);
419 			return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100);
420 		};
421 		addSample(s);
422 	}
423 
424 	/// Requires vorbis.d to be compiled in (module arsd.vorbis)
425 	void playOgg()(string filename, bool loop = false) {
426 		import arsd.vorbis;
427 
428 		auto v = new VorbisDecoder(filename);
429 
430 		addChannel(
431 			delegate bool(short[] buffer) {
432 				if(cast(int) buffer.length != buffer.length)
433 					throw new Exception("eeeek");
434 				auto got = v.getSamplesShortInterleaved(2, buffer.ptr, cast(int) buffer.length);
435 				if(got == 0) {
436 					if(loop) {
437 						v.seekStart();
438 						return true;
439 					}
440 
441 					return false;
442 				}
443 				return true;
444 			}
445 		);
446 	}
447 
448 	/// Requires mp3.d to be compiled in (module arsd.mp3) which is LGPL licensed.
449 	void playMp3()(string filename) {
450 		import arsd.mp3;
451 
452 		import std.stdio;
453 		auto fi = File(filename);
454 		scope auto reader = delegate(void[] buf) {
455 			return cast(int) fi.rawRead(buf[]).length;
456 		};
457 
458 		auto mp3 = new MP3Decoder(reader);
459 		if(!mp3.valid)
460 			throw new Exception("no file");
461 
462 		auto next = mp3.frameSamples;
463 
464 		addChannel(
465 			delegate bool(short[] buffer) {
466 				if(cast(int) buffer.length != buffer.length)
467 					throw new Exception("eeeek");
468 
469 				more:
470 				if(next.length >= buffer.length) {
471 					buffer[] = next[0 .. buffer.length];
472 					next = next[buffer.length .. $];
473 				} else {
474 					buffer[0 .. next.length] = next[];
475 					buffer = buffer[next.length .. $];
476 
477 					next = next[$..$];
478 
479 					if(buffer.length) {
480 						if(mp3.valid) {
481 							mp3.decodeNextFrame(reader);
482 							next = mp3.frameSamples;
483 							goto more;
484 						} else {
485 							buffer[] = 0;
486 							return false;
487 						}
488 					}
489 				}
490 
491 				return true;
492 			}
493 		);
494 	}
495 
496 
497 
498 	struct Sample {
499 		int operation;
500 		int frequency; /* in samples */
501 		int duration; /* in samples */
502 		int volume; /* between 1 and 100. You should generally shoot for something lowish, like 20. */
503 		int delay; /* in samples */
504 
505 		int x;
506 		short delegate(int x) f;
507 	}
508 
509 	final void addSample(Sample currentSample) {
510 		int frequencyCounter;
511 		short val = cast(short) (cast(int) short.max * currentSample.volume / 100);
512 		addChannel(
513 			delegate bool (short[] buffer) {
514 				if(currentSample.duration) {
515 					size_t i = 0;
516 					if(currentSample.delay) {
517 						if(buffer.length <= currentSample.delay * 2) {
518 							// whole buffer consumed by delay
519 							buffer[] = 0;
520 							currentSample.delay -= buffer.length / 2;
521 						} else {
522 							i = currentSample.delay * 2;
523 							buffer[0 .. i] = 0;
524 							currentSample.delay = 0;
525 						}
526 					}
527 					if(currentSample.delay > 0)
528 						return true;
529 
530 					size_t sampleFinish;
531 					if(currentSample.duration * 2 <= buffer.length) {
532 						sampleFinish = currentSample.duration * 2;
533 						currentSample.duration = 0;
534 					} else {
535 						sampleFinish = buffer.length;
536 						currentSample.duration -= buffer.length / 2;
537 					}
538 
539 					switch(currentSample.operation) {
540 						case 0: // square wave
541 							for(; i < sampleFinish; i++) {
542 								buffer[i] = val;
543 								// left and right do the same thing so we only count
544 								// every other sample
545 								if(i & 1) {
546 									if(frequencyCounter)
547 										frequencyCounter--;
548 									if(frequencyCounter == 0) {
549 										// are you kidding me dmd? random casts suck
550 										val = cast(short) -cast(int)(val);
551 										frequencyCounter = currentSample.frequency / 2;
552 									}
553 								}
554 							}
555 						break;
556 						case 1: // noise
557 							for(; i < sampleFinish; i++) {
558 								import std.random;
559 								buffer[i] = uniform(cast(short) -cast(int)val, val);
560 							}
561 						break;
562 						/+
563 						case 2: // triangle wave
564 
565 		short[] tone;
566 		tone.length = 22050 * len / 1000;
567 
568 		short valmax = cast(short) (cast(int) volume * short.max / 100);
569 		int wavelength = 22050 / freq;
570 		wavelength /= 2;
571 		int da = valmax / wavelength;
572 		int val = 0;
573 
574 		for(int a = 0; a < tone.length; a++){
575 			tone[a] = cast(short) val;
576 			val+= da;
577 			if(da > 0 && val >= valmax)
578 				da *= -1;
579 			if(da < 0 && val <= -valmax)
580 				da *= -1;
581 		}
582 
583 		data ~= tone;
584 
585 
586 							for(; i < sampleFinish; i++) {
587 								buffer[i] = val;
588 								// left and right do the same thing so we only count
589 								// every other sample
590 								if(i & 1) {
591 									if(frequencyCounter)
592 										frequencyCounter--;
593 									if(frequencyCounter == 0) {
594 										val = 0;
595 										frequencyCounter = currentSample.frequency / 2;
596 									}
597 								}
598 							}
599 
600 						break;
601 						case 3: // sawtooth wave
602 		short[] tone;
603 		tone.length = 22050 * len / 1000;
604 
605 		int valmax = volume * short.max / 100;
606 		int wavelength = 22050 / freq;
607 		int da = valmax / wavelength;
608 		short val = 0;
609 
610 		for(int a = 0; a < tone.length; a++){
611 			tone[a] = val;
612 			val+= da;
613 			if(val >= valmax)
614 				val = 0;
615 		}
616 
617 		data ~= tone;
618 						case 4: // sine wave
619 		short[] tone;
620 		tone.length = 22050 * len / 1000;
621 
622 		int valmax = volume * short.max / 100;
623 		int val = 0;
624 
625 		float i = 2*PI / (22050/freq);
626 
627 		float f = 0;
628 		for(int a = 0; a < tone.length; a++){
629 			tone[a] = cast(short) (valmax * sin(f));
630 			f += i;
631 			if(f>= 2*PI)
632 				f -= 2*PI;
633 		}
634 
635 		data ~= tone;
636 
637 						+/
638 						case 5: // custom function
639 							val = currentSample.f(currentSample.x);
640 							for(; i < sampleFinish; i++) {
641 								buffer[i] = val;
642 								if(i & 1) {
643 									currentSample.x++;
644 									val = currentSample.f(currentSample.x);
645 								}
646 							}
647 						break;
648 						default: // unknown; use silence
649 							currentSample.duration = 0;
650 					}
651 
652 					if(i < buffer.length)
653 						buffer[i .. $] = 0;
654 
655 					return currentSample.duration > 0;
656 				} else {
657 					return false;
658 				}
659 			}
660 		);
661 	}
662 
663 	/++
664 		The delegate returns false when it is finished (true means keep going).
665 		It must fill the buffer with waveform data on demand and must be latency
666 		sensitive; as fast as possible.
667 	+/
668 	public void addChannel(bool delegate(short[] buffer) dg) {
669 		synchronized(this) {
670 			// silently drops info if we don't have room in the buffer...
671 			// don't do a lot of long running things lol
672 			if(fillDatasLength < fillDatas.length)
673 				fillDatas[fillDatasLength++] = dg;
674 		}
675 	}
676 
677 	private {
678 		AudioOutput* ao;
679 
680 		bool delegate(short[] buffer)[32] fillDatas;
681 		int fillDatasLength = 0;
682 	}
683 
684 	private void run() {
685 
686 		version(linux) {
687 			// this thread has no business intercepting signals from the main thread,
688 			// so gonna block a couple of them
689 			import core.sys.posix.signal;
690 			sigset_t sigset;
691 			auto err = sigemptyset(&sigset);
692 			assert(!err);
693 
694 			err = sigaddset(&sigset, SIGINT); assert(!err);
695 			err = sigaddset(&sigset, SIGCHLD); assert(!err);
696 
697 			err = sigprocmask(SIG_BLOCK, &sigset, null);
698 			assert(!err);
699 		}
700 
701 		AudioOutput ao = AudioOutput(0);
702 		this.ao = &ao;
703 		scope(exit) this.ao = null;
704 		auto omg = this;
705 		ao.fillData = (short[] buffer) {
706 			short[BUFFER_SIZE_SHORT] bfr;
707 			bool first = true;
708 			if(fillDatasLength) {
709 				for(int idx = 0; idx < fillDatasLength; idx++) {
710 					auto dg = fillDatas[idx];
711 					auto ret = dg(bfr[0 .. buffer.length][]);
712 					foreach(i, v; bfr[0 .. buffer.length][]) {
713 						int val;
714 						if(first)
715 							val = 0;
716 						else
717 							val = buffer[i];
718 
719 						int a = val;
720 						int b = v;
721 						int cap = a + b;
722 						if(cap > short.max) cap = short.max;
723 						else if(cap < short.min) cap = short.min;
724 						val = cast(short) cap;
725 						buffer[i] = cast(short) val;
726 					}
727 					if(!ret) {
728 						// it returned false meaning this one is finished...
729 						synchronized(omg) {
730 							fillDatas[idx] = fillDatas[fillDatasLength - 1];
731 							fillDatasLength--;
732 						}
733 						idx--;
734 					}
735 
736 					first = false;
737 				}
738 			} else {
739 				buffer[] = 0;
740 			}
741 		};
742 		ao.play();
743 	}
744 }
745 
746 
747 import core.stdc.config;
748 
749 version(linux) version=ALSA;
750 version(Windows) version=WinMM;
751 
752 version(ALSA) {
753 	enum cardName = "default";
754 
755 	// this is the virtual rawmidi device on my computer at least
756 	// maybe later i'll make it probe
757 	//
758 	// Getting midi to actually play on Linux is a bit of a pain.
759 	// Here's what I did:
760 	/*
761 		# load the kernel driver, if amidi -l gives ioctl error,
762 		# you haven't done this yet!
763 		modprobe snd-virmidi
764 
765 		# start a software synth. timidity -iA is also an option
766 		fluidsynth soundfont.sf2
767 
768 		# connect the virtual hardware port to the synthesizer
769 		aconnect 24:0 128:0
770 
771 
772 		I might also add a snd_seq client here which is a bit
773 		easier to setup but for now I'm using the rawmidi so you
774 		gotta get them connected somehow.
775 	*/
776 	enum midiName = "hw:3,0";
777 
778 	enum midiCaptureName = "hw:4,0";
779 
780 	// fyi raw midi dump:  amidi -d --port hw:4,0
781 	// connect my midi out to fluidsynth: aconnect 28:0 128:0
782 	// and my keyboard to it: aconnect 32:0 128:0
783 }
784 
785 /// Thrown on audio failures.
786 /// Subclass this to provide OS-specific exceptions
787 class AudioException : Exception {
788 	this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
789 		super(message, file, line, next);
790 	}
791 }
792 
793 /++
794 	Gives PCM input access (such as a microphone).
795 
796 	History:
797 		Windows support added May 10, 2020 and the API got overhauled too.
798 +/
799 struct AudioInput {
800 	version(ALSA) {
801 		snd_pcm_t* handle;
802 	} else version(WinMM) {
803 		HWAVEIN handle;
804 		HANDLE event;
805 	} else static assert(0);
806 
807 	@disable this();
808 	@disable this(this);
809 
810 	int channels;
811 	int SampleRate;
812 
813 	/// Always pass card == 0.
814 	this(int card, int SampleRate = 44100, int channels = 2) {
815 		assert(card == 0);
816 
817 		assert(channels == 1 || channels == 2);
818 
819 		this.channels = channels;
820 		this.SampleRate = SampleRate;
821 
822 		version(ALSA) {
823 			handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_CAPTURE, SampleRate, channels);
824 		} else version(WinMM) {
825 			event = CreateEvent(null, false /* manual reset */, false /* initially triggered */, null);
826 
827 			WAVEFORMATEX format;
828 			format.wFormatTag = WAVE_FORMAT_PCM;
829 			format.nChannels = 2;
830 			format.nSamplesPerSec = SampleRate;
831 			format.nAvgBytesPerSec = SampleRate * channels * 2; // two channels, two bytes per sample
832 			format.nBlockAlign = 4;
833 			format.wBitsPerSample = 16;
834 			format.cbSize = 0;
835 			if(auto err = waveInOpen(&handle, WAVE_MAPPER, &format, cast(DWORD_PTR) event, cast(DWORD_PTR) &this, CALLBACK_EVENT))
836 				throw new WinMMException("wave in open", err);
837 
838 		} else static assert(0);
839 	}
840 
841 	/// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz
842 	/// Each item in the array thus alternates between left and right channel
843 	/// and it takes a total of 88,200 items to make one second of sound.
844 	///
845 	/// Returns the slice of the buffer actually read into
846 	///
847 	/// LINUX ONLY. You should prolly use [record] instead
848 	version(ALSA)
849 	short[] read(short[] buffer) {
850 		snd_pcm_sframes_t read;
851 
852 		read = snd_pcm_readi(handle, buffer.ptr, buffer.length / channels /* div number of channels apparently */);
853 		if(read < 0)
854 			throw new AlsaException("pcm read", cast(int)read);
855 
856 		return buffer[0 .. read * channels];
857 	}
858 
859 	/// passes a buffer of data to fill
860 	///
861 	/// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz
862 	/// Each item in the array thus alternates between left and right channel
863 	/// and it takes a total of 88,200 items to make one second of sound.
864 	void delegate(short[]) receiveData;
865 
866 	///
867 	void stop() {
868 		recording = false;
869 	}
870 
871 	/// First, set [receiveData], then call this.
872 	void record() {
873 		assert(receiveData !is null);
874 		recording = true;
875 
876 		version(ALSA) {
877 			short[BUFFER_SIZE_SHORT] buffer;
878 			while(recording) {
879 				auto got = read(buffer);
880 				receiveData(got);
881 			}
882 		} else version(WinMM) {
883 
884 			enum numBuffers = 2; // use a lot of buffers to get smooth output with Sleep, see below
885 			short[BUFFER_SIZE_SHORT][numBuffers] buffers;
886 
887 			WAVEHDR[numBuffers] headers;
888 
889 			foreach(i, ref header; headers) {
890 				auto buffer = buffers[i][];
891 				header.lpData = cast(char*) buffer.ptr;
892 				header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof;
893 				header.dwFlags = 0;// WHDR_BEGINLOOP | WHDR_ENDLOOP;
894 				header.dwLoops = 0;
895 
896 				if(auto err = waveInPrepareHeader(handle, &header, header.sizeof))
897 					throw new WinMMException("prepare header", err);
898 
899 				header.dwUser = 1; // mark that the driver is using it
900 				if(auto err = waveInAddBuffer(handle, &header, header.sizeof))
901 					throw new WinMMException("wave in read", err);
902 			}
903 
904 			waveInStart(handle);
905 			scope(failure) waveInReset(handle);
906 
907 			while(recording) {
908 				if(auto err = WaitForSingleObject(event, INFINITE))
909 					throw new Exception("WaitForSingleObject");
910 				if(!recording)
911 					break;
912 
913 				foreach(ref header; headers) {
914 					if(!(header.dwFlags & WHDR_DONE)) continue;
915 
916 					receiveData((cast(short*) header.lpData)[0 .. header.dwBytesRecorded / short.sizeof]);
917 					if(!recording) break;
918 					header.dwUser = 1; // mark that the driver is using it
919 					if(auto err = waveInAddBuffer(handle, &header, header.sizeof)) {
920                                                 throw new WinMMException("waveInAddBuffer", err);
921                                         }
922 				}
923 			}
924 
925 			/*
926 			if(auto err = waveInStop(handle))
927 				throw new WinMMException("wave in stop", err);
928 			*/
929 
930 			if(auto err = waveInReset(handle)) {
931 				throw new WinMMException("wave in reset", err);
932 			}
933 
934 			still_in_use:
935 			foreach(idx, header; headers)
936 				if(!(header.dwFlags & WHDR_DONE)) {
937 					Sleep(1);
938 					goto still_in_use;
939 				}
940 
941 			foreach(ref header; headers)
942 				if(auto err = waveInUnprepareHeader(handle, &header, header.sizeof)) {
943 					throw new WinMMException("unprepare header", err);
944 				}
945 
946 			ResetEvent(event);
947 		} else static assert(0);
948 	}
949 
950 	private bool recording;
951 
952 	~this() {
953 		receiveData = null;
954 		version(ALSA) {
955 			snd_pcm_close(handle);
956 		} else version(WinMM) {
957 			if(auto err = waveInClose(handle))
958 				throw new WinMMException("close", err);
959 
960 			CloseHandle(event);
961 			// in wine (though not Windows nor winedbg as far as I can tell)
962 			// this randomly segfaults. the sleep prevents it. idk why.
963 			Sleep(5);
964 		} else static assert(0);
965 	}
966 }
967 
968 ///
969 enum SampleRateFull = 44100;
970 
971 /// Gives PCM output access (such as the speakers).
972 struct AudioOutput {
973 	version(ALSA) {
974 		snd_pcm_t* handle;
975 	} else version(WinMM) {
976 		HWAVEOUT handle;
977 	}
978 
979 	@disable this();
980 	// This struct must NEVER be moved or copied, a pointer to it may
981 	// be passed to a device driver and stored!
982 	@disable this(this);
983 
984 	private int SampleRate;
985 	private int channels;
986 
987 	/// Always pass card == 0.
988 	this(int card, int SampleRate = 44100, int channels = 2) {
989 		assert(card == 0);
990 
991 		assert(channels == 1 || channels == 2);
992 
993 		this.SampleRate = SampleRate;
994 		this.channels = channels;
995 
996 		version(ALSA) {
997 			handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_PLAYBACK, SampleRate, channels);
998 		} else version(WinMM) {
999 			WAVEFORMATEX format;
1000 			format.wFormatTag = WAVE_FORMAT_PCM;
1001 			format.nChannels = cast(ushort) channels;
1002 			format.nSamplesPerSec = SampleRate;
1003 			format.nAvgBytesPerSec = SampleRate * channels * 2; // two channels, two bytes per sample
1004 			format.nBlockAlign = 4;
1005 			format.wBitsPerSample = 16;
1006 			format.cbSize = 0;
1007 			if(auto err = waveOutOpen(&handle, WAVE_MAPPER, &format, cast(DWORD_PTR) &mmCallback, cast(DWORD_PTR) &this, CALLBACK_FUNCTION))
1008 				throw new WinMMException("wave out open", err);
1009 		} else static assert(0);
1010 	}
1011 
1012 	/// passes a buffer of data to fill
1013 	///
1014 	/// Data is assumed to be interleaved stereo, LE 16 bit, 44.1 kHz (unless you change that in the ctor)
1015 	/// Each item in the array thus alternates between left and right channel (unless you change that in the ctor)
1016 	/// and it takes a total of 88,200 items to make one second of sound.
1017 	void delegate(short[]) fillData;
1018 
1019 	shared(bool) playing = false; // considered to be volatile
1020 
1021 	/// Starts playing, loops until stop is called
1022 	void play() {
1023 		assert(fillData !is null);
1024 		playing = true;
1025 
1026 		version(ALSA) {
1027 			short[BUFFER_SIZE_SHORT] buffer;
1028 			while(playing) {
1029 				auto err = snd_pcm_wait(handle, 500);
1030 				if(err < 0)
1031 					throw new AlsaException("uh oh", err);
1032 				// err == 0 means timeout
1033 				// err == 1 means ready
1034 
1035 				auto ready = snd_pcm_avail_update(handle);
1036 				if(ready < 0)
1037 					throw new AlsaException("avail", cast(int)ready);
1038 				if(ready > BUFFER_SIZE_FRAMES)
1039 					ready = BUFFER_SIZE_FRAMES;
1040 				//import std.stdio; writeln("filling ", ready);
1041 				fillData(buffer[0 .. ready * channels]);
1042 				if(playing) {
1043 					snd_pcm_sframes_t written;
1044 					auto data = buffer[0 .. ready * channels];
1045 
1046 					while(data.length) {
1047 						written = snd_pcm_writei(handle, data.ptr, data.length / channels);
1048 						if(written < 0) {
1049 							written = snd_pcm_recover(handle, cast(int)written, 0);
1050 							if (written < 0) throw new AlsaException("pcm write", cast(int)written);
1051 						}
1052 						data = data[written * channels .. $];
1053 					}
1054 				}
1055 			}
1056 		} else version(WinMM) {
1057 
1058 			enum numBuffers = 4; // use a lot of buffers to get smooth output with Sleep, see below
1059 			short[BUFFER_SIZE_SHORT][numBuffers] buffers;
1060 
1061 			WAVEHDR[numBuffers] headers;
1062 
1063 			foreach(i, ref header; headers) {
1064 				// since this is wave out, it promises not to write...
1065 				auto buffer = buffers[i][];
1066 				header.lpData = cast(char*) buffer.ptr;
1067 				header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof;
1068 				header.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
1069 				header.dwLoops = 1;
1070 
1071 				if(auto err = waveOutPrepareHeader(handle, &header, header.sizeof))
1072 					throw new WinMMException("prepare header", err);
1073 
1074 				// prime it
1075 				fillData(buffer[]);
1076 
1077 				// indicate that they are filled and good to go
1078 				header.dwUser = 1;
1079 			}
1080 
1081 			while(playing) {
1082 				// and queue both to be played, if they are ready
1083 				foreach(ref header; headers)
1084 					if(header.dwUser) {
1085 						if(auto err = waveOutWrite(handle, &header, header.sizeof))
1086 							throw new WinMMException("wave out write", err);
1087 						header.dwUser = 0;
1088 					}
1089 				Sleep(1);
1090 				// the system resolution may be lower than this sleep. To avoid gaps
1091 				// in output, we use multiple buffers. Might introduce latency, not
1092 				// sure how best to fix. I don't want to busy loop...
1093 			}
1094 
1095 			// wait for the system to finish with our buffers
1096 			bool anyInUse = true;
1097 
1098 			while(anyInUse) {
1099 				anyInUse = false;
1100 				foreach(header; headers) {
1101 					if(!header.dwUser) {
1102 						anyInUse = true;
1103 						break;
1104 					}
1105 				}
1106 				if(anyInUse)
1107 					Sleep(1);
1108 			}
1109 
1110 			foreach(ref header; headers) 
1111 				if(auto err = waveOutUnprepareHeader(handle, &header, header.sizeof))
1112 					throw new WinMMException("unprepare", err);
1113 		} else static assert(0);
1114 	}
1115 
1116 	/// Breaks the play loop
1117 	void stop() {
1118 		playing = false;
1119 	}
1120 
1121 	///
1122 	void pause() {
1123 		version(WinMM)
1124 			waveOutPause(handle);
1125 		else version(ALSA)
1126 			snd_pcm_pause(handle, 1);
1127 	}
1128 
1129 	///
1130 	void unpause() {
1131 		version(WinMM)
1132 			waveOutRestart(handle);
1133 		else version(ALSA)
1134 			snd_pcm_pause(handle, 0);
1135 
1136 	}
1137 
1138 	version(WinMM) {
1139 		extern(Windows)
1140 		static void mmCallback(HWAVEOUT handle, UINT msg, void* userData, WAVEHDR* header, DWORD_PTR param2) {
1141 			AudioOutput* ao = cast(AudioOutput*) userData;
1142 			if(msg == WOM_DONE) {
1143 				// we want to bounce back and forth between two buffers
1144 				// to keep the sound going all the time
1145 				if(ao.playing) {
1146 					ao.fillData((cast(short*) header.lpData)[0 .. header.dwBufferLength / short.sizeof]);
1147 				}
1148 				header.dwUser = 1;
1149 			}
1150 		}
1151 	}
1152 
1153 	// FIXME: add async function hooks
1154 
1155 	~this() {
1156 		version(ALSA) {
1157 			snd_pcm_close(handle);
1158 		} else version(WinMM) {
1159 			waveOutClose(handle);
1160 		} else static assert(0);
1161 	}
1162 }
1163 
1164 /++
1165 	For reading midi events from hardware, for example, an electronic piano keyboard
1166 	attached to the computer.
1167 +/
1168 struct MidiInput {
1169 	// reading midi devices...
1170 	version(ALSA) {
1171 		snd_rawmidi_t* handle;
1172 	} else version(WinMM) {
1173 		HMIDIIN handle;
1174 	}
1175 
1176 	@disable this();
1177 	@disable this(this);
1178 
1179 	/+
1180 B0 40 7F # pedal on
1181 B0 40 00 # sustain pedal off
1182 	+/
1183 
1184 	/// Always pass card == 0.
1185 	this(int card) {
1186 		assert(card == 0);
1187 
1188 		version(ALSA) {
1189 			if(auto err = snd_rawmidi_open(&handle, null, midiCaptureName, 0))
1190 				throw new AlsaException("rawmidi open", err);
1191 		} else version(WinMM) {
1192 			if(auto err = midiInOpen(&handle, 0, cast(DWORD_PTR) &mmCallback, cast(DWORD_PTR) &this, CALLBACK_FUNCTION))
1193 				throw new WinMMException("midi in open", err);
1194 		} else static assert(0);
1195 	}
1196 
1197 	private bool recording = false;
1198 
1199 	///
1200 	void stop() {
1201 		recording = false;
1202 	}
1203 
1204 	/++
1205 		Records raw midi input data from the device.
1206 
1207 		The timestamp is given in milliseconds since recording
1208 		began (if you keep this program running for 23ish days
1209 		it might overflow! so... don't do that.). The other bytes
1210 		are the midi messages.
1211 
1212 		$(PITFALL Do not call any other multimedia functions from the callback!)
1213 	+/
1214 	void record(void delegate(uint timestamp, ubyte b1, ubyte b2, ubyte b3) dg) {
1215 		version(ALSA) {
1216 			recording = true;
1217 			ubyte[1024] data;
1218 			import core.time;
1219 			auto start = MonoTime.currTime;
1220 			while(recording) {
1221 				auto read = snd_rawmidi_read(handle, data.ptr, data.length);
1222 				if(read < 0)
1223 					throw new AlsaException("midi read", cast(int) read);
1224 
1225 				auto got = data[0 .. read];
1226 				while(got.length) {
1227 					// FIXME some messages are fewer bytes....
1228 					dg(cast(uint) (MonoTime.currTime - start).total!"msecs", got[0], got[1], got[2]);
1229 					got = got[3 .. $];
1230 				}
1231 			}
1232 		} else version(WinMM) {
1233 			recording = true;
1234 			this.dg = dg;
1235 			scope(exit)
1236 				this.dg = null;
1237 			midiInStart(handle);
1238 			scope(exit)
1239 				midiInReset(handle);
1240 
1241 			while(recording) {
1242 				Sleep(1);
1243 			}
1244 		} else static assert(0);
1245 	}
1246 
1247 	version(WinMM)
1248 	private void delegate(uint timestamp, ubyte b1, ubyte b2, ubyte b3) dg;
1249 
1250 
1251 	version(WinMM)
1252 	extern(Windows)
1253 	static
1254 	void mmCallback(HMIDIIN handle, UINT msg, DWORD_PTR user, DWORD_PTR param1, DWORD_PTR param2) {
1255 		MidiInput* mi = cast(MidiInput*) user;
1256 		if(msg == MIM_DATA) {
1257 			mi.dg(
1258 				cast(uint) param2,
1259 				param1 & 0xff,
1260 				(param1 >> 8) & 0xff,
1261 				(param1 >> 16) & 0xff
1262 			);
1263 		}
1264 	}
1265 
1266 	~this() {
1267 		version(ALSA) {
1268 			snd_rawmidi_close(handle);
1269 		} else version(WinMM) {
1270 			midiInClose(handle);
1271 		} else static assert(0);
1272 	}
1273 }
1274 
1275 // plays a midi file in the background with methods to tweak song as it plays
1276 struct MidiOutputThread {
1277 	void injectCommand() {}
1278 	void pause() {}
1279 	void unpause() {}
1280 
1281 	void trackEnabled(bool on) {}
1282 	void channelEnabled(bool on) {}
1283 
1284 	void loopEnabled(bool on) {}
1285 
1286 	// stops the current song, pushing its position to the stack for later
1287 	void pushSong() {}
1288 	// restores a popped song from where it was.
1289 	void popSong() {}
1290 }
1291 
1292 version(Posix) {
1293 	import core.sys.posix.signal;
1294 	private sigaction_t oldSigIntr;
1295 	void setSigIntHandler() {
1296 		sigaction_t n;
1297 		n.sa_handler = &interruptSignalHandler;
1298 		n.sa_mask = cast(sigset_t) 0;
1299 		n.sa_flags = 0;
1300 		sigaction(SIGINT, &n, &oldSigIntr);
1301 	}
1302 	void restoreSigIntHandler() {
1303 		sigaction(SIGINT, &oldSigIntr, null);
1304 	}
1305 
1306 	__gshared bool interrupted;
1307 
1308 	private
1309 	extern(C)
1310 	void interruptSignalHandler(int sigNumber) nothrow {
1311 		interrupted = true;
1312 	}
1313 }
1314 
1315 /// Gives MIDI output access.
1316 struct MidiOutput {
1317 	version(ALSA) {
1318 		snd_rawmidi_t* handle;
1319 	} else version(WinMM) {
1320 		HMIDIOUT handle;
1321 	}
1322 
1323 	@disable this();
1324 	@disable this(this);
1325 
1326 	/// Always pass card == 0.
1327 	this(int card) {
1328 		assert(card == 0);
1329 
1330 		version(ALSA) {
1331 			if(auto err = snd_rawmidi_open(null, &handle, midiName, 0))
1332 				throw new AlsaException("rawmidi open", err);
1333 		} else version(WinMM) {
1334 			if(auto err = midiOutOpen(&handle, 0, 0, 0, CALLBACK_NULL))
1335 				throw new WinMMException("midi out open", err);
1336 		} else static assert(0);
1337 	}
1338 
1339 	void silenceAllNotes() {
1340 		foreach(a; 0 .. 16)
1341 			writeMidiMessage((0x0b << 4)|a /*MIDI_EVENT_CONTROLLER*/, 123, 0);
1342 	}
1343 
1344 	/// Send a reset message, silencing all notes
1345 	void reset() {
1346 		version(ALSA) {
1347 			static immutable ubyte[3] resetSequence = [0x0b << 4, 123, 0];
1348 			// send a controller event to reset it
1349 			writeRawMessageData(resetSequence[]);
1350 			static immutable ubyte[1] resetCmd = [0xff];
1351 			writeRawMessageData(resetCmd[]);
1352 			// and flush it immediately
1353 			snd_rawmidi_drain(handle);
1354 		} else version(WinMM) {
1355 			if(auto error = midiOutReset(handle))
1356 				throw new WinMMException("midi reset", error);
1357 		} else static assert(0);
1358 	}
1359 
1360 	/// Writes a single low-level midi message
1361 	/// Timing and sending sane data is your responsibility!
1362 	void writeMidiMessage(int status, int param1, int param2) {
1363 		version(ALSA) {
1364 			ubyte[3] dataBuffer;
1365 
1366 			dataBuffer[0] = cast(ubyte) status;
1367 			dataBuffer[1] = cast(ubyte) param1;
1368 			dataBuffer[2] = cast(ubyte) param2;
1369 
1370 			auto msg = status >> 4;
1371 			ubyte[] data;
1372 			if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch)
1373 				data = dataBuffer[0 .. 2];
1374 			else
1375 				data = dataBuffer[];
1376 
1377 			writeRawMessageData(data);
1378 		} else version(WinMM) {
1379 			DWORD word = (param2 << 16) | (param1 << 8) | status;
1380 			if(auto error = midiOutShortMsg(handle, word))
1381 				throw new WinMMException("midi out", error);
1382 		} else static assert(0);
1383 
1384 	}
1385 
1386 	/// Writes a series of individual raw messages.
1387 	/// Timing and sending sane data is your responsibility!
1388 	/// The data should NOT include any timestamp bytes - each midi message should be 2 or 3 bytes.
1389 	void writeRawMessageData(scope const(ubyte)[] data) {
1390 		if(data.length == 0)
1391 			return;
1392 		version(ALSA) {
1393 			ssize_t written;
1394 
1395 			while(data.length) {
1396 				written = snd_rawmidi_write(handle, data.ptr, data.length);
1397 				if(written < 0)
1398 					throw new AlsaException("midi write", cast(int) written);
1399 				data = data[cast(int) written .. $];
1400 			}
1401 		} else version(WinMM) {
1402 			while(data.length) {
1403 				auto msg = data[0] >> 4;
1404 				if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) {
1405 					writeMidiMessage(data[0], data[1], 0);
1406 					data = data[2 .. $];
1407 				} else {
1408 					writeMidiMessage(data[0], data[1], data[2]);
1409 					data = data[3 .. $];
1410 				}
1411 			}
1412 		} else static assert(0);
1413 	}
1414 
1415 	~this() {
1416 		version(ALSA) {
1417 			snd_rawmidi_close(handle);
1418 		} else version(WinMM) {
1419 			midiOutClose(handle);
1420 		} else static assert(0);
1421 	}
1422 }
1423 
1424 
1425 // FIXME: maybe add a PC speaker beep function for completeness
1426 
1427 /// Interfaces with the default sound card. You should only have a single instance of this and it should
1428 /// be stack allocated, so its destructor cleans up after it.
1429 ///
1430 /// A mixer gives access to things like volume controls and mute buttons. It should also give a
1431 /// callback feature to alert you of when the settings are changed by another program.
1432 version(ALSA) // FIXME
1433 struct AudioMixer {
1434 	// To port to a new OS: put the data in the right version blocks
1435 	// then implement each function. Leave else static assert(0) at the
1436 	// end of each version group in a function so it is easier to implement elsewhere later.
1437 	//
1438 	// If a function is only relevant on your OS, put the whole function in a version block
1439 	// and give it an OS specific name of some sort.
1440 	//
1441 	// Feel free to do that btw without worrying about lowest common denominator: we want low level access when we want it.
1442 	//
1443 	// Put necessary bindings at the end of the file, or use an import if you like, but I prefer these files to be standalone.
1444 	version(ALSA) {
1445 		snd_mixer_t* handle;
1446 		snd_mixer_selem_id_t* sid;
1447 		snd_mixer_elem_t* selem;
1448 
1449 		c_long maxVolume, minVolume; // these are ok to use if you are writing ALSA specific code i guess
1450 
1451 		enum selemName = "Master";
1452 	}
1453 
1454 	@disable this();
1455 	@disable this(this);
1456 
1457 	/// Only cardId == 0 is supported
1458 	this(int cardId) {
1459 		assert(cardId == 0, "Pass 0 to use default sound card.");
1460 
1461 		version(ALSA) {
1462 			if(auto err = snd_mixer_open(&handle, 0))
1463 				throw new AlsaException("open sound", err);
1464 			scope(failure)
1465 				snd_mixer_close(handle);
1466 			if(auto err = snd_mixer_attach(handle, cardName))
1467 				throw new AlsaException("attach to sound card", err);
1468 			if(auto err = snd_mixer_selem_register(handle, null, null))
1469 				throw new AlsaException("register mixer", err);
1470 			if(auto err = snd_mixer_load(handle))
1471 				throw new AlsaException("load mixer", err);
1472 
1473 			if(auto err = snd_mixer_selem_id_malloc(&sid))
1474 				throw new AlsaException("master channel open", err);
1475 			scope(failure)
1476 				snd_mixer_selem_id_free(sid);
1477 			snd_mixer_selem_id_set_index(sid, 0);
1478 			snd_mixer_selem_id_set_name(sid, selemName);
1479 			selem = snd_mixer_find_selem(handle, sid);
1480 			if(selem is null)
1481 				throw new AlsaException("find master element", 0);
1482 
1483 			if(auto err = snd_mixer_selem_get_playback_volume_range(selem, &minVolume, &maxVolume))
1484 				throw new AlsaException("get volume range", err);
1485 
1486 			version(with_eventloop) {
1487 				import arsd.eventloop;
1488 				addFileEventListeners(getAlsaFileDescriptors()[0], &eventListener, null, null);
1489 				setAlsaElemCallback(&alsaCallback);
1490 			}
1491 		} else static assert(0);
1492 	}
1493 
1494 	~this() {
1495 		version(ALSA) {
1496 			version(with_eventloop) {
1497 				import arsd.eventloop;
1498 				removeFileEventListeners(getAlsaFileDescriptors()[0]);
1499 			}
1500 			snd_mixer_selem_id_free(sid);
1501 			snd_mixer_close(handle);
1502 		} else static assert(0);
1503 	}
1504 
1505 	version(ALSA)
1506 	version(with_eventloop) {
1507 		static struct MixerEvent {}
1508 		nothrow @nogc
1509 		extern(C) static int alsaCallback(snd_mixer_elem_t*, uint) {
1510 			import arsd.eventloop;
1511 			try
1512 				send(MixerEvent());
1513 			catch(Exception)
1514 				return 1;
1515 
1516 			return 0;
1517 		}
1518 
1519 		void eventListener(int fd) {
1520 			handleAlsaEvents();
1521 		}
1522 	}
1523 
1524 	/// Gets the master channel's mute state
1525 	/// Note: this affects shared system state and you should not use it unless the end user wants you to.
1526 	@property bool muteMaster() {
1527 		version(ALSA) {
1528 			int result;
1529 			if(auto err = snd_mixer_selem_get_playback_switch(selem, 0, &result))
1530 				throw new AlsaException("get mute state", err);
1531 			return result == 0;
1532 		} else static assert(0);
1533 	}
1534 
1535 	/// Mutes or unmutes the master channel
1536 	/// Note: this affects shared system state and you should not use it unless the end user wants you to.
1537 	@property void muteMaster(bool mute) {
1538 		version(ALSA) {
1539 			if(auto err = snd_mixer_selem_set_playback_switch_all(selem, mute ? 0 : 1))
1540 				throw new AlsaException("set mute state", err);
1541 		} else static assert(0);
1542 	}
1543 
1544 	/// returns a percentage, between 0 and 100 (inclusive)
1545 	int getMasterVolume() {
1546 		version(ALSA) {
1547 			auto volume = getMasterVolumeExact();
1548 			return cast(int)(volume * 100 / (maxVolume - minVolume));
1549 		} else static assert(0);
1550 	}
1551 
1552 	/// Gets the exact value returned from the operating system. The range may vary.
1553 	int getMasterVolumeExact() {
1554 		version(ALSA) {
1555 			c_long volume;
1556 			snd_mixer_selem_get_playback_volume(selem, 0, &volume);
1557 			return cast(int)volume;
1558 		} else static assert(0);
1559 	}
1560 
1561 	/// sets a percentage on the volume, so it must be 0 <= volume <= 100
1562 	/// Note: this affects shared system state and you should not use it unless the end user wants you to.
1563 	void setMasterVolume(int volume) {
1564 		version(ALSA) {
1565 			assert(volume >= 0 && volume <= 100);
1566 			setMasterVolumeExact(cast(int)(volume * (maxVolume - minVolume) / 100));
1567 		} else static assert(0);
1568 	}
1569 
1570 	/// Sets an exact volume. Must be in range of the OS provided min and max.
1571 	void setMasterVolumeExact(int volume) {
1572 		version(ALSA) {
1573 			if(auto err = snd_mixer_selem_set_playback_volume_all(selem, volume))
1574 				throw new AlsaException("set volume", err);
1575 		} else static assert(0);
1576 	}
1577 
1578 	version(ALSA) {
1579 		/// Gets the ALSA descriptors which you can watch for events
1580 		/// on using regular select, poll, epoll, etc.
1581 		int[] getAlsaFileDescriptors() {
1582 			import core.sys.posix.poll;
1583 			pollfd[32] descriptors = void;
1584 			int got = snd_mixer_poll_descriptors(handle, descriptors.ptr, descriptors.length);
1585 			int[] result;
1586 			result.length = got;
1587 			foreach(i, desc; descriptors[0 .. got])
1588 				result[i] = desc.fd;
1589 			return result;
1590 		}
1591 
1592 		/// When the FD is ready, call this to let ALSA do its thing.
1593 		void handleAlsaEvents() {
1594 			snd_mixer_handle_events(handle);
1595 		}
1596 
1597 		/// Set a callback for the master volume change events.
1598 		void setAlsaElemCallback(snd_mixer_elem_callback_t dg) {
1599 			snd_mixer_elem_set_callback(selem, dg);
1600 		}
1601 	}
1602 }
1603 
1604 // ****************
1605 // Midi helpers
1606 // ****************
1607 
1608 // FIXME: code the .mid file format, read and write
1609 
1610 enum MidiEvent {
1611 	NoteOff           = 0x08,
1612 	NoteOn            = 0x09,
1613 	NoteAftertouch    = 0x0a,
1614 	Controller        = 0x0b,
1615 	ProgramChange     = 0x0c, // one param
1616 	ChannelAftertouch = 0x0d, // one param
1617 	PitchBend         = 0x0e,
1618 }
1619 
1620 enum MidiNote : ubyte {
1621 	middleC = 60,
1622 	A =  69, // 440 Hz
1623 	As = 70,
1624 	B =  71,
1625 	C =  72,
1626 	Cs = 73,
1627 	D =  74,
1628 	Ds = 75,
1629 	E =  76,
1630 	F =  77,
1631 	Fs = 78,
1632 	G =  79,
1633 	Gs = 80,
1634 }
1635 
1636 /// Puts a note on at the beginning of the passed slice, advancing it by the amount of the message size.
1637 /// Returns the message slice.
1638 ///
1639 /// See: http://www.midi.org/techspecs/midimessages.php
1640 ubyte[] midiNoteOn(ref ubyte[] where, ubyte channel, byte note, byte velocity) {
1641 	where[0] = (MidiEvent.NoteOn << 4) | (channel&0x0f);
1642 	where[1] = note;
1643 	where[2] = velocity;
1644 	auto it = where[0 .. 3];
1645 	where = where[3 .. $];
1646 	return it;
1647 }
1648 
1649 /// Note off.
1650 ubyte[] midiNoteOff(ref ubyte[] where, ubyte channel, byte note, byte velocity) {
1651 	where[0] = (MidiEvent.NoteOff << 4) | (channel&0x0f);
1652 	where[1] = note;
1653 	where[2] = velocity;
1654 	auto it = where[0 .. 3];
1655 	where = where[3 .. $];
1656 	return it;
1657 }
1658 
1659 /// Aftertouch.
1660 ubyte[] midiNoteAftertouch(ref ubyte[] where, ubyte channel, byte note, byte pressure) {
1661 	where[0] = (MidiEvent.NoteAftertouch << 4) | (channel&0x0f);
1662 	where[1] = note;
1663 	where[2] = pressure;
1664 	auto it = where[0 .. 3];
1665 	where = where[3 .. $];
1666 	return it;
1667 }
1668 
1669 /// Controller.
1670 ubyte[] midiNoteController(ref ubyte[] where, ubyte channel, byte controllerNumber, byte controllerValue) {
1671 	where[0] = (MidiEvent.Controller << 4) | (channel&0x0f);
1672 	where[1] = controllerNumber;
1673 	where[2] = controllerValue;
1674 	auto it = where[0 .. 3];
1675 	where = where[3 .. $];
1676 	return it;
1677 }
1678 
1679 /// Program change.
1680 ubyte[] midiProgramChange(ref ubyte[] where, ubyte channel, byte program) {
1681 	where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f);
1682 	where[1] = program;
1683 	auto it = where[0 .. 2];
1684 	where = where[2 .. $];
1685 	return it;
1686 }
1687 
1688 /// Channel aftertouch.
1689 ubyte[] midiChannelAftertouch(ref ubyte[] where, ubyte channel, byte amount) {
1690 	where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f);
1691 	where[1] = amount;
1692 	auto it = where[0 .. 2];
1693 	where = where[2 .. $];
1694 	return it;
1695 }
1696 
1697 /// Pitch bend. FIXME doesn't work right
1698 ubyte[] midiNotePitchBend(ref ubyte[] where, ubyte channel, short change) {
1699 /*
1700 first byte is llllll
1701 second byte is mmmmmm
1702 
1703 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.
1704 */
1705 	where[0] = (MidiEvent.PitchBend << 4) | (channel&0x0f);
1706 	// FIXME
1707 	where[1] = 0;
1708 	where[2] = 0;
1709 	auto it = where[0 .. 3];
1710 	where = where[3 .. $];
1711 	return it;
1712 }
1713 
1714 
1715 // ****************
1716 // Wav helpers
1717 // ****************
1718 
1719 // FIXME: the .wav file format should be here, read and write (at least basics)
1720 // as well as some kind helpers to generate some sounds.
1721 
1722 // ****************
1723 // OS specific helper stuff follows
1724 // ****************
1725 
1726 version(ALSA)
1727 // Opens the PCM device with default settings: stereo, 16 bit, 44.1 kHz, interleaved R/W.
1728 snd_pcm_t* openAlsaPcm(snd_pcm_stream_t direction, int SampleRate, int channels) {
1729 	snd_pcm_t* handle;
1730 	snd_pcm_hw_params_t* hwParams;
1731 
1732 	/* Open PCM and initialize hardware */
1733 
1734 	if (auto err = snd_pcm_open(&handle, cardName, direction, 0))
1735 		throw new AlsaException("open device", err);
1736 	scope(failure)
1737 		snd_pcm_close(handle);
1738 
1739 
1740 	if (auto err = snd_pcm_hw_params_malloc(&hwParams))
1741 		throw new AlsaException("params malloc", err);
1742 	scope(exit)
1743 		snd_pcm_hw_params_free(hwParams);
1744 			 
1745 	if (auto err = snd_pcm_hw_params_any(handle, hwParams))
1746 		// can actually survive a failure here, we will just move forward
1747 		{} // throw new AlsaException("params init", err);
1748 
1749 	if (auto err = snd_pcm_hw_params_set_access(handle, hwParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED))
1750 		throw new AlsaException("params access", err);
1751 
1752 	if (auto err = snd_pcm_hw_params_set_format(handle, hwParams, snd_pcm_format.SND_PCM_FORMAT_S16_LE))
1753 		throw new AlsaException("params format", err);
1754 
1755 	uint rate = SampleRate;
1756 	int dir = 0;
1757 	if (auto err = snd_pcm_hw_params_set_rate_near(handle, hwParams, &rate, &dir))
1758 		throw new AlsaException("params rate", err);
1759 
1760 	assert(rate == SampleRate); // cheap me
1761 
1762 	if (auto err = snd_pcm_hw_params_set_channels(handle, hwParams, channels))
1763 		throw new AlsaException("params channels", err);
1764 
1765 	uint periods = 2;
1766 	{
1767 	auto err = snd_pcm_hw_params_set_periods_near(handle, hwParams, &periods, 0);
1768 	if(err < 0)
1769 		throw new AlsaException("periods", err);
1770 
1771 	snd_pcm_uframes_t sz = (BUFFER_SIZE_FRAMES * periods);
1772 	err = snd_pcm_hw_params_set_buffer_size_near(handle, hwParams, &sz);
1773 	if(err < 0)
1774 		throw new AlsaException("buffer size", err);
1775 	}
1776 
1777 	if (auto err = snd_pcm_hw_params(handle, hwParams))
1778 		throw new AlsaException("params install", err);
1779 
1780 	/* Setting up the callbacks */
1781 
1782 	snd_pcm_sw_params_t* swparams;
1783 	if(auto err = snd_pcm_sw_params_malloc(&swparams))
1784 		throw new AlsaException("sw malloc", err);
1785 	scope(exit)
1786 		snd_pcm_sw_params_free(swparams);
1787 	if(auto err = snd_pcm_sw_params_current(handle, swparams))
1788 		throw new AlsaException("sw set", err);
1789 	if(auto err = snd_pcm_sw_params_set_avail_min(handle, swparams, BUFFER_SIZE_FRAMES))
1790 		throw new AlsaException("sw min", err);
1791 	if(auto err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0))
1792 		throw new AlsaException("sw threshold", err);
1793 	if(auto err = snd_pcm_sw_params(handle, swparams))
1794 		throw new AlsaException("sw params", err);
1795 
1796 	/* finish setup */
1797 
1798 	if (auto err = snd_pcm_prepare(handle))
1799 		throw new AlsaException("prepare", err);
1800 
1801 	assert(handle !is null);
1802 	return handle;
1803 }
1804 
1805 version(ALSA)
1806 class AlsaException : AudioException {
1807 	this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
1808 		auto msg = snd_strerror(error);
1809 		import core.stdc.string;
1810 		super(cast(string) (message ~ ": " ~ msg[0 .. strlen(msg)]), file, line, next);
1811 	}
1812 }
1813 
1814 version(WinMM)
1815 class WinMMException : AudioException {
1816 	this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
1817 		// FIXME: format the error
1818 		// midiOutGetErrorText, etc.
1819 		super(message, file, line, next);
1820 	}
1821 }
1822 
1823 // ****************
1824 // Bindings follow
1825 // ****************
1826 
1827 version(ALSA) {
1828 extern(C):
1829 @nogc nothrow:
1830 	pragma(lib, "asound");
1831 	private import core.sys.posix.poll;
1832 
1833 	const(char)* snd_strerror(int);
1834 
1835 	// pcm
1836 	enum snd_pcm_stream_t {
1837 		SND_PCM_STREAM_PLAYBACK,
1838 		SND_PCM_STREAM_CAPTURE
1839 	}
1840 
1841 	enum snd_pcm_access_t {
1842 		/** mmap access with simple interleaved channels */ 
1843 		SND_PCM_ACCESS_MMAP_INTERLEAVED = 0, 
1844 		/** mmap access with simple non interleaved channels */ 
1845 		SND_PCM_ACCESS_MMAP_NONINTERLEAVED, 
1846 		/** mmap access with complex placement */ 
1847 		SND_PCM_ACCESS_MMAP_COMPLEX, 
1848 		/** snd_pcm_readi/snd_pcm_writei access */ 
1849 		SND_PCM_ACCESS_RW_INTERLEAVED, 
1850 		/** snd_pcm_readn/snd_pcm_writen access */ 
1851 		SND_PCM_ACCESS_RW_NONINTERLEAVED, 
1852 		SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED
1853 	}
1854 
1855 	enum snd_pcm_format {
1856 		/** Unknown */
1857 		SND_PCM_FORMAT_UNKNOWN = -1,
1858 		/** Signed 8 bit */
1859 		SND_PCM_FORMAT_S8 = 0,
1860 		/** Unsigned 8 bit */
1861 		SND_PCM_FORMAT_U8,
1862 		/** Signed 16 bit Little Endian */
1863 		SND_PCM_FORMAT_S16_LE,
1864 		/** Signed 16 bit Big Endian */
1865 		SND_PCM_FORMAT_S16_BE,
1866 		/** Unsigned 16 bit Little Endian */
1867 		SND_PCM_FORMAT_U16_LE,
1868 		/** Unsigned 16 bit Big Endian */
1869 		SND_PCM_FORMAT_U16_BE,
1870 		/** Signed 24 bit Little Endian using low three bytes in 32-bit word */
1871 		SND_PCM_FORMAT_S24_LE,
1872 		/** Signed 24 bit Big Endian using low three bytes in 32-bit word */
1873 		SND_PCM_FORMAT_S24_BE,
1874 		/** Unsigned 24 bit Little Endian using low three bytes in 32-bit word */
1875 		SND_PCM_FORMAT_U24_LE,
1876 		/** Unsigned 24 bit Big Endian using low three bytes in 32-bit word */
1877 		SND_PCM_FORMAT_U24_BE,
1878 		/** Signed 32 bit Little Endian */
1879 		SND_PCM_FORMAT_S32_LE,
1880 		/** Signed 32 bit Big Endian */
1881 		SND_PCM_FORMAT_S32_BE,
1882 		/** Unsigned 32 bit Little Endian */
1883 		SND_PCM_FORMAT_U32_LE,
1884 		/** Unsigned 32 bit Big Endian */
1885 		SND_PCM_FORMAT_U32_BE,
1886 		/** Float 32 bit Little Endian, Range -1.0 to 1.0 */
1887 		SND_PCM_FORMAT_FLOAT_LE,
1888 		/** Float 32 bit Big Endian, Range -1.0 to 1.0 */
1889 		SND_PCM_FORMAT_FLOAT_BE,
1890 		/** Float 64 bit Little Endian, Range -1.0 to 1.0 */
1891 		SND_PCM_FORMAT_FLOAT64_LE,
1892 		/** Float 64 bit Big Endian, Range -1.0 to 1.0 */
1893 		SND_PCM_FORMAT_FLOAT64_BE,
1894 		/** IEC-958 Little Endian */
1895 		SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
1896 		/** IEC-958 Big Endian */
1897 		SND_PCM_FORMAT_IEC958_SUBFRAME_BE,
1898 		/** Mu-Law */
1899 		SND_PCM_FORMAT_MU_LAW,
1900 		/** A-Law */
1901 		SND_PCM_FORMAT_A_LAW,
1902 		/** Ima-ADPCM */
1903 		SND_PCM_FORMAT_IMA_ADPCM,
1904 		/** MPEG */
1905 		SND_PCM_FORMAT_MPEG,
1906 		/** GSM */
1907 		SND_PCM_FORMAT_GSM,
1908 		/** Special */
1909 		SND_PCM_FORMAT_SPECIAL = 31,
1910 		/** Signed 24bit Little Endian in 3bytes format */
1911 		SND_PCM_FORMAT_S24_3LE = 32,
1912 		/** Signed 24bit Big Endian in 3bytes format */
1913 		SND_PCM_FORMAT_S24_3BE,
1914 		/** Unsigned 24bit Little Endian in 3bytes format */
1915 		SND_PCM_FORMAT_U24_3LE,
1916 		/** Unsigned 24bit Big Endian in 3bytes format */
1917 		SND_PCM_FORMAT_U24_3BE,
1918 		/** Signed 20bit Little Endian in 3bytes format */
1919 		SND_PCM_FORMAT_S20_3LE,
1920 		/** Signed 20bit Big Endian in 3bytes format */
1921 		SND_PCM_FORMAT_S20_3BE,
1922 		/** Unsigned 20bit Little Endian in 3bytes format */
1923 		SND_PCM_FORMAT_U20_3LE,
1924 		/** Unsigned 20bit Big Endian in 3bytes format */
1925 		SND_PCM_FORMAT_U20_3BE,
1926 		/** Signed 18bit Little Endian in 3bytes format */
1927 		SND_PCM_FORMAT_S18_3LE,
1928 		/** Signed 18bit Big Endian in 3bytes format */
1929 		SND_PCM_FORMAT_S18_3BE,
1930 		/** Unsigned 18bit Little Endian in 3bytes format */
1931 		SND_PCM_FORMAT_U18_3LE,
1932 		/** Unsigned 18bit Big Endian in 3bytes format */
1933 		SND_PCM_FORMAT_U18_3BE,
1934 		/* G.723 (ADPCM) 24 kbit/s, 8 samples in 3 bytes */
1935 		SND_PCM_FORMAT_G723_24,
1936 		/* G.723 (ADPCM) 24 kbit/s, 1 sample in 1 byte */
1937 		SND_PCM_FORMAT_G723_24_1B,
1938 		/* G.723 (ADPCM) 40 kbit/s, 8 samples in 3 bytes */
1939 		SND_PCM_FORMAT_G723_40,
1940 		/* G.723 (ADPCM) 40 kbit/s, 1 sample in 1 byte */
1941 		SND_PCM_FORMAT_G723_40_1B,
1942 		/* Direct Stream Digital (DSD) in 1-byte samples (x8) */
1943 		SND_PCM_FORMAT_DSD_U8,
1944 		/* Direct Stream Digital (DSD) in 2-byte samples (x16) */
1945 		SND_PCM_FORMAT_DSD_U16_LE,
1946 		SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_DSD_U16_LE,
1947 
1948 		// I snipped a bunch of endian-specific ones!
1949 	}
1950 
1951 	struct snd_pcm_t {}
1952 	struct snd_pcm_hw_params_t {}
1953 	struct snd_pcm_sw_params_t {}
1954 
1955 	int snd_pcm_open(snd_pcm_t**, const char*, snd_pcm_stream_t, int);
1956 	int snd_pcm_close(snd_pcm_t*);
1957 	int snd_pcm_pause(snd_pcm_t*, int);
1958 	int snd_pcm_prepare(snd_pcm_t*);
1959 	int snd_pcm_hw_params(snd_pcm_t*, snd_pcm_hw_params_t*);
1960 	int snd_pcm_hw_params_set_periods(snd_pcm_t*, snd_pcm_hw_params_t*, uint, int);
1961 	int snd_pcm_hw_params_set_periods_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int);
1962 	int snd_pcm_hw_params_set_buffer_size(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t);
1963 	int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t*);
1964 	int snd_pcm_hw_params_set_channels(snd_pcm_t*, snd_pcm_hw_params_t*, uint);
1965 	int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t**);
1966 	void snd_pcm_hw_params_free(snd_pcm_hw_params_t*);
1967 	int snd_pcm_hw_params_any(snd_pcm_t*, snd_pcm_hw_params_t*);
1968 	int snd_pcm_hw_params_set_access(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_access_t);
1969 	int snd_pcm_hw_params_set_format(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_format);
1970 	int snd_pcm_hw_params_set_rate_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int*);
1971 
1972 	int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t**);
1973 	void snd_pcm_sw_params_free(snd_pcm_sw_params_t*);
1974 
1975 	int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
1976 	int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
1977 	int snd_pcm_sw_params_set_avail_min(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
1978 	int snd_pcm_sw_params_set_start_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
1979 	int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
1980 
1981 	alias snd_pcm_sframes_t = c_long;
1982 	alias snd_pcm_uframes_t = c_ulong;
1983 	snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t*, const void*, snd_pcm_uframes_t size);
1984 	snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t*, void*, snd_pcm_uframes_t size);
1985 
1986 	int snd_pcm_wait(snd_pcm_t *pcm, int timeout);
1987 	snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm);
1988 	snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm);
1989 
1990 	int snd_pcm_recover (snd_pcm_t* pcm, int err, int silent);
1991 
1992 	alias snd_lib_error_handler_t = void function (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...);
1993 	int snd_lib_error_set_handler (snd_lib_error_handler_t handler);
1994 
1995 	import core.stdc.stdarg;
1996 	private void alsa_message_silencer (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...) {}
1997 	//k8: ALSAlib loves to trash stderr; shut it up
1998 	void silence_alsa_messages () { snd_lib_error_set_handler(&alsa_message_silencer); }
1999 	shared static this () { silence_alsa_messages(); }
2000 
2001 	// raw midi
2002 
2003 	static if(is(size_t == uint))
2004 		alias ssize_t = int;
2005 	else
2006 		alias ssize_t = long;
2007 
2008 
2009 	struct snd_rawmidi_t {}
2010 	int snd_rawmidi_open(snd_rawmidi_t**, snd_rawmidi_t**, const char*, int);
2011 	int snd_rawmidi_close(snd_rawmidi_t*);
2012 	int snd_rawmidi_drain(snd_rawmidi_t*);
2013 	ssize_t snd_rawmidi_write(snd_rawmidi_t*, const void*, size_t);
2014 	ssize_t snd_rawmidi_read(snd_rawmidi_t*, void*, size_t);
2015 
2016 	// mixer
2017 
2018 	struct snd_mixer_t {}
2019 	struct snd_mixer_elem_t {}
2020 	struct snd_mixer_selem_id_t {}
2021 
2022 	alias snd_mixer_elem_callback_t = int function(snd_mixer_elem_t*, uint);
2023 
2024 	int snd_mixer_open(snd_mixer_t**, int mode);
2025 	int snd_mixer_close(snd_mixer_t*);
2026 	int snd_mixer_attach(snd_mixer_t*, const char*);
2027 	int snd_mixer_load(snd_mixer_t*);
2028 
2029 	// FIXME: those aren't actually void*
2030 	int snd_mixer_selem_register(snd_mixer_t*, void*, void*);
2031 	int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t**);
2032 	void snd_mixer_selem_id_free(snd_mixer_selem_id_t*);
2033 	void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t*, uint);
2034 	void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t*, const char*);
2035 	snd_mixer_elem_t* snd_mixer_find_selem(snd_mixer_t*, in snd_mixer_selem_id_t*);
2036 
2037 	// FIXME: the int should be an enum for channel identifier
2038 	int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t*, int, c_long*);
2039 
2040 	int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t*, c_long*, c_long*);
2041 
2042 	int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t*, c_long);
2043 
2044 	void snd_mixer_elem_set_callback(snd_mixer_elem_t*, snd_mixer_elem_callback_t);
2045 	int snd_mixer_poll_descriptors(snd_mixer_t*, pollfd*, uint space);
2046 
2047 	int snd_mixer_handle_events(snd_mixer_t*);
2048 
2049 	// FIXME: the first int should be an enum for channel identifier
2050 	int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t*, int, int* value);
2051 	int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t*, int);
2052 }
2053 
2054 version(WinMM) {
2055 extern(Windows):
2056 @nogc nothrow:
2057 	pragma(lib, "winmm");
2058 	import core.sys.windows.windows;
2059 
2060 /*
2061 	Windows functions include:
2062 	http://msdn.microsoft.com/en-us/library/ms713762%28VS.85%29.aspx
2063 	http://msdn.microsoft.com/en-us/library/ms713504%28v=vs.85%29.aspx
2064 	http://msdn.microsoft.com/en-us/library/windows/desktop/dd798480%28v=vs.85%29.aspx#
2065 	http://msdn.microsoft.com/en-US/subscriptions/ms712109.aspx
2066 */
2067 
2068 	// pcm
2069 
2070 	// midi
2071 /+
2072 	alias HMIDIOUT = HANDLE;
2073 	alias MMRESULT = UINT;
2074 
2075 	MMRESULT midiOutOpen(HMIDIOUT*, UINT, DWORD, DWORD, DWORD);
2076 	MMRESULT midiOutClose(HMIDIOUT);
2077 	MMRESULT midiOutReset(HMIDIOUT);
2078 	MMRESULT midiOutShortMsg(HMIDIOUT, DWORD);
2079 
2080 	alias HWAVEOUT = HANDLE;
2081 
2082 	struct WAVEFORMATEX {
2083 		WORD wFormatTag;
2084 		WORD nChannels;
2085 		DWORD nSamplesPerSec;
2086 		DWORD nAvgBytesPerSec;
2087 		WORD nBlockAlign;
2088 		WORD wBitsPerSample;
2089 		WORD cbSize;
2090 	}
2091 
2092 	struct WAVEHDR {
2093 		void* lpData;
2094 		DWORD dwBufferLength;
2095 		DWORD dwBytesRecorded;
2096 		DWORD dwUser;
2097 		DWORD dwFlags;
2098 		DWORD dwLoops;
2099 		WAVEHDR *lpNext;
2100 		DWORD reserved;
2101 	}
2102 
2103 	enum UINT WAVE_MAPPER= -1;
2104 
2105 	MMRESULT waveOutOpen(HWAVEOUT*, UINT_PTR, WAVEFORMATEX*, void* callback, void*, DWORD);
2106 	MMRESULT waveOutClose(HWAVEOUT);
2107 	MMRESULT waveOutPrepareHeader(HWAVEOUT, WAVEHDR*, UINT);
2108 	MMRESULT waveOutUnprepareHeader(HWAVEOUT, WAVEHDR*, UINT);
2109 	MMRESULT waveOutWrite(HWAVEOUT, WAVEHDR*, UINT);
2110 
2111 	MMRESULT waveOutGetVolume(HWAVEOUT, PDWORD);
2112 	MMRESULT waveOutSetVolume(HWAVEOUT, DWORD);
2113 
2114 	enum CALLBACK_TYPEMASK = 0x70000;
2115 	enum CALLBACK_NULL     = 0;
2116 	enum CALLBACK_WINDOW   = 0x10000;
2117 	enum CALLBACK_TASK     = 0x20000;
2118 	enum CALLBACK_FUNCTION = 0x30000;
2119 	enum CALLBACK_THREAD   = CALLBACK_TASK;
2120 	enum CALLBACK_EVENT    = 0x50000;
2121 
2122 	enum WAVE_FORMAT_PCM = 1;
2123 
2124 	enum WHDR_PREPARED = 2;
2125 	enum WHDR_BEGINLOOP = 4;
2126 	enum WHDR_ENDLOOP = 8;
2127 	enum WHDR_INQUEUE = 16;
2128 
2129 	enum WinMMMessage : UINT {
2130 		MM_JOY1MOVE            = 0x3A0,
2131 		MM_JOY2MOVE,
2132 		MM_JOY1ZMOVE,
2133 		MM_JOY2ZMOVE,       // = 0x3A3
2134 		MM_JOY1BUTTONDOWN      = 0x3B5,
2135 		MM_JOY2BUTTONDOWN,
2136 		MM_JOY1BUTTONUP,
2137 		MM_JOY2BUTTONUP,
2138 		MM_MCINOTIFY,       // = 0x3B9
2139 		MM_WOM_OPEN            = 0x3BB,
2140 		MM_WOM_CLOSE,
2141 		MM_WOM_DONE,
2142 		MM_WIM_OPEN,
2143 		MM_WIM_CLOSE,
2144 		MM_WIM_DATA,
2145 		MM_MIM_OPEN,
2146 		MM_MIM_CLOSE,
2147 		MM_MIM_DATA,
2148 		MM_MIM_LONGDATA,
2149 		MM_MIM_ERROR,
2150 		MM_MIM_LONGERROR,
2151 		MM_MOM_OPEN,
2152 		MM_MOM_CLOSE,
2153 		MM_MOM_DONE,        // = 0x3C9
2154 		MM_DRVM_OPEN           = 0x3D0,
2155 		MM_DRVM_CLOSE,
2156 		MM_DRVM_DATA,
2157 		MM_DRVM_ERROR,
2158 		MM_STREAM_OPEN,
2159 		MM_STREAM_CLOSE,
2160 		MM_STREAM_DONE,
2161 		MM_STREAM_ERROR,    // = 0x3D7
2162 		MM_MOM_POSITIONCB      = 0x3CA,
2163 		MM_MCISIGNAL,
2164 		MM_MIM_MOREDATA,    // = 0x3CC
2165 		MM_MIXM_LINE_CHANGE    = 0x3D0,
2166 		MM_MIXM_CONTROL_CHANGE = 0x3D1
2167 	}
2168 
2169 
2170 	enum WOM_OPEN  = WinMMMessage.MM_WOM_OPEN;
2171 	enum WOM_CLOSE = WinMMMessage.MM_WOM_CLOSE;
2172 	enum WOM_DONE  = WinMMMessage.MM_WOM_DONE;
2173 	enum WIM_OPEN  = WinMMMessage.MM_WIM_OPEN;
2174 	enum WIM_CLOSE = WinMMMessage.MM_WIM_CLOSE;
2175 	enum WIM_DATA  = WinMMMessage.MM_WIM_DATA;
2176 
2177 
2178 	uint mciSendStringA(in char*,char*,uint,void*);
2179 
2180 +/
2181 }
2182 
2183 private enum scriptable = "arsd_jsvar_compatible";