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 }