| |
| |
| #include "tsio_aiff.h" |
| #include "tmachine.h" |
| #include "tsound_t.h" |
| #include "tsystem.h" |
| #include "tfilepath_io.h" |
| #include "tsioutils.h" |
| #include <math.h> |
| |
| #define DEFAULT_OFFSET 0 |
| #define DEFAULT_BLOCKSIZE 0 |
| #define AIFF_NBYTE 4 |
| #define COMM_NBYTE 24 |
| #define OFFSETBLOCSIZE_NBYTE 8 |
| #define SSND_PREDATA_NBYTE 16 |
| |
| #if !defined(TNZ_LITTLE_ENDIAN) |
| TNZ_LITTLE_ENDIAN undefined !! |
| #endif |
| |
| using namespace std; |
| |
| TUINT32 convertToLong(UCHAR *buffer); |
| void storeFloat(unsigned char *buffer, TUINT32 value); |
| |
| |
| |
| |
| |
| class TAIFFChunk { |
| public: |
| string m_name; |
| TINT32 m_length; |
| |
| TAIFFChunk(string name, TINT32 length) : m_name(name), m_length(length) {} |
| |
| virtual ~TAIFFChunk() {} |
| |
| virtual bool read(ifstream &is) { |
| skip(is); |
| return true; |
| } |
| |
| void skip(ifstream &is) { is.seekg((TINT32)is.tellg() + (TINT32)m_length); } |
| |
| static bool readHeader(ifstream &is, string &name, TINT32 &length) { |
| char cName[5]; |
| TINT32 len; |
| |
| is.read((char *)&cName, 4); |
| if (is.fail()) return false; |
| cName[4] = '\0'; |
| |
| is.read((char *)&len, sizeof(len)); |
| |
| if (TNZ_LITTLE_ENDIAN) len = swapTINT32(len); |
| |
| if (is.fail()) return false; |
| |
| name = string(cName); |
| length = len; |
| return true; |
| } |
| }; |
| |
| |
| |
| |
| |
| class TCOMMChunk final : public TAIFFChunk { |
| public: |
| USHORT m_chans; |
| TUINT32 m_frames; |
| USHORT m_bitPerSample; |
| TUINT32 m_sampleRate; |
| |
| TCOMMChunk(string name, TINT32 length) : TAIFFChunk(name, length) {} |
| |
| bool read(ifstream &is) override { |
| is.read((char *)&m_chans, sizeof(m_chans)); |
| is.read((char *)&m_frames, sizeof(m_frames)); |
| is.read((char *)&m_bitPerSample, sizeof(m_bitPerSample)); |
| |
| if (TNZ_LITTLE_ENDIAN) { |
| m_chans = swapUshort(m_chans); |
| m_frames = swapTINT32(m_frames); |
| m_bitPerSample = swapUshort(m_bitPerSample); |
| } |
| |
| UCHAR sampleRateBuffer[10]; |
| memset(sampleRateBuffer, 0, 10); |
| is.read((char *)&sampleRateBuffer, sizeof(sampleRateBuffer)); |
| m_sampleRate = convertToLong(sampleRateBuffer); |
| return true; |
| } |
| |
| bool write(ofstream &os) { |
| TINT32 length = m_length; |
| USHORT chans = m_chans; |
| TUINT32 frames = m_frames; |
| USHORT bitPerSample = m_bitPerSample; |
| TUINT32 sampleRate = m_sampleRate; |
| |
| if (TNZ_LITTLE_ENDIAN) { |
| length = swapTINT32(length); |
| chans = swapUshort(chans); |
| frames = swapTINT32(frames); |
| bitPerSample = swapUshort(bitPerSample); |
| } |
| |
| UCHAR sampleRateBuffer[10]; |
| storeFloat(sampleRateBuffer, sampleRate); |
| |
| |
| |
| os.write((char *)"COMM", 4); |
| os.write((char *)&length, sizeof(TINT32)); |
| os.write((char *)&chans, sizeof(short)); |
| os.write((char *)&frames, sizeof(TINT32)); |
| os.write((char *)&bitPerSample, sizeof(short)); |
| os.write((char *)&sampleRateBuffer, sizeof(sampleRateBuffer)); |
| |
| return true; |
| } |
| |
| virtual void print(ostream &os) const { |
| os << "canali = '" << m_chans << endl; |
| os << "frames = '" << (unsigned int)m_frames << endl; |
| os << "bitxsam = '" << m_bitPerSample << endl; |
| os << "rate = '" << (unsigned int)m_sampleRate << endl; |
| } |
| }; |
| |
| |
| |
| static ostream &operator<<(ostream &os, const TCOMMChunk &commChunk) { |
| commChunk.print(os); |
| return os; |
| } |
| |
| |
| |
| |
| |
| class TSSNDChunk final : public TAIFFChunk { |
| public: |
| TUINT32 m_offset; |
| TUINT32 m_blockSize; |
| std::unique_ptr<UCHAR[]> m_waveData; |
| |
| TSSNDChunk(string name, TINT32 length) : TAIFFChunk(name, length) {} |
| |
| bool read(ifstream &is) override { |
| is.read((char *)&m_offset, sizeof(m_offset)); |
| is.read((char *)&m_blockSize, sizeof(m_blockSize)); |
| |
| if (TNZ_LITTLE_ENDIAN) { |
| m_offset = swapTINT32(m_offset); |
| m_blockSize = swapTINT32(m_blockSize); |
| } |
| |
| |
| m_waveData.reset(new UCHAR[m_length - OFFSETBLOCSIZE_NBYTE]); |
| if (!m_waveData) cout << " ERRORE " << endl; |
| is.read((char *)m_waveData.get(), m_length - OFFSETBLOCSIZE_NBYTE); |
| return true; |
| } |
| |
| bool write(ofstream &os) { |
| TINT32 length = m_length; |
| TUINT32 offset = m_offset; |
| TUINT32 blockSize = m_blockSize; |
| |
| if (TNZ_LITTLE_ENDIAN) { |
| length = swapTINT32(length); |
| offset = swapTINT32(offset); |
| blockSize = swapTINT32(blockSize); |
| } |
| |
| os.write((char *)"SSND", 4); |
| os.write((char *)&length, sizeof(TINT32)); |
| os.write((char *)&offset, sizeof(TINT32)); |
| os.write((char *)&blockSize, sizeof(TINT32)); |
| os.write((char *)m_waveData.get(), m_length - OFFSETBLOCSIZE_NBYTE); |
| return true; |
| } |
| }; |
| |
| |
| |
| static ostream &operator<<(ostream &os, const TSSNDChunk &ssndChunk) { |
| os << "name = '" << ssndChunk.m_name << endl; |
| os << "length = '" << ssndChunk.m_length << endl; |
| os << "offset = '" << (unsigned int)ssndChunk.m_offset << endl; |
| os << "blocksize = '" << (unsigned int)ssndChunk.m_blockSize << endl; |
| |
| #ifdef PRINT_SAMPLES |
| os << " samples" << endl; |
| for (int i = 0; i < ((ssndChunk.m_length - 8) / 2); ++i) |
| os << i << ((short *)*(ssndChunk.m_waveData + i)) << dec << endl; |
| #endif |
| |
| return os; |
| } |
| |
| |
| |
| static void flipLong(unsigned char *ptrc) { |
| unsigned char val; |
| |
| val = *(ptrc); |
| *(ptrc) = *(ptrc + 3); |
| *(ptrc + 3) = val; |
| ptrc += 1; |
| val = *(ptrc); |
| *(ptrc) = *(ptrc + 1); |
| *(ptrc + 1) = val; |
| } |
| |
| |
| |
| static TUINT32 fetchLong(TUINT32 *ptrl) { return (*ptrl); } |
| |
| |
| |
| TUINT32 convertToLong(UCHAR *buffer) { |
| TUINT32 mantissa; |
| TUINT32 last = 0; |
| UCHAR exp; |
| |
| if (TNZ_LITTLE_ENDIAN) { |
| |
| flipLong(buffer + 2); |
| } |
| |
| mantissa = *((TUINT32 *)(buffer + 2)); |
| exp = 30 - *(buffer + 1); |
| while (exp--) { |
| last = mantissa; |
| mantissa >>= 1; |
| } |
| |
| if (last & 0x00000001) mantissa++; |
| return (mantissa); |
| } |
| |
| |
| |
| static void storeLong(TUINT32 val, TUINT32 *ptr) { *ptr = val; } |
| |
| |
| |
| void storeFloat(unsigned char *buffer, TUINT32 value) { |
| TUINT32 exp; |
| unsigned char i; |
| |
| memset(buffer, 0, 10); |
| exp = value; |
| exp >>= 1; |
| for (i = 0; i < 32; i++) { |
| exp >>= 1; |
| if (!exp) break; |
| } |
| *(buffer + 1) = i; |
| |
| for (i = 32; i; i--) { |
| if (value & 0x80000000) break; |
| value <<= 1; |
| } |
| |
| *((TUINT32 *)(buffer + 2)) = value; |
| |
| buffer[0] = 0x40; |
| |
| if (TNZ_LITTLE_ENDIAN) { |
| |
| flipLong(buffer + 2); |
| } |
| } |
| |
| |
| |
| TSoundTrackReaderAiff::TSoundTrackReaderAiff(const TFilePath &fp) |
| : TSoundTrackReader(fp) {} |
| |
| |
| |
| TSoundTrackP TSoundTrackReaderAiff::load() { |
| char ckID[5]; |
| char formType[5]; |
| TINT32 ckSize; |
| |
| Tifstream is(m_path); |
| |
| if (!is) |
| throw TException(L"Unable to load the AIFF file " + m_path.getWideString() + |
| L" : doesn't exist"); |
| |
| |
| is.read((char *)&ckID, sizeof(ckID) - 1); |
| ckID[4] = '\0'; |
| |
| |
| is.read((char *)&ckSize, sizeof(ckSize)); |
| |
| if (TNZ_LITTLE_ENDIAN) ckSize = swapTINT32(ckSize); |
| |
| |
| is.read((char *)&formType, sizeof(formType) - 1); |
| formType[4] = '\0'; |
| |
| |
| if ((string(formType, 4) != "AIFF")) |
| throw TException("The AIFF file doesn't contain the AIFF form"); |
| |
| TCOMMChunk *commChunk = 0; |
| TSSNDChunk *ssndChunk = 0; |
| |
| while (!is.eof()) { |
| string name; |
| TINT32 length; |
| |
| bool ret = TAIFFChunk::readHeader(is, name, length); |
| |
| if (!ret) break; |
| |
| |
| |
| if (name == "COMM") { |
| |
| commChunk = new TCOMMChunk("COMM", length); |
| commChunk->read(is); |
| |
| |
| |
| if (length % 2) is.seekg((TINT32)is.tellg() + 1); |
| } else if (name == "SSND") { |
| |
| ssndChunk = new TSSNDChunk("SSND", length); |
| ssndChunk->read(is); |
| |
| |
| |
| if (length % 2) is.seekg((TINT32)is.tellg() + 1); |
| } else { |
| |
| if (!(length % 2)) |
| is.seekg((TINT32)is.tellg() + length); |
| else |
| is.seekg((TINT32)is.tellg() + (TINT32)length + 1); |
| } |
| } |
| |
| TSoundTrack *track = 0; |
| |
| if (commChunk && ssndChunk) { |
| if (commChunk->m_chans < 1) |
| throw TException("Invalid channels number in sound file"); |
| |
| if (commChunk->m_chans > 2) |
| throw TException("Unsupported channels number in sound file"); |
| |
| switch (commChunk->m_bitPerSample) { |
| case 8: |
| if (commChunk->m_chans == 1) |
| track = new TSoundTrackMono8Signed(commChunk->m_sampleRate, 1, |
| (TINT32)commChunk->m_frames); |
| else |
| track = new TSoundTrackStereo8Signed(commChunk->m_sampleRate, 2, |
| (TINT32)commChunk->m_frames); |
| |
| memcpy((void *)track->getRawData(), |
| (void *)(ssndChunk->m_waveData.get() + ssndChunk->m_offset), |
| commChunk->m_frames * commChunk->m_chans); |
| break; |
| |
| case 16: |
| if (commChunk->m_chans == 1) |
| track = new TSoundTrackMono16(commChunk->m_sampleRate, 1, |
| (TINT32)commChunk->m_frames); |
| else |
| track = new TSoundTrackStereo16(commChunk->m_sampleRate, 2, |
| (TINT32)commChunk->m_frames); |
| |
| if (!TNZ_LITTLE_ENDIAN) |
| memcpy((void *)track->getRawData(), |
| (void *)(ssndChunk->m_waveData.get() + ssndChunk->m_offset), |
| commChunk->m_frames * track->getSampleSize()); |
| else |
| swapAndCopySamples( |
| (short *)(ssndChunk->m_waveData.get() + ssndChunk->m_offset), |
| (short *)track->getRawData(), |
| (TINT32)(commChunk->m_frames * commChunk->m_chans)); |
| break; |
| |
| case 24: |
| if (commChunk->m_chans == 1) |
| track = new TSoundTrackMono24(commChunk->m_sampleRate, 1, |
| (TINT32)commChunk->m_frames); |
| else |
| track = new TSoundTrackStereo24(commChunk->m_sampleRate, 2, |
| (TINT32)commChunk->m_frames); |
| |
| if (!TNZ_LITTLE_ENDIAN) { |
| UCHAR *begin = (UCHAR *)track->getRawData(); |
| for (int i = 0; i < (int)(commChunk->m_frames * commChunk->m_chans); |
| ++i) { |
| *(begin + 4 * i) = 0; |
| *(begin + 4 * i + 1) = |
| *(ssndChunk->m_waveData.get() + ssndChunk->m_offset + 3 * i); |
| *(begin + 4 * i + 2) = |
| *(ssndChunk->m_waveData.get() + ssndChunk->m_offset + 3 * i + 1); |
| *(begin + 4 * i + 3) = |
| *(ssndChunk->m_waveData.get() + ssndChunk->m_offset + 3 * i + 2); |
| } |
| } else { |
| UCHAR *begin = (UCHAR *)track->getRawData(); |
| for (int i = 0; i < (int)(commChunk->m_frames * commChunk->m_chans); |
| ++i) { |
| *(begin + 4 * i) = |
| *(ssndChunk->m_waveData.get() + ssndChunk->m_offset + 3 * i + 2); |
| *(begin + 4 * i + 1) = |
| *(ssndChunk->m_waveData.get() + ssndChunk->m_offset + 3 * i + 1); |
| *(begin + 4 * i + 2) = |
| *(ssndChunk->m_waveData.get() + ssndChunk->m_offset + 3 * i); |
| *(begin + 4 * i + 3) = 0; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| } |
| break; |
| } |
| |
| if (commChunk) delete commChunk; |
| if (ssndChunk) delete ssndChunk; |
| } |
| |
| return track; |
| } |
| |
| |
| |
| TSoundTrackWriterAiff::TSoundTrackWriterAiff(const TFilePath &fp) |
| : TSoundTrackWriter(fp) {} |
| |
| |
| |
| bool TSoundTrackWriterAiff::save(const TSoundTrackP &st) { |
| if (!st) |
| throw TException(L"Unable to save the soundtrack: " + |
| m_path.getWideString()); |
| |
| TSoundTrackP sndtrack; |
| if (st->getBitPerSample() == 8 && !st->isSampleSigned()) |
| throw TException( |
| "The format (8 bit unsigned) is incompatible with AIFF file"); |
| else |
| sndtrack = st; |
| |
| TINT32 soundDataCount = |
| (TINT32)(sndtrack->getSampleCount() * sndtrack->getChannelCount() * |
| tceil(sndtrack->getBitPerSample() / 8)); |
| |
| TINT32 postHeadData = |
| AIFF_NBYTE + COMM_NBYTE + SSND_PREDATA_NBYTE + soundDataCount; |
| |
| 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); |
| |
| TCOMMChunk commChunk("COMM", 18); |
| commChunk.m_chans = sndtrack->getChannelCount(); |
| commChunk.m_frames = sndtrack->getSampleCount(); |
| commChunk.m_bitPerSample = |
| sndtrack->getBitPerSample(); |
| commChunk.m_sampleRate = sndtrack->getSampleRate(); |
| |
| TSSNDChunk ssndChunk("SSND", soundDataCount + OFFSETBLOCSIZE_NBYTE); |
| ssndChunk.m_offset = DEFAULT_OFFSET; |
| ssndChunk.m_blockSize = DEFAULT_BLOCKSIZE; |
| |
| std::unique_ptr<UCHAR[]> waveData(new UCHAR[soundDataCount]); |
| |
| if (TNZ_LITTLE_ENDIAN) { |
| postHeadData = swapTINT32(postHeadData); |
| |
| if (commChunk.m_bitPerSample == 16) { |
| swapAndCopySamples((short *)sndtrack->getRawData(), |
| (short *)waveData.get(), |
| (TINT32)(commChunk.m_frames * commChunk.m_chans)); |
| } else if (commChunk.m_bitPerSample == 24) { |
| UCHAR *begin = (UCHAR *)sndtrack->getRawData(); |
| for (int i = 0; i < (int)commChunk.m_frames * commChunk.m_chans; ++i) { |
| *(waveData.get() + 3 * i) = *(begin + 4 * i + 2); |
| *(waveData.get() + 3 * i + 1) = *(begin + 4 * i + 1); |
| *(waveData.get() + 3 * i + 2) = *(begin + 4 * i); |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| } else |
| memcpy((void *)waveData.get(), (void *)sndtrack->getRawData(), |
| soundDataCount); |
| } else { |
| if (commChunk.m_bitPerSample != 24) |
| memcpy((void *)waveData.get(), (void *)sndtrack->getRawData(), |
| soundDataCount); |
| else { |
| UCHAR *begin = (UCHAR *)sndtrack->getRawData(); |
| for (int i = 0; i < (int)commChunk.m_frames * commChunk.m_chans; ++i) { |
| *(waveData.get() + 3 * i) = *(begin + 4 * i + 1); |
| *(waveData.get() + 3 * i + 1) = *(begin + 4 * i + 2); |
| *(waveData.get() + 3 * i + 2) = *(begin + 4 * i + 3); |
| } |
| } |
| } |
| |
| ssndChunk.m_waveData = std::move(waveData); |
| |
| os.write("FORM", 4); |
| os.write((char *)&postHeadData, sizeof(TINT32)); |
| os.write("AIFF", 4); |
| commChunk.write(os); |
| ssndChunk.write(os); |
| |
| return true; |
| } |
| |