/* * 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(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(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 infoChunk; FileReader::off_t sampleLength; WAVFormatChunk formatInfo; uint16 subFormat; uint16 codePage; bool isDLS; bool mayBeCoolEdit16_8; uint16 GetFileCodePage(ChunkReader::ChunkList &chunks); public: WAVReader(FileReader &inputFile); bool IsValid() const { return sampleData.IsValid(); } void FindMetadataChunks(ChunkReader::ChunkList &chunks); // Self-explanatory getters. WAVFormatChunk::SampleFormats GetSampleFormat() const { return IsExtensibleFormat() ? static_cast(subFormat) : static_cast(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(sampleLength); } // Apply sample settings from file (loop points, MPT extra settings, ...) to a sample. void ApplySampleSettings(ModSample &sample, mpt::Charset sampleCharset, mpt::charbuf &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 void Write(const T &data) { static_assert((mpt::is_binary_safe::value)); Write(&data, sizeof(T)); } // Write a buffer to the file. void WriteBuffer(const std::byte *data, size_t size) { Write(data, size); } // Write an array to the file. template 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