//--------------------------------------------------------------------------------------
// ADPCM/M44/M30/P16 → WAV変換
// 変換処理：Oh!X 1992/6 p52-56(一部正しくない) + PCMCONV v1.8のX68_CONV.ASM + 自己解析
// WAVヘッダー：http://www.kk.iij4u.or.jp/~kondo/wave/
//--------------------------------------------------------------------------------------

#include <stdio.h>		// FILE
#include "adpcm.h"
#import "malloc2.h"
#import "libs.h"
#import "FileControl2.h"

//--------------------------------------------------------------------------------------

// ADPCMデータ～倍率変換表(表2)
// 符号ビット(bit3)+データ(bit2-0)
// (n*2)+1の値
static SWord baiTable[]={
	// +[0～7]
	1,
	3,
	5,
	7,
	9,
	11,
	13,
	15,
	// -[0～7]
	-1,
	-3,
	-5,
	-7,
	-9,
	-11,
	-13,
	-15,
};

// 予測指標～予測値変換表(表3)
static SWord scl_tbl[]={
// [0]～[48]
	16,	17,	19,	21,	23,
	25,	28,	31,	34,	37,
	41,	45,	50,	55,	60,
	66,	73,	80,	88,	97,
	107,118,130,143,157,
	173,190,209,230,253,
	279,307,337,371,408,
	449,494,544,598,658,
	724,796,875,963,1060,
	1166,1282,1411,1552,	// *16しても16bitを超えない
};

// ADPCMデータ～予測指標修正値 変換表(表4)
static SWord scl_chg[]=
{
	-1,
	-1,
	-1,
	-1,
	+2,
	+4,
	+6,
	+8,
	//
	-1,
	-1,
	-1,
	-1,
	+2,
	+4,
	+6,
	+8,
};

// ADPCMを16bit PCMに変換する
// リターン値:データサイズ(UWord単位)
SLong adpcm2pcm(UByte *adpcmbuff,SLong size,UWord *pcmbuff)
{
	UByte *p=adpcmbuff;
	UWord *q=pcmbuff;
	SWord s_level=0;
	SWord a_level=0;
	do {
		UByte c=*p++;
		printf("c=$%02x : ",c);
		/* X68000のADPCM MSM6258V
			4bit ADPCM
			下位4ビット→上位4ビットの順
			bit 3=符号(1=負,0=正)
			bit 2-0=データ
		*/
		int i;
		for (i=0;i<2;i++) {
			UByte al=(c&0x0f); // 下位4ビット
			printf("[$%02x",al);
			//
			SWord ax=baiTable[al];
			printf("*%d",ax);
			ax*=scl_tbl[s_level];
			ax/=8;
			printf("->%d",ax);
			//
			SWord dx=a_level;
			printf("/a_level=%d->",a_level);
			ax+=dx;
			a_level=ax; // a_levelは倍率を掛ける前の値で保存
			// 上限下限を付ける;最終的にpcmbufferに格納する値が符号付き16bitに納まるようにする
			if (ax>=2048) ax= 2048-1;
			if (ax<-2048) ax=-2048;
			printf("=%d]",a_level);
			*q++=(ax*16); // 符号付き16bitに納まる
			printf("←%d($%04x)/",ax*16,(UWord)(ax*16));
			//
			dx=s_level;
			printf("s_level=%2d->",dx);
			dx+=scl_chg[al];
			printf("(%d)",scl_chg[al]);
			if (dx<0)  dx=0;
			if (dx>48) dx=48;
			s_level=dx;
			printf("%2d\n        ",dx);
			//
			c>>=4;	// 上位4ビット
		}
		printf("\n");
	} while (--size>0);
	return (int)(q-pcmbuff);
}

// 変換後のPCMデータサイズを計算する(Byte)
// バッファーを確保するため
SLong getAdpcm2pcmSize(SLong size)
{
	// 4bitが1データで、1データ16ビット
	return (size*2*2);
}

//--------------------------------------------------------------------------------------
// WAVヘッダー用構造体
// ARMは86と同じメモリ格納順なのでこれでいいが、68系では書き換える必要がある。

// WAVEファイルヘッダーの作成
static
RIFF_WAVE_Chunk wavChunk={
	"RIFF",	// 		[4]なのでEOSは入らない(以下同様)
	0,		// .l	サイズは後で入れる
	"WAVE"	//		[4]
};

static
FormatChunk fmtChunk={
	"fmt ",	// [4]
	16,		// .l	sizeof(FormatChunk)-(chunkID+chunkSize)
	//
	1,		// .w	リニアPCM
	1,		// .w	チャンネル数；1=モノラル,2=ステレオ
	0,		// .l	サンプリングレートHz
	0,		// .l	データ速度 bytes/sec
	2*1,	// .w	1データ要素サイズ ; bit数(2=16bit,1=モノラル)
	16,		// .w	ビット数 (16bit)
};

static
DataChunk dataChunk={
	"data",	// [4]
	0		// .l	波形データのバイト数
};

// WAVヘッダー部分のサイズ
#define SZ_WAV_HEADER	(sizeof(wavChunk)+sizeof(fmtChunk)+sizeof(dataChunk))

// WAVヘッダーの確定
static void setWavHeader(SLong pcmsize,ULong rate)
{
	printFunc();
	wavChunk.chunkSize		 =4+sizeof(fmtChunk)+sizeof(dataChunk)+pcmsize; // 4='WAVE'
	fmtChunk.dwSamplesPerSec =rate;							// サンプリングレートHz(15.6KHz=15600)
	nslog(@"rate=%d",(int)rate);
	fmtChunk.dwAvgBytesPerSec=rate*fmtChunk.wBlockAlign;	// データ速度 bytes/sec
	if (pcmsize>=0) {
	dataChunk.chunkSize		 =pcmsize;						// 波形データのバイト数
	}
}

static UByte *_memcpy(UByte *des,UByte *src,size_t size)
{
	while (size-->0) *des++=*src++;
	return (des);
}

// WAVヘッダーを情報書き込む
UByte *storeWavHeader(UByte *pcmbuff,SLong pcmsize,ULong rate)
{
	printFunc();
	
	// WAVヘッダーの確定
	setWavHeader(pcmsize,rate);
	// WAVヘッダーをメモリに書き込む
	pcmbuff=_memcpy(pcmbuff,(UByte *)&wavChunk 	,sizeof(wavChunk));
	pcmbuff=_memcpy(pcmbuff,(UByte *)&fmtChunk 	,sizeof(fmtChunk));
	pcmbuff=_memcpy(pcmbuff,(UByte *)&dataChunk	,sizeof(dataChunk));
	// この後にPCMデータが書き込まれる
	return (pcmbuff);
}

// pcmデータをwavファイルに書き出す
int writeWav2File(UByte *pcmbuff,SLong pcmsize,char *wavfname,int rate)
{
	// WAVヘッダーの作成
	setWavHeader(pcmsize,rate);
	//
	FILE *fp=fopen(wavfname,"wb");
	if (fp==NULL) {
		printf("File can not make:%s\n",wavfname);
		return(1);
	}
	fwrite(&wavChunk ,sizeof(wavChunk) ,1,fp);
	fwrite(&fmtChunk ,sizeof(fmtChunk) ,1,fp);
	fwrite(&dataChunk,sizeof(dataChunk),1,fp);
	fwrite(pcmbuff	 ,pcmsize		   ,1,fp);
	fclose(fp);
	return(0);
}

//--------------------------------------------------------------------------------------

// サンプリングレートに変換(Hz)
// rateNo : サンプリングレート番号
int adpcmRateNo2Rate(int rateNo)
{
	static int rateTable[]={
		// サンプリングレート Hz
		15600,
		10400,
		7800,
		5200,
		3900,
	};
#define NO_RATETABLE	(sizeof(rateTable)/sizeof(int)) // テーブル数
	if (rateNo<0||rateNo>=NO_RATETABLE) rateNo=0;
	return rateTable[rateNo]; // サンプリングレートに変換(Hz)
}

// ADPCMファイルをWAVフェイル形式でメモリに読み込む
// rateNo   : サンプリングレート番号
// リターン値 : 確保されたバッファーアドレス
// bufsize  : バッファーサイズ
UByte *adpcmFile2WavMem(char *adpcmfname,int rateNo,SLong *bufsize)
{
	// サンプリングレートに変換(Hz)
	int rate=adpcmRateNo2Rate(rateNo);

	FILE *fp;
	fp=fopen(adpcmfname,"rb");
	if (fp==NULL) {
		printf("File not found:%s\n",adpcmfname);
		return (NULL);
	}
	SLong size=(SLong)filelength(fp);
	printf("filesize=%d\n",(int)size);
	//
	UByte *adpcmbuff=malloc2(size);
	if (adpcmbuff==NULL) {
		fclose(fp);
		printf("Memory can not alloc(1)\n");
		return (NULL);
	}
	// ADPCMデータをメモリに読み込む
	// それほど大きくないはずなので
	fread(adpcmbuff,size,1,fp);
	fclose(fp);

	// 変換後のデータサイズを計算する
	SLong pcmsize=getAdpcm2pcmSize(size);
	printf("PCM coverted size=%d(B)\n",(int)pcmsize);
	// WAVヘッダー+PCMデータ部用のバッファーを確保する
	*bufsize=SZ_WAV_HEADER+pcmsize;
	UByte *pcmbuff=malloc2(SZ_WAV_HEADER+pcmsize);
	if (pcmbuff==NULL) {
		free2(adpcmbuff);
		printf("Memory can not alloc(2)\n");
		return (NULL);
	}
	
	// WAVヘッダーを情報書き込む
	UByte *b=storeWavHeader(pcmbuff,pcmsize,rate);
	
	// wavファイルをバッファーに入れたのと同じにするためメモリに格納する
	adpcm2pcm(adpcmbuff,size,(UWord *)b);
	// ADPCMバッファーを解放
	free2(adpcmbuff);
	//
	return(pcmbuff);
}

//--------------------------------------------------------------------------------------

// M44/M30/P16ファイルをWAV形式でメモリに読み込む
// 実際には、16bitモノラルであればサンプリングレートは任意に指定可能
// rate     : 元ファイルサンプリングレート(Hz) : M44=44100 , M30=30000 , P16=15625
// octave	: +1,2,3,4:上げる / -1,2,3,4:下げる
// multiple	: 音声倍率 ; P16/M30は音量が小さいので、>=2で設定した方が良い(P16/M30は実質12bitなので4ビット分の嵩上げ=*16が良さそう)
//			  聴覚上の倍率ではなく、計算上の倍率。
// bufsize  : バッファーサイズ
// リターン値	: 確保されたバッファーアドレス
UByte *pcmFile2WavMem(char *pcmfname,int rate,int octave,int multiple,SLong *bufsize)
{
	FILE *fp;
	fp=fopen(pcmfname,"rb");
	if (fp==NULL) {
		printf("File not found:%s\n",pcmfname);
		return (NULL);
	}
	// M44/M30/P16はベタファイルなのでファイルサイズ＝データサイズである
	SLong pcmsize=(SLong)filelength(fp);
	// 変換後のデータサイズ
	SLong size=(octave<=0? pcmsize*(-octave+1):pcmsize/(octave+1));
	printf("body size=%d->%d\n",(int)pcmsize,(int)size);
	*bufsize=SZ_WAV_HEADER+size; // できあがるデータのサイズ

	//-------------------------------
	// 書き出し用バッファーの確保
	// 場合によっては読み込みバッファーにもなる
	int largeSize=(size>=pcmsize? size:pcmsize);
	// WAVヘッダー+PCMデータ部用のバッファーを確保する
	UByte *pcmbuff=malloc2(SZ_WAV_HEADER+largeSize);
	if (pcmbuff==NULL) {
		printf("Memory can not alloc(2)\n");
		return (NULL);
	}

	// オンメモリのWAVヘッダー部に情報を書き込む
	UByte *b=storeWavHeader(pcmbuff,size,rate);
	
	//-------------------------------
	// M44/M30/P16はビッグエンディアンなのでリトルエンディアンに直す
	typedef union {
		UWord	w;
		UByte	b[sizeof(UWord)];
	} WB;

	if (size<=pcmsize) { // octave>=0
		// 元と同じか小さくなるとき(octave<=0)
		printf("Octave up=%d\n",octave);
		
		// 元のバッファーをそのまま使う

		// ヘッダー後部に元PCMデータをメモリに読み込む
		fread(b,pcmsize,1,fp);
		fclose(fp);

		// エンディアン変換＋音量変換＋オクターブ変換(上昇)
		WB *rwbp=(WB *)b;	// 読み出し位置
		WB *wwbp=rwbp;		// 書き込み位置
		printf("rwbp=%p\nwwbp=%p\n",rwbp,wwbp);
		while ((size-=sizeof(UWord))>0) {
			WB wb=*rwbp;
			rwbp+=(octave+1);
			UByte c=wb.b[0];
			wb.b[0]=wb.b[1];
			wb.b[1]=c;
			wb.w*=multiple; // 音量調整
			*wwbp++=wb;
		}
		printf("wwbp=%p\n",wwbp);
	} else {
		// 元より大きくなるとき(octave<0)
		printf("Octave down=%d\n",octave);
		
		// 読み込みバッファーを別途確保
		UByte *rbuff=malloc2(pcmsize);
		if (rbuff==NULL) {
			printf("Memory can not alloc(3)\n");
			return (NULL);
		}
		
		// 元PCMデータをメモリに読み込む
		fread(rbuff,pcmsize,1,fp);
		fclose(fp);
		
		// エンディアン変換＋音量変換＋オクターブ変換(下降)
		WB *rwbp=(WB *)rbuff;	// 読み出し位置
		WB *wwbp=(WB *)b;		// 書き込み位置
		printf("rwbp=%p\nwwbp=%p\n",rwbp,wwbp);
		while ((pcmsize-=sizeof(UWord))>0) {
			WB wb=*rwbp++;
			UByte c=wb.b[0];
			wb.b[0]=wb.b[1];
			wb.b[1]=c;
			wb.w*=multiple; // 音量調整
			*wwbp++=wb;
			int i;
			for (i=0;i<-octave;i++) *wwbp++=wb;
		}
		printf("wwbp=%p:size=%d\n",wwbp,(int)wwbp-(int)b);
		// 読み込みバッファーは解放してしまう
		free2(rbuff);
	}
	printf("convert end\n");
	//
	return(pcmbuff);
}

//--------------------------------------------------------------------------------------

#if 0
#import "music.h"

int main(int argc,char *argv[])
{
	if (argc<2) {
//		printf("adpcm adpcm-file wav-file\n");
		printf("adpcm m44-file\n");
//		printf("adpcm p16-file wav-file\n");
		return(1);
	}
	
	char fname[512];
	SLong bufsize;

	sprintf(fname,"%s.m44",argv[1]);
	printf("inFile:%s\n",fname);
//	UByte *wavBuff=adpcmFile2WavMem(fname,0,&bufsize); // PCM
//	UByte *wavBuff=pcmFile2WavMem(fname,15625, 0,16,&bufsize); // P16/M30
	UByte *wavBuff=pcmFile2WavMem(fname,44100, 0, 1,&bufsize); // M44
	printf("PCM converted size=%d(B)\n",(int)bufsize);

	float rates[MUSIC_OCTAVES][MUSIC_SCALES];
	makeSamplingTable(rates);

	char *note[MUSIC_SCALES]={
							"C",
							"C#",
							"D",
							"D#",
							"E",
							"F",
							"F#",
							"G",
							"G#",
							"A",	// 基準
							"A#",
							"B"
						};
	
	int octave;
	for (octave=0;octave<=8;octave++) {
		int scale;
		for (scale=0;scale<MUSIC_SCALES;scale++) {
			sprintf(fname,"%s_O%c_%s.wav",argv[1],'0'+octave,note[scale]);
			printf("%2s : %fHz -> %s\n",note[scale],rates[octave][scale],fname);
			
			// ヘッダー部を書き換える
			storeWavHeader(wavBuff,-1,(int)rates[octave][scale]);
			
			FILE *fp=fopen(fname,"wb");
			if (fp==NULL) {
				printf("File can not make:%s\n",argv[2]);
				return (2);
			}
			fwrite(wavBuff,bufsize,1,fp);
			fclose(fp);
		}
		printf("----------------------\n");
	}
	free(wavBuff);
	//
	return(0);
}
#endif
