1 module iota.audio.device; 2 3 import std.string; 4 import std.utf; 5 import core.time : Duration; 6 7 public import iota.audio.types; 8 public import iota.audio.output; 9 10 version (Windows) { 11 public import iota.audio.wasapi; 12 import core.sys.windows.windows; 13 import core.sys.windows.objidl; 14 import core.sys.windows.wtypes; 15 } else version (linux) { 16 public import iota.audio.alsa; 17 } 18 /** 19 * The type of the initialized driver, or none if no driver was initialized yet. 20 */ 21 public static DriverType initializedDriver; 22 /** 23 * Initializes the audio driver, then sets up the device list. 24 * Params: 25 * type = The type of driver, defaults to system recommended otherwise. 26 * Returns: 0 (or AudioInitializationStatus.AllOk) if there was no error, or a specific errorcode. 27 */ 28 public int initDriver(DriverType type = OS_PREFERRED_DRIVER) { 29 final switch (type) with (DriverType) { 30 case None: 31 return AudioInitializationStatus.AllOk; 32 case WASAPI: 33 version (Windows) { 34 lastErrorCode = CoInitialize(null); 35 lastErrorCode = CoCreateInstance(&CLSID_MMDeviceEnumerator, null, CLSCTX_ALL, &IID_IMMDeviceEnumerator, 36 cast(void**)&immEnum); 37 switch (lastErrorCode) { 38 case S_OK: return initDriverWASAPI(); 39 case REGDB_E_CLASSNOTREG, E_NOINTERFACE, CLASS_E_NOAGGREGATION: return AudioInitializationStatus.DriverNotAvailable; 40 case E_POINTER: return AudioInitializationStatus.InternalError; 41 default: return AudioInitializationStatus.Unknown; 42 } 43 } else { 44 return AudioInitializationStatus.OSNotSupported; 45 } 46 case DirectAudio: 47 48 break; 49 case ALSA: 50 version (linux) { 51 initializedDriver = DriverType.ALSA; 52 return AudioInitializationStatus.AllOk; 53 } else { 54 return AudioInitializationStatus.OSNotSupported; 55 } 56 case JACK: 57 58 break; 59 } 60 return AudioInitializationStatus.Unknown; 61 } 62 /** 63 * Returns the list of names of output devices, or null if there are none or driver haven't been initialized. 64 */ 65 public string[] getOutputDeviceNames() { 66 version (Windows) { 67 if (initializedDriver == DriverType.WASAPI) { 68 string[] result; 69 UINT nOfDevices; 70 lastErrorCode = deviceList.GetCount(nOfDevices); 71 if (lastErrorCode) return null; 72 result.length = nOfDevices; 73 for (uint i ; i < result.length ; i++) { 74 IMMDevice ppDevice; 75 IPropertyStore properties; 76 lastErrorCode = deviceList.Item(i, ppDevice); 77 PROPVARIANT str; 78 if (lastErrorCode == S_OK) { 79 ppDevice.OpenPropertyStore(STGM_READ, properties); 80 properties.GetValue(DEVPKEY_Device_FriendlyName, str); 81 } 82 result[i] = toUTF8(fromStringz(str.pwszVal)); 83 CoTaskMemFree(str.pwszVal); 84 properties.Release(); 85 ppDevice.Release(); 86 } 87 return result; 88 } else { 89 return null; 90 } 91 } else version (linux) { 92 if (initializedDriver == DriverType.ALSA) { 93 string[] result; 94 if (!devicenames.length) { 95 int cardIndex = -1; 96 do { 97 lastErrorCode = snd_card_next(&cardIndex); 98 if (cardIndex != -1) { 99 char* str; 100 lastErrorCode = snd_card_get_name(cardIndex, &str); 101 devicenames ~= str; 102 } 103 } while (cardIndex != -1); 104 } 105 foreach (char* key; devicenames) { 106 result ~= fromStringz(key).idup; 107 } 108 return result; 109 } else return null; 110 } 111 } 112 /** 113 * Initializes an audio device. 114 * Params: 115 * devID = The number of the device, or -1 if default output device is needed. 116 * device = Reference to newly opened device passed here. 117 * Returns: 0 (or AudioInitializationStatus.AllOk) if there was no error, or a specific errorcode. 118 */ 119 public int openDevice(int devID, ref AudioDevice device) { 120 version (Windows) { 121 if (initializedDriver == DriverType.WASAPI) { 122 IMMDevice dev; 123 if (devID >= 0) { 124 lastErrorCode = deviceList.Item(devID, dev); 125 } else { 126 lastErrorCode = immEnum.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, dev); 127 } 128 if (dev is null) return AudioInitializationStatus.DeviceNotFound; 129 device = new WASAPIDevice(dev); 130 return AudioInitializationStatus.AllOk; 131 } else { 132 return AudioInitializationStatus.UninitializedDriver; 133 } 134 } else version (linux) { 135 if (initializedDriver == DriverType.ALSA) { 136 snd_pcm_t* pcmHandle; 137 //lastErrorCode = snd_ctl_open(&pcmHandle, devID >= 0 ? devicenames[devID] : "default", 0); 138 lastErrorCode = snd_pcm_open(&pcmHandle, devID >= 0 ? devicenames[devID] : "default", 139 snd_pcm_stream_t.SND_PCM_STREAM_PLAYBACK, 0); 140 if (lastErrorCode) { 141 return AudioInitializationStatus.DeviceNotFound; 142 } 143 device = new ALSADevice(pcmHandle); 144 return AudioInitializationStatus.AllOk; 145 } else { 146 return AudioInitializationStatus.UninitializedDriver; 147 } 148 } else { 149 return AudioInitializationStatus.Unknown; 150 } 151 } 152 /** 153 * Implements a universal interface to an audio device. 154 * Each driver inherits from this. 155 */ 156 public abstract class AudioDevice { 157 /// Stores either all supported sample rates, or two values of lowest and highest supported sample rates if any 158 /// value can be set without stepping. 159 /// NOTE: It is not guaranteed, that at all sample ranges, all formats will be available. 160 protected int[] _sampleRateRange; 161 /// Stores supported formats. 162 protected SampleFormat[] _supportedFormats; 163 /// Number of input channels, or zero if there's none. 164 protected short _nOfInputs; 165 /// Number of output channels, or zero if there's none. 166 protected short _nOfOutputs; 167 /// The last error code if there's any, or 0. 168 /// Shall not be set externally, although should not impede on operation. 169 public int errCode; 170 /// The audio specifications set and available to this device. 171 protected AudioSpecs _specs; 172 /// Tells whether the device is in shared or exclusive mode. 173 /// In some cases, this can be set, in others it's either shared or exclusive. 174 protected AudioShareMode _shareMode = AudioShareMode.Shared; 175 /** 176 * Called when device is disconnected. 177 */ 178 public @nogc nothrow void delegate() callback_onDeviceDisconnect; 179 /** 180 * Returns the range of sample rates that this audio device supports. Might return null if it's enforced at audio 181 * spec request. 182 * NOTE: It is not guaranteed, that at all sample ranges, all formats will be available. 183 */ 184 public @property int[] sampleRateRange() @safe pure nothrow const { 185 return _sampleRateRange.dup; 186 } 187 /** 188 * Returns the range of audio formats that this audio device supports. Might return null if it's enforced at audio 189 * spec request. 190 * NOTE: It is not guaranteed, that at all sample ranges, all formats will be available. 191 */ 192 public @property SampleFormat[] supportedFormats() @safe pure nothrow const { 193 return _supportedFormats.dup; 194 } 195 /** 196 * Returns the number of input channels this device has, or zero if there's none, or -1 if it's enforced at audio 197 * spec request. 198 * NOTE: Depending on the backend, devices' inputs and outputs (or even different inputs and outputs) might show up 199 * as different devices. 200 */ 201 public @property short nOfInputs() @nogc @safe pure nothrow const { 202 return _nOfInputs; 203 } 204 /** 205 * Returns the number of output channels this device has, or zero if there's none, or -1 if it's enforced at audio 206 * spec request. 207 * NOTE: Depending on the backend, devices' inputs and outputs (or even different inputs and outputs) might show up 208 * as different devices. 209 */ 210 public @property short nOfOutputs() @nogc @safe pure nothrow const { 211 return _nOfOutputs; 212 } 213 /** 214 * Returns the currently set audio specifications. 215 */ 216 public @property AudioSpecs specs() @nogc @safe pure nothrow const { 217 return _specs; 218 } 219 public @property AudioShareMode shareMode() @nogc @safe pure nothrow const { 220 return _shareMode; 221 } 222 /** 223 * Sets the device's audio specifications to the closest possible specifications. 224 * Params: 225 * reqSpecs = The requested audio secifications. Can be set AudioSpecs.init if don't care. 226 * flags = Tells the function what specifications are must or can be compromised. 227 * Returns: The actual audio specs, or AudioSpecs.init in case of failure. 228 * In case of a failure, `errCode` is also set with the corresponding flags. 229 */ 230 public abstract AudioSpecs requestSpecs(AudioSpecs reqSpecs, int flags = 0); 231 /** 232 * Creates an OutputStream specific to the given driver/device, with the requested parameters, then returns it. Or 233 * null in case of an error, then it also sets `errCode` to a given error code. Might also set a separate error 234 * code variable, depending on the OS/backend. 235 */ 236 public abstract OutputStream createOutputStream(); 237 }