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 	version(none) {
55 	import iv.stb.vorbis;
56 
57 	int channels;
58 	short* decoded;
59 	auto v = new VorbisDecoder("test.ogg");
60 
61 	auto ao = AudioOutput(0);
62 	ao.fillData = (short[] buffer) {
63 		auto got = v.getSamplesShortInterleaved(2, buffer.ptr, buffer.length);
64 		if(got == 0) {
65 			ao.stop();
66 		}
67 	};
68 
69 	ao.play();
70 	return;
71 	}
72 
73 
74 
75 
76 	auto thread = new AudioPcmOutThread();
77 	thread.start();
78 
79 	thread.playOgg("test.ogg");
80 
81 	Thread.sleep(5.seconds);
82 
83 	//Thread.sleep(150.msecs);
84 	thread.beep();
85 	Thread.sleep(250.msecs);
86 	thread.blip();
87 	Thread.sleep(250.msecs);
88 	thread.boop();
89 	Thread.sleep(1000.msecs);
90 	/*
91 	thread.beep(800, 500);
92 	Thread.sleep(500.msecs);
93 	thread.beep(366, 500);
94 	Thread.sleep(600.msecs);
95 	thread.beep(800, 500);
96 	thread.beep(366, 500);
97 	Thread.sleep(500.msecs);
98 	Thread.sleep(150.msecs);
99 	thread.beep(200);
100 	Thread.sleep(150.msecs);
101 	thread.beep(100);
102 	Thread.sleep(150.msecs);
103 	thread.noise();
104 	Thread.sleep(150.msecs);
105 	*/
106 
107 
108 	thread.stop();
109 
110 	thread.join();
111 
112 	return;
113 
114 	/*
115 	auto aio = AudioMixer(0);
116 
117 	import std.stdio;
118 	writeln(aio.muteMaster);
119 	*/
120 
121 	/*
122 	mciSendStringA("play test.wav", null, 0, null);
123 	Sleep(3000);
124 	import std.stdio;
125 	if(auto err = mciSendStringA("play test2.wav", null, 0, null))
126 		writeln(err);
127 	Sleep(6000);
128 	return;
129 	*/
130 
131 	// output about a second of random noise to demo PCM
132 	auto ao = AudioOutput(0);
133 	short[BUFFER_SIZE_SHORT] randomSpam = void;
134 	import core.stdc.stdlib;
135 	foreach(ref s; randomSpam)
136 		s = cast(short)((cast(short) rand()) - short.max / 2);
137 
138 	int loopCount = 40;
139 
140 	//import std.stdio;
141 	//writeln("Should be about ", loopCount * BUFFER_SIZE_FRAMES * 1000 / SampleRate, " microseconds");
142 
143 	int loops = 0;
144 	// only do simple stuff in here like fill the data, set simple
145 	// variables, or call stop anything else might cause deadlock
146 	ao.fillData = (short[] buffer) {
147 		buffer[] = randomSpam[0 .. buffer.length];
148 		loops++;
149 		if(loops == loopCount)
150 			ao.stop();
151 	};
152 
153 	ao.play();
154 
155 	return;
156 
157 	// Play a C major scale on the piano to demonstrate midi
158 	auto midi = MidiOutput(0);
159 
160 	ubyte[16] buffer = void;
161 	ubyte[] where = buffer[];
162 	midi.writeRawMessageData(where.midiProgramChange(1, 1));
163 	for(ubyte note = MidiNote.C; note <= MidiNote.C + 12; note++) {
164 		where = buffer[];
165 		midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
166 		import core.thread;
167 		Thread.sleep(dur!"msecs"(500));
168 		midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
169 
170 		if(note != 76 && note != 83)
171 			note++;
172 	}
173 	import core.thread;
174 	Thread.sleep(dur!"msecs"(500)); // give the last note a chance to finish
175 }
176 
177 import core.thread;
178 /++
179 	Makes an audio thread for you that you can make
180 	various sounds on and it will mix them with good
181 	enough latency for simple games.
182 
183 	---
184 		auto audio = new AudioPcmOutThread();
185 		audio.start();
186 		scope(exit) {
187 			audio.stop();
188 			audio.join();
189 		}
190 
191 		audio.beep();
192 
193 		// you need to keep the main program alive long enough
194 		// to keep this thread going to hear anything
195 		Thread.sleep(1.seconds);
196 	---
197 +/
198 final class AudioPcmOutThread : Thread {
199 	///
200 	this() {
201 		this.isDaemon = true;
202 		version(linux) {
203 			// this thread has no business intercepting signals from the main thread,
204 			// so gonna block a couple of them
205 			import core.sys.posix.signal;
206 			sigset_t sigset;
207 			auto err = sigfillset(&sigset);
208 			assert(!err);
209 			err = sigprocmask(SIG_BLOCK, &sigset, null);
210 			assert(!err);
211 		}
212 
213 		super(&run);
214 	}
215 
216 	///
217 	void stop() {
218 		if(ao) {
219 			ao.stop();
220 		}
221 	}
222 
223 	/// Args in hertz and milliseconds
224 	void beep(int freq = 900, int dur = 150, int volume = DEFAULT_VOLUME) {
225 		Sample s;
226 		s.operation = 0; // square wave
227 		s.frequency = SampleRate / freq;
228 		s.duration = dur * SampleRate / 1000;
229 		s.volume = volume;
230 		addSample(s);
231 	}
232 
233 	///
234 	void noise(int dur = 150, int volume = DEFAULT_VOLUME) {
235 		Sample s;
236 		s.operation = 1; // noise
237 		s.frequency = 0;
238 		s.volume = volume;
239 		s.duration = dur * SampleRate / 1000;
240 		addSample(s);
241 	}
242 
243 	///
244 	void boop(float attack = 8, int freqBase = 500, int dur = 150, int volume = DEFAULT_VOLUME) {
245 		Sample s;
246 		s.operation = 5; // custom
247 		s.volume = volume;
248 		s.duration = dur * SampleRate / 1000;
249 		s.f = delegate short(int x) {
250 			auto currentFrequency = cast(float) freqBase / (1 + cast(float) x / (cast(float) SampleRate / attack));
251 			import std.math;
252 			auto freq = 2 * PI /  (cast(float) SampleRate / currentFrequency);
253 			return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100);
254 		};
255 		addSample(s);
256 	}
257 
258 	///
259 	void blip(float attack = 6, int freqBase = 800, int dur = 150, int volume = DEFAULT_VOLUME) {
260 		Sample s;
261 		s.operation = 5; // custom
262 		s.volume = volume;
263 		s.duration = dur * SampleRate / 1000;
264 		s.f = delegate short(int x) {
265 			auto currentFrequency = cast(float) freqBase * (1 + cast(float) x / (cast(float) SampleRate / attack));
266 			import std.math;
267 			auto freq = 2 * PI /  (cast(float) SampleRate / currentFrequency);
268 			return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100);
269 		};
270 		addSample(s);
271 	}
272 
273 	version(none)
274 	void custom(int dur = 150, int volume = DEFAULT_VOLUME) {
275 		Sample s;
276 		s.operation = 5; // custom
277 		s.volume = volume;
278 		s.duration = dur * SampleRate / 1000;
279 		s.f = delegate short(int x) {
280 			auto currentFrequency = 500.0 / (1 + cast(float) x / (cast(float) SampleRate / 8));
281 			import std.math;
282 			auto freq = 2 * PI /  (cast(float) SampleRate / currentFrequency);
283 			return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100);
284 		};
285 		addSample(s);
286 	}
287 
288 	/// Requires vorbis.d to be compiled in (module arsd.vorbis)
289 	void playOgg()(string filename, bool loop = false) {
290 		import arsd.vorbis;
291 
292 		auto v = new VorbisDecoder(filename);
293 
294 		addChannel(
295 			delegate bool(short[] buffer) {
296 				if(cast(int) buffer.length != buffer.length)
297 					throw new Exception("eeeek");
298 				auto got = v.getSamplesShortInterleaved(2, buffer.ptr, cast(int) buffer.length);
299 				if(got == 0) {
300 					if(loop) {
301 						v.seekStart();
302 						return true;
303 					}
304 
305 					return false;
306 				}
307 				return true;
308 			}
309 		);
310 	}
311 
312 
313 	struct Sample {
314 		int operation;
315 		int frequency; /* in samples */
316 		int duration; /* in samples */
317 		int volume; /* between 1 and 100. You should generally shoot for something lowish, like 20. */
318 		int delay; /* in samples */
319 
320 		int x;
321 		short delegate(int x) f;
322 	}
323 
324 	final void addSample(Sample currentSample) {
325 		int frequencyCounter;
326 		short val = cast(short) (cast(int) short.max * currentSample.volume / 100);
327 		addChannel(
328 			delegate bool (short[] buffer) {
329 				if(currentSample.duration) {
330 					size_t i = 0;
331 					if(currentSample.delay) {
332 						if(buffer.length <= currentSample.delay * 2) {
333 							// whole buffer consumed by delay
334 							buffer[] = 0;
335 							currentSample.delay -= buffer.length / 2;
336 						} else {
337 							i = currentSample.delay * 2;
338 							buffer[0 .. i] = 0;
339 							currentSample.delay = 0;
340 						}
341 					}
342 					if(currentSample.delay > 0)
343 						return true;
344 
345 					size_t sampleFinish;
346 					if(currentSample.duration * 2 <= buffer.length) {
347 						sampleFinish = currentSample.duration * 2;
348 						currentSample.duration = 0;
349 					} else {
350 						sampleFinish = buffer.length;
351 						currentSample.duration -= buffer.length / 2;
352 					}
353 
354 					switch(currentSample.operation) {
355 						case 0: // square wave
356 							for(; i < sampleFinish; i++) {
357 								buffer[i] = val;
358 								// left and right do the same thing so we only count
359 								// every other sample
360 								if(i & 1) {
361 									if(frequencyCounter)
362 										frequencyCounter--;
363 									if(frequencyCounter == 0) {
364 										// are you kidding me dmd? random casts suck
365 										val = cast(short) -cast(int)(val);
366 										frequencyCounter = currentSample.frequency / 2;
367 									}
368 								}
369 							}
370 						break;
371 						case 1: // noise
372 							for(; i < sampleFinish; i++) {
373 								import std.random;
374 								buffer[i] = uniform(cast(short) -cast(int)val, val);
375 							}
376 						break;
377 						/+
378 						case 2: // triangle wave
379 							for(; i < sampleFinish; i++) {
380 								buffer[i] = val;
381 								// left and right do the same thing so we only count
382 								// every other sample
383 								if(i & 1) {
384 									if(frequencyCounter)
385 										frequencyCounter--;
386 									if(frequencyCounter == 0) {
387 										val = 0;
388 										frequencyCounter = currentSample.frequency / 2;
389 									}
390 								}
391 							}
392 
393 						break;
394 						case 3: // sawtooth wave
395 						case 4: // sine wave
396 						+/
397 						case 5: // custom function
398 							val = currentSample.f(currentSample.x);
399 							for(; i < sampleFinish; i++) {
400 								buffer[i] = val;
401 								if(i & 1) {
402 									currentSample.x++;
403 									val = currentSample.f(currentSample.x);
404 								}
405 							}
406 						break;
407 						default: // unknown; use silence
408 							currentSample.duration = 0;
409 					}
410 
411 					if(i < buffer.length)
412 						buffer[i .. $] = 0;
413 
414 					return currentSample.duration > 0;
415 				} else {
416 					return false;
417 				}
418 			}
419 		);
420 	}
421 
422 	/++
423 		The delegate returns false when it is finished (true means keep going).
424 		It must fill the buffer with waveform data on demand and must be latency
425 		sensitive; as fast as possible.
426 	+/
427 	public void addChannel(bool delegate(short[] buffer) dg) {
428 		synchronized(this) {
429 			// silently drops info if we don't have room in the buffer...
430 			// don't do a lot of long running things lol
431 			if(fillDatasLength < fillDatas.length)
432 				fillDatas[fillDatasLength++] = dg;
433 		}
434 	}
435 
436 	private {
437 		AudioOutput* ao;
438 
439 		bool delegate(short[] buffer)[32] fillDatas;
440 		int fillDatasLength = 0;
441 	}
442 
443 	private void run() {
444 		AudioOutput ao = AudioOutput(0);
445 		this.ao = &ao;
446 		ao.fillData = (short[] buffer) {
447 			short[BUFFER_SIZE_SHORT] bfr;
448 			bool first = true;
449 			if(fillDatasLength) {
450 				for(int idx = 0; idx < fillDatasLength; idx++) {
451 					auto dg = fillDatas[idx];
452 					auto ret = dg(bfr[0 .. buffer.length][]);
453 					foreach(i, v; bfr[0 .. buffer.length][]) {
454 						int val;
455 						if(first)
456 							val = 0;
457 						else
458 							val = buffer[i];
459 
460 						int a = val;
461 						int b = v;
462 						int cap = a + b;
463 						if(cap > short.max) cap = short.max;
464 						else if(cap < short.min) cap = short.min;
465 						val = cast(short) cap;
466 						buffer[i] = cast(short) val;
467 					}
468 					if(!ret) {
469 						// it returned false meaning this one is finished...
470 						synchronized(this) {
471 							fillDatas[idx] = fillDatas[fillDatasLength - 1];
472 							fillDatasLength--;
473 						}
474 						idx--;
475 					}
476 
477 					first = false;
478 				}
479 			} else {
480 				buffer[] = 0;
481 			}
482 		};
483 		ao.play();
484 	}
485 }
486 
487 
488 import core.stdc.config;
489 
490 version(linux) version=ALSA;
491 version(Windows) version=WinMM;
492 
493 enum SampleRate = 44100;
494 
495 version(ALSA) {
496 	enum cardName = "default";
497 
498 	// this is the virtual rawmidi device on my computer at least
499 	// maybe later i'll make it probe
500 	//
501 	// Getting midi to actually play on Linux is a bit of a pain.
502 	// Here's what I did:
503 	/*
504 		# load the kernel driver, if amidi -l gives ioctl error,
505 		# you haven't done this yet!
506 		modprobe snd-virmidi
507 
508 		# start a software synth. timidity -iA is also an option
509 		fluidsynth soundfont.sf2
510 
511 		# connect the virtual hardware port to the synthesizer
512 		aconnect 24:0 128:0
513 
514 
515 		I might also add a snd_seq client here which is a bit
516 		easier to setup but for now I'm using the rawmidi so you
517 		gotta get them connected somehow.
518 	*/
519 	enum midiName = "hw:3,0";
520 }
521 
522 /// Thrown on audio failures.
523 /// Subclass this to provide OS-specific exceptions
524 class AudioException : Exception {
525 	this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
526 		super(message, file, line, next);
527 	}
528 }
529 
530 /// Gives PCM input access (such as a microphone).
531 version(ALSA) // FIXME
532 struct AudioInput {
533 	version(ALSA) {
534 		snd_pcm_t* handle;
535 	}
536 
537 	@disable this();
538 	@disable this(this);
539 
540 	/// Always pass card == 0.
541 	this(int card) {
542 		assert(card == 0);
543 
544 		version(ALSA) {
545 			handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_CAPTURE);
546 		} else static assert(0);
547 	}
548 
549 	/// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz
550 	/// Each item in the array thus alternates between left and right channel
551 	/// and it takes a total of 88,200 items to make one second of sound.
552 	///
553 	/// Returns the slice of the buffer actually read into
554 	short[] read(short[] buffer) {
555 		version(ALSA) {
556 			snd_pcm_sframes_t read;
557 
558 			read = snd_pcm_readi(handle, buffer.ptr, buffer.length / 2 /* div number of channels apparently */);
559 			if(read < 0)
560 				throw new AlsaException("pcm read", cast(int)read);
561 
562 			return buffer[0 .. read * 2];
563 		} else static assert(0);
564 	}
565 
566 	// FIXME: add async function hooks
567 
568 	~this() {
569 		version(ALSA) {
570 			snd_pcm_close(handle);
571 		} else static assert(0);
572 	}
573 }
574 
575 /// Gives PCM output access (such as the speakers).
576 struct AudioOutput {
577 	version(ALSA) {
578 		snd_pcm_t* handle;
579 	} else version(WinMM) {
580 		HWAVEOUT handle;
581 	}
582 
583 	@disable this();
584 	// This struct must NEVER be moved or copied, a pointer to it may
585 	// be passed to a device driver and stored!
586 	@disable this(this);
587 
588 	/// Always pass card == 0.
589 	this(int card) {
590 		assert(card == 0);
591 
592 		version(ALSA) {
593 			handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_PLAYBACK);
594 		} else version(WinMM) {
595 			WAVEFORMATEX format;
596 			format.wFormatTag = WAVE_FORMAT_PCM;
597 			format.nChannels = 2;
598 			format.nSamplesPerSec = SampleRate;
599 			format.nAvgBytesPerSec = SampleRate * 2 * 2; // two channels, two bytes per sample
600 			format.nBlockAlign = 4;
601 			format.wBitsPerSample = 16;
602 			format.cbSize = 0;
603 			if(auto err = waveOutOpen(&handle, WAVE_MAPPER, &format, &mmCallback, &this, CALLBACK_FUNCTION))
604 				throw new WinMMException("wave out open", err);
605 		} else static assert(0);
606 	}
607 
608 	/// passes a buffer of data to fill
609 	///
610 	/// Data is assumed to be interleaved stereo, LE 16 bit, 44.1 kHz
611 	/// Each item in the array thus alternates between left and right channel
612 	/// and it takes a total of 88,200 items to make one second of sound.
613 	void delegate(short[]) fillData;
614 
615 	shared(bool) playing = false; // considered to be volatile
616 
617 	/// Starts playing, loops until stop is called
618 	void play() {
619 		assert(fillData !is null);
620 		playing = true;
621 
622 		version(ALSA) {
623 			short[BUFFER_SIZE_SHORT] buffer;
624 			while(playing) {
625 				auto err = snd_pcm_wait(handle, 500);
626 				if(err < 0)
627 					throw new AlsaException("uh oh", err);
628 				// err == 0 means timeout
629 				// err == 1 means ready
630 
631 				auto ready = snd_pcm_avail_update(handle);
632 				if(ready < 0)
633 					throw new AlsaException("avail", cast(int)ready);
634 				if(ready > BUFFER_SIZE_FRAMES)
635 					ready = BUFFER_SIZE_FRAMES;
636 				//import std.stdio; writeln("filling ", ready);
637 				fillData(buffer[0 .. ready * 2]);
638 				if(playing) {
639 					snd_pcm_sframes_t written;
640 					auto data = buffer[0 .. ready * 2];
641 
642 					while(data.length) {
643 						written = snd_pcm_writei(handle, data.ptr, data.length / 2);
644 						if(written < 0) {
645 							written = snd_pcm_recover(handle, cast(int)written, 0);
646 							if (written < 0) throw new AlsaException("pcm write", cast(int)written);
647 						}
648 						data = data[written * 2 .. $];
649 					}
650 				}
651 			}
652 		} else version(WinMM) {
653 
654 			enum numBuffers = 4; // use a lot of buffers to get smooth output with Sleep, see below
655 			short[BUFFER_SIZE_SHORT][numBuffers] buffers;
656 
657 			WAVEHDR[numBuffers] headers;
658 
659 			foreach(i, ref header; headers) {
660 				// since this is wave out, it promises not to write...
661 				auto buffer = buffers[i][];
662 				header.lpData = cast(void*) buffer.ptr;
663 				header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof;
664 				header.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
665 				header.dwLoops = 1;
666 
667 				if(auto err = waveOutPrepareHeader(handle, &header, header.sizeof))
668 					throw new WinMMException("prepare header", err);
669 
670 				// prime it
671 				fillData(buffer[]);
672 
673 				// indicate that they are filled and good to go
674 				header.dwUser = 1;
675 			}
676 
677 			while(playing) {
678 				// and queue both to be played, if they are ready
679 				foreach(ref header; headers)
680 					if(header.dwUser) {
681 						if(auto err = waveOutWrite(handle, &header, header.sizeof))
682 							throw new WinMMException("wave out write", err);
683 						header.dwUser = 0;
684 					}
685 				Sleep(1);
686 				// the system resolution may be lower than this sleep. To avoid gaps
687 				// in output, we use multiple buffers. Might introduce latency, not
688 				// sure how best to fix. I don't want to busy loop...
689 			}
690 
691 			// wait for the system to finish with our buffers
692 			bool anyInUse = true;
693 
694 			while(anyInUse) {
695 				anyInUse = false;
696 				foreach(header; headers) {
697 					if(!header.dwUser) {
698 						anyInUse = true;
699 						break;
700 					}
701 				}
702 				if(anyInUse)
703 					Sleep(1);
704 			}
705 
706 			foreach(ref header; headers) 
707 				if(auto err = waveOutUnprepareHeader(handle, &header, header.sizeof))
708 					throw new WinMMException("unprepare", err);
709 		} else static assert(0);
710 	}
711 
712 	/// Breaks the play loop
713 	void stop() {
714 		playing = false;
715 	}
716 
717 	version(WinMM) {
718 		extern(Windows)
719 		static void mmCallback(HWAVEOUT handle, UINT msg, void* userData, DWORD param1, DWORD param2) {
720 			AudioOutput* ao = cast(AudioOutput*) userData;
721 			if(msg == WOM_DONE) {
722 				auto header = cast(WAVEHDR*) param1;
723 				// we want to bounce back and forth between two buffers
724 				// to keep the sound going all the time
725 				if(ao.playing) {
726 					ao.fillData((cast(short*) header.lpData)[0 .. header.dwBufferLength / short.sizeof]);
727 				}
728 				header.dwUser = 1;
729 			}
730 		}
731 	}
732 
733 	// FIXME: add async function hooks
734 
735 	~this() {
736 		version(ALSA) {
737 			snd_pcm_close(handle);
738 		} else version(WinMM) {
739 			waveOutClose(handle);
740 		} else static assert(0);
741 	}
742 }
743 
744 /// Gives MIDI output access.
745 struct MidiOutput {
746 	version(ALSA) {
747 		snd_rawmidi_t* handle;
748 	} else version(WinMM) {
749 		HMIDIOUT handle;
750 	}
751 
752 	@disable this();
753 	@disable this(this);
754 
755 	/// Always pass card == 0.
756 	this(int card) {
757 		assert(card == 0);
758 
759 		version(ALSA) {
760 			if(auto err = snd_rawmidi_open(null, &handle, midiName, 0))
761 				throw new AlsaException("rawmidi open", err);
762 		} else version(WinMM) {
763 			if(auto err = midiOutOpen(&handle, 0, 0, 0, CALLBACK_NULL))
764 				throw new WinMMException("midi out open", err);
765 		} else static assert(0);
766 	}
767 
768 	void silenceAllNotes() {
769 		foreach(a; 0 .. 16)
770 			writeMidiMessage((0x0b << 4)|a /*MIDI_EVENT_CONTROLLER*/, 123, 0);
771 	}
772 
773 	/// Send a reset message, silencing all notes
774 	void reset() {
775 		version(ALSA) {
776 			static immutable ubyte[3] resetSequence = [0x0b << 4, 123, 0];
777 			// send a controller event to reset it
778 			writeRawMessageData(resetSequence[]);
779 			// and flush it immediately
780 			snd_rawmidi_drain(handle);
781 		} else version(WinMM) {
782 			if(auto error = midiOutReset(handle))
783 				throw new WinMMException("midi reset", error);
784 		} else static assert(0);
785 	}
786 
787 	/// Writes a single low-level midi message
788 	/// Timing and sending sane data is your responsibility!
789 	void writeMidiMessage(int status, int param1, int param2) {
790 		version(ALSA) {
791 			ubyte[3] dataBuffer;
792 
793 			dataBuffer[0] = cast(ubyte) status;
794 			dataBuffer[1] = cast(ubyte) param1;
795 			dataBuffer[2] = cast(ubyte) param2;
796 
797 			auto msg = status >> 4;
798 			ubyte[] data;
799 			if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch)
800 				data = dataBuffer[0 .. 2];
801 			else
802 				data = dataBuffer[];
803 
804 			writeRawMessageData(data);
805 		} else version(WinMM) {
806 			DWORD word = (param2 << 16) | (param1 << 8) | status;
807 			if(auto error = midiOutShortMsg(handle, word))
808 				throw new WinMMException("midi out", error);
809 		} else static assert(0);
810 
811 	}
812 
813 	/// Writes a series of individual raw messages.
814 	/// Timing and sending sane data is your responsibility!
815 	/// The data should NOT include any timestamp bytes - each midi message should be 2 or 3 bytes.
816 	void writeRawMessageData(scope const(ubyte)[] data) {
817 		version(ALSA) {
818 			ssize_t written;
819 
820 			while(data.length) {
821 				written = snd_rawmidi_write(handle, data.ptr, data.length);
822 				if(written < 0)
823 					throw new AlsaException("midi write", cast(int) written);
824 				data = data[cast(int) written .. $];
825 			}
826 		} else version(WinMM) {
827 			while(data.length) {
828 				auto msg = data[0] >> 4;
829 				if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) {
830 					writeMidiMessage(data[0], data[1], 0);
831 					data = data[2 .. $];
832 				} else {
833 					writeMidiMessage(data[0], data[1], data[2]);
834 					data = data[3 .. $];
835 				}
836 			}
837 		} else static assert(0);
838 	}
839 
840 	~this() {
841 		version(ALSA) {
842 			snd_rawmidi_close(handle);
843 		} else version(WinMM) {
844 			midiOutClose(handle);
845 		} else static assert(0);
846 	}
847 }
848 
849 
850 // FIXME: maybe add a PC speaker beep function for completeness
851 
852 /// Interfaces with the default sound card. You should only have a single instance of this and it should
853 /// be stack allocated, so its destructor cleans up after it.
854 ///
855 /// A mixer gives access to things like volume controls and mute buttons. It should also give a
856 /// callback feature to alert you of when the settings are changed by another program.
857 version(ALSA) // FIXME
858 struct AudioMixer {
859 	// To port to a new OS: put the data in the right version blocks
860 	// then implement each function. Leave else static assert(0) at the
861 	// end of each version group in a function so it is easier to implement elsewhere later.
862 	//
863 	// If a function is only relevant on your OS, put the whole function in a version block
864 	// and give it an OS specific name of some sort.
865 	//
866 	// Feel free to do that btw without worrying about lowest common denominator: we want low level access when we want it.
867 	//
868 	// Put necessary bindings at the end of the file, or use an import if you like, but I prefer these files to be standalone.
869 	version(ALSA) {
870 		snd_mixer_t* handle;
871 		snd_mixer_selem_id_t* sid;
872 		snd_mixer_elem_t* selem;
873 
874 		c_long maxVolume, minVolume; // these are ok to use if you are writing ALSA specific code i guess
875 
876 		enum selemName = "Master";
877 	}
878 
879 	@disable this();
880 	@disable this(this);
881 
882 	/// Only cardId == 0 is supported
883 	this(int cardId) {
884 		assert(cardId == 0, "Pass 0 to use default sound card.");
885 
886 		version(ALSA) {
887 			if(auto err = snd_mixer_open(&handle, 0))
888 				throw new AlsaException("open sound", err);
889 			scope(failure)
890 				snd_mixer_close(handle);
891 			if(auto err = snd_mixer_attach(handle, cardName))
892 				throw new AlsaException("attach to sound card", err);
893 			if(auto err = snd_mixer_selem_register(handle, null, null))
894 				throw new AlsaException("register mixer", err);
895 			if(auto err = snd_mixer_load(handle))
896 				throw new AlsaException("load mixer", err);
897 
898 			if(auto err = snd_mixer_selem_id_malloc(&sid))
899 				throw new AlsaException("master channel open", err);
900 			scope(failure)
901 				snd_mixer_selem_id_free(sid);
902 			snd_mixer_selem_id_set_index(sid, 0);
903 			snd_mixer_selem_id_set_name(sid, selemName);
904 			selem = snd_mixer_find_selem(handle, sid);
905 			if(selem is null)
906 				throw new AlsaException("find master element", 0);
907 
908 			if(auto err = snd_mixer_selem_get_playback_volume_range(selem, &minVolume, &maxVolume))
909 				throw new AlsaException("get volume range", err);
910 
911 			version(with_eventloop) {
912 				import arsd.eventloop;
913 				addFileEventListeners(getAlsaFileDescriptors()[0], &eventListener, null, null);
914 				setAlsaElemCallback(&alsaCallback);
915 			}
916 		} else static assert(0);
917 	}
918 
919 	~this() {
920 		version(ALSA) {
921 			version(with_eventloop) {
922 				import arsd.eventloop;
923 				removeFileEventListeners(getAlsaFileDescriptors()[0]);
924 			}
925 			snd_mixer_selem_id_free(sid);
926 			snd_mixer_close(handle);
927 		} else static assert(0);
928 	}
929 
930 	version(ALSA)
931 	version(with_eventloop) {
932 		static struct MixerEvent {}
933 		nothrow @nogc
934 		extern(C) static int alsaCallback(snd_mixer_elem_t*, uint) {
935 			import arsd.eventloop;
936 			try
937 				send(MixerEvent());
938 			catch(Exception)
939 				return 1;
940 
941 			return 0;
942 		}
943 
944 		void eventListener(int fd) {
945 			handleAlsaEvents();
946 		}
947 	}
948 
949 	/// Gets the master channel's mute state
950 	/// Note: this affects shared system state and you should not use it unless the end user wants you to.
951 	@property bool muteMaster() {
952 		version(ALSA) {
953 			int result;
954 			if(auto err = snd_mixer_selem_get_playback_switch(selem, 0, &result))
955 				throw new AlsaException("get mute state", err);
956 			return result == 0;
957 		} else static assert(0);
958 	}
959 
960 	/// Mutes or unmutes the master channel
961 	/// Note: this affects shared system state and you should not use it unless the end user wants you to.
962 	@property void muteMaster(bool mute) {
963 		version(ALSA) {
964 			if(auto err = snd_mixer_selem_set_playback_switch_all(selem, mute ? 0 : 1))
965 				throw new AlsaException("set mute state", err);
966 		} else static assert(0);
967 	}
968 
969 	/// returns a percentage, between 0 and 100 (inclusive)
970 	int getMasterVolume() {
971 		version(ALSA) {
972 			auto volume = getMasterVolumeExact();
973 			return cast(int)(volume * 100 / (maxVolume - minVolume));
974 		} else static assert(0);
975 	}
976 
977 	/// Gets the exact value returned from the operating system. The range may vary.
978 	int getMasterVolumeExact() {
979 		version(ALSA) {
980 			c_long volume;
981 			snd_mixer_selem_get_playback_volume(selem, 0, &volume);
982 			return cast(int)volume;
983 		} else static assert(0);
984 	}
985 
986 	/// sets a percentage on the volume, so it must be 0 <= volume <= 100
987 	/// Note: this affects shared system state and you should not use it unless the end user wants you to.
988 	void setMasterVolume(int volume) {
989 		version(ALSA) {
990 			assert(volume >= 0 && volume <= 100);
991 			setMasterVolumeExact(cast(int)(volume * (maxVolume - minVolume) / 100));
992 		} else static assert(0);
993 	}
994 
995 	/// Sets an exact volume. Must be in range of the OS provided min and max.
996 	void setMasterVolumeExact(int volume) {
997 		version(ALSA) {
998 			if(auto err = snd_mixer_selem_set_playback_volume_all(selem, volume))
999 				throw new AlsaException("set volume", err);
1000 		} else static assert(0);
1001 	}
1002 
1003 	version(ALSA) {
1004 		/// Gets the ALSA descriptors which you can watch for events
1005 		/// on using regular select, poll, epoll, etc.
1006 		int[] getAlsaFileDescriptors() {
1007 			import core.sys.posix.poll;
1008 			pollfd[32] descriptors = void;
1009 			int got = snd_mixer_poll_descriptors(handle, descriptors.ptr, descriptors.length);
1010 			int[] result;
1011 			result.length = got;
1012 			foreach(i, desc; descriptors[0 .. got])
1013 				result[i] = desc.fd;
1014 			return result;
1015 		}
1016 
1017 		/// When the FD is ready, call this to let ALSA do its thing.
1018 		void handleAlsaEvents() {
1019 			snd_mixer_handle_events(handle);
1020 		}
1021 
1022 		/// Set a callback for the master volume change events.
1023 		void setAlsaElemCallback(snd_mixer_elem_callback_t dg) {
1024 			snd_mixer_elem_set_callback(selem, dg);
1025 		}
1026 	}
1027 }
1028 
1029 // ****************
1030 // Midi helpers
1031 // ****************
1032 
1033 // FIXME: code the .mid file format, read and write
1034 
1035 enum MidiEvent {
1036 	NoteOff           = 0x08,
1037 	NoteOn            = 0x09,
1038 	NoteAftertouch    = 0x0a,
1039 	Controller        = 0x0b,
1040 	ProgramChange     = 0x0c, // one param
1041 	ChannelAftertouch = 0x0d, // one param
1042 	PitchBend         = 0x0e,
1043 }
1044 
1045 enum MidiNote : ubyte {
1046 	middleC = 60,
1047 	A =  69, // 440 Hz
1048 	As = 70,
1049 	B =  71,
1050 	C =  72,
1051 	Cs = 73,
1052 	D =  74,
1053 	Ds = 75,
1054 	E =  76,
1055 	F =  77,
1056 	Fs = 78,
1057 	G =  79,
1058 	Gs = 80,
1059 }
1060 
1061 /// Puts a note on at the beginning of the passed slice, advancing it by the amount of the message size.
1062 /// Returns the message slice.
1063 ///
1064 /// See: http://www.midi.org/techspecs/midimessages.php
1065 ubyte[] midiNoteOn(ref ubyte[] where, ubyte channel, byte note, byte velocity) {
1066 	where[0] = (MidiEvent.NoteOn << 4) | (channel&0x0f);
1067 	where[1] = note;
1068 	where[2] = velocity;
1069 	auto it = where[0 .. 3];
1070 	where = where[3 .. $];
1071 	return it;
1072 }
1073 
1074 /// Note off.
1075 ubyte[] midiNoteOff(ref ubyte[] where, ubyte channel, byte note, byte velocity) {
1076 	where[0] = (MidiEvent.NoteOff << 4) | (channel&0x0f);
1077 	where[1] = note;
1078 	where[2] = velocity;
1079 	auto it = where[0 .. 3];
1080 	where = where[3 .. $];
1081 	return it;
1082 }
1083 
1084 /// Aftertouch.
1085 ubyte[] midiNoteAftertouch(ref ubyte[] where, ubyte channel, byte note, byte pressure) {
1086 	where[0] = (MidiEvent.NoteAftertouch << 4) | (channel&0x0f);
1087 	where[1] = note;
1088 	where[2] = pressure;
1089 	auto it = where[0 .. 3];
1090 	where = where[3 .. $];
1091 	return it;
1092 }
1093 
1094 /// Controller.
1095 ubyte[] midiNoteController(ref ubyte[] where, ubyte channel, byte controllerNumber, byte controllerValue) {
1096 	where[0] = (MidiEvent.Controller << 4) | (channel&0x0f);
1097 	where[1] = controllerNumber;
1098 	where[2] = controllerValue;
1099 	auto it = where[0 .. 3];
1100 	where = where[3 .. $];
1101 	return it;
1102 }
1103 
1104 /// Program change.
1105 ubyte[] midiProgramChange(ref ubyte[] where, ubyte channel, byte program) {
1106 	where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f);
1107 	where[1] = program;
1108 	auto it = where[0 .. 2];
1109 	where = where[2 .. $];
1110 	return it;
1111 }
1112 
1113 /// Channel aftertouch.
1114 ubyte[] midiChannelAftertouch(ref ubyte[] where, ubyte channel, byte amount) {
1115 	where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f);
1116 	where[1] = amount;
1117 	auto it = where[0 .. 2];
1118 	where = where[2 .. $];
1119 	return it;
1120 }
1121 
1122 /// Pitch bend. FIXME doesn't work right
1123 ubyte[] midiNotePitchBend(ref ubyte[] where, ubyte channel, short change) {
1124 /*
1125 first byte is llllll
1126 second byte is mmmmmm
1127 
1128 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.
1129 */
1130 	where[0] = (MidiEvent.PitchBend << 4) | (channel&0x0f);
1131 	// FIXME
1132 	where[1] = 0;
1133 	where[2] = 0;
1134 	auto it = where[0 .. 3];
1135 	where = where[3 .. $];
1136 	return it;
1137 }
1138 
1139 
1140 // ****************
1141 // Wav helpers
1142 // ****************
1143 
1144 // FIXME: the .wav file format should be here, read and write (at least basics)
1145 // as well as some kind helpers to generate some sounds.
1146 
1147 // ****************
1148 // OS specific helper stuff follows
1149 // ****************
1150 
1151 version(ALSA)
1152 // Opens the PCM device with default settings: stereo, 16 bit, 44.1 kHz, interleaved R/W.
1153 snd_pcm_t* openAlsaPcm(snd_pcm_stream_t direction) {
1154 	snd_pcm_t* handle;
1155 	snd_pcm_hw_params_t* hwParams;
1156 
1157 	/* Open PCM and initialize hardware */
1158 
1159 	if (auto err = snd_pcm_open(&handle, cardName, direction, 0))
1160 		throw new AlsaException("open device", err);
1161 	scope(failure)
1162 		snd_pcm_close(handle);
1163 
1164 
1165 	if (auto err = snd_pcm_hw_params_malloc(&hwParams))
1166 		throw new AlsaException("params malloc", err);
1167 	scope(exit)
1168 		snd_pcm_hw_params_free(hwParams);
1169 			 
1170 	if (auto err = snd_pcm_hw_params_any(handle, hwParams))
1171 		// can actually survive a failure here, we will just move forward
1172 		{} // throw new AlsaException("params init", err);
1173 
1174 	if (auto err = snd_pcm_hw_params_set_access(handle, hwParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED))
1175 		throw new AlsaException("params access", err);
1176 
1177 	if (auto err = snd_pcm_hw_params_set_format(handle, hwParams, snd_pcm_format.SND_PCM_FORMAT_S16_LE))
1178 		throw new AlsaException("params format", err);
1179 
1180 	uint rate = SampleRate;
1181 	int dir = 0;
1182 	if (auto err = snd_pcm_hw_params_set_rate_near(handle, hwParams, &rate, &dir))
1183 		throw new AlsaException("params rate", err);
1184 
1185 	assert(rate == SampleRate); // cheap me
1186 
1187 	if (auto err = snd_pcm_hw_params_set_channels(handle, hwParams, 2))
1188 		throw new AlsaException("params channels", err);
1189 
1190 	uint periods = 2;
1191 	{
1192 	auto err = snd_pcm_hw_params_set_periods_near(handle, hwParams, &periods, 0);
1193 	if(err < 0)
1194 		throw new AlsaException("periods", err);
1195 
1196 	snd_pcm_uframes_t sz = (BUFFER_SIZE_FRAMES * periods);
1197 	err = snd_pcm_hw_params_set_buffer_size_near(handle, hwParams, &sz);
1198 	if(err < 0)
1199 		throw new AlsaException("buffer size", err);
1200 	}
1201 
1202 	if (auto err = snd_pcm_hw_params(handle, hwParams))
1203 		throw new AlsaException("params install", err);
1204 
1205 	/* Setting up the callbacks */
1206 
1207 	snd_pcm_sw_params_t* swparams;
1208 	if(auto err = snd_pcm_sw_params_malloc(&swparams))
1209 		throw new AlsaException("sw malloc", err);
1210 	scope(exit)
1211 		snd_pcm_sw_params_free(swparams);
1212 	if(auto err = snd_pcm_sw_params_current(handle, swparams))
1213 		throw new AlsaException("sw set", err);
1214 	if(auto err = snd_pcm_sw_params_set_avail_min(handle, swparams, BUFFER_SIZE_FRAMES))
1215 		throw new AlsaException("sw min", err);
1216 	if(auto err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0))
1217 		throw new AlsaException("sw threshold", err);
1218 	if(auto err = snd_pcm_sw_params(handle, swparams))
1219 		throw new AlsaException("sw params", err);
1220 
1221 	/* finish setup */
1222 
1223 	if (auto err = snd_pcm_prepare(handle))
1224 		throw new AlsaException("prepare", err);
1225 
1226 	assert(handle !is null);
1227 	return handle;
1228 }
1229 
1230 version(ALSA)
1231 class AlsaException : AudioException {
1232 	this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
1233 		auto msg = snd_strerror(error);
1234 		import core.stdc.string;
1235 		super(cast(string) (message ~ ": " ~ msg[0 .. strlen(msg)]), file, line, next);
1236 	}
1237 }
1238 
1239 version(WinMM)
1240 class WinMMException : AudioException {
1241 	this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
1242 		// FIXME: format the error
1243 		// midiOutGetErrorText, etc.
1244 		super(message, file, line, next);
1245 	}
1246 }
1247 
1248 // ****************
1249 // Bindings follow
1250 // ****************
1251 
1252 version(ALSA) {
1253 extern(C):
1254 @nogc nothrow:
1255 	pragma(lib, "asound");
1256 	private import core.sys.posix.poll;
1257 
1258 	const(char)* snd_strerror(int);
1259 
1260 	// pcm
1261 	enum snd_pcm_stream_t {
1262 		SND_PCM_STREAM_PLAYBACK,
1263 		SND_PCM_STREAM_CAPTURE
1264 	}
1265 
1266 	enum snd_pcm_access_t {
1267 		/** mmap access with simple interleaved channels */ 
1268 		SND_PCM_ACCESS_MMAP_INTERLEAVED = 0, 
1269 		/** mmap access with simple non interleaved channels */ 
1270 		SND_PCM_ACCESS_MMAP_NONINTERLEAVED, 
1271 		/** mmap access with complex placement */ 
1272 		SND_PCM_ACCESS_MMAP_COMPLEX, 
1273 		/** snd_pcm_readi/snd_pcm_writei access */ 
1274 		SND_PCM_ACCESS_RW_INTERLEAVED, 
1275 		/** snd_pcm_readn/snd_pcm_writen access */ 
1276 		SND_PCM_ACCESS_RW_NONINTERLEAVED, 
1277 		SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED
1278 	}
1279 
1280 	enum snd_pcm_format {
1281 		/** Unknown */
1282 		SND_PCM_FORMAT_UNKNOWN = -1,
1283 		/** Signed 8 bit */
1284 		SND_PCM_FORMAT_S8 = 0,
1285 		/** Unsigned 8 bit */
1286 		SND_PCM_FORMAT_U8,
1287 		/** Signed 16 bit Little Endian */
1288 		SND_PCM_FORMAT_S16_LE,
1289 		/** Signed 16 bit Big Endian */
1290 		SND_PCM_FORMAT_S16_BE,
1291 		/** Unsigned 16 bit Little Endian */
1292 		SND_PCM_FORMAT_U16_LE,
1293 		/** Unsigned 16 bit Big Endian */
1294 		SND_PCM_FORMAT_U16_BE,
1295 		/** Signed 24 bit Little Endian using low three bytes in 32-bit word */
1296 		SND_PCM_FORMAT_S24_LE,
1297 		/** Signed 24 bit Big Endian using low three bytes in 32-bit word */
1298 		SND_PCM_FORMAT_S24_BE,
1299 		/** Unsigned 24 bit Little Endian using low three bytes in 32-bit word */
1300 		SND_PCM_FORMAT_U24_LE,
1301 		/** Unsigned 24 bit Big Endian using low three bytes in 32-bit word */
1302 		SND_PCM_FORMAT_U24_BE,
1303 		/** Signed 32 bit Little Endian */
1304 		SND_PCM_FORMAT_S32_LE,
1305 		/** Signed 32 bit Big Endian */
1306 		SND_PCM_FORMAT_S32_BE,
1307 		/** Unsigned 32 bit Little Endian */
1308 		SND_PCM_FORMAT_U32_LE,
1309 		/** Unsigned 32 bit Big Endian */
1310 		SND_PCM_FORMAT_U32_BE,
1311 		/** Float 32 bit Little Endian, Range -1.0 to 1.0 */
1312 		SND_PCM_FORMAT_FLOAT_LE,
1313 		/** Float 32 bit Big Endian, Range -1.0 to 1.0 */
1314 		SND_PCM_FORMAT_FLOAT_BE,
1315 		/** Float 64 bit Little Endian, Range -1.0 to 1.0 */
1316 		SND_PCM_FORMAT_FLOAT64_LE,
1317 		/** Float 64 bit Big Endian, Range -1.0 to 1.0 */
1318 		SND_PCM_FORMAT_FLOAT64_BE,
1319 		/** IEC-958 Little Endian */
1320 		SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
1321 		/** IEC-958 Big Endian */
1322 		SND_PCM_FORMAT_IEC958_SUBFRAME_BE,
1323 		/** Mu-Law */
1324 		SND_PCM_FORMAT_MU_LAW,
1325 		/** A-Law */
1326 		SND_PCM_FORMAT_A_LAW,
1327 		/** Ima-ADPCM */
1328 		SND_PCM_FORMAT_IMA_ADPCM,
1329 		/** MPEG */
1330 		SND_PCM_FORMAT_MPEG,
1331 		/** GSM */
1332 		SND_PCM_FORMAT_GSM,
1333 		/** Special */
1334 		SND_PCM_FORMAT_SPECIAL = 31,
1335 		/** Signed 24bit Little Endian in 3bytes format */
1336 		SND_PCM_FORMAT_S24_3LE = 32,
1337 		/** Signed 24bit Big Endian in 3bytes format */
1338 		SND_PCM_FORMAT_S24_3BE,
1339 		/** Unsigned 24bit Little Endian in 3bytes format */
1340 		SND_PCM_FORMAT_U24_3LE,
1341 		/** Unsigned 24bit Big Endian in 3bytes format */
1342 		SND_PCM_FORMAT_U24_3BE,
1343 		/** Signed 20bit Little Endian in 3bytes format */
1344 		SND_PCM_FORMAT_S20_3LE,
1345 		/** Signed 20bit Big Endian in 3bytes format */
1346 		SND_PCM_FORMAT_S20_3BE,
1347 		/** Unsigned 20bit Little Endian in 3bytes format */
1348 		SND_PCM_FORMAT_U20_3LE,
1349 		/** Unsigned 20bit Big Endian in 3bytes format */
1350 		SND_PCM_FORMAT_U20_3BE,
1351 		/** Signed 18bit Little Endian in 3bytes format */
1352 		SND_PCM_FORMAT_S18_3LE,
1353 		/** Signed 18bit Big Endian in 3bytes format */
1354 		SND_PCM_FORMAT_S18_3BE,
1355 		/** Unsigned 18bit Little Endian in 3bytes format */
1356 		SND_PCM_FORMAT_U18_3LE,
1357 		/** Unsigned 18bit Big Endian in 3bytes format */
1358 		SND_PCM_FORMAT_U18_3BE,
1359 		/* G.723 (ADPCM) 24 kbit/s, 8 samples in 3 bytes */
1360 		SND_PCM_FORMAT_G723_24,
1361 		/* G.723 (ADPCM) 24 kbit/s, 1 sample in 1 byte */
1362 		SND_PCM_FORMAT_G723_24_1B,
1363 		/* G.723 (ADPCM) 40 kbit/s, 8 samples in 3 bytes */
1364 		SND_PCM_FORMAT_G723_40,
1365 		/* G.723 (ADPCM) 40 kbit/s, 1 sample in 1 byte */
1366 		SND_PCM_FORMAT_G723_40_1B,
1367 		/* Direct Stream Digital (DSD) in 1-byte samples (x8) */
1368 		SND_PCM_FORMAT_DSD_U8,
1369 		/* Direct Stream Digital (DSD) in 2-byte samples (x16) */
1370 		SND_PCM_FORMAT_DSD_U16_LE,
1371 		SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_DSD_U16_LE,
1372 
1373 		// I snipped a bunch of endian-specific ones!
1374 	}
1375 
1376 	struct snd_pcm_t {}
1377 	struct snd_pcm_hw_params_t {}
1378 	struct snd_pcm_sw_params_t {}
1379 
1380 	int snd_pcm_open(snd_pcm_t**, const char*, snd_pcm_stream_t, int);
1381 	int snd_pcm_close(snd_pcm_t*);
1382 	int snd_pcm_prepare(snd_pcm_t*);
1383 	int snd_pcm_hw_params(snd_pcm_t*, snd_pcm_hw_params_t*);
1384 	int snd_pcm_hw_params_set_periods(snd_pcm_t*, snd_pcm_hw_params_t*, uint, int);
1385 	int snd_pcm_hw_params_set_periods_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int);
1386 	int snd_pcm_hw_params_set_buffer_size(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t);
1387 	int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t*);
1388 	int snd_pcm_hw_params_set_channels(snd_pcm_t*, snd_pcm_hw_params_t*, uint);
1389 	int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t**);
1390 	void snd_pcm_hw_params_free(snd_pcm_hw_params_t*);
1391 	int snd_pcm_hw_params_any(snd_pcm_t*, snd_pcm_hw_params_t*);
1392 	int snd_pcm_hw_params_set_access(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_access_t);
1393 	int snd_pcm_hw_params_set_format(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_format);
1394 	int snd_pcm_hw_params_set_rate_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int*);
1395 
1396 	int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t**);
1397 	void snd_pcm_sw_params_free(snd_pcm_sw_params_t*);
1398 
1399 	int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
1400 	int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
1401 	int snd_pcm_sw_params_set_avail_min(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
1402 	int snd_pcm_sw_params_set_start_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
1403 	int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t);
1404 
1405 	alias snd_pcm_sframes_t = c_long;
1406 	alias snd_pcm_uframes_t = c_ulong;
1407 	snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t*, const void*, snd_pcm_uframes_t size);
1408 	snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t*, void*, snd_pcm_uframes_t size);
1409 
1410 	int snd_pcm_wait(snd_pcm_t *pcm, int timeout);
1411 	snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm);
1412 	snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm);
1413 
1414 	int snd_pcm_recover (snd_pcm_t* pcm, int err, int silent);
1415 
1416 	alias snd_lib_error_handler_t = void function (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...);
1417 	int snd_lib_error_set_handler (snd_lib_error_handler_t handler);
1418 
1419 	import core.stdc.stdarg;
1420 	private void alsa_message_silencer (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...) {}
1421 	//k8: ALSAlib loves to trash stderr; shut it up
1422 	void silence_alsa_messages () { snd_lib_error_set_handler(&alsa_message_silencer); }
1423 	shared static this () { silence_alsa_messages(); }
1424 
1425 	// raw midi
1426 
1427 	static if(is(size_t == uint))
1428 		alias ssize_t = int;
1429 	else
1430 		alias ssize_t = long;
1431 
1432 
1433 	struct snd_rawmidi_t {}
1434 	int snd_rawmidi_open(snd_rawmidi_t**, snd_rawmidi_t**, const char*, int);
1435 	int snd_rawmidi_close(snd_rawmidi_t*);
1436 	int snd_rawmidi_drain(snd_rawmidi_t*);
1437 	ssize_t snd_rawmidi_write(snd_rawmidi_t*, const void*, size_t);
1438 	ssize_t snd_rawmidi_read(snd_rawmidi_t*, void*, size_t);
1439 
1440 	// mixer
1441 
1442 	struct snd_mixer_t {}
1443 	struct snd_mixer_elem_t {}
1444 	struct snd_mixer_selem_id_t {}
1445 
1446 	alias snd_mixer_elem_callback_t = int function(snd_mixer_elem_t*, uint);
1447 
1448 	int snd_mixer_open(snd_mixer_t**, int mode);
1449 	int snd_mixer_close(snd_mixer_t*);
1450 	int snd_mixer_attach(snd_mixer_t*, const char*);
1451 	int snd_mixer_load(snd_mixer_t*);
1452 
1453 	// FIXME: those aren't actually void*
1454 	int snd_mixer_selem_register(snd_mixer_t*, void*, void*);
1455 	int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t**);
1456 	void snd_mixer_selem_id_free(snd_mixer_selem_id_t*);
1457 	void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t*, uint);
1458 	void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t*, const char*);
1459 	snd_mixer_elem_t* snd_mixer_find_selem(snd_mixer_t*, in snd_mixer_selem_id_t*);
1460 
1461 	// FIXME: the int should be an enum for channel identifier
1462 	int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t*, int, c_long*);
1463 
1464 	int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t*, c_long*, c_long*);
1465 
1466 	int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t*, c_long);
1467 
1468 	void snd_mixer_elem_set_callback(snd_mixer_elem_t*, snd_mixer_elem_callback_t);
1469 	int snd_mixer_poll_descriptors(snd_mixer_t*, pollfd*, uint space);
1470 
1471 	int snd_mixer_handle_events(snd_mixer_t*);
1472 
1473 	// FIXME: the first int should be an enum for channel identifier
1474 	int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t*, int, int* value);
1475 	int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t*, int);
1476 }
1477 
1478 version(WinMM) {
1479 extern(Windows):
1480 @nogc nothrow:
1481 	pragma(lib, "winmm");
1482 	import core.sys.windows.windows;
1483 
1484 /*
1485 	Windows functions include:
1486 	http://msdn.microsoft.com/en-us/library/ms713762%28VS.85%29.aspx
1487 	http://msdn.microsoft.com/en-us/library/ms713504%28v=vs.85%29.aspx
1488 	http://msdn.microsoft.com/en-us/library/windows/desktop/dd798480%28v=vs.85%29.aspx#
1489 	http://msdn.microsoft.com/en-US/subscriptions/ms712109.aspx
1490 */
1491 
1492 	// pcm
1493 
1494 	// midi
1495 
1496 	alias HMIDIOUT = HANDLE;
1497 	alias MMRESULT = UINT;
1498 
1499 	MMRESULT midiOutOpen(HMIDIOUT*, UINT, DWORD, DWORD, DWORD);
1500 	MMRESULT midiOutClose(HMIDIOUT);
1501 	MMRESULT midiOutReset(HMIDIOUT);
1502 	MMRESULT midiOutShortMsg(HMIDIOUT, DWORD);
1503 
1504 	alias HWAVEOUT = HANDLE;
1505 
1506 	struct WAVEFORMATEX {
1507 		WORD wFormatTag;
1508 		WORD nChannels;
1509 		DWORD nSamplesPerSec;
1510 		DWORD nAvgBytesPerSec;
1511 		WORD nBlockAlign;
1512 		WORD wBitsPerSample;
1513 		WORD cbSize;
1514 	}
1515 
1516 	struct WAVEHDR {
1517 		void* lpData;
1518 		DWORD dwBufferLength;
1519 		DWORD dwBytesRecorded;
1520 		DWORD dwUser;
1521 		DWORD dwFlags;
1522 		DWORD dwLoops;
1523 		WAVEHDR *lpNext;
1524 		DWORD reserved;
1525 	}
1526 
1527 	enum UINT WAVE_MAPPER= -1;
1528 
1529 	MMRESULT waveOutOpen(HWAVEOUT*, UINT_PTR, WAVEFORMATEX*, void* callback, void*, DWORD);
1530 	MMRESULT waveOutClose(HWAVEOUT);
1531 	MMRESULT waveOutPrepareHeader(HWAVEOUT, WAVEHDR*, UINT);
1532 	MMRESULT waveOutUnprepareHeader(HWAVEOUT, WAVEHDR*, UINT);
1533 	MMRESULT waveOutWrite(HWAVEOUT, WAVEHDR*, UINT);
1534 
1535 	MMRESULT waveOutGetVolume(HWAVEOUT, PDWORD);
1536 	MMRESULT waveOutSetVolume(HWAVEOUT, DWORD);
1537 
1538 	enum CALLBACK_TYPEMASK = 0x70000;
1539 	enum CALLBACK_NULL     = 0;
1540 	enum CALLBACK_WINDOW   = 0x10000;
1541 	enum CALLBACK_TASK     = 0x20000;
1542 	enum CALLBACK_FUNCTION = 0x30000;
1543 	enum CALLBACK_THREAD   = CALLBACK_TASK;
1544 	enum CALLBACK_EVENT    = 0x50000;
1545 
1546 	enum WAVE_FORMAT_PCM = 1;
1547 
1548 	enum WHDR_PREPARED = 2;
1549 	enum WHDR_BEGINLOOP = 4;
1550 	enum WHDR_ENDLOOP = 8;
1551 	enum WHDR_INQUEUE = 16;
1552 
1553 	enum WinMMMessage : UINT {
1554 		MM_JOY1MOVE            = 0x3A0,
1555 		MM_JOY2MOVE,
1556 		MM_JOY1ZMOVE,
1557 		MM_JOY2ZMOVE,       // = 0x3A3
1558 		MM_JOY1BUTTONDOWN      = 0x3B5,
1559 		MM_JOY2BUTTONDOWN,
1560 		MM_JOY1BUTTONUP,
1561 		MM_JOY2BUTTONUP,
1562 		MM_MCINOTIFY,       // = 0x3B9
1563 		MM_WOM_OPEN            = 0x3BB,
1564 		MM_WOM_CLOSE,
1565 		MM_WOM_DONE,
1566 		MM_WIM_OPEN,
1567 		MM_WIM_CLOSE,
1568 		MM_WIM_DATA,
1569 		MM_MIM_OPEN,
1570 		MM_MIM_CLOSE,
1571 		MM_MIM_DATA,
1572 		MM_MIM_LONGDATA,
1573 		MM_MIM_ERROR,
1574 		MM_MIM_LONGERROR,
1575 		MM_MOM_OPEN,
1576 		MM_MOM_CLOSE,
1577 		MM_MOM_DONE,        // = 0x3C9
1578 		MM_DRVM_OPEN           = 0x3D0,
1579 		MM_DRVM_CLOSE,
1580 		MM_DRVM_DATA,
1581 		MM_DRVM_ERROR,
1582 		MM_STREAM_OPEN,
1583 		MM_STREAM_CLOSE,
1584 		MM_STREAM_DONE,
1585 		MM_STREAM_ERROR,    // = 0x3D7
1586 		MM_MOM_POSITIONCB      = 0x3CA,
1587 		MM_MCISIGNAL,
1588 		MM_MIM_MOREDATA,    // = 0x3CC
1589 		MM_MIXM_LINE_CHANGE    = 0x3D0,
1590 		MM_MIXM_CONTROL_CHANGE = 0x3D1
1591 	}
1592 
1593 
1594 	enum WOM_OPEN  = WinMMMessage.MM_WOM_OPEN;
1595 	enum WOM_CLOSE = WinMMMessage.MM_WOM_CLOSE;
1596 	enum WOM_DONE  = WinMMMessage.MM_WOM_DONE;
1597 	enum WIM_OPEN  = WinMMMessage.MM_WIM_OPEN;
1598 	enum WIM_CLOSE = WinMMMessage.MM_WIM_CLOSE;
1599 	enum WIM_DATA  = WinMMMessage.MM_WIM_DATA;
1600 
1601 
1602 	uint mciSendStringA(in char*,char*,uint,void*);
1603 }