summaryrefslogtreecommitdiff
path: root/soundlib/WAVTools.h
blob: 8d3e6e4e0a7e0333b73987a13a9fb9646af0f9da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
/*
 * WAVTools.h
 * ----------
 * Purpose: Definition of WAV file structures and helper functions
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#pragma once

#include "BuildSettings.h"

#include "ChunkReader.h"
#include "Loaders.h"
#include "../common/mptUUID.h"

OPENMPT_NAMESPACE_BEGIN

struct FileTags;

// RIFF header
struct RIFFHeader
{
	// 32-Bit chunk identifiers
	enum RIFFMagic
	{
		idRIFF	= MagicLE("RIFF"),	// magic for WAV files
		idLIST	= MagicLE("LIST"),	// magic for samples in DLS banks
		idWAVE	= MagicLE("WAVE"),	// type for WAV files
		idwave	= MagicLE("wave"),	// type for samples in DLS banks
	};

	uint32le magic;		// RIFF (in WAV files) or LIST (in DLS banks)
	uint32le length;	// Size of the file, not including magic and length
	uint32le type;		// WAVE (in WAV files) or wave (in DLS banks)
};

MPT_BINARY_STRUCT(RIFFHeader, 12)


// General RIFF Chunk header
struct RIFFChunk
{
	// 32-Bit chunk identifiers
	enum ChunkIdentifiers
	{
		idfmt_	= MagicLE("fmt "),	// Sample format information
		iddata	= MagicLE("data"),	// Sample data
		idpcm_	= MagicLE("pcm "),	// IMA ADPCM samples
		idfact	= MagicLE("fact"),	// Compressed samples
		idsmpl	= MagicLE("smpl"),	// Sampler and loop information
		idinst	= MagicLE("inst"),	// Instrument information
		idLIST	= MagicLE("LIST"),	// List of chunks
		idxtra	= MagicLE("xtra"),	// OpenMPT extra infomration
		idcue_	= MagicLE("cue "),	// Cue points
		idwsmp	= MagicLE("wsmp"),	// DLS bank samples
		idCSET	= MagicLE("CSET"),	// Character Set
		id____	= 0x00000000,	// Found when loading buggy MPT samples

		// Identifiers in "LIST" chunk
		idINAM	= MagicLE("INAM"), // title
		idISFT	= MagicLE("ISFT"), // software
		idICOP	= MagicLE("ICOP"), // copyright
		idIART	= MagicLE("IART"), // artist
		idIPRD	= MagicLE("IPRD"), // product (album)
		idICMT	= MagicLE("ICMT"), // comment
		idIENG	= MagicLE("IENG"), // engineer
		idISBJ	= MagicLE("ISBJ"), // subject
		idIGNR	= MagicLE("IGNR"), // genre
		idICRD	= MagicLE("ICRD"), // date created

		idYEAR  = MagicLE("YEAR"), // year
		idTRCK  = MagicLE("TRCK"), // track number
		idTURL  = MagicLE("TURL"), // url
	};

	uint32le id;		// See ChunkIdentifiers
	uint32le length;	// Chunk size without header

	size_t GetLength() const
	{
		return length;
	}

	ChunkIdentifiers GetID() const
	{
		return static_cast<ChunkIdentifiers>(id.get());
	}
};

MPT_BINARY_STRUCT(RIFFChunk, 8)


// Format Chunk
struct WAVFormatChunk
{
	// Sample formats
	enum SampleFormats
	{
		fmtPCM			= 1,
		fmtFloat		= 3,
		fmtALaw			= 6,
		fmtULaw			= 7,
		fmtIMA_ADPCM	= 17,
		fmtMP3			= 85,
		fmtExtensible	= 0xFFFE,
	};

	uint16le format;			// Sample format, see SampleFormats
	uint16le numChannels;		// Number of audio channels
	uint32le sampleRate;		// Sample rate in Hz
	uint32le byteRate;			// Bytes per second (should be freqHz * blockAlign)
	uint16le blockAlign;		// Size of a sample, in bytes (do not trust this value, it's incorrect in some files)
	uint16le bitsPerSample;		// Bits per sample
};

MPT_BINARY_STRUCT(WAVFormatChunk, 16)


// Extension of the WAVFormatChunk structure, used if format == formatExtensible
struct WAVFormatChunkExtension
{
	uint16le size;
	uint16le validBitsPerSample;
	uint32le channelMask;
	GUIDms   subFormat;
};

MPT_BINARY_STRUCT(WAVFormatChunkExtension, 24)


// Sample information chunk
struct WAVSampleInfoChunk
{
	uint32le manufacturer;
	uint32le product;
	uint32le samplePeriod;	// 1000000000 / sampleRate
	uint32le baseNote;		// MIDI base note of sample
	uint32le pitchFraction;
	uint32le SMPTEFormat;
	uint32le SMPTEOffset;
	uint32le numLoops;		// number of loops
	uint32le samplerData;

	// Set up information
	void ConvertToWAV(uint32 freq, uint8 rootNote)
	{
		manufacturer = 0;
		product = 0;
		samplePeriod = 1000000000 / freq;
		if(rootNote != 0)
			baseNote = rootNote - NOTE_MIN;
		else
			baseNote = NOTE_MIDDLEC - NOTE_MIN;
		pitchFraction = 0;
		SMPTEFormat = 0;
		SMPTEOffset = 0;
		numLoops = 0;
		samplerData = 0;
	}
};

MPT_BINARY_STRUCT(WAVSampleInfoChunk, 36)


// Sample loop information chunk (found after WAVSampleInfoChunk in "smpl" chunk)
struct WAVSampleLoop
{
	// Sample Loop Types
	enum LoopType
	{
		loopForward		= 0,
		loopBidi		= 1,
		loopBackward	= 2,
	};

	uint32le identifier;
	uint32le loopType;		// See LoopType
	uint32le loopStart;		// Loop start in samples
	uint32le loopEnd;		// Loop end in samples
	uint32le fraction;
	uint32le playCount;		// Loop Count, 0 = infinite

	// Apply WAV loop information to a mod sample.
	void ApplyToSample(SmpLength &start, SmpLength &end, SmpLength sampleLength, SampleFlags &flags, ChannelFlags enableFlag, ChannelFlags bidiFlag, bool mptLoopFix) const;

	// Convert internal loop information into a WAV loop.
	void ConvertToWAV(SmpLength start, SmpLength end, bool bidi);
};

MPT_BINARY_STRUCT(WAVSampleLoop, 24)


// Instrument information chunk
struct WAVInstrumentChunk
{
	uint8 unshiftedNote;	// Root key of sample, 0...127
	int8  finetune;			// Finetune of root key in cents
	int8  gain;				// in dB
	uint8 lowNote;			// Note range, 0...127
	uint8 highNote;
	uint8 lowVelocity;		// Velocity range, 0...127
	uint8 highVelocity;
};

MPT_BINARY_STRUCT(WAVInstrumentChunk, 7)


// MPT-specific "xtra" chunk
struct WAVExtraChunk
{
	enum Flags
	{
		setPanning	= 0x20,
	};

	uint32le flags;
	uint16le defaultPan;
	uint16le defaultVolume;
	uint16le globalVolume;
	uint16le reserved;
	uint8le  vibratoType;
	uint8le  vibratoSweep;
	uint8le  vibratoDepth;
	uint8le  vibratoRate;

	// Set up sample information
	void ConvertToWAV(const ModSample &sample, MODTYPE modType)
	{
		if(sample.uFlags[CHN_PANNING])
		{
			flags = WAVExtraChunk::setPanning;
		} else
		{
			flags = 0;
		}

		defaultPan = sample.nPan;
		defaultVolume = sample.nVolume;
		globalVolume = sample.nGlobalVol;
		vibratoType = sample.nVibType;
		vibratoSweep = sample.nVibSweep;
		vibratoDepth = sample.nVibDepth;
		vibratoRate = sample.nVibRate;

		if((modType & MOD_TYPE_XM) && (vibratoDepth | vibratoRate))
		{
			// XM vibrato is upside down
			vibratoSweep = 255 - vibratoSweep;
		}
	}
};

MPT_BINARY_STRUCT(WAVExtraChunk, 16)


// Sample cue point structure for the "cue " chunk
struct WAVCuePoint
{
	uint32le id;			// Unique identification value
	uint32le position;		// Play order position
	uint32le riffChunkID;	// RIFF ID of corresponding data chunk
	uint32le chunkStart;	// Byte Offset of Data Chunk
	uint32le blockStart;	// Byte Offset to sample of First Channel
	uint32le offset;		// Byte Offset to sample byte of First Channel

	// Set up sample information
	void ConvertToWAV(uint32 id_, SmpLength offset_)
	{
		id = id_;
		position = offset_;
		riffChunkID = static_cast<uint32>(RIFFChunk::iddata);
		chunkStart = 0;	// we use no Wave List Chunk (wavl) as we have only one data block, so this should be 0.
		blockStart = 0;	// ditto
		offset = offset_;
	}
};

MPT_BINARY_STRUCT(WAVCuePoint, 24)


class WAVReader
{
protected:
	ChunkReader file;
	FileReader sampleData, smplChunk, instChunk, xtraChunk, wsmpChunk, cueChunk;
	ChunkReader::ChunkList<RIFFChunk> infoChunk;

	FileReader::off_t sampleLength;
	WAVFormatChunk formatInfo;
	uint16 subFormat;
	uint16 codePage;
	bool isDLS;
	bool mayBeCoolEdit16_8;

	uint16 GetFileCodePage(ChunkReader::ChunkList<RIFFChunk> &chunks);

public:
	WAVReader(FileReader &inputFile);

	bool IsValid() const { return sampleData.IsValid(); }

	void FindMetadataChunks(ChunkReader::ChunkList<RIFFChunk> &chunks);

	// Self-explanatory getters.
	WAVFormatChunk::SampleFormats GetSampleFormat() const { return IsExtensibleFormat() ? static_cast<WAVFormatChunk::SampleFormats>(subFormat) : static_cast<WAVFormatChunk::SampleFormats>(formatInfo.format.get()); }
	uint16 GetNumChannels() const { return formatInfo.numChannels; }
	uint16 GetBitsPerSample() const { return formatInfo.bitsPerSample; }
	uint32 GetSampleRate() const { return formatInfo.sampleRate; }
	uint16 GetBlockAlign() const { return formatInfo.blockAlign; }
	FileReader GetSampleData() const { return sampleData; }
	FileReader GetWsmpChunk() const { return wsmpChunk; }
	bool IsExtensibleFormat() const { return formatInfo.format == WAVFormatChunk::fmtExtensible; }
	bool MayBeCoolEdit16_8() const { return mayBeCoolEdit16_8; }

	// Get size of a single sample point, in bytes.
	uint16 GetSampleSize() const { return ((GetNumChannels() * GetBitsPerSample()) + 7) / 8; }

	// Get sample length (in samples)
	SmpLength GetSampleLength() const { return mpt::saturate_cast<SmpLength>(sampleLength); }

	// Apply sample settings from file (loop points, MPT extra settings, ...) to a sample.
	void ApplySampleSettings(ModSample &sample, mpt::Charset charset, char (&sampleCharset)[MAX_SAMPLENAME]);
};


#ifndef MODPLUG_NO_FILESAVE

class WAVWriter
{
protected:
	// When writing to a stream: Stream pointer
	std::ostream *s = nullptr;
	// When writing to memory: Memory address + length
	mpt::byte_span memory;

	// Cursor position
	size_t position = 0;
	// Total number of bytes written to file / memory
	size_t totalSize = 0;

	// Currently written chunk
	size_t chunkStartPos = 0;
	RIFFChunk chunkHeader;

public:
	// Output to stream: Initialize with std::ostream*.
	WAVWriter(std::ostream *stream);
	// Output to clipboard: Initialize with pointer to memory and size of reserved memory.
	WAVWriter(mpt::byte_span data);

	~WAVWriter() noexcept(false);

	// Check if anything can be written to the file.
	bool IsValid() const { return s != nullptr || !memory.empty(); }

	// Finalize the file by closing the last open chunk and updating the file header. Returns total size of file.
	size_t Finalize();
	// Begin writing a new chunk to the file.
	void StartChunk(RIFFChunk::ChunkIdentifiers id);

	// Skip some bytes... For example after writing sample data.
	void Skip(size_t numBytes) { Seek(position + numBytes); }
	// Get position in file (not counting any changes done to the file from outside this class, i.e. through GetFile())
	size_t GetPosition() const { return position; }

	// Shrink file size to current position.
	void Truncate() { totalSize = position; }

	// Write some data to the file.
	template<typename T>
	void Write(const T &data)
	{
		MPT_STATIC_ASSERT((mpt::is_binary_safe<T>::value));
		Write(&data, sizeof(T));
	}

	// Write a buffer to the file.
	void WriteBuffer(const char *data, size_t size)
	{
		Write(data, size);
	}

	// Write an array to the file.
	template<typename T, size_t size>
	void WriteArray(const T (&data)[size])
	{
		Write(data, sizeof(T) * size);
	}

	// Write the WAV format to the file.
	void WriteFormat(uint32 sampleRate, uint16 bitDepth, uint16 numChannels, WAVFormatChunk::SampleFormats encoding);
	// Write text tags to the file.
	void WriteMetatags(const FileTags &tags);
	// Write a sample loop information chunk to the file.
	void WriteLoopInformation(const ModSample &sample);
	// Write a sample's cue points to the file.
	void WriteCueInformation(const ModSample &sample);
	// Write MPT's sample information chunk to the file.
	void WriteExtraInformation(const ModSample &sample, MODTYPE modType, const char *sampleName = nullptr);

protected:
	// Seek to a position in file.
	void Seek(size_t pos);
	// End current chunk by updating the chunk header and writing a padding byte if necessary.
	void FinalizeChunk();

	// Write some data to the file.
	void Write(const void *data, size_t numBytes);

	// Write a single tag into a open idLIST chunk
	void WriteTag(RIFFChunk::ChunkIdentifiers id, const mpt::ustring &utext);
};

#endif // MODPLUG_NO_FILESAVE

OPENMPT_NAMESPACE_END