1 module iota.audio.alsa;
2 
3 version (linux):
4 
5 package import iota.audio.backend.linux;
6 
7 import iota.audio.device;
8 import iota.audio.output;
9 
10 import core.time;
11 
12 import core.stdc.stdlib;
13 
14 import core.thread;
15 
16 /// Name of all devices
17 package static char*[] devicenames;
18 /// The last ALSA specific error code
19 public static int lastErrorCode;
20 
21 /// Automatic cleanup.
22 shared static ~this() {
23 	flushNames();
24 }
25 /** 
26  * Flushes all device names automatically.
27  */
28 package void flushNames() {
29 	foreach (key ; devicenames)
30 		free(key);
31 }
32 /** 
33  * Returns the error string from an error code.
34  * Params:
35  *   errCode = 
36  */
37 public string getErrorString(int errCode) {
38 	import std.string : fromStringz;
39 	return fromStringz(snd_strerror(errCode)).idup;
40 }
41 /** 
42  * Implements an ALSA device.
43  */
44 public class ALSADevice : AudioDevice {
45 	package snd_pcm_t*		pcmHandle;
46 	protected snd_pcm_hw_params_t* pcmParams;
47 	/// Contains ALSA-related error codes.
48 	public int				alsaerrCode;
49 	package this (snd_pcm_t* pcmHandle) {
50 		this.pcmHandle = pcmHandle;
51 		snd_pcm_hw_params_malloc(&pcmParams);
52 		snd_pcm_hw_params_any(pcmHandle, pcmParams);
53 		uint val;
54 		snd_pcm_hw_params_get_channels_max(pcmParams, &val);
55 		this._nOfOutputs = cast(short)val;
56 		this._nOfInputs = -1;
57 	}
58 	~this() {
59 		snd_pcm_close(pcmHandle);
60 		snd_pcm_hw_params_free(pcmParams);
61 
62 	}
63 
64 	override public AudioSpecs requestSpecs(AudioSpecs reqSpecs, int flags = 0) {
65 		reqSpecs.mirrorBufferSizes();
66 		alsaerrCode = snd_pcm_hw_params_set_access(pcmHandle, pcmParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED);
67 		if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;}
68 		switch (reqSpecs.format.bits) {
69 			case 0x08:
70 				switch (reqSpecs.format.flags & SampleFormat.Flag.Type_Test) {
71 					case SampleFormat.Flag.Type_Unsigned:
72 						alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_U8);
73 						break;
74 					case SampleFormat.Flag.Type_Signed:
75 						alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_S8);
76 						break;
77 					default:
78 						errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;
79 				}
80 				break;
81 			case 0x10:
82 				switch (reqSpecs.format.flags & SampleFormat.Flag.Type_Test) {
83 					case SampleFormat.Flag.Type_Unsigned:
84 						alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_U16_LE);
85 						break;
86 					case SampleFormat.Flag.Type_Signed:
87 						alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_S16_LE);
88 						break;
89 					default:
90 						errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;
91 				}
92 				break;
93 			case 0x18:
94 				switch (reqSpecs.format.flags & SampleFormat.Flag.Type_Test) {
95 					case SampleFormat.Flag.Type_Unsigned:
96 						alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_U24_LE);
97 						break;
98 					case SampleFormat.Flag.Type_Signed:
99 						alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_S24_LE);
100 						break;
101 					default:
102 						errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;
103 				}
104 				break;
105 			case 0x20:
106 				switch (reqSpecs.format.flags & SampleFormat.Flag.Type_Test) {
107 					case SampleFormat.Flag.Type_Float:
108 						alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_FLOAT_LE);
109 						break;
110 					case SampleFormat.Flag.Type_Unsigned:
111 						alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_U32_LE);
112 						break;
113 					case SampleFormat.Flag.Type_Signed:
114 						alsaerrCode = snd_pcm_hw_params_set_format(pcmHandle, pcmParams, snd_pcm_format.SND_PCM_FORMAT_S32_LE);
115 						break;
116 					default:
117 						errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;
118 				}
119 				break;
120 			default: 
121 				errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;
122 		}
123 		if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;}
124 		alsaerrCode = snd_pcm_hw_params_set_channels(pcmHandle, pcmParams, reqSpecs.outputChannels);
125 		if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;}
126 		//snd_pcm_hw_params_set_access(pcmHandle, pcmParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED);
127 		int something = 0;
128 		alsaerrCode = snd_pcm_hw_params_set_rate_near(pcmHandle, pcmParams, cast(uint*)&reqSpecs.sampleRate, &something);
129 		if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;}
130 		uint periods = (reqSpecs.bits() * reqSpecs.outputChannels) / 8;
131 		alsaerrCode = snd_pcm_hw_params_set_periods_near(pcmHandle, pcmParams, &periods, 0);
132 		if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;}
133 		snd_pcm_uframes_t bufferlen = reqSpecs.bufferSize_slmp * periods;
134 		alsaerrCode = snd_pcm_hw_params_set_buffer_size_near(pcmHandle, pcmParams, &bufferlen);
135 		if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;}
136 		reqSpecs.bufferSize_slmp = cast(uint)(bufferlen / periods);
137 		reqSpecs.bufferSize_time = Duration.init;
138 		reqSpecs.mirrorBufferSizes();
139 		alsaerrCode = snd_pcm_hw_params(pcmHandle, pcmParams);
140 		if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;}
141 		// Set up software parameters
142 		snd_pcm_sw_params_t* swparams;
143 		alsaerrCode = snd_pcm_sw_params_malloc(&swparams);
144 		scope (exit)
145 			snd_pcm_sw_params_free(swparams);
146 		if (alsaerrCode) {errCode = StreamInitializationStatus.OutOfMemory; return AudioSpecs.init;}
147 		alsaerrCode = snd_pcm_sw_params_current(pcmHandle, swparams);
148 		if (alsaerrCode) {errCode = StreamInitializationStatus.OutOfMemory; return AudioSpecs.init;}
149 		alsaerrCode = snd_pcm_sw_params_set_avail_min(pcmHandle, swparams, reqSpecs.bufferSize_slmp);
150 		if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;}
151 		alsaerrCode = snd_pcm_sw_params_set_start_threshold(pcmHandle, swparams, 0);
152 		if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;}
153 		alsaerrCode = snd_pcm_sw_params(pcmHandle, swparams);
154 		if (alsaerrCode) {errCode = StreamInitializationStatus.UnsupportedFormat; return AudioSpecs.init;}
155 		//snd_pcm_nonblock(pcmHandle, 0);
156 		return _specs = reqSpecs;
157 	}
158 
159 	override public OutputStream createOutputStream() {
160 		alsaerrCode = snd_pcm_prepare(pcmHandle);
161 		if (alsaerrCode) {
162 			errCode = StreamInitializationStatus.Unknown;
163 			return null; // TODO: Implement better error-handling
164 		} else {
165 			return new ALSAOutStream(this);
166 		}
167 	}
168 }
169 /**
170  * Implements an ALSA output stream.
171  */
172 public class ALSAOutStream : OutputStream {
173 	/// Due to how ALSA works, the pcm handle is accessed from there directly, also keeps a reference of it to avoid
174 	/// calling the destructor.
175 	protected ALSADevice		dev;
176 	/// Buffer for outputting audio data.
177 	protected ubyte[]			buffer;
178 	/// Contains buffer size in samples.
179 	protected uint				bufferSize;
180 	/// Contains the last ALSA specific error code.
181 	public int					alsaerrCode;
182 
183 	package this (ALSADevice dev) {
184 		this.dev = dev;
185 		buffer.length = (dev.specs().bufferSize_slmp * dev.specs().bits * dev.specs().outputChannels) / 8;
186 		this.bufferSize = dev.specs().bufferSize_slmp;
187 	}
188 	~this() {
189 		
190 	}
191 	protected void audioThread() @nogc nothrow {
192 		snd_pcm_sframes_t currBufferSize;
193 		while (statusCode & StatusFlags.IsRunning) {
194 			callback_buffer(buffer);
195 			do {
196 				currBufferSize = snd_pcm_writei(dev.pcmHandle, cast(const(void*))buffer.ptr, bufferSize);
197 				if (currBufferSize < 0) {
198 					errCode = StreamRuntimeStatus.Unknown;
199 					return;
200 				}
201 			} while (currBufferSize < bufferSize && statusCode & StatusFlags.IsRunning);
202 			alsaerrCode = snd_pcm_wait(dev.pcmHandle, 1000);
203 			if (!alsaerrCode) {
204 				statusCode |= StatusFlags.BufferUnderrun;
205 			}
206 		}
207 	}
208 	override public int runAudioThread() @nogc nothrow {
209 		if (callback_buffer is null) return errCode = StreamRuntimeStatus.BufferCallbackNotSet;
210 		alsaerrCode = snd_pcm_start(dev.pcmHandle);
211 		if (alsaerrCode) {
212 			return errCode = StreamRuntimeStatus.Unknown;
213 		}
214 		statusCode |= StatusFlags.IsRunning;
215 		_threadID = createLowLevelThread(&audioThread);
216 		return errCode = StreamRuntimeStatus.AllOk;
217 	}
218 
219 	override public int suspendAudioThread() @nogc nothrow {
220 		alsaerrCode = snd_pcm_drain(dev.pcmHandle);
221 		statusCode &= ~StatusFlags.IsRunning;
222 		return errCode;
223 	}
224 }