PageRenderTime 65ms CodeModel.GetById 17ms app.highlight 43ms RepoModel.GetById 1ms app.codeStats 0ms

/decoders/audio/wav.d

http://github.com/wilkie/djehuty
D | 439 lines | 260 code | 89 blank | 90 comment | 82 complexity | ad5f1d459dc714d6fafcf012e39f3cde MD5 | raw file
  1/*
  2 * wav.d
  3 *
  4 * This file implements the WAV audio standard. A "wave" is a chunked format
  5 * and a container format. It contains audio data in a myriad of different
  6 * encodings.
  7 *
  8 * Author: Dave Wilkinson
  9 *
 10 */
 11
 12module decoders.audio.wav;
 13
 14import decoders.audio.decoder;
 15import decoders.audio.mp2 : MP2Decoder;
 16
 17import decoders.decoder;
 18
 19import core.stream;
 20import core.time;
 21import core.string;
 22import core.definitions;
 23
 24import io.wavelet;
 25import io.audio;
 26import io.console;
 27
 28// Section: Codecs/Audio
 29
 30// Description: This is the Microsoft Wave file codec.
 31class WAVDecoder : AudioDecoder {
 32
 33	override string name() {
 34		return "Microsoft Wave";
 35	}
 36
 37	override string extension() {
 38		return "wav;wave";
 39	}
 40
 41	StreamData decode(Stream stream, Wavelet toBuffer, ref AudioInfo wi) {
 42		for (;;) {
 43			switch (decoderState) {
 44				case WAVE_STATE_INIT:
 45					//initial stuff
 46
 47					decoderState = WAVE_STATE_READ_RIFF;
 48
 49					// *** fall through *** //
 50				case WAVE_STATE_READ_RIFF:
 51
 52					// read in the RIFF header
 53					// and check the validity
 54
 55					//reading the header
 56					if (!stream.read(&RIFFHeader, RIFFHeader.sizeof)) {
 57						return StreamData.Required;
 58					}
 59
 60					if (RIFFHeader.magic != *(cast(uint*)"RIFF"c.ptr)) {
 61						// RIFF header is wrong
 62						Console.putln("WAVE: Invalid RIFF Header");
 63						return StreamData.Invalid;
 64					}
 65
 66					decoderState = WAVE_STATE_READ_CHUNK;
 67
 68					// *** fall through *** //
 69				case WAVE_STATE_READ_CHUNK:
 70
 71					// read in a chunk header
 72					if (!stream.read(&curChunk, curChunk.sizeof)) {
 73						return StreamData.Required;
 74					}
 75
 76					char arr[4] = (cast(char*)&curChunk.chunkID)[0..4];
 77
 78					// figure out what the chunk means
 79					if (curChunk.chunkID == *(cast(uint*)"data"c.ptr)) {
 80						// DATA Chunk
 81						decoderState = WAVE_STATE_CHUNK_DATA;
 82
 83						// Get Audio Length
 84						//Console.putln(curChunk.chunkSize, " , ", FMTHeader.avgBytesPerSecond, " = ", (cast(float)curChunk.chunkSize / cast(float)FMTHeader.avgBytesPerSecond));
 85						wi.totalTime = cast(long)((cast(float)curChunk.chunkSize / cast(float)FMTHeader.avgBytesPerSecond) * 1000.0);
 86
 87						dataToRead = curChunk.chunkSize;
 88						continue;
 89					}
 90					else if (curChunk.chunkID == *(cast(uint*)"fmt "c.ptr)) {
 91						// FMT Chunk
 92						decoderState = WAVE_STATE_CHUNK_FMT;
 93						continue;
 94					}
 95					else {
 96						// Unknown Chunk
 97						// Just skip it
 98						decoderState = WAVE_STATE_SKIP_CHUNK;
 99					}
100
101					// *** fall through on an unknown chunk *** //
102				case WAVE_STATE_SKIP_CHUNK:
103					if (!stream.skip(curChunk.chunkSize)) {
104						return StreamData.Required;
105					}
106					decoderState = WAVE_STATE_READ_CHUNK;
107					continue;
108
109					// -- Process a Chunk -- //
110
111				case WAVE_STATE_CHUNK_DATA:
112
113					// if the buffer is null, this is acceptable
114					// in this case, the decoder will wait
115
116					/*	Console.putln("to seek");
117						toSeek.toString();
118						Console.putln("cur");
119						bufferTime.toString();*/
120
121					uint bufferSize = FMTHeader.avgBytesPerSecond << 1 ;
122
123					if (isSeek && !isSeekBack && (toSeek < (curTime + bufferTime))) {
124						// seeking
125						Console.putln("seek no more");
126						isSeek = false;
127						return StreamData.Accepted;
128					}
129					else if (isSeek && isSeekBack && (toSeek >= curTime)) {
130						// seeking
131						Console.putln("seek no more");
132						isSeek = false;
133						return StreamData.Accepted;
134					}
135					else if (toBuffer is null && isSeek == false) {
136						return StreamData.Accepted;
137					}
138
139					if (isSeek && isSeekBack) {
140						// go backwards
141
142						if (dataToRead == 0 && (curChunk.chunkSize % bufferSize) != 0) {
143							if (!stream.rewind(curChunk.chunkSize % bufferSize)) {
144								Console.putln("Audio Codec : Data Required");
145								return StreamData.Required;
146							}
147
148							dataToRead = curChunk.chunkSize % bufferSize;
149
150							curTime -= bufferTime;
151
152						}
153						else {
154
155							if (!stream.rewind(FMTHeader.avgBytesPerSecond << 1)) {
156								Console.putln("Audio Codec : Data Required");
157								return StreamData.Required;
158							}
159
160							curTime -= bufferTime;
161
162							dataToRead += FMTHeader.avgBytesPerSecond << 1;
163						}
164						continue;
165					}
166					//writeln("Audio Codec : Decoding Data Chunk");
167
168					if (dataToRead == 0) {
169						return StreamData.Complete;
170					}
171
172					// we should have a format header
173					if (!formatHeaderFound) {
174						// no format header, yet...
175						// this file is invalid
176						return StreamData.Invalid;
177					}
178
179					if (FMTHeader.compressionCode == 0x50) {
180						if (embeddedCodec is null) {
181							embeddedCodec = new MP2Decoder();
182						}
183						return embeddedCodec.decode(stream, toBuffer, wi);
184					}
185					else if (FMTHeader.compressionCode == 1 || FMTHeader.compressionCode == 3) {
186						// are we getting the last piece?
187						if (dataToRead < bufferSize) {
188							//writeln("Audio Codec : Allocating ", dataToRead, " bytes (last chunk)");
189							// allocate a whole buffer
190
191							if (toBuffer !is null) {
192								toBuffer.setAudioFormat(wf);
193
194								if (toBuffer.length() != dataToRead) {
195									// allocate
196									//  (this may look redundant, but this may occur
197									//   when there is only one chunk in the file)
198//									Console.putln("Audio Codec : Resizing : " , toBuffer.length(), " : ", dataToRead);
199//									toBuffer.resize(dataToRead);
200								}
201								toBuffer.rewind();
202
203								//writeln("Audio Codec : Appending ", dataToRead, " bytes (last chunk)");
204
205								// this is the last chunk of data
206								if (!toBuffer.write(stream, dataToRead)) {
207									Console.putln("Audio Codec : Data Required");
208									return StreamData.Required;
209								}
210							}
211							else {
212								if (!stream.skip(dataToRead)) {
213									//writeln("Audio Codec : Data Required");
214									return StreamData.Required;
215								}
216							}
217
218							dataToRead = 0;
219							curTime += bufferTime;
220
221							// go back to reading chunks
222							//decoderState = WAVE_STATE_READ_CHUNK;
223
224							return StreamData.Complete;
225						}
226						else {
227							//writeln("Audio Codec : Allocating ", bufferSize, " bytes");
228							// allocate a whole buffer
229
230							if (toBuffer !is null) {
231								toBuffer.setAudioFormat(wf);
232								if (toBuffer.length() != bufferSize) {
233									// allocate
234//									Console.putln("Audio Codec : Resizing : " , toBuffer.length(), " : ", bufferSize);
235//									toBuffer.resize(bufferSize);
236								}
237								toBuffer.rewind();
238								//writeln("Audio Codec : Appending ", bufferSize, " bytes");
239
240								// Read in a second worth of information
241								if (!toBuffer.write(stream, bufferSize)) {
242									//writeln("Audio Codec : Data Required");
243									return StreamData.Required;
244								}
245								curTime += bufferTime;
246								curTime.toString();
247
248								dataToRead -= bufferSize;
249								return StreamData.Accepted;
250							}
251							else {
252								if (!stream.skip(bufferSize)) {
253									//writeln("Audio Codec : Data Required");
254									return StreamData.Required;
255								}
256
257								dataToRead -= bufferSize;
258								curTime += bufferTime;
259							}
260
261
262							// add the amount of time to decoder's time counter
263							// this is the time stamp of the next data block
264						}
265					}
266
267					continue;
268
269				case WAVE_STATE_CHUNK_FMT:
270
271					// Read in the format information
272
273					if (!stream.read(&FMTHeader, FMTHeader.sizeof)) {
274						return StreamData.Required;
275					}
276
277					// TODO: just have a state selector (can use nextState or subState within CodecState decoder!)
278
279					if (FMTHeader.compressionCode != 0x01 &&
280						FMTHeader.compressionCode != 0x03 &&
281						FMTHeader.compressionCode != 0x50) {
282
283						Console.putln("WAVE: Unsupported Compression Type");
284						return StreamData.Invalid;
285					}
286
287					if (FMTHeader.compressionCode == 1 || FMTHeader.compressionCode == 3) {
288						wf.compressionType = FMTHeader.compressionCode;
289
290						wf.numChannels = FMTHeader.numChannels;
291						wf.samplesPerSecond = FMTHeader.sampleRate;
292						wf.averageBytesPerSecond =
293							//FMTHeader.sampleRate * (FMTHeader.significantBitsPerSample / 8) * FMTHeader.numChannels;
294							FMTHeader.avgBytesPerSecond;
295						wf.blockAlign = FMTHeader.blockAlign;
296						wf.bitsPerSample = FMTHeader.significantBitsPerSample;
297
298						// Output for sanity check
299
300						/*
301						Console.putln("  Comression Code: ", wf.compressionType);
302						Console.putln("  Sample Frequency: ", wf.samplesPerSecond);
303						Console.putln("  Average Bytes Per Second: ", wf.averageBytesPerSecond);
304						Console.putln("  Number of Channels: ", wf.numChannels);
305						Console.putln("  Bits per Sample: ", wf.bitsPerSample);
306						Console.putln("  Block Align: ", wf.blockAlign );
307
308						Console.putln("");
309						*/
310
311						bufferTime = new Time(2000000);
312
313					}
314					else {
315						Console.putln("WAVE: Alternate Codec Requested Via Compression Code");
316						embeddedCodec = null;
317					}
318
319					formatHeaderFound = true;
320
321					stream.rewind(FMTHeader.sizeof);
322
323					decoderState = WAVE_STATE_SKIP_CHUNK;
324					continue;
325
326				default:
327					// -- Default for corrupt files -- //
328					break;
329			}
330			break;
331		}
332		return StreamData.Invalid;
333	}
334
335	// Description: This function will advance the stream to the beginning of the buffer that contains the time requested.
336	StreamData seek(Stream stream, AudioFormat wf, AudioInfo wi, ref Time amount) {
337		if (decoderState == 0) {
338			// not inited?
339			return StreamData.Invalid;
340		}
341
342		if (FMTHeader.compressionCode == 0x50 && embeddedCodec !is null) {
343			StreamData ret = embeddedCodec.seek(stream, wf, wi, amount);
344			amount -= embeddedCodec.getCurrentTime();
345			return ret;
346		}
347		else if (!(FMTHeader.compressionCode == 1 || FMTHeader.compressionCode == 3)) {
348			return StreamData.Invalid;
349		}
350
351		if (amount == curTime) {
352			Console.putln("ON TIME");
353			return StreamData.Accepted;
354		}
355
356		if (amount > curTime) {
357			Console.putln("WE NEED TO GO AHEAD");
358			// good!
359			// simply find the section we need to be
360			// we are buffering 2 seconds...
361			toSeek = amount;
362			isSeekBack = false;
363			isSeek = true;
364			StreamData ret = decode(stream, null, wi);
365			amount -= curTime;
366			return ret;
367		}
368		else {
369			Console.putln("WE NEED TO FALL BEHIND");
370			// for wave files, this is not altogether a bad thing
371			// for other types of files, it might be
372			// mp3 can be variable, and would require a seek from the
373			// beginning or maintaining a cache of some sort.
374			toSeek = amount;
375			isSeekBack = true;
376			isSeek = true;
377			StreamData ret =  decode(stream, null, wi);
378			amount -= curTime;
379			return ret;
380		}
381	}
382
383	Time length(Stream stream, ref AudioFormat wf, ref AudioInfo wi) {
384		Time tme = Time.init;
385		return tme;
386	}
387
388	Time lengthQuick(Stream stream, ref AudioFormat wf, ref AudioInfo wi) {
389		Time tme = Time.init;
390		return tme;
391	}
392
393private:
394
395	align(2) struct _djehuty_wave_riff_header {
396		uint magic;
397		uint filesize;
398		uint rifftype;
399	}
400
401	align(2) struct _djehuty_wave_chunk_header {
402		uint chunkID;
403		uint chunkSize;
404	}
405
406	struct _djehuty_wave_format_chunk {
407		ushort compressionCode;
408		ushort numChannels;
409		uint sampleRate;
410		uint avgBytesPerSecond;
411		ushort blockAlign;
412		ushort significantBitsPerSample;
413		ushort extraBytes;
414	}
415
416	const auto WAVE_STATE_INIT 			= 0;
417	const auto WAVE_STATE_READ_RIFF		= 1;
418	const auto WAVE_STATE_READ_CHUNK	= 2;
419	const auto WAVE_STATE_SKIP_CHUNK	= 3;
420
421	const auto WAVE_STATE_CHUNK_FMT		= 4;
422	const auto WAVE_STATE_CHUNK_DATA	= 5;
423
424protected:
425
426	_djehuty_wave_riff_header RIFFHeader;
427	_djehuty_wave_format_chunk FMTHeader;
428
429
430	_djehuty_wave_chunk_header curChunk;
431
432	bool formatHeaderFound = false;
433	uint dataToRead = 0;
434
435	AudioDecoder embeddedCodec;
436
437	AudioFormat wf;
438	Time bufferTime;
439}