#include #include "tmachine.h" #include "tsio_wav.h" #include "tsystem.h" #include "tfilepath_io.h" #include "tsop.h" using namespace std; #if !defined(TNZ_LITTLE_ENDIAN) TNZ_LITTLE_ENDIAN undefined !! #endif //------------------------------------------------------------------------------ void swapAndCopySamples(short *srcBuffer, short *dstBuffer, TINT32 sampleCount); //============================================================================== // TWAVChunk: classe base per i vari chunk WAV class TWAVChunk { public: static TINT32 HDR_LENGTH; string m_name; TINT32 m_length; // lunghezza del chunk in byte TWAVChunk(string name, TINT32 length) : m_name(name), m_length(length) {} virtual ~TWAVChunk() {} virtual bool read(Tifstream &is) { skip(is); return true; } void skip(Tifstream &is) { is.seekg(m_length, ios::cur); } static bool readHeader(Tifstream &is, string &name, TINT32 &length) { char cName[5]; TINT32 len = 0; memset(cName, 0, sizeof(cName)); is.read(cName, 4); if (is.fail()) return false; cName[4] = '\0'; is.read((char *)&len, sizeof(len)); if (is.fail()) return false; // il formato WAV memorizza i dati come little-endian // se la piattaforma non e' little-endian bisogna scambiare i byte if (!TNZ_LITTLE_ENDIAN) len = swapTINT32(len); name = string(cName); length = len; return true; } }; TINT32 TWAVChunk::HDR_LENGTH = 8; //==================================================================== // FMT Chunk: Chunk contenente le informazioni sulla traccia class TFMTChunk final : public TWAVChunk { public: static TINT32 LENGTH; USHORT m_encodingType; // PCM, ... USHORT m_chans; // numero di canali TUINT32 m_sampleRate; TUINT32 m_avgBytesPerSecond; USHORT m_bytesPerSample; USHORT m_bitPerSample; TFMTChunk(TINT32 length) : TWAVChunk("fmt ", length) {} bool read(Tifstream &is) override { is.read((char *)&m_encodingType, sizeof(m_encodingType)); is.read((char *)&m_chans, sizeof(m_chans)); is.read((char *)&m_sampleRate, sizeof(m_sampleRate)); is.read((char *)&m_avgBytesPerSecond, sizeof(m_avgBytesPerSecond)); is.read((char *)&m_bytesPerSample, sizeof(m_bytesPerSample)); is.read((char *)&m_bitPerSample, sizeof(m_bitPerSample)); if (!TNZ_LITTLE_ENDIAN) { m_encodingType = swapUshort(m_encodingType); m_chans = swapUshort(m_chans); m_sampleRate = swapTINT32(m_sampleRate); m_avgBytesPerSecond = swapTINT32(m_avgBytesPerSecond); m_bytesPerSample = swapUshort(m_bytesPerSample); m_bitPerSample = swapUshort(m_bitPerSample); } assert(m_length >= 16); if (m_length > 16) is.seekg((long)is.tellg() + m_length - 16); return true; } bool write(ofstream &os) { TUINT32 length = m_length; USHORT type = m_encodingType; USHORT chans = m_chans; TUINT32 sampleRate = m_sampleRate; TUINT32 bytesPerSecond = m_avgBytesPerSecond; USHORT bytesPerSample = m_bytesPerSample; USHORT bitPerSample = m_bitPerSample; if (!TNZ_LITTLE_ENDIAN) { length = swapTINT32(length); type = swapUshort(type); chans = swapUshort(chans); sampleRate = swapTINT32(sampleRate); bytesPerSecond = swapTINT32(bytesPerSecond); bytesPerSample = swapUshort(bytesPerSample); bitPerSample = swapUshort(bitPerSample); } os.write((char *)"fmt ", 4); os.write((char *)&length, sizeof(length)); os.write((char *)&type, sizeof(type)); os.write((char *)&chans, sizeof(chans)); os.write((char *)&sampleRate, sizeof(sampleRate)); os.write((char *)&bytesPerSecond, sizeof(bytesPerSecond)); os.write((char *)&bytesPerSample, sizeof(bytesPerSample)); os.write((char *)&bitPerSample, sizeof(bitPerSample)); return true; } }; TINT32 TFMTChunk::LENGTH = TWAVChunk::HDR_LENGTH + 16; //==================================================================== // DATA Chunk: Chunk contenente i campioni class TDATAChunk final : public TWAVChunk { public: std::unique_ptr m_samples; TDATAChunk(TINT32 length) : TWAVChunk("data", length) {} bool read(Tifstream &is) override { // alloca il buffer dei campioni m_samples.reset(new UCHAR[m_length]); if (!m_samples) return false; is.read((char *)m_samples.get(), m_length); return true; } bool write(ofstream &os) { TINT32 length = m_length; if (!TNZ_LITTLE_ENDIAN) { length = swapTINT32(length); } os.write((char *)"data", 4); os.write((char *)&length, sizeof(length)); os.write((char *)m_samples.get(), m_length); return true; } }; //============================================================================== TSoundTrackReaderWav::TSoundTrackReaderWav(const TFilePath &fp) : TSoundTrackReader(fp) {} //------------------------------------------------------------------------------ TSoundTrackP TSoundTrackReaderWav::load() { char chunkName[5]; char RIFFType[5]; TINT32 chunkLength; Tifstream is(m_path); if (!is) throw TException(m_path.getWideString() + L" : File doesn't exist"); // legge il nome del chunk is.read((char *)&chunkName, sizeof(chunkName) - 1); chunkName[4] = '\0'; // legge la lunghezza del chunk is.read((char *)&chunkLength, sizeof(chunkLength)); if (!TNZ_LITTLE_ENDIAN) chunkLength = swapTINT32(chunkLength); // legge il RIFFType is.read((char *)&RIFFType, sizeof(RIFFType) - 1); RIFFType[4] = '\0'; // per i .wav il RIFFType DEVE essere uguale a "WAVE" if ((string(RIFFType, 4) != "WAVE")) throw TException("The WAV file doesn't contain the WAVE form"); TFMTChunk *fmtChunk = 0; TDATAChunk *dataChunk = 0; while (!is.eof()) { string name = ""; TINT32 length = 0; bool ret = TWAVChunk::readHeader(is, name, length); if (!ret) break; // legge solo i chunk che ci interessano, ossia FMT e DATA if (name == "fmt ") { // legge i dati del chunk FMT fmtChunk = new TFMTChunk(length); fmtChunk->read(is); // considera il byte di pad alla fine del chunk nel caso // in cui la lunghezza di questi e' dispari if (length & 1) { is.seekg((long)is.tellg() + 1); } } else if (name == "data") { // legge i dati del chunk DATA dataChunk = new TDATAChunk(length); dataChunk->read(is); // considera il byte di pad alla fine del chunk nel caso // in cui la lunghezza di questi e' dispari if (length & 1) { is.seekg((long)is.tellg() + 1); } } else { // spostati nello stream di un numero di byte pari a length if (length & 1) is.seekg((long)is.tellg() + (long)length + 1); else is.seekg((long)is.tellg() + (long)length); } } TSoundTrackP track = 0; if (fmtChunk && dataChunk) { TINT32 sampleCount = dataChunk->m_length / fmtChunk->m_bytesPerSample; bool signedSample = (fmtChunk->m_bitPerSample != 8); track = TSoundTrack::create( (int)fmtChunk->m_sampleRate, fmtChunk->m_bitPerSample, fmtChunk->m_chans, sampleCount, signedSample, fmtChunk->m_encodingType); if (track) { switch (fmtChunk->m_bitPerSample) { case 8: memcpy((void *)track->getRawData(), (void *)(dataChunk->m_samples.get()), sampleCount * fmtChunk->m_bytesPerSample); break; case 16: if (!TNZ_LITTLE_ENDIAN) swapAndCopySamples((short *)dataChunk->m_samples.get(), (short *)track->getRawData(), sampleCount * fmtChunk->m_chans); else memcpy((void *)track->getRawData(), (void *)(dataChunk->m_samples.get()), sampleCount * fmtChunk->m_bytesPerSample); //#endif break; case 24: // NOTE: This effectively changes from 24bit to 32bit bitPerSample if (!TNZ_LITTLE_ENDIAN) { UCHAR *begin = (UCHAR *)track->getRawData(); for (int i = 0; i < (int)(sampleCount * fmtChunk->m_chans); ++i) { *(begin + 4 * i) = 0; *(begin + 4 * i + 1) = *(dataChunk->m_samples.get() + 3 * i + 2); *(begin + 4 * i + 2) = *(dataChunk->m_samples.get() + 3 * i + 1); *(begin + 4 * i + 3) = *(dataChunk->m_samples.get() + 3 * i); } } else { UCHAR *begin = (UCHAR *)track->getRawData(); for (int i = 0; i < (int)(sampleCount * fmtChunk->m_chans); ++i) { memcpy((void *)(begin + 4 * i), (void *)(dataChunk->m_samples.get() + 3 * i), 3); *(begin + 4 * i + 3) = 0; } } //#endif break; case 32: if (!TNZ_LITTLE_ENDIAN) { swapAndCopySamples((short *)dataChunk->m_samples.get(), (short *)track->getRawData(), sampleCount * fmtChunk->m_chans); } else { memcpy((void *)track->getRawData(), (void *)(dataChunk->m_samples.get()), sampleCount * fmtChunk->m_bytesPerSample); } break; } // Convert all WAV to 32bit PCM if (fmtChunk->m_bitPerSample != 32 || fmtChunk->m_encodingType != WAVE_FORMAT_PCM) { TSoundTrackP origTrack = track; TSoundTrackFormat fmt = track->getFormat(); fmt.m_bitPerSample = 32; fmt.m_formatType = WAVE_FORMAT_PCM; track = TSop::convert(origTrack, fmt); } } /*if (!TNZ_LITTLE_ENDIAN) { if (fmtChunk->m_bitPerSample > 8) { assert(fmtChunk->m_bitPerSample <= 16); swapAndCopySamples( (short*)dataChunk->m_samples, (short*)track->getRawData(), sampleCount*fmtChunk->m_chans); } else memcpy( (void*)track->getRawData(), (void*)(dataChunk->m_samples), sampleCount*fmtChunk->m_bytesPerSample); } else memcpy( (void*)track->getRawData(), (void*)(dataChunk->m_samples), sampleCount*fmtChunk->m_bytesPerSample);*/ } if (fmtChunk) delete fmtChunk; if (dataChunk) delete dataChunk; return track; } //============================================================================== TSoundTrackWriterWav::TSoundTrackWriterWav(const TFilePath &fp) : TSoundTrackWriter(fp) {} //------------------------------------------------------------------------------ bool TSoundTrackWriterWav::save(const TSoundTrackP &sndtrack) { if (!sndtrack) throw TException(L"Unable to save the soundtrack: " + m_path.getWideString()); if (sndtrack->getBitPerSample() == 8 && sndtrack->isSampleSigned()) throw TException("The format (8 bit signed) is incompatible with WAV file"); TINT32 soundDataLength = (TINT32)(sndtrack->getSampleCount() * (sndtrack->getBitPerSample() / 8) * sndtrack->getChannelCount() /*sndtrack->getSampleSize()*/); TINT32 RIFFChunkLength = TFMTChunk::LENGTH + TWAVChunk::HDR_LENGTH + soundDataLength; TFileStatus fs(m_path); if (fs.doesExist() && !fs.isWritable()) throw TException(L"Unable to save the soundtrack: " + m_path.getWideString() + L" is read-only"); Tofstream os(m_path); TFMTChunk fmtChunk(16); fmtChunk.m_encodingType = 1; // PCM fmtChunk.m_chans = sndtrack->getChannelCount(); fmtChunk.m_sampleRate = sndtrack->getSampleRate(); fmtChunk.m_avgBytesPerSecond = (sndtrack->getBitPerSample() / 8) * fmtChunk.m_chans * sndtrack->getSampleRate(); // sndtrack->getSampleSize()*sndtrack->getSampleRate(); fmtChunk.m_bytesPerSample = (sndtrack->getBitPerSample() / 8) * fmtChunk.m_chans; // sndtrack->getSampleSize(); fmtChunk.m_bitPerSample = sndtrack->getBitPerSample(); TDATAChunk dataChunk(soundDataLength); std::unique_ptr waveData(new UCHAR[soundDataLength]); if (!TNZ_LITTLE_ENDIAN) RIFFChunkLength = swapTINT32(RIFFChunkLength); // era if defined(MACOSX) #if (!TNZ_LITTLE_ENDIAN) { if (fmtChunk.m_bitPerSample == 8) memcpy((void *)waveData.get(), (void *)sndtrack->getRawData(), soundDataLength); else if (fmtChunk.m_bitPerSample == 16) { swapAndCopySamples((short *)sndtrack->getRawData(), (short *)waveData.get(), sndtrack->getSampleCount() * fmtChunk.m_chans); } else if (fmtChunk.m_bitPerSample == 24) { // swap e togliere quarto byte UCHAR *begin = (UCHAR *)sndtrack->getRawData(); for (int i = 0; i < (int)sndtrack->getSampleCount() * fmtChunk.m_chans; ++i) { *(waveData.get() + 3 * i) = *(begin + 4 * i + 3); *(waveData.get() + 3 * i + 1) = *(begin + 4 * i + 2); *(waveData.get() + 3 * i + 2) = *(begin + 4 * i + 1); } } } #else { if (fmtChunk.m_bitPerSample != 24) memcpy((void *)waveData.get(), (void *)sndtrack->getRawData(), soundDataLength); else { // togliere quarto byte UCHAR *begin = (UCHAR *)sndtrack->getRawData(); for (int i = 0; i < (int)sndtrack->getSampleCount() * fmtChunk.m_chans; ++i) { *(waveData.get() + 3 * i) = *(begin + 4 * i); *(waveData.get() + 3 * i + 1) = *(begin + 4 * i + 1); *(waveData.get() + 3 * i + 2) = *(begin + 4 * i + 2); } } } #endif dataChunk.m_samples = std::move(waveData); os.write("RIFF", 4); os.write((char *)&RIFFChunkLength, sizeof(TINT32)); os.write("WAVE", 4); fmtChunk.write(os); dataChunk.write(os); return true; }