diff --git a/simpleaudio.d b/simpleaudio.d index 1684a21..36605b9 100644 --- a/simpleaudio.d +++ b/simpleaudio.d @@ -1,10 +1,14 @@ /** The purpose of this module is to provide audio functions for - things like playback, capture, and volume on both Windows and - Linux (through ALSA). + things like playback, capture, and volume on both Windows + (via the mmsystem calls)and Linux (through ALSA). It is only aimed at the basics, and will be filled in as I want - a particular feature. + a particular feature. I don't generally need super configurability + and see it as a minus, since I don't generally care either, so I'm + going to be going for defaults that just work. If you need more though, + you can hack the source or maybe just use it for the operating system + bindings. For example, I'm starting this because I want to write a volume control program for my linux box, so that's what is going first. @@ -13,23 +17,291 @@ TODO: - * register callbacks for volume change - * play audio with options to wait until completion or return immediately - * capture audio + * play audio high level with options to wait until completion or return immediately + * midi mid-level stuff + * audio callback stuff (it tells us when to fill the buffer) - * Windows support + * Windows support for waveOut and waveIn. Maybe mixer too, but that's lowest priority. - * I'll also write midi and .wav functions at least eventually with maybe some synthesizer stuff. + * I'll also write .mid and .wav functions at least eventually. Maybe in separate modules but probably here since they aren't that complex. + + I will probably NOT do OSS anymore, since my computer doesn't even work with it now. + Ditto for Macintosh, as I don't have one and don't really care about them. */ module arsd.simpleaudio; +version(Demo) +void main() { + /* + auto aio = AudioMixer(0); + + import std.stdio; + writeln(aio.muteMaster); + */ + + // output about a second of random noise to demo PCM + auto ao = AudioOutput(0); + short[1024] randomSpam = void; + foreach(i; 0 .. 50) { + ao.write(randomSpam[]); + } + + + // Play a C major scale on the piano to demonstrate midi + auto midi = MidiOutput(0); + + ubyte[16] buffer = void; + ubyte[] where = buffer[]; + midi.writeRawMessageData(where.midiProgramChange(1, 1)); + for(ubyte note = MidiNote.middleC; note <= MidiNote.middleC + 12; note++) { + where = buffer[]; + midi.writeRawMessageData(where.midiNoteOn(1, note, 127)); + import core.thread; + Thread.sleep(dur!"msecs"(500)); + midi.writeRawMessageData(where.midiNoteOff(1, note, 127)); + + if(note != 76 && note != 83) + note++; + } +} + + import core.stdc.config; version(linux) version=ALSA; +version(Windows) version=WinMM; + +version(ALSA) { + enum cardName = "default"; + enum SampleRate = 44100; + + // this is the virtual rawmidi device on my computer at least + // maybe later i'll make it probe + // + // Getting midi to actually play on Linux is a bit of a pain. + // Here's what I did: + /* + # load the kernel driver, if amidi -l gives ioctl error, + # you haven't done this yet! + modprobe snd-virmidi + + # start a software synth. timidity -iA is also an option + fluidsynth soundfont.sf2 + + # connect the virtual hardware port to the synthesizer + aconnect 24:0 128:0 + + + I might also add a snd_seq client here which is a bit + easier to setup but for now I'm using the rawmidi so you + gotta get them connected somehow. + */ + enum midiName = "hw:2,0"; +} + +/// Thrown on audio failures. +/// Subclass this to provide OS-specific exceptions +class AudioException : Exception { + this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { + super(message, file, line, next); + } +} + +/// Gives PCM input access (such as a microphone). +struct AudioInput { + version(ALSA) { + snd_pcm_t* handle; + } + + @disable this(); + @disable this(this); + + /// Always pass card == 0. + this(int card) { + assert(card == 0); + + version(ALSA) { + handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_CAPTURE); + } else static assert(0); + } + + /// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz + /// Each item in the array thus alternates between left and right channel + /// and it takes a total of 88,200 items to make one second of sound. + /// + /// Returns the slice of the buffer actually read into + short[] read(short[] buffer) { + version(ALSA) { + snd_pcm_sframes_t read; + + read = snd_pcm_readi(handle, buffer.ptr, buffer.length / 2 /* div number of channels apparently */); + if(read < 0) + throw new AlsaException("pcm read", read); + + return buffer[0 .. read]; + } else static assert(0); + } + + // FIXME: add async function hooks + + ~this() { + version(ALSA) { + snd_pcm_close(handle); + } else static assert(0); + } +} + +/// Gives PCM output access (such as the speakers). +struct AudioOutput { + version(ALSA) { + snd_pcm_t* handle; + } + + @disable this(); + @disable this(this); + + /// Always pass card == 0. + this(int card) { + assert(card == 0); + + version(ALSA) { + handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_PLAYBACK); + } else static assert(0); + } + + /// Data is assumed to be interleaved stereo, LE 16 bit, 44.1 kHz + /// Each item in the array thus alternates between left and right channel + /// and it takes a total of 88,200 items to make one second of sound. + void write(scope const(short)[] data) { + version(ALSA) { + snd_pcm_sframes_t written; + + while(data.length) { + written = snd_pcm_writei(handle, data.ptr, data.length); + if(written < 0) + throw new AlsaException("pcm write", written); + data = data[written .. $]; + } + } else static assert(0); + } + + // FIXME: add async function hooks + + ~this() { + version(ALSA) { + snd_pcm_close(handle); + } else static assert(0); + } +} + +/// Gives MIDI output access. +struct MidiOutput { + version(ALSA) { + snd_rawmidi_t* handle; + } else version(WinMM) { + HMIDIOUT handle; + } + + @disable this(); + @disable this(this); + + /// Always pass card == 0. + this(int card) { + assert(card == 0); + + version(ALSA) { + if(auto err = snd_rawmidi_open(null, &handle, midiName, 0)) + throw new AlsaException("rawmidi open", err); + } else version(WinMM) { + if(auto err = midiOutOpen(&handle, 0, 0, 0, CALLBACK_NULL)) + throw new WinMMException("midi out open", err); + } else static assert(0); + } + + /// Send a reset message, silencing all notes + void reset() { + version(ALSA) { + static immutable ubyte[3] resetSequence = [0x0b << 4, 123, 0]; + // send a controller event to reset it + writeRawMessageData(resetSequence[]); + // and flush it immediately + snd_rawmidi_drain(handle); + } else version(WinMM) { + if(auto error = midiOutReset(handle)) + throw new WinMMException("midi reset", error); + } else static assert(0); + } + + /// Writes a single low-level midi message + /// Timing and sending sane data is your responsibility! + void writeMidiMessage(int status, int param1, int param2) { + version(ALSA) { + ubyte[3] dataBuffer; + + dataBuffer[0] = cast(ubyte) status; + dataBuffer[1] = cast(ubyte) param1; + dataBuffer[2] = cast(ubyte) param2; + + auto msg = status >> 4; + ubyte[] data; + if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) + data = dataBuffer[0 .. 2]; + else + data = dataBuffer[]; + + writeRawMessageData(data); + } else version(WinMM) { + DWORD word = (param2 << 16) | (param1 << 8) | status; + if(auto error = midiOutShortMsg(handle, word)) + throw new WinMMException("midi out", error); + } else static assert(0); + + } + + /// Writes a series of individual raw messages. + /// Timing and sending sane data is your responsibility! + /// The data should NOT include any timestamp bytes - each midi message should be 2 or 3 bytes. + void writeRawMessageData(scope const(ubyte)[] data) { + version(ALSA) { + ssize_t written; + + while(data.length) { + written = snd_rawmidi_write(handle, data.ptr, data.length); + if(written < 0) + throw new AlsaException("midi write", cast(int) written); + data = data[cast(int) written .. $]; + } + } else version(WinMM) { + while(data.length) { + auto msg = data[0] >> 4; + if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) { + writeMidiMessage(data[0], data[1], 0); + data = data[2 .. $]; + } else { + writeMidiMessage(data[0], data[1], data[2]); + data = data[3 .. $]; + } + } + } else static assert(0); + } + + ~this() { + version(ALSA) { + snd_rawmidi_close(handle); + } else version(WinMM) { + midiOutClose(handle); + } else static assert(0); + } +} + +// FIXME: maybe add a PC speaker beep function for completeness /// Interfaces with the default sound card. You should only have a single instance of this and it should /// be stack allocated, so its destructor cleans up after it. -struct AudioIO { +/// +/// A mixer gives access to things like volume controls and mute buttons. It should also give a +/// callback feature to alert you of when the settings are changed by another program. +struct AudioMixer { // To port to a new OS: put the data in the right version blocks // then implement each function. Leave else static assert(0) at the // end of each version group in a function so it is easier to implement elsewhere later. @@ -41,13 +313,12 @@ struct AudioIO { // // Put necessary bindings at the end of the file, or use an import if you like, but I prefer these files to be standalone. version(ALSA) { - snd_mixer_t* mixerHandle; + snd_mixer_t* handle; snd_mixer_selem_id_t* sid; snd_mixer_elem_t* selem; c_long maxVolume, minVolume; - enum cardName = "default"; enum selemName = "Master"; } @@ -59,58 +330,61 @@ struct AudioIO { assert(cardId == 0, "Pass 0 to use default sound card."); version(ALSA) { - if(snd_mixer_open(&mixerHandle, 0)) - throw new Exception("open sound"); + if(auto err = snd_mixer_open(&handle, 0)) + throw new AlsaException("open sound", err); scope(failure) - snd_mixer_close(mixerHandle); - if(snd_mixer_attach(mixerHandle, cardName)) - throw new Exception("attach to sound card"); - if(snd_mixer_selem_register(mixerHandle, null, null)) - throw new Exception("register mixer"); - if(snd_mixer_load(mixerHandle)) - throw new Exception("load mixer"); + snd_mixer_close(handle); + if(auto err = snd_mixer_attach(handle, cardName)) + throw new AlsaException("attach to sound card", err); + if(auto err = snd_mixer_selem_register(handle, null, null)) + throw new AlsaException("register mixer", err); + if(auto err = snd_mixer_load(handle)) + throw new AlsaException("load mixer", err); - if(snd_mixer_selem_id_malloc(&sid)) - throw new Exception("master channel open"); + if(auto err = snd_mixer_selem_id_malloc(&sid)) + throw new AlsaException("master channel open", err); scope(failure) snd_mixer_selem_id_free(sid); snd_mixer_selem_id_set_index(sid, 0); snd_mixer_selem_id_set_name(sid, selemName); - selem = snd_mixer_find_selem(mixerHandle, sid); + selem = snd_mixer_find_selem(handle, sid); if(selem is null) - throw new Exception("find master element"); + throw new AlsaException("find master element", 0); - if(snd_mixer_selem_get_playback_volume_range(selem, &minVolume, &maxVolume)) - throw new Exception("get volume range"); + if(auto err = snd_mixer_selem_get_playback_volume_range(selem, &minVolume, &maxVolume)) + throw new AlsaException("get volume range", err); } else static assert(0); } ~this() { version(ALSA) { snd_mixer_selem_id_free(sid); - snd_mixer_close(mixerHandle); + snd_mixer_close(handle); } else static assert(0); } /// Gets the master channel's mute state + /// Note: this affects shared system state and you should not use it unless the end user wants you to. @property bool muteMaster() { version(ALSA) { int result; - if(snd_mixer_selem_get_playback_switch(selem, 0, &result)) - throw new Exception("get mute state"); + if(auto err = snd_mixer_selem_get_playback_switch(selem, 0, &result)) + throw new AlsaException("get mute state", err); return result == 0; } else static assert(0); } /// Mutes or unmutes the master channel + /// Note: this affects shared system state and you should not use it unless the end user wants you to. @property void muteMaster(bool mute) { version(ALSA) { - if(snd_mixer_selem_set_playback_switch_all(selem, mute ? 0 : 1)) - throw new Exception("set mute state"); + if(auto err = snd_mixer_selem_set_playback_switch_all(selem, mute ? 0 : 1)) + throw new AlsaException("set mute state", err); } else static assert(0); } /// returns a percentage, between 0 and 100 (inclusive) + /// Note: this affects shared system state and you should not use it unless the end user wants you to. int getMasterVolume() { version(ALSA) { c_long volume; @@ -120,6 +394,7 @@ struct AudioIO { } /// sets a percentage on the volume, so it must be 0 <= volume <= 100 + /// Note: this affects shared system state and you should not use it unless the end user wants you to. void setMasterVolume(int volume) { version(ALSA) { assert(volume >= 0 && volume <= 100); @@ -127,27 +402,387 @@ struct AudioIO { volume * (maxVolume - minVolume) / 100); } else static assert(0); } + + version(ALSA) { + /// Gets the ALSA descriptors which you can watch for events + /// on using regular select, poll, epoll, etc. + int[] getAlsaFileDescriptors() { + import core.sys.posix.poll; + pollfd[32] descriptors = void; + int got = snd_mixer_poll_descriptors(handle, descriptors.ptr, descriptors.length); + int[] result; + result.length = got; + foreach(i, desc; descriptors[0 .. got]) + result[i] = desc.fd; + return result; + } + + /// When the FD is ready, call this to let ALSA do its thing. + void handleAlsaEvents() { + snd_mixer_handle_events(handle); + } + + /// Set a callback for the master volume change events. + void setAlsaElemCallback(snd_mixer_elem_callback_t dg) { + snd_mixer_elem_set_callback(selem, dg); + } + } } -// version(Test) -void main() { - auto aio = AudioIO(0); +// **************** +// Midi helpers +// **************** - import std.stdio; - writeln(aio.muteMaster); +// FIXME: code the .mid file format, read and write + +enum MidiEvent { + NoteOff = 0x08, + NoteOn = 0x09, + NoteAftertouch = 0x0a, + Controller = 0x0b, + ProgramChange = 0x0c, // one param + ChannelAftertouch = 0x0d, // one param + PitchBend = 0x0e, } +enum MidiNote : ubyte { + middleC = 60, + A = 69, // 440 Hz + As = 70, + B = 71, + C = 72, + Cs = 73, + D = 74, + Ds = 75, + E = 76, + F = 77, + Fs = 78, + G = 79, + Gs = 80, +} + +/// Puts a note on at the beginning of the passed slice, advancing it by the amount of the message size. +/// Returns the message slice. +/// +/// See: http://www.midi.org/techspecs/midimessages.php +ubyte[] midiNoteOn(ref ubyte[] where, ubyte channel, byte note, byte velocity) { + where[0] = (MidiEvent.NoteOn << 4) | (channel&0x0f); + where[1] = note; + where[2] = velocity; + auto it = where[0 .. 3]; + where = where[3 .. $]; + return it; +} + +/// Note off. +ubyte[] midiNoteOff(ref ubyte[] where, ubyte channel, byte note, byte velocity) { + where[0] = (MidiEvent.NoteOff << 4) | (channel&0x0f); + where[1] = note; + where[2] = velocity; + auto it = where[0 .. 3]; + where = where[3 .. $]; + return it; +} + +/// Aftertouch. +ubyte[] midiNoteAftertouch(ref ubyte[] where, ubyte channel, byte note, byte pressure) { + where[0] = (MidiEvent.NoteAftertouch << 4) | (channel&0x0f); + where[1] = note; + where[2] = pressure; + auto it = where[0 .. 3]; + where = where[3 .. $]; + return it; +} + +/// Controller. +ubyte[] midiNoteController(ref ubyte[] where, ubyte channel, byte controllerNumber, byte controllerValue) { + where[0] = (MidiEvent.Controller << 4) | (channel&0x0f); + where[1] = controllerNumber; + where[2] = controllerValue; + auto it = where[0 .. 3]; + where = where[3 .. $]; + return it; +} + +/// Program change. +ubyte[] midiProgramChange(ref ubyte[] where, ubyte channel, byte program) { + where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f); + where[1] = program; + auto it = where[0 .. 2]; + where = where[2 .. $]; + return it; +} + +/// Channel aftertouch. +ubyte[] midiChannelAftertouch(ref ubyte[] where, ubyte channel, byte amount) { + where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f); + where[1] = amount; + auto it = where[0 .. 2]; + where = where[2 .. $]; + return it; +} + +/// Pitch bend. FIXME doesn't work right +ubyte[] midiNotePitchBend(ref ubyte[] where, ubyte channel, short change) { +/* +first byte is llllll +second byte is mmmmmm + +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. +*/ + where[0] = (MidiEvent.PitchBend << 4) | (channel&0x0f); + // FIXME + where[1] = 0; + where[2] = 0; + auto it = where[0 .. 3]; + where = where[3 .. $]; + return it; +} + + +// **************** +// Wav helpers +// **************** + +// FIXME: the .wav file format should be here, read and write (at least basics) +// as well as some kind helpers to generate some sounds. + +// **************** +// OS specific helper stuff follows +// **************** + +version(ALSA) +// Opens the PCM device with default settings: stereo, 16 bit, 44.1 kHz, interleaved R/W. +snd_pcm_t* openAlsaPcm(snd_pcm_stream_t direction) { + snd_pcm_t* handle; + snd_pcm_hw_params_t* hwParams; + + if (auto err = snd_pcm_open(&handle, cardName, direction, 0)) + throw new AlsaException("open device", err); + scope(failure) + snd_pcm_close(handle); + + if (auto err = snd_pcm_hw_params_malloc(&hwParams)) + throw new AlsaException("params malloc", err); + scope(exit) + snd_pcm_hw_params_free(hwParams); + + if (auto err = snd_pcm_hw_params_any(handle, hwParams)) + throw new AlsaException("params init", err); + + if (auto err = snd_pcm_hw_params_set_access(handle, hwParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED)) + throw new AlsaException("params access", err); + + if (auto err = snd_pcm_hw_params_set_format(handle, hwParams, snd_pcm_format.SND_PCM_FORMAT_S16_LE)) + throw new AlsaException("params format", err); + + uint rate = SampleRate; + int dir = 0; + if (auto err = snd_pcm_hw_params_set_rate_near(handle, hwParams, &rate, &dir)) + throw new AlsaException("params rate", err); + + if (auto err = snd_pcm_hw_params_set_channels(handle, hwParams, 2)) + throw new AlsaException("params channels", err); + + if (auto err = snd_pcm_hw_params(handle, hwParams)) + throw new AlsaException("params install", err); + + if (auto err = snd_pcm_prepare(handle)) + throw new AlsaException("prepare", err); + + assert(handle !is null); + return handle; +} + +version(ALSA) +class AlsaException : AudioException { + this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { + auto msg = snd_strerror(error); + import core.stdc.string; + super(cast(string) (message ~ ": " ~ msg[0 .. strlen(msg)]), file, line, next); + } +} + +version(WinMM) +class WinMMException : AudioException { + this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { + // FIXME: format the error + // midiOutGetErrorText, etc. + super(message, file, line, next); + } +} + +// **************** // Bindings follow +// **************** version(ALSA) { extern(C): @nogc nothrow: pragma(lib, "asound"); + private import core.sys.posix.poll; + + const(char)* snd_strerror(int); + + // pcm + enum snd_pcm_stream_t { + SND_PCM_STREAM_PLAYBACK, + SND_PCM_STREAM_CAPTURE + } + + enum snd_pcm_access_t { + /** mmap access with simple interleaved channels */ + SND_PCM_ACCESS_MMAP_INTERLEAVED = 0, + /** mmap access with simple non interleaved channels */ + SND_PCM_ACCESS_MMAP_NONINTERLEAVED, + /** mmap access with complex placement */ + SND_PCM_ACCESS_MMAP_COMPLEX, + /** snd_pcm_readi/snd_pcm_writei access */ + SND_PCM_ACCESS_RW_INTERLEAVED, + /** snd_pcm_readn/snd_pcm_writen access */ + SND_PCM_ACCESS_RW_NONINTERLEAVED, + SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED + } + + enum snd_pcm_format { + /** Unknown */ + SND_PCM_FORMAT_UNKNOWN = -1, + /** Signed 8 bit */ + SND_PCM_FORMAT_S8 = 0, + /** Unsigned 8 bit */ + SND_PCM_FORMAT_U8, + /** Signed 16 bit Little Endian */ + SND_PCM_FORMAT_S16_LE, + /** Signed 16 bit Big Endian */ + SND_PCM_FORMAT_S16_BE, + /** Unsigned 16 bit Little Endian */ + SND_PCM_FORMAT_U16_LE, + /** Unsigned 16 bit Big Endian */ + SND_PCM_FORMAT_U16_BE, + /** Signed 24 bit Little Endian using low three bytes in 32-bit word */ + SND_PCM_FORMAT_S24_LE, + /** Signed 24 bit Big Endian using low three bytes in 32-bit word */ + SND_PCM_FORMAT_S24_BE, + /** Unsigned 24 bit Little Endian using low three bytes in 32-bit word */ + SND_PCM_FORMAT_U24_LE, + /** Unsigned 24 bit Big Endian using low three bytes in 32-bit word */ + SND_PCM_FORMAT_U24_BE, + /** Signed 32 bit Little Endian */ + SND_PCM_FORMAT_S32_LE, + /** Signed 32 bit Big Endian */ + SND_PCM_FORMAT_S32_BE, + /** Unsigned 32 bit Little Endian */ + SND_PCM_FORMAT_U32_LE, + /** Unsigned 32 bit Big Endian */ + SND_PCM_FORMAT_U32_BE, + /** Float 32 bit Little Endian, Range -1.0 to 1.0 */ + SND_PCM_FORMAT_FLOAT_LE, + /** Float 32 bit Big Endian, Range -1.0 to 1.0 */ + SND_PCM_FORMAT_FLOAT_BE, + /** Float 64 bit Little Endian, Range -1.0 to 1.0 */ + SND_PCM_FORMAT_FLOAT64_LE, + /** Float 64 bit Big Endian, Range -1.0 to 1.0 */ + SND_PCM_FORMAT_FLOAT64_BE, + /** IEC-958 Little Endian */ + SND_PCM_FORMAT_IEC958_SUBFRAME_LE, + /** IEC-958 Big Endian */ + SND_PCM_FORMAT_IEC958_SUBFRAME_BE, + /** Mu-Law */ + SND_PCM_FORMAT_MU_LAW, + /** A-Law */ + SND_PCM_FORMAT_A_LAW, + /** Ima-ADPCM */ + SND_PCM_FORMAT_IMA_ADPCM, + /** MPEG */ + SND_PCM_FORMAT_MPEG, + /** GSM */ + SND_PCM_FORMAT_GSM, + /** Special */ + SND_PCM_FORMAT_SPECIAL = 31, + /** Signed 24bit Little Endian in 3bytes format */ + SND_PCM_FORMAT_S24_3LE = 32, + /** Signed 24bit Big Endian in 3bytes format */ + SND_PCM_FORMAT_S24_3BE, + /** Unsigned 24bit Little Endian in 3bytes format */ + SND_PCM_FORMAT_U24_3LE, + /** Unsigned 24bit Big Endian in 3bytes format */ + SND_PCM_FORMAT_U24_3BE, + /** Signed 20bit Little Endian in 3bytes format */ + SND_PCM_FORMAT_S20_3LE, + /** Signed 20bit Big Endian in 3bytes format */ + SND_PCM_FORMAT_S20_3BE, + /** Unsigned 20bit Little Endian in 3bytes format */ + SND_PCM_FORMAT_U20_3LE, + /** Unsigned 20bit Big Endian in 3bytes format */ + SND_PCM_FORMAT_U20_3BE, + /** Signed 18bit Little Endian in 3bytes format */ + SND_PCM_FORMAT_S18_3LE, + /** Signed 18bit Big Endian in 3bytes format */ + SND_PCM_FORMAT_S18_3BE, + /** Unsigned 18bit Little Endian in 3bytes format */ + SND_PCM_FORMAT_U18_3LE, + /** Unsigned 18bit Big Endian in 3bytes format */ + SND_PCM_FORMAT_U18_3BE, + /* G.723 (ADPCM) 24 kbit/s, 8 samples in 3 bytes */ + SND_PCM_FORMAT_G723_24, + /* G.723 (ADPCM) 24 kbit/s, 1 sample in 1 byte */ + SND_PCM_FORMAT_G723_24_1B, + /* G.723 (ADPCM) 40 kbit/s, 8 samples in 3 bytes */ + SND_PCM_FORMAT_G723_40, + /* G.723 (ADPCM) 40 kbit/s, 1 sample in 1 byte */ + SND_PCM_FORMAT_G723_40_1B, + /* Direct Stream Digital (DSD) in 1-byte samples (x8) */ + SND_PCM_FORMAT_DSD_U8, + /* Direct Stream Digital (DSD) in 2-byte samples (x16) */ + SND_PCM_FORMAT_DSD_U16_LE, + SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_DSD_U16_LE, + + // I snipped a bunch of endian-specific ones! + } + + struct snd_pcm_t {} + struct snd_pcm_hw_params_t {} + + int snd_pcm_open(snd_pcm_t**, const char*, snd_pcm_stream_t, int); + int snd_pcm_close(snd_pcm_t*); + int snd_pcm_prepare(snd_pcm_t*); + int snd_pcm_hw_params(snd_pcm_t*, snd_pcm_hw_params_t*); + int snd_pcm_hw_params_set_channels(snd_pcm_t*, snd_pcm_hw_params_t*, uint); + int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t**); + void snd_pcm_hw_params_free(snd_pcm_hw_params_t*); + int snd_pcm_hw_params_any(snd_pcm_t*, snd_pcm_hw_params_t*); + int snd_pcm_hw_params_set_access(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_access_t); + int snd_pcm_hw_params_set_format(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_format); + int snd_pcm_hw_params_set_rate_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int*); + + alias snd_pcm_sframes_t = c_long; + alias snd_pcm_uframes_t = c_ulong; + snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t*, const void*, snd_pcm_uframes_t size); + snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t*, void*, snd_pcm_uframes_t size); + + // raw midi + + static if(is(ssize_t == uint)) + alias ssize_t = int; + else + alias ssize_t = long; + + + struct snd_rawmidi_t {} + int snd_rawmidi_open(snd_rawmidi_t**, snd_rawmidi_t**, const char*, int); + int snd_rawmidi_close(snd_rawmidi_t*); + int snd_rawmidi_drain(snd_rawmidi_t*); + ssize_t snd_rawmidi_write(snd_rawmidi_t*, const void*, size_t); + ssize_t snd_rawmidi_read(snd_rawmidi_t*, void*, size_t); + + // mixer struct snd_mixer_t {} struct snd_mixer_elem_t {} struct snd_mixer_selem_id_t {} + alias snd_mixer_elem_callback_t = int function(snd_mixer_elem_t*, uint); + int snd_mixer_open(snd_mixer_t**, int mode); int snd_mixer_close(snd_mixer_t*); int snd_mixer_attach(snd_mixer_t*, const char*); @@ -168,8 +803,40 @@ extern(C): int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t*, c_long); + void snd_mixer_elem_set_callback(snd_mixer_elem_t*, snd_mixer_elem_callback_t); + int snd_mixer_poll_descriptors(snd_mixer_t*, pollfd*, uint space); + + int snd_mixer_handle_events(snd_mixer_t*); // FIXME: the first int should be an enum for channel identifier int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t*, int, int* value); int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t*, int); } + +version(WinMM) { +extern(Windows): +@nogc nothrow: + pragma(lib, "winmm"); + import core.sys.windows.windows; + +/* + Windows functions include: + http://msdn.microsoft.com/en-us/library/ms713762%28VS.85%29.aspx + http://msdn.microsoft.com/en-us/library/ms713504%28v=vs.85%29.aspx + http://msdn.microsoft.com/en-us/library/windows/desktop/dd798480%28v=vs.85%29.aspx# + http://msdn.microsoft.com/en-US/subscriptions/ms712109.aspx +*/ + + // pcm + + // midi + + alias HMIDIOUT = HANDLE; + alias MMRESULT = UINT; + enum CALLBACK_NULL = 0; + + MMRESULT midiOutOpen(HMIDIOUT*, UINT, DWORD, DWORD, DWORD); + MMRESULT midiOutClose(HMIDIOUT); + MMRESULT midiOutReset(HMIDIOUT); + MMRESULT midiOutShortMsg(HMIDIOUT, DWORD); +}