Created
January 23, 2021 12:17
-
-
Save bnnm/5154f9ac6dce85788251e29840d93413 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Decompresses CWAV | |
| #include <math.h> | |
| #include <stdio.h> | |
| #include <stdint.h> | |
| #include <string.h> | |
| #include <stdlib.h> | |
| /* Decodes CWav (CompressWave) audio codec, based on original delphi/pascal source code by Ko-Ta: | |
| * - http://kota.dokkoisho.com/ | |
| * - http://kota.dokkoisho.com/library/CompressWave.zip | |
| * - https://web.archive.org/web/20180819144937/http://d.hatena.ne.jp/Ko-Ta/20070318/p1 | |
| * (no license given) | |
| * Apparently found in few Japanese (doujin?) games around 1995-2002, most notably RADIO ZONDE. | |
| * | |
| * This is mostly a simple re-implementation following original code, basically pascal-classes-to-plain-C | |
| * because why not. Only decoder part is replicated though (some parts where removed or cleaned up). | |
| * Results should be byte-exact (all is int math). | |
| * | |
| * Codec is basically huffman-coded ADPCM, that includes diff table and huffman tree setup in | |
| * CWav header. Described by the author as being "big, heavy and with bad sound quality". | |
| * An oddity is that mono files are output as fake stereo (repeats L/R), this is correct and agrees | |
| * by PCM byte totals in header. Output sample rate is always 44100 and files marked as 22050 just | |
| * decode slightly differently. Also PCM output size in header may be not be multiple of 4, meaning | |
| * files that end with half-a-sample (L sample = 0, nothing for R). | |
| */ | |
| /* ************************************************************************* */ | |
| /* UTIL */ | |
| /* ************************************************************************* */ | |
| #define VGM_LOG printf | |
| static FILE* reopen_streamfile(FILE* sf, size_t buffer) { | |
| return sf; | |
| } | |
| static void close_streamfile(FILE* sf) { | |
| ; | |
| } | |
| static size_t get_streamfile_size(FILE* sf) { | |
| off_t current = ftell(sf); | |
| fseek(sf, 0, SEEK_END); | |
| off_t size = ftell(sf); | |
| fseek(sf, current, SEEK_SET); | |
| return size; | |
| } | |
| static size_t read_streamfile(uint8_t* dst, off_t offset, size_t length, FILE* sf) { | |
| fseek(sf, offset, SEEK_SET); | |
| return fread(dst, sizeof(uint8_t), length, sf); | |
| } | |
| static uint8_t get_u8(const uint8_t *p) { | |
| uint8_t ret; | |
| ret = ((uint16_t)(const uint8_t)p[0]) << 0; | |
| return ret; | |
| } | |
| static uint16_t get_u16le(const uint8_t *p) { | |
| uint16_t ret; | |
| ret = ((uint16_t)(const uint8_t)p[0]) << 0; | |
| ret |= ((uint16_t)(const uint8_t)p[1]) << 8; | |
| return ret; | |
| } | |
| static uint32_t get_u32le(const uint8_t *p) { | |
| uint32_t ret; | |
| ret = ((uint32_t)(const uint8_t)p[0]) << 0; | |
| ret |= ((uint32_t)(const uint8_t)p[1]) << 8; | |
| ret |= ((uint32_t)(const uint8_t)p[2]) << 16; | |
| ret |= ((uint32_t)(const uint8_t)p[3]) << 24; | |
| return ret; | |
| } | |
| static uint32_t get_u32be(const uint8_t *p) { | |
| uint32_t ret; | |
| ret = ((uint32_t)(const uint8_t)p[0]) << 24; | |
| ret |= ((uint32_t)(const uint8_t)p[1]) << 16; | |
| ret |= ((uint32_t)(const uint8_t)p[2]) << 8; | |
| ret |= ((uint32_t)(const uint8_t)p[3]) << 0; | |
| return ret; | |
| } | |
| static int32_t get_s32le(const uint8_t *p) { | |
| int32_t ret; | |
| ret = ((uint32_t)(const uint8_t)p[0]) << 0; | |
| ret |= ((uint32_t)(const uint8_t)p[1]) << 8; | |
| ret |= ((uint32_t)(const uint8_t)p[2]) << 16; | |
| ret |= ((uint32_t)(const uint8_t)p[3]) << 24; | |
| return (int32_t)ret; | |
| } | |
| static uint32_t get_u64le(const uint8_t *p) { | |
| uint64_t ret; | |
| ret = ((uint64_t)(const uint8_t)p[0]) << 0; | |
| ret |= ((uint64_t)(const uint8_t)p[1]) << 8; | |
| ret |= ((uint64_t)(const uint8_t)p[2]) << 16; | |
| ret |= ((uint64_t)(const uint8_t)p[3]) << 24; | |
| ret |= ((uint64_t)(const uint8_t)p[4]) << 32; | |
| ret |= ((uint64_t)(const uint8_t)p[5]) << 40; | |
| ret |= ((uint64_t)(const uint8_t)p[6]) << 48; | |
| ret |= ((uint64_t)(const uint8_t)p[7]) << 56; | |
| return ret; | |
| } | |
| /* ************************************************************************* */ | |
| /* common */ | |
| /* ************************************************************************* */ | |
| // pascal reader simulated in C | |
| typedef struct { | |
| FILE* File; | |
| int64_t Position; | |
| int64_t Size; | |
| } TStream; | |
| static void TStream_Read_Uint32(TStream* this, uint32_t* value) { | |
| uint8_t buf[0x4] = {0}; | |
| read_streamfile(buf, this->Position, sizeof(buf), this->File); | |
| this->Position += 0x4; | |
| *value = get_u32le(buf); | |
| } | |
| /* ************************************************************************* */ | |
| /* HuffLib.pas */ | |
| /* ************************************************************************* */ | |
| #define CW_TRUE 1 | |
| #define CW_FALSE 0 | |
| typedef enum { nsEmpty, nsBranch, nsLeaf, nsRoot } TNodeState; | |
| //------------------------------------------------------------------------------ | |
| //structure declaration | |
| //node structure for huffman tree | |
| typedef struct { | |
| uint8_t Value; //value (0..255) | |
| int32_t Weight; //weight value used during huffman tree creation | |
| TNodeState State; //state | |
| int32_t Link[2]; //bidimensional tree L/R path (-1: unused, >=0: index) | |
| } THuffTreeNode; | |
| //header info for file writting | |
| typedef struct { | |
| char HedChar[4]; //head info | |
| int32_t Version; //Version | |
| uint32_t HistGraph[256]; //appearance rate | |
| int64_t FileSize; //file size | |
| } THuffHedState; | |
| //for jumping to arbitrary places (^^), various usages | |
| typedef struct { | |
| uint32_t BitBuf; | |
| int32_t BitCount; | |
| int64_t StreamPos; | |
| uint32_t CipherBuf; | |
| } THuffPositionData; | |
| //------------------------------------------------------------------------------ | |
| //huffman encoding class | |
| //handled values are 0~255 of 1 byte. | |
| //takes appearance rate and makes a huffman tree for encoding | |
| //EXTRA: external lib part, but not really needed so all is static | |
| typedef struct { | |
| //control | |
| TStream Buff; // target stream | |
| int64_t BeginPos; // encoded area | |
| int Mode; // (0=initial, 1=read, 2=write) | |
| //bit IO | |
| int IoCount; // 0..initial state, 1..read, 2..write | |
| uint32_t BitBuf; // held buffer | |
| int BitCount; // processed bit count | |
| #if 0 | |
| int64_t BitWriteLen; // written size | |
| #endif | |
| uint32_t CipherBuf; | |
| //huffman | |
| THuffTreeNode Node[512]; //tree structure | |
| uint8_t Code[256][256]; //fork support | |
| int32_t Root; //root | |
| //huffman cipher bits | |
| uint32_t CipherList[16]; | |
| //header info | |
| THuffHedState Hed; | |
| } THuff; | |
| //related to huffman encoding | |
| static void THuff_InitHuffTree(THuff* this); //initializes tree | |
| static int THuff_InsertHuffNode(THuff* this, int v, int w, TNodeState s, int b1, int b2); //add node to tree | |
| static void THuff_MakeHuffTree(THuff* this); | |
| //related to single bit IO | |
| static void THuff_BeginBitIO(THuff* this); | |
| static void THuff_EndBitIO(THuff* this); | |
| static int THuff_ReadBit(THuff* this); | |
| static uint32_t THuff__ROR(uint32_t src, uint32_t shift); | |
| static THuff* THuff_Create(TStream* buf); // creation | |
| static void THuff_Free(THuff* this); // release the power | |
| static void THuff_SetCipherCode(THuff* this, uint32_t msk); // encryption mask bits | |
| //functions for reading | |
| static void THuff_BeginRead(THuff* this); | |
| static int THuff_Read(THuff* this); | |
| #if 0 | |
| static int64_t THuff_GetFileSize(THuff* this); // get file size before encoding | |
| static int THuff_GetEOF(THuff* this); // EOF detection | |
| #endif | |
| static void THuff_MoveBeginPosition(THuff* this); // return to initial state | |
| static void THuff_GetPositionData(THuff* this, THuffPositionData* s); // secret | |
| static void THuff_SetPositionData(THuff* this, THuffPositionData* s); | |
| //------------------------------------------------------------------------------ | |
| //create | |
| static THuff* THuff_Create(TStream* buf) { | |
| THuff* this = malloc(sizeof(THuff)); | |
| if (!this) return NULL; | |
| //define stream | |
| this->Buff = *buf; | |
| //initialization | |
| THuff_InitHuffTree(this); | |
| memcpy(this->Hed.HedChar, "HUF\0", 0x4); | |
| this->Hed.Version = 1; | |
| this->Hed.FileSize = 0; | |
| //set cipher bits | |
| this->CipherBuf = 0; | |
| THuff_SetCipherCode(this, 0x0); | |
| //mode | |
| this->Mode = 0; | |
| return this; | |
| } | |
| //------------------------------------------------------------------------------ | |
| //free | |
| static void THuff_Free(THuff* this) { | |
| if (this == NULL) return; | |
| if (this->Mode == 2) | |
| THuff_EndBitIO(this); | |
| free(this); | |
| } | |
| //------------------------------------------------------------------------------ | |
| //init tree structure (unused state) | |
| static void THuff_InitHuffTree(THuff* this) { | |
| int i; | |
| for (i = 0; i < 512; i++) { | |
| this->Node[i].State = nsEmpty; | |
| } | |
| } | |
| //------------------------------------------------------------------------------ | |
| //add node to huffman tree | |
| static int THuff_InsertHuffNode(THuff* this, int v, int w, TNodeState s, int b1, int b2) { | |
| int result = 0; | |
| int i; | |
| i = 0; | |
| while ((this->Node[i].State != nsEmpty) && (i < 512)) { | |
| i++; | |
| } | |
| if (i == 512) { | |
| result = -1; | |
| return result; //exit; | |
| } | |
| this->Node[i].Value = v & 0xFF; //BYTE(v); | |
| this->Node[i].Weight = w; | |
| this->Node[i].State = s; | |
| this->Node[i].Link[0] = b1; | |
| if (this->Node[i].Link[0] > 511) { | |
| return -1;//? //halt; | |
| } | |
| this->Node[i].Link[1] = b2; | |
| if (this->Node[i].Link[1] > 511) { | |
| return -1;//? //halt; | |
| } | |
| //return entry number | |
| result = i; | |
| return result; | |
| } | |
| //------------------------------------------------------------------------------ | |
| //reads and expands huffman-encoded data | |
| static int THuff_Read(THuff* this) { | |
| int i; | |
| i = this->Root; | |
| while (this->Node[i].State != nsLeaf) { | |
| i = this->Node[i].Link[THuff_ReadBit(this)]; | |
| } | |
| return this->Node[i].Value; | |
| } | |
| //------------------------------------------------------------------------------ | |
| //creates fork code from tree | |
| //finds node of lowest weight | |
| static int THuff_MakeHuffTree_SerchMinNode(THuff* this, int* tNode) { | |
| int ii, aaa1, aaa2; | |
| aaa1 = 0xFFFFFFF; | |
| aaa2 = 0; | |
| for (ii = 0 ; ii < 256; ii++) { | |
| if (tNode[ii] != -1) { | |
| if (this->Node[tNode[ii]].Weight < aaa1) { | |
| aaa2 = ii; | |
| aaa1 = this->Node[tNode[ii]].Weight; | |
| } | |
| } | |
| } | |
| return aaa2; | |
| } | |
| //finds closest node | |
| static int THuff_MakeHuffTree_SerchNearNode(THuff* this, int* tNode, int pos) { | |
| int ii, aaa1, aaa2; | |
| aaa1 = 0xFFFFFFF; | |
| aaa2 = 0; | |
| for (ii = 0 ; ii < 256; ii++) { | |
| if (tNode[ii] != -1) { | |
| if ((abs(this->Node[tNode[ii]].Weight - this->Node[tNode[pos]].Weight) < aaa1) && (pos != ii)) { | |
| aaa2 = ii; | |
| aaa1 = this->Node[tNode[ii]].Weight; | |
| } | |
| } | |
| } | |
| return aaa2; | |
| } | |
| static void THuff_MakeHuffTree_MakeHuffCodeFromTree(THuff* this, uint8_t* tCode1, int* tCodePos, int pos) { | |
| int ii, aaa1; | |
| if (this->Node[pos].State == nsLeaf) { //found | |
| tCode1[*tCodePos] = 0xFF; | |
| aaa1 = this->Node[pos].Value; | |
| for (ii = 0; ii < 256; ii++) { | |
| this->Code[aaa1][ii] = tCode1[ii]; | |
| } | |
| } | |
| else { //not | |
| if (this->Node[pos].Link[0] != -1) { | |
| tCode1[*tCodePos] = 0; | |
| (*tCodePos)++; | |
| THuff_MakeHuffTree_MakeHuffCodeFromTree(this, tCode1, tCodePos, this->Node[pos].Link[0]); | |
| } | |
| if (this->Node[pos].Link[1] != -1) { | |
| tCode1[*tCodePos] = 1; | |
| (*tCodePos)++; | |
| THuff_MakeHuffTree_MakeHuffCodeFromTree(this, tCode1, tCodePos, this->Node[pos].Link[1]); | |
| } | |
| } | |
| (*tCodePos)--; | |
| } | |
| // creates huffman tree/codes from apparance rate (0..255) | |
| static void THuff_MakeHuffTree(THuff* this) { | |
| int i, aa1, aa2, aa3; | |
| int tCodePos; | |
| uint8_t tCode1[257]; | |
| #if 0 | |
| uint8_t tCode2[257]; | |
| #endif | |
| int tNode[257]; | |
| //initializes huffman tree | |
| THuff_InitHuffTree(this); | |
| for (i = 0; i < 256; i++) { | |
| tNode[i] = -1; | |
| tCode1[i] = 0; | |
| #if 0 | |
| tCode2[i] = 0; | |
| #endif | |
| } | |
| //adds child nodes + comparison target nodes | |
| for (i = 0; i < 256; i++) { | |
| tNode[i] = THuff_InsertHuffNode(this, i, this->Hed.HistGraph[i], nsLeaf, -1, -1); | |
| } | |
| //creates optimal tree | |
| for (i = 0; i < 256 - 1; i++) { | |
| //find smallest node | |
| aa1 = THuff_MakeHuffTree_SerchMinNode(this, tNode); | |
| //find value closest to smallest node | |
| aa2 = THuff_MakeHuffTree_SerchNearNode(this, tNode, aa1); | |
| //make new node joining both together | |
| aa3 = THuff_InsertHuffNode(this, -1, this->Node[tNode[aa1]].Weight + this->Node[tNode[aa2]].Weight, nsBranch, tNode[aa1], tNode[aa2]); | |
| //remove aa1/2 from comparison target nodes. | |
| tNode[aa1] = -1; | |
| tNode[aa2] = -1; | |
| //add created node to comparison target nodes | |
| tNode[aa1] = aa3; | |
| } | |
| //finally make added node top of the tree | |
| this->Root = aa3; | |
| //create stack for data expansion from tree info | |
| tCodePos = 0; | |
| THuff_MakeHuffTree_MakeHuffCodeFromTree(this, tCode1, &tCodePos, this->Root); | |
| } | |
| //------------------------------------------------------------------------------ | |
| //bit IO start process | |
| static void THuff_BeginBitIO(THuff* this) { | |
| this->IoCount = 0; | |
| this->BitBuf = 0; | |
| this->BitCount = 32; | |
| #if 0 | |
| this->BitWriteLen = 0; | |
| #endif | |
| this->CipherBuf = 0; | |
| } | |
| //------------------------------------------------------------------------------ | |
| //bit IO end process | |
| static void THuff_EndBitIO(THuff* this) { | |
| #if 0 | |
| if (this->IoCount == 2 && this->BitCount > 0) { | |
| this->BitBuf = this->BitBuf ^ this->CipherBuf; | |
| TStream_Write(this->Buff, BitBuf,4); | |
| } | |
| #endif | |
| THuff_BeginBitIO(this); | |
| } | |
| //------------------------------------------------------------------------------ | |
| //read 1 bit from file | |
| static int THuff_ReadBit(THuff* this) { | |
| int result; | |
| uint32_t aaa; | |
| if (this->BitCount == 32) { | |
| this->IoCount = 1; //ReadMode | |
| if (this->Buff.Position < this->Buff.Size) { | |
| //read | |
| TStream_Read_Uint32(&this->Buff, &aaa); //Buff.Read(aaa,sizeof(DWORD)); | |
| this->BitBuf = aaa ^ this->CipherBuf; | |
| //decryption phase | |
| this->CipherBuf = THuff__ROR(this->CipherBuf, aaa & 7); | |
| this->CipherBuf = this->CipherBuf ^ this->CipherList[aaa & 7]; | |
| } | |
| this->BitCount = 0; | |
| } | |
| //return 1 bit | |
| result = this->BitBuf & 1; | |
| this->BitBuf = this->BitBuf >> 1; | |
| //advance BitCount | |
| this->BitCount++; | |
| return result; | |
| } | |
| //------------------------------------------------------------------------------ | |
| //starts reading encoded data from stream | |
| static void TStream_Read_THuffHedState(TStream* this, THuffHedState* Hed) { | |
| uint8_t buf[0x410]; | |
| int i; | |
| read_streamfile(buf, this->Position, sizeof(buf), this->File); | |
| this->Position += sizeof(buf); | |
| /* 0x00: string size (always 3) */ | |
| memcpy(Hed->HedChar, buf+0x01, 0x03); | |
| Hed->Version = get_u32le(buf+0x04); | |
| for (i = 0; i < 256; i++) { | |
| Hed->HistGraph[i] = get_u32le(buf+0x08 + i*0x04); | |
| } | |
| Hed->FileSize = get_u64le(buf+0x408); /* seems always 0 */ | |
| } | |
| static void THuff_BeginRead(THuff* this) { | |
| TStream_Read_THuffHedState(&this->Buff, &this->Hed); //Buff.Read(Hed,sizeof(THuffHedState)); | |
| THuff_MakeHuffTree(this); | |
| this->BeginPos = this->Buff.Position; | |
| THuff_BeginBitIO(this); | |
| this->Mode = 1; | |
| } | |
| #if 0 | |
| //------------------------------------------------------------------------------ | |
| //get file size before encoding | |
| static int64_t THuff_GetFileSize(THuff* this) { | |
| return this->Hed.FileSize; | |
| } | |
| //------------------------------------------------------------------------------ | |
| //EOF detection | |
| static int THuff_GetEOF(THuff* this) { | |
| if (this->Buff.Position < this->Buff.Size) | |
| return CW_FALSE; | |
| else | |
| return CW_TRUE; | |
| } | |
| #endif | |
| //------------------------------------------------------------------------------ | |
| //return to initial positon | |
| static void THuff_MoveBeginPosition(THuff* this) { | |
| THuff_EndBitIO(this); | |
| this->Buff.Position = this->BeginPos; | |
| THuff_BeginBitIO(this); | |
| } | |
| //------------------------------------------------------------------------------ | |
| static void THuff_GetPositionData(THuff* this, THuffPositionData* s) { | |
| s->BitBuf = this->BitBuf; | |
| s->BitCount = this->BitCount; | |
| s->StreamPos = this->Buff.Position; | |
| s->CipherBuf = this->CipherBuf; | |
| } | |
| //------------------------------------------------------------------------------ | |
| static void THuff_SetPositionData(THuff* this, THuffPositionData* s) { | |
| this->BitBuf = s->BitBuf; | |
| this->BitCount = s->BitCount; | |
| this->Buff.Position = s->StreamPos; | |
| this->CipherBuf = s->CipherBuf; | |
| } | |
| //------------------------------------------------------------------------------ | |
| static void THuff_SetCipherCode(THuff* this, uint32_t msk) { | |
| //creates mask list | |
| this->CipherList[0] = msk / 3; | |
| this->CipherList[1] = msk / 17; | |
| this->CipherList[2] = msk / 7; | |
| this->CipherList[3] = msk / 5; | |
| this->CipherList[4] = msk / 3; | |
| this->CipherList[5] = msk / 11; | |
| this->CipherList[6] = msk / 13; | |
| this->CipherList[7] = msk / 19; | |
| } | |
| //------------------------------------------------------------------------------ | |
| static uint32_t THuff__ROR(uint32_t src, uint32_t shift) { | |
| uint8_t num = shift % 0xFF; | |
| return ((uint32_t)src >> num) | ((uint32_t)src << (32 - num)); | |
| } | |
| //------------------------------------------------------------------- | |
| // CompressWaveLib.pas | |
| //------------------------------------------------------------------- | |
| #define PW_MAXVOLUME 0xFFFFFFF //don't change | |
| //proprietary compression file header | |
| typedef struct { | |
| //RIFF chunk | |
| char HedChar[8]; // 'CmpWave' | |
| uint32_t Channel; // 2(STEREO) / 1(MONO) | |
| uint32_t Sample; // 44100Hz / 22050Hz | |
| uint32_t Bit; // 16bit | |
| int32_t Tbl[256]; // conversion table value | |
| int64_t UnPressSize; // decompressed data size | |
| int64_t LoopStart; // loop start/end position | |
| int64_t LoopEnd; | |
| uint8_t LoopCount; // loop times | |
| char MusicTitle[128*2]; // song name | |
| char MusicArtist[128*2]; // composer | |
| } PRESSWAVEDATAHED; | |
| //for writting | |
| typedef struct { | |
| short RBuf; | |
| short LBuf; | |
| } TLRWRITEBUFFER; | |
| //compression data class | |
| typedef struct { | |
| //rendering flag (sets during rendering) | |
| int NowRendering; | |
| //flag for playback | |
| int32_t Faa1; | |
| int32_t Faa2; | |
| int32_t Fvv1; | |
| int32_t Fvv2; | |
| int32_t FVolume; | |
| int32_t Ffade; | |
| int32_t FSetVolume; | |
| int FEndLoop; | |
| int32_t FLoop; | |
| int FPlay; | |
| int64_t FWavePosition; | |
| int64_t FWaveLength; | |
| //flag for 22050kHz | |
| int32_t LBackBuf; | |
| int32_t RBackBuf; | |
| //for restoration | |
| THuffPositionData PosData; | |
| int32_t LPFaa1; | |
| int32_t LPFaa2; | |
| int32_t LPFvv1; | |
| int32_t LPFvv2; | |
| //cipher code | |
| uint32_t CipherCode; | |
| //hafu-hafu-hafuman | |
| THuff* RH; | |
| #if 0 | |
| TMemoryStream Data; | |
| #endif | |
| PRESSWAVEDATAHED Hed; | |
| } TCompressWaveData; | |
| static void TCompressWaveData_GetLoopState(TCompressWaveData* this); | |
| static void TCompressWaveData_SetLoopState(TCompressWaveData* this); | |
| static TCompressWaveData* TCompressWaveData_Create(); | |
| static void TCompressWaveData_Free(TCompressWaveData* this); | |
| static int TCompressWaveData_Rendering(TCompressWaveData* this, int16_t* buf, uint32_t Len); | |
| static int TCompressWaveData_LoadFromStream(TCompressWaveData* this, FILE* ss); | |
| static void TCompressWaveData_SetCipherCode(TCompressWaveData* this, uint32_t Num); | |
| //control related | |
| static void TCompressWaveData_Play(TCompressWaveData* this, int loop); | |
| static void TCompressWaveData_Stop(TCompressWaveData* this); | |
| static void TCompressWaveData_Previous(TCompressWaveData* this); | |
| static void TCompressWaveData_Pause(TCompressWaveData* this); | |
| static void TCompressWaveData_SetVolume(TCompressWaveData* this, float vol, float fade); | |
| static float TCompressWaveData_GetVolume(TCompressWaveData* this); | |
| static float TCompressWaveData_GetSetVolume(TCompressWaveData* this); | |
| static float TCompressWaveData_GetFade(TCompressWaveData* this); | |
| static float TCompressWaveData_GetPlayTime(TCompressWaveData* this); | |
| static float TCompressWaveData_GetTotalTime(TCompressWaveData* this); | |
| //----------------------------------------------------------- | |
| //create | |
| static TCompressWaveData* TCompressWaveData_Create() { | |
| TCompressWaveData* this = malloc(sizeof(TCompressWaveData)); | |
| if (!this) return NULL; | |
| #if 0 | |
| this->Data = NULL; | |
| #endif | |
| this->RH = NULL; | |
| this->FWavePosition = 0; | |
| this->FWaveLength = 0; | |
| this->FVolume = PW_MAXVOLUME; | |
| this->FSetVolume = PW_MAXVOLUME; | |
| this->Ffade = 0; | |
| this->FEndLoop = CW_FALSE; | |
| this->FPlay = CW_FALSE; | |
| this->NowRendering = CW_FALSE; | |
| TCompressWaveData_SetCipherCode(this, 0); | |
| return this; | |
| } | |
| //----------------------------------------------------------- | |
| //free | |
| static void TCompressWaveData_Free(TCompressWaveData* this) { | |
| if (!this) | |
| return; | |
| //EXTRA: presumably for threading but OG lib doesn't properly set this to false on all errors | |
| #if 0 | |
| //sync | |
| while (this->NowRendering) { | |
| ; | |
| } | |
| #endif | |
| //free | |
| if (this->RH != NULL) | |
| THuff_Free(this->RH); | |
| #if 0 | |
| if (this->Data != NULL) | |
| TMemoryStream_Free(this->Data); | |
| #endif | |
| free(this); | |
| } | |
| //----------------------------------------------------------- | |
| //outpus 44100/16bit/stereo waveform to designed buffer | |
| static void TCompressWaveData_Rendering_ReadPress(TCompressWaveData* this, int32_t* RFlg, int32_t* LFlg) { | |
| if (this->Hed.Channel == 2) { | |
| *RFlg = THuff_Read(this->RH); //STEREO | |
| *LFlg = THuff_Read(this->RH); | |
| } | |
| else { | |
| *RFlg = THuff_Read(this->RH); //MONO | |
| *LFlg = *RFlg; | |
| } | |
| } | |
| static void TCompressWaveData_Rendering_WriteWave(TCompressWaveData* this, int16_t** buf1, int32_t RVol, int32_t LVol) { | |
| TLRWRITEBUFFER bbb = {0}; | |
| if (this->Hed.Sample == 44100) { //44100 STEREO/MONO | |
| bbb.RBuf = RVol; | |
| bbb.LBuf = LVol; | |
| (*buf1)[0] = bbb.RBuf; | |
| (*buf1)[1] = bbb.LBuf; | |
| (*buf1) += 2; | |
| } | |
| if (this->Hed.Sample == 22050) { //22050 STEREO/MONO | |
| bbb.RBuf = (this->RBackBuf + RVol) / 2; | |
| bbb.LBuf = (this->LBackBuf + LVol) / 2; | |
| (*buf1)[0] = bbb.RBuf; | |
| (*buf1)[1] = bbb.LBuf; | |
| (*buf1) += 2; | |
| bbb.RBuf = RVol; | |
| bbb.LBuf = LVol; | |
| (*buf1)[0] = bbb.RBuf; | |
| (*buf1)[1] = bbb.LBuf; | |
| (*buf1) += 2; | |
| this->RBackBuf = RVol; | |
| this->LBackBuf = LVol; | |
| } | |
| } | |
| static int TCompressWaveData_Rendering(TCompressWaveData* this, int16_t* buf, uint32_t Len) { | |
| int result; | |
| int32_t RFlg, LFlg, RVol, LVol; | |
| int i, aaa; | |
| int16_t* buf1; | |
| int32_t PressLength, WaveStep; | |
| this->NowRendering = CW_TRUE; | |
| result = CW_FALSE; | |
| #if 0 | |
| if (this->Data == NULL) { | |
| this->NowRendering = CW_FALSE; | |
| return result; //exit; | |
| } | |
| #endif | |
| //fadeout song stop | |
| if ((this->FVolume < 1) && (this->FSetVolume < 1)) { | |
| this->FPlay = CW_FALSE; | |
| } | |
| //if (abs(this->FSetVolume - this->FVolume) < this->Ffade) { | |
| // this->FPlay = CW_FALSE; | |
| //} | |
| //stop if FPlay (play flag) wasn't set | |
| if (this->FPlay == CW_FALSE) { | |
| this->NowRendering = CW_FALSE; | |
| return result; //exit; | |
| } | |
| //pre processing | |
| RVol = this->Fvv1; | |
| LVol = this->Fvv2; | |
| if (this->Hed.Sample == 44100) | |
| WaveStep = 4; | |
| else | |
| WaveStep = 8; | |
| PressLength = (int32_t)Len / WaveStep; | |
| //expansion processing | |
| buf1 = buf; | |
| for (i = 0; i < PressLength; i++) { | |
| //crossed over? | |
| if (this->FWavePosition > this->FWaveLength) { | |
| if (this->FEndLoop == CW_TRUE) { //playback with loop? | |
| TCompressWaveData_Previous(this); | |
| } | |
| else { //in case of playback without loop | |
| this->FPlay = CW_FALSE; | |
| return result; //exit | |
| } | |
| } | |
| //loop related | |
| if (this->Hed.LoopCount > this->FLoop) { | |
| //if position is loop start, hold current flag/state | |
| //shr 3 matches 8 bit aligment | |
| if ((this->Hed.LoopStart >> 3) == (this->FWavePosition >> 3)) { | |
| TCompressWaveData_GetLoopState(this); | |
| } | |
| //if reached loop end do loop. | |
| if ((this->Hed.LoopEnd >> 3) == (this->FWavePosition >> 3)) { | |
| if (this->Hed.LoopCount != 255) | |
| this->FLoop++; | |
| TCompressWaveData_SetLoopState(this); | |
| } | |
| } | |
| //read | |
| TCompressWaveData_Rendering_ReadPress(this, &RFlg, &LFlg); | |
| this->Faa1 = this->Faa1 + this->Hed.Tbl[RFlg]; | |
| this->Faa2 = this->Faa2 + this->Hed.Tbl[LFlg]; | |
| this->Fvv1 = this->Fvv1 + this->Faa1; | |
| this->Fvv2 = this->Fvv2 + this->Faa2; | |
| //volume adjustment | |
| aaa = this->FSetVolume - this->FVolume; | |
| if (abs(aaa) < this->Ffade) { | |
| this->FVolume = this->FSetVolume; | |
| } | |
| else { | |
| if (aaa > 0) | |
| this->FVolume = this->FVolume + this->Ffade; | |
| else | |
| this->FVolume = this->FVolume - this->Ffade; | |
| } | |
| //threshold calcs (due to overflow) | |
| if (this->Fvv1 > +32760) { | |
| this->Fvv1 = +32760; | |
| this->Faa1 = 0; | |
| } | |
| if (this->Fvv1 < -32760) { | |
| this->Fvv1 = -32760; | |
| this->Faa1 = 0; | |
| } | |
| if (this->Fvv2 > +32760) { | |
| this->Fvv2 = +32760; | |
| this->Faa2 = 0; | |
| } | |
| if (this->Fvv2 < -32760) { | |
| this->Fvv2 = -32760; | |
| this->Faa2 = 0; | |
| } | |
| aaa = (this->FVolume >> 20); | |
| RVol = this->Fvv1 * aaa / 256; | |
| LVol = this->Fvv2 * aaa / 256; | |
| //expand to buffer | |
| TCompressWaveData_Rendering_WriteWave(this, &buf1, RVol, LVol); | |
| //advance playback position | |
| this->FWavePosition += WaveStep; | |
| } | |
| //remainder calcs | |
| //depending on buffer lenght remainder may happen | |
| //example: 44100 / 4 = 11025...OK 44100 / 8 = 5512.5...NG | |
| // in that case appear as noise | |
| if (Len % 8 == 4) { | |
| TCompressWaveData_Rendering_WriteWave(this, &buf1, RVol, LVol); | |
| } | |
| this->NowRendering = CW_FALSE; | |
| result = CW_TRUE; | |
| return result; | |
| } | |
| //----------------------------------------------------------- | |
| //read compressed file from stream | |
| static void TStream_Read_PRESSWAVEDATAHED(TStream* this, PRESSWAVEDATAHED* Hed) { | |
| uint8_t buf[0x538]; | |
| int i, len; | |
| read_streamfile(buf, this->Position, sizeof(buf), this->File); | |
| this->Position += sizeof(buf); | |
| memcpy(Hed->HedChar, buf + 0x00, 8); | |
| Hed->Channel = get_u32le(buf + 0x08); | |
| Hed->Sample = get_u32le(buf + 0x0c); | |
| Hed->Bit = get_u32le(buf + 0x10); | |
| for (i = 0; i < 256; i++) { | |
| Hed->Tbl[i] = get_s32le(buf + 0x14 + i * 0x04); | |
| } | |
| Hed->UnPressSize = get_u64le(buf + 0x418); | |
| Hed->LoopStart = get_u64le(buf + 0x420); | |
| Hed->LoopEnd = get_u64le(buf + 0x428); | |
| Hed->LoopCount = get_u8 (buf + 0x430); | |
| len = get_u8 (buf + 0x431); | |
| memcpy(Hed->MusicTitle, buf + 0x432, len); | |
| len = get_u8 (buf + 0x4B1); | |
| memcpy(Hed->MusicArtist, buf + 0x4B2, len); | |
| /* 0x538: huffman table */ | |
| /* 0x948: data start */ | |
| } | |
| static int TCompressWaveData_LoadFromStream(TCompressWaveData* this, FILE* ss) { | |
| int result = CW_FALSE; | |
| TStream data = {0}; | |
| if (ss == NULL) | |
| return result; | |
| #if 0 | |
| if (this->Data != NULL) | |
| TMemoryStream_Free(this->Data); | |
| #endif | |
| data.File = ss; //data = TMemoryStream.Create; | |
| data.Size = get_streamfile_size(ss); //data.SetSize(ss.Size); | |
| //data.CopyFrom(ss,0); | |
| //get header info | |
| data.Position = 0; | |
| TStream_Read_PRESSWAVEDATAHED(&data, &this->Hed); //data.Read(Hed,sizeof(PRESSWAVEDATAHED)); | |
| this->FWaveLength = this->Hed.UnPressSize; | |
| if (this->RH != NULL) | |
| THuff_Free(this->RH); | |
| this->RH = THuff_Create(&data); | |
| if (!this->RH) return result; | |
| THuff_SetCipherCode(this->RH, 0x00); | |
| THuff_BeginRead(this->RH); | |
| //initialize playback flag | |
| TCompressWaveData_Stop(this); | |
| TCompressWaveData_SetVolume(this, 1.0, 0.0); | |
| result = CW_TRUE; | |
| return result; | |
| } | |
| //------------------------------------------------------------------------------ | |
| //temp pause | |
| static void TCompressWaveData_Pause(TCompressWaveData* this) { | |
| this->FPlay = CW_FALSE; | |
| } | |
| //----------------------------------------------------------- | |
| //sets volume | |
| static void TCompressWaveData_SetVolume(TCompressWaveData* this, float vol, float fade) { | |
| float aaa; | |
| //EXTRA: C float seemingly can't store PW_MAXVOLUME (268435455 becomes 268435456.0), so must cast to double | |
| // to get proper results. Otherwise volume gets slightly different vs original (no casting needed there). | |
| // vol=1.0, fade=0.0 is the same as default params. | |
| aaa = vol; | |
| //set volume threshold | |
| if (aaa > 1.0) aaa = 1.0; | |
| if (aaa < 0.0) aaa = 0.0; | |
| //calc volume increse | |
| if (fade < 0.01) { //with fade value | |
| this->Ffade = 0; | |
| this->FVolume = round(aaa * (double)PW_MAXVOLUME); | |
| this->FSetVolume = this->FVolume; | |
| } | |
| else { //without fade value | |
| this->Ffade = round(PW_MAXVOLUME / fade / 44100); | |
| this->FSetVolume = round(aaa * PW_MAXVOLUME); | |
| } | |
| } | |
| //----------------------------------------------------------- | |
| //returns fade value | |
| static float TCompressWaveData_GetFade(TCompressWaveData* this) { | |
| if ((this->Ffade == 0) || (abs(this->FVolume - this->FSetVolume) == 0)) { | |
| return 0; //exit; | |
| } | |
| return (abs(this->FVolume - this->FSetVolume)/44100) / this->Ffade; | |
| } | |
| //----------------------------------------------------------- | |
| //returns volume value | |
| static float TCompressWaveData_GetVolume(TCompressWaveData* this) { | |
| return this->FVolume / PW_MAXVOLUME; | |
| } | |
| //------------------------------------------------------------------------------ | |
| //returns volume after fade | |
| static float TCompressWaveData_GetSetVolume(TCompressWaveData* this) { | |
| return this->FSetVolume / PW_MAXVOLUME; | |
| } | |
| //------------------------------------------------------------------------------ | |
| //returns play time (current position). unit is secs | |
| static float TCompressWaveData_GetPlayTime(TCompressWaveData* this) { | |
| return this->FWavePosition / (44100*4); | |
| } | |
| //----------------------------------------------------------- | |
| //returns song length. unit is secs | |
| static float TCompressWaveData_GetTotalTime(TCompressWaveData* this) { | |
| return this->FWaveLength / (44100*4); | |
| } | |
| //----------------------------------------------------------- | |
| //play stop command. returns song to beginning | |
| static void TCompressWaveData_Stop(TCompressWaveData* this) { | |
| //play flags to initial state | |
| this->FWavePosition = 0; | |
| this->Fvv1 = 0; | |
| this->Faa1 = 0; | |
| this->Fvv2 = 0; | |
| this->Faa2 = 0; | |
| this->LBackBuf = 0; | |
| this->RBackBuf = 0; | |
| TCompressWaveData_SetVolume(this, 1.0, 0); | |
| this->FPlay = CW_FALSE; | |
| this->FLoop = 0; | |
| #if 0 | |
| if (this->Data == NULL) | |
| return; | |
| #endif | |
| //EXTRA: presumably for threading but OG lib doesn't properly set this to false on all errors | |
| #if 0 | |
| //sync | |
| while (this->NowRendering) { | |
| ; | |
| } | |
| #endif | |
| //return to initial location | |
| THuff_MoveBeginPosition(this->RH); | |
| } | |
| //----------------------------------------------------------- | |
| //returns song to beginning. difference vs STOP is that fade isn't initialized | |
| static void TCompressWaveData_Previous(TCompressWaveData* this) { | |
| //play flags to initial state | |
| this->FWavePosition = 0; | |
| this->Fvv1 = 0; | |
| this->Faa1 = 0; | |
| this->Fvv2 = 0; | |
| this->Faa2 = 0; | |
| this->LBackBuf = 0; | |
| this->RBackBuf = 0; | |
| this->FLoop = 0; | |
| #if 0 | |
| if (this->Data == NULL) | |
| return; | |
| #endif | |
| //return to initial location | |
| THuff_MoveBeginPosition(this->RH); | |
| } | |
| //------------------------------------------------------------ | |
| //starts song playback | |
| static void TCompressWaveData_Play(TCompressWaveData* this, int loop) { | |
| this->FPlay = CW_TRUE; | |
| this->FEndLoop = loop; | |
| if ((this->FVolume == 0) && (this->FSetVolume == 0)) | |
| TCompressWaveData_SetVolume(this, 1.0,0); | |
| } | |
| //------------------------------------------------------------ | |
| //set parameters for looping | |
| //-------------------------------------------------------------- | |
| //record encoded file position | |
| //since it uses huffman needs to held those flags too | |
| static void TCompressWaveData_GetLoopState(TCompressWaveData* this) { | |
| this->LPFaa1 = this->Faa1; | |
| this->LPFaa2 = this->Faa2; | |
| this->LPFvv1 = this->Fvv1; | |
| this->LPFvv2 = this->Fvv2; | |
| THuff_GetPositionData(this->RH, &this->PosData); | |
| } | |
| //-------------------------------------------------------------- | |
| //return to recorded encoded file position | |
| static void TCompressWaveData_SetLoopState(TCompressWaveData* this) { | |
| this->Faa1 = this->LPFaa1; | |
| this->Faa2 = this->LPFaa2; | |
| this->Fvv1 = this->LPFvv1; | |
| this->Fvv2 = this->LPFvv2; | |
| THuff_SetPositionData(this->RH, &this->PosData); | |
| this->FWavePosition = this->Hed.LoopStart; | |
| } | |
| //----------------------------------------------------------- | |
| //sets cipher code | |
| static void TCompressWaveData_SetCipherCode(TCompressWaveData* this, uint32_t Num) { | |
| this->CipherCode = Num; | |
| } | |
| /* ************************************************************************* */ | |
| /* DATA */ | |
| /* ************************************************************************* */ | |
| typedef struct cwav_codec_data cwav_codec_data; | |
| cwav_codec_data* cwav_init(FILE* sf); | |
| void cwav_free(cwav_codec_data* data); | |
| void cwav_reset(cwav_codec_data* data); | |
| int cwav_decode(cwav_codec_data* data, int16_t* buf, int samples_to_do); | |
| struct cwav_codec_data { | |
| FILE* sf; | |
| TCompressWaveData* cw; | |
| }; | |
| //------------------------------------------------------------------- | |
| cwav_codec_data* cwav_init(FILE* sf) { | |
| cwav_codec_data* data = NULL; | |
| data = malloc(sizeof(cwav_codec_data)); | |
| if (!data) goto fail; | |
| data->sf = reopen_streamfile(sf, 0); | |
| if (!data->sf) goto fail; | |
| data->cw = TCompressWaveData_Create(); | |
| if (!data->cw) goto fail; | |
| TCompressWaveData_LoadFromStream(data->cw, data->sf); | |
| cwav_reset(data); | |
| return data; | |
| fail: | |
| cwav_free(data); | |
| return NULL; | |
| } | |
| void cwav_free(cwav_codec_data* data) { | |
| if (!data) | |
| return; | |
| TCompressWaveData_Free(data->cw); | |
| close_streamfile(data->sf); | |
| free(data); | |
| } | |
| void cwav_reset(cwav_codec_data* data) { | |
| if (!data) | |
| return; | |
| /* reset internal flags */ | |
| TCompressWaveData_Stop(data->cw); | |
| TCompressWaveData_Play(data->cw, 0); | |
| } | |
| int cwav_decode(cwav_codec_data* data, int16_t* buf, int samples_to_do) { | |
| uint32_t Len = samples_to_do * sizeof(int16_t) * 2; //forced stereo | |
| int ok; | |
| ok = TCompressWaveData_Rendering(data->cw, buf, Len); | |
| if (!ok) goto fail; | |
| return 1; | |
| fail: | |
| return 0; | |
| } | |
| /* ************************************************************ */ | |
| /* MAIN */ | |
| /* ************************************************************ */ | |
| void put_s8(uint8_t * buf, int8_t i) { | |
| buf[0] = i; | |
| } | |
| void put_s16le(uint8_t * buf, int16_t i) { | |
| buf[0] = (i & 0xFF); | |
| buf[1] = i >> 8; | |
| } | |
| void put_s32le(uint8_t * buf, int32_t i) { | |
| buf[0] = (uint8_t)(i & 0xFF); | |
| buf[1] = (uint8_t)((i >> 8) & 0xFF); | |
| buf[2] = (uint8_t)((i >> 16) & 0xFF); | |
| buf[3] = (uint8_t)((i >> 24) & 0xFF); | |
| } | |
| /* make a RIFF header for .wav */ | |
| static size_t make_wav_header(uint8_t* buf, size_t buf_size, int32_t data_size, int32_t sample_rate, int channels) { | |
| size_t header_size; | |
| //data_size = sample_count * channels * sizeof(int16_t); | |
| header_size = 0x2c; | |
| if (header_size > buf_size) | |
| goto fail; | |
| memcpy(buf+0x00, "RIFF", 4); /* RIFF header */ | |
| put_s32le(buf+4, (int32_t)(header_size - 0x08 + data_size)); /* size of RIFF */ | |
| memcpy(buf+0x08, "WAVE", 4); /* WAVE header */ | |
| memcpy(buf+0x0c, "fmt ", 4); /* WAVE fmt chunk */ | |
| put_s32le(buf+0x10, 0x10); /* size of WAVE fmt chunk */ | |
| put_s16le(buf+0x14, 1); /* compression code 1=PCM */ | |
| put_s16le(buf+0x16, channels); /* channel count */ | |
| put_s32le(buf+0x18, sample_rate); /* sample rate */ | |
| put_s32le(buf+0x1c, sample_rate*channels*sizeof(int16_t)); /* bytes per second */ | |
| put_s16le(buf+0x20, (int16_t)(channels*sizeof(int16_t))); /* block align */ | |
| put_s16le(buf+0x22, sizeof(int16_t)*8); /* significant bits per sample */ | |
| memcpy(buf+0x24, "data", 0x04); /* WAVE data chunk */ | |
| put_s32le(buf+0x28, (int32_t)data_size); /* size of WAVE data chunk */ | |
| /* could try to add channel_layout, but would need to write WAVEFORMATEXTENSIBLE (maybe only if arg flag?) */ | |
| return header_size; | |
| fail: | |
| return 0; | |
| } | |
| #define CWAV_PATH_LIMIT 32767 | |
| typedef struct { | |
| char head[8]; | |
| uint32_t channels; | |
| uint32_t sample_rate; | |
| uint32_t bits; | |
| uint64_t pcm_size; | |
| uint64_t loop_start; | |
| uint64_t loop_end; | |
| uint8_t loop_count; | |
| uint8_t music_title_len; | |
| char music_title[256+1]; | |
| uint8_t music_artist_len; | |
| char music_artist[256+1]; | |
| } cwav_header_t; | |
| int main(int argc, const char* argv[]) { | |
| if(argc < 2) { | |
| printf("Usage: %s file.cwav\n", argv[0]); | |
| return 0; | |
| } | |
| FILE* infile = NULL; | |
| FILE* outfile = NULL; | |
| cwav_codec_data* data = NULL; | |
| uint8_t* src = NULL; | |
| uint8_t* dst = NULL; | |
| const char* inpath = argv[1]; | |
| infile = fopen(inpath, "rb"); | |
| if (!infile) { | |
| printf("can't open '%s'\n", inpath); | |
| goto fail; | |
| } | |
| char outpath[CWAV_PATH_LIMIT]; | |
| strcpy(outpath, inpath); | |
| strcat(outpath, ".wav"); | |
| outfile = fopen(outpath, "wb"); | |
| if (!outfile) { | |
| printf("can't write '%s'\n", outpath); | |
| goto fail; | |
| } | |
| cwav_header_t cwav = {0}; | |
| uint8_t header[0x948]; | |
| int bytes = fread(header, sizeof(int8_t), sizeof(header), infile); | |
| if (bytes != sizeof(header)) goto fail; | |
| memcpy(cwav.head, header + 0x00, 8); | |
| cwav.channels = get_u32le(header + 0x08); | |
| cwav.sample_rate = get_u32le(header + 0x0c); | |
| cwav.bits = get_u32le(header + 0x10); | |
| /* 0x14: diff table */ | |
| /* 0x414: null? */ | |
| cwav.pcm_size = get_u64le(header + 0x418); | |
| cwav.loop_start = get_u64le(header + 0x420); | |
| cwav.loop_end = get_u64le(header + 0x428); | |
| cwav.loop_count = get_u8 (header + 0x430); | |
| cwav.music_title_len = get_u8 (header + 0x431); /* max 127 */ | |
| memcpy(cwav.music_title, header + 0x432, cwav.music_title_len); | |
| cwav.music_artist_len = get_u8 (header + 0x4B1); /* max 127 */ | |
| memcpy(cwav.music_artist, header + 0x4B2, cwav.music_artist_len); | |
| /* 0x538: huffman table */ | |
| /* 0x948: data start */ | |
| if (strcmp(cwav.head, "CmpWave")) { | |
| printf("file is not CWAV\n"); | |
| goto fail; | |
| } | |
| data = cwav_init(infile); | |
| if (!data) { | |
| printf("can't open data\n"); | |
| return 0; | |
| } | |
| printf("decoding...\n"); | |
| uint32_t data_size = cwav.pcm_size / 4 * 4; /* fix trailing bytes */ | |
| int sample_count = data_size / sizeof(int16_t) / 2; | |
| uint8_t wav[0x100]; | |
| int wav_size = make_wav_header(wav, sizeof(wav), data_size, 44100, 2); | |
| fwrite(wav, sizeof(uint8_t), wav_size, outfile); | |
| int16_t* pcmbuf = malloc(sample_count * cwav.channels * sizeof(int16_t)); | |
| if (!pcmbuf) goto fail; | |
| #if 0 | |
| int ok = cwav_decode(data, pcmbuf, sample_count); | |
| if (!ok) { | |
| printf("CWAV: frame error 1\n"); | |
| goto fail; | |
| } | |
| fwrite(pcmbuf, sizeof(short), sample_count * 2, outfile); | |
| #else | |
| int done = 0; | |
| while (done < sample_count) { | |
| int samples_to_do = 1024; | |
| if (samples_to_do + done > sample_count) | |
| samples_to_do = sample_count - done; | |
| int ok = cwav_decode(data, pcmbuf, samples_to_do); | |
| if (!ok) { | |
| printf("CWAV: frame error 2\n"); | |
| goto fail; | |
| } | |
| fwrite(pcmbuf, sizeof(short), samples_to_do * 2, outfile); | |
| done += samples_to_do; | |
| } | |
| #endif | |
| fail: | |
| cwav_free(data); | |
| fclose(infile); | |
| fclose(outfile); | |
| free(src); | |
| free(dst); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment