diff music2/nwatowav.cc @ 0:223b71206888

Initial import
author thib
date Fri, 01 Aug 2008 16:32:45 +0000
parents
children 01aa5ddf7dc8
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/music2/nwatowav.cc
@@ -0,0 +1,866 @@
+/* nwatowav : Visual Arts 系のゲームのデモで使われる nwa 形式の
+**            ファイルを wav 形式に変換する
+**
+**     compile : gcc -O2 -o nwatowav nwatowav.cc
+**     usage : nwatowav [nwa-file [outfile]]
+**	       nwatowav [nwk-file [outfile]]
+**     example : nwatowav HM06.nwa HM06.wav	# BGMファイル。HM06.wav に展開される
+**		 nwatowav z2813.nwk z2813	# 音声ファイル。 z2813-100.wav などのファイル名で展開される
+**		 nwatowav z0513.ovk z0513	# 音声ファイル。 z0513-100.ogg などのファイル名で展開される
+**
+**
+** 2004.5.19 小松さん<s1100089@u-aizu.ac.jp> から CLANNAD の無圧縮nwa形式に対応する
+**           パッチをいただいたので、適用しました。ありがとうございます。
+** 2006.9.10 「智代アフター」の音声ファイル形式 (complevel = 5) をサポート
+**	     .nwk という拡張子を持つファイルを受け取ると音声ファイルとして
+**	     解釈、分割して展開するようにする
+** 2007.7.28 「リトルバスターズ!」の音声ファイル形式 (*.ovk; ogg 連結型)
+**		をサポート。.ovk という拡張子をもつファイルを受け取ると
+**		音声ファイルとして解釈、分割して展開するようにする
+**	     「リトルバスターズ!」のBGMファイルに多量のノイズが乗る問題も
+**	      解決(ランレングス圧縮の処理が不必要だった)
+*/
+
+/*
+ * Copyright 2001-2007  jagarl / Kazunori Ueno <jagarl@creator.club.ne.jp>
+ * All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ *
+ * このプログラムの作者は jagarl です。
+ *
+ * このプログラム、及びコンパイルによって生成したバイナリは
+ * プログラムを変更する、しないにかかわらず再配布可能です。
+ * その際、上記 Copyright 表示を保持するなどの条件は課しま
+ * せん。対応が面倒なのでバグ報告を除き、メールで連絡をする
+ * などの必要もありません。ソースの一部を流用することを含め、
+ * ご自由にお使いください。
+ *
+ * THIS SOFTWARE IS PROVIDED BY KAZUNORI 'jagarl' UENO ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL KAZUNORI UENO BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * 
+ */
+
+/********************************************
+**
+**	nwa フォーマットについて
+**
+**		全体としては以下の構造を持つ
+**		NWA Header
+**		data offset index
+**		data block<0>
+**		data block<1>
+**		...
+**		data block<N>
+**
+**	NWA Header: ファイル先頭から 44 bytes
+**		magic number などはないのでnwa ファイルかは
+**		データの整合性から判断する必要がある
+**		データは全て little endian で、
+**		short(signed 2byte)または int(signed 4byte) である。
+**
+**		+00 short   channel 数(1/2)
+**		+02 short   sample 一つあたりの bit 数(16)
+**		+04 int     周波数(一秒あたりのデータ数)
+**		+08 int     圧縮レベル:-1~5.2で最小のデータ、0で最大の復元度(-1は無圧縮rawデータとみなされる)
+**		+12 int     ?
+**		+16 int     ブロック数
+**		+20 int     展開後のデータの大きさ(バイト単位)
+**		+24 int     圧縮時のデータの大きさ(nwa ファイルの大きさ。バイト単位)
+**		+28 int     サンプル数:展開後のデータ数(16bit dataなら short 単位==サンプル単位のデータの大きさ)
+**		+32 int     データ1ブロックを展開した時のサンプル単位のデータ数
+**		+36 int     最終ブロックを展開した時のサンプル単位のデータ数
+**		+40 int     ?
+**		
+**	data offset index
+**		全ブロック数 x 4 byte のデータ
+**		それぞれ int のデータが全ブロック数続いている
+**
+**		データブロックの先頭を指すファイル先頭からの位置(オフセット)
+**		が格納されている
+**
+**	data block
+**		長さは可変。展開することで一定の大きさをもつデータに展開される。
+**		データはDPCM形式。元 PCM データが a,b,c ならば (a),b-a, c-b と
+**		いった差分データが、仮数3-5bit,指数3bitの形式で保存されている。
+**		結果的に、16bit のデータが多くの場合 6-8bit で格納される。
+**		仮数のビット数は圧縮レベル0で5bit、圧縮レベル2で3bitとなる。
+**		以下、圧縮レベル2の場合について話を進める。
+**		モノラルの場合:
+**			+00 short  ブロック内の最初のデータ
+**			+02- bit stream
+**		ステレオの場合:
+**			+00 short  左(?)チャンネルの最初のデータ
+**			+02 short  右(?)チャンネルの最初のデータ
+**			+04- bit stream
+**
+**		差分データの精度が高くないので各ブロックの先頭で
+**		正確なデータにより補正される(?)
+**
+**	bit stream
+**		little endian
+**		+0 - +2 : 指数
+**		+3 - +5 : 仮数
+**		の形式。例えば a,b,c という8bitデータがあれば、
+**		a&0x07 : データ1の指数
+**		(a>>3)&0x07 : データ1の仮数(signed ; 
+**		((b<<2)|(a>>6))&0x07 : データ2の指数
+**		(b>>1)&0x07 : データ2の仮数
+**		となる。
+**		ただし、指数の値により仮数のbit数が変化することがある。
+**		指数 = 1 - 6 の場合:
+**			a=指数、b=仮数、p=前のデータとして、今回のデータd は
+**			bの2bit目が立っている場合:
+**				d = p - (b&3)<<(4+a)
+**			立ってない場合:
+**				d = p + (b&3)<<(4+a)
+**		指数 = 0 の場合:仮数は存在しない(データは3bitとなる)
+**			d = p
+**			「智代アフター」の音声ファイル (complevel == 5) ではランレングス圧縮用に使われている。
+**		指数 = 7
+**			次の bit が立っている場合:
+**				d = 0 (現在未使用)
+**				(データは4bitとなる)
+**			次の bit が立ってない場合:
+**				complevel = 0,1,2:
+**				   仮数 b = 6bit
+**				   b の 5bit 目が立っている場合:
+**					d = p - (b&0x1f)<<(4+7)
+**				   立ってない場合:
+**					d = p + (b&0x1f)<<(4+7)
+**				   (データは10bitとなる)
+**				complevel = 3,4,5:
+**				   仮数 b = 8bit
+**				   b の 7bit 目が立っている場合:
+**					d = p - (b&0x7f)<<9
+**				   立ってない場合:
+**					d = p + (b&0x1f)<<9
+**				   (データは10bitとなる)
+**
+**		圧縮レベルが異なる場合、たとえば圧縮レベル==0で
+**			指数==1~6でdの最上位bitが立っている場合
+**				d = p - (b&0x0f)<<(2+a)
+**			指数==7でdの最上位bitが立っている場合
+**				d = p - (b&0x7f)<<(2+7)
+**				(b : 8bitなのでデータは12bitとなる)
+**		のように、精度だけが変化するようになっている。
+**
+**	ヘッダ読み込みについてはNWAData::ReadHeader()参照
+**	bit stream からのデータ展開については NWADecode()参照
+**************************************************************
+*/
+
+// #define NDEBUG /* なぜか assertが入った方が速い、、、 */
+
+#include<stdio.h>
+#include<stdlib.h>
+#include<unistd.h>	// for isatty() function
+#include<sys/stat.h>
+#include<string.h>
+
+
+#ifdef WORDS_BIGENDIAN
+#error Sorry, This program does not support BIG-ENDIAN system yet.
+/* もし big endian のシステムに対応させる場合
+** 以下の *_little_endian_* 及び
+** getbits() 関数を変更する必要がある
+*/
+#endif
+
+inline int read_little_endian_int(const char* buf) {
+	return *(int*)buf;
+}
+
+inline int read_little_endian_short(const char* buf) {
+	return *(short*)buf;
+}
+
+inline int write_little_endian_int(char* buf, int number) {
+	int c = *(int*)buf; *(int*)buf = number; return c;
+}
+
+inline int write_little_endian_short(char* buf, int number) {
+	int c = *(short*)buf; *(short*)buf = number; return c;
+}
+inline int getbits(const char*& data, int& shift, int bits) {
+	if (shift > 8) { data++; shift-=8;}
+	int ret = read_little_endian_short(data)>>shift;
+	shift += bits;
+	return ret & ((1<<bits)-1); /* mask */
+}
+
+/* 指定された形式のヘッダをつくる */
+const char* make_wavheader(int size, int channels, int bps, int freq) {
+	static char wavheader[0x2c] = {
+		'R','I','F','F',
+		0,0,0,0, /* +0x04: riff size*/
+		'W','A','V','E',
+		'f','m','t',' ',
+		16,0,0,0, /* +0x10 : fmt size=0x10 */
+		1, 0,    /* +0x14 : tag : pcm = 1 */
+		2, 0,    /* +0x16 : channels */
+		0,0,0,0, /* +0x18 : samples per second */
+		0,0,0,0, /* +0x1c : average bytes per second */
+		0,0,     /* +0x20 : block alignment */
+		0,0,     /* +0x22 : bits per sample */
+		'd','a','t','a',
+		0,0,0,0};/* +0x28 : data size */
+	write_little_endian_int(wavheader+0x04, size+0x24);
+	write_little_endian_int(wavheader+0x28, size);
+	write_little_endian_short(wavheader+0x16, channels);
+	write_little_endian_short(wavheader+0x22, bps);
+	write_little_endian_int(wavheader+0x18, freq);
+	int byps = (bps+7)>>3;
+	write_little_endian_int(wavheader+0x1c, freq*byps*channels);
+	write_little_endian_short(wavheader+0x20, byps*channels);
+	return wavheader;
+}
+
+/* NWA の bitstream展開に必要となる情報 */
+class NWAInfo {
+	int channels;
+	int bps;
+	int complevel;
+	bool use_runlength;
+public:
+	NWAInfo(int c,int b,int cl) {
+		channels=c;
+		bps=b;
+		complevel=cl;
+		use_runlength = false;
+		if (cl == 5) {
+			use_runlength = true; // Tomoyo After (.nwk koe file)
+			if (channels == 2) use_runlength = false; // BGM*.nwa in Little Busters!
+		}
+	}
+	int Channels(void) const{return channels;}
+	int Bps(void) const { return bps;}
+	int CompLevel(void) const { return complevel;}
+	int UseRunLength(void) const { return use_runlength; }
+};
+
+template<class NWAI> void NWADecode(const NWAI& info,const char* data, char* outdata, int datasize, int outdatasize) {
+	int d[2];
+	int i,j;
+	int shift = 0;
+	const char* dataend = data+datasize;
+	/* 最初のデータを読み込む */
+	if (info.Bps() == 8) {d[0] = *data++; datasize--;}
+	else /* info.Bps() == 16 */ {d[0] = read_little_endian_short(data); data+=2; datasize-=2;}
+	if (info.Channels() == 2) {
+		if (info.Bps() == 8) {d[1] = *data++; datasize--;}
+		else /* info.Bps() == 16 */ {d[1] = read_little_endian_short(data); data+=2; datasize-=2;}
+	}
+	int dsize = outdatasize / (info.Bps()/8);
+	int flip_flag = 0; /* stereo 用 */
+	int runlength = 0;
+	for (i=0; i<dsize; i++) {
+		if (data >= dataend) break;
+		if (runlength == 0) { // コピーループ中でないならデータ読み込み
+			int type = getbits(data, shift, 3);
+			/* type により分岐:0, 1-6, 7 */
+			if (type == 7) {
+				/* 7 : 大きな差分 */
+				/* RunLength() 有効時(CompLevel==5, 音声ファイル) では無効 */
+				if (getbits(data, shift, 1) == 1) {
+					d[flip_flag] = 0; /* 未使用 */
+				} else {
+					int BITS, SHIFT;
+					if (info.CompLevel() >= 3) {
+						BITS = 8;
+						SHIFT = 9;
+					} else {
+						BITS = 8-info.CompLevel();
+						SHIFT = 2+7+info.CompLevel();
+					}
+					const int MASK1 = (1<<(BITS-1));
+					const int MASK2 = (1<<(BITS-1))-1;
+					int b = getbits(data, shift, BITS);
+					if (b&MASK1)
+						d[flip_flag] -= (b&MASK2)<<SHIFT;
+					else
+						d[flip_flag] += (b&MASK2)<<SHIFT;
+				}
+			} else if (type != 0) {
+				/* 1-6 : 通常の差分 */
+				int BITS, SHIFT;
+				if (info.CompLevel() >= 3) {
+					BITS = info.CompLevel()+3;
+					SHIFT = 1+type;
+				} else {
+					BITS = 5-info.CompLevel();
+					SHIFT = 2+type+info.CompLevel();
+				}
+				const int MASK1 = (1<<(BITS-1));
+				const int MASK2 = (1<<(BITS-1))-1;
+				int b = getbits(data, shift, BITS);
+				if (b&MASK1)
+					d[flip_flag] -= (b&MASK2)<<SHIFT;
+				else
+					d[flip_flag] += (b&MASK2)<<SHIFT;
+			} else { /* type == 0 */
+				/* ランレングス圧縮なしの場合はなにもしない */
+				if (info.UseRunLength() == true) {
+					/* ランレングス圧縮ありの場合 */
+					runlength = getbits(data,shift,1);
+					if (runlength==1) {
+						runlength = getbits(data,shift,2);
+						if (runlength == 3) {
+							runlength = getbits(data, shift, 8);
+						}
+					}
+				}
+			}
+		} else {
+			runlength--;
+		}
+		if (info.Bps() == 8) {
+			*outdata++ = d[flip_flag];
+		} else {
+			write_little_endian_short(outdata, d[flip_flag]);
+			outdata += 2;
+		}
+		if (info.Channels() == 2) flip_flag ^= 1; /* channel 切り替え */
+	}
+	return;
+};
+
+class NWAData {
+public:
+	int channels;
+	int bps; /* bits per sample */
+	int freq; /* samples per second */
+private:
+	int complevel; /* compression level */
+	int dummy; /* ? : 0x00 */
+public:
+	int blocks; /* block count */
+	int datasize; /* all data size */
+private:
+	int compdatasize; /* compressed data size */
+	int samplecount; /* all samples */
+	int blocksize; /* samples per block */
+	int restsize; /* samples of the last block */
+	int dummy2; /* ? : 0x89 */
+	int curblock;
+	int* offsets;
+	int offset_start;
+	int filesize;
+	char* tmpdata;
+public:
+	void ReadHeader(FILE* in, int file_size=-1);
+	int CheckHeader(void); /* false: invalid true: valid */
+	NWAData(void) {
+		offsets = 0;
+		tmpdata = 0;
+	}
+	~NWAData(void) {
+		if (offsets) delete[] offsets;
+		if (tmpdata) delete[] tmpdata;
+	}
+	int BlockLength(void) {
+		if (complevel != -1) {
+			if (offsets == 0) return false;
+			if (tmpdata == 0) return false;
+		}
+		return blocksize * (bps/8);
+	}
+	/* data は BlockLength 以上の長さを持つこと
+	** 返り値は作成したデータの長さ。終了時は 0。
+	** エラー時は -1
+	*/
+	int Decode(FILE* in, char* data, int& skip_count);
+	void Rewind(FILE* in);
+};
+
+void NWAData::ReadHeader(FILE* in, int _file_size) {
+	char header[0x2c];
+	struct stat sb;
+	int i;
+	if (offsets) delete[] offsets;
+	if (tmpdata) delete[] tmpdata;
+	offsets = 0;
+	tmpdata = 0;
+	filesize = 0;
+	offset_start = ftell(in);
+	if (offset_start == -1) offset_start = 0;
+	if (_file_size != -1) filesize = _file_size;
+	curblock = -1;
+	/* header 読み込み */
+	if (in == 0 || feof(in) || ferror(in)) {
+		fprintf(stderr,"invalid stream\n");
+		return;
+	}
+	fread(header, 0x2c, 1, in);
+	if (feof(in) || ferror(in)) {
+		fprintf(stderr,"invalid stream\n");
+		return;
+	}
+	channels = read_little_endian_short(header+0x00);
+	bps = read_little_endian_short(header+0x02);
+	freq = read_little_endian_int(header+0x04);
+	complevel = read_little_endian_int(header+0x08);
+	dummy = read_little_endian_int(header+0x0c);
+	blocks = read_little_endian_int(header+0x10);
+	datasize = read_little_endian_int(header+0x14);
+	compdatasize = read_little_endian_int(header+0x18);
+	samplecount = read_little_endian_int(header+0x1c);
+	blocksize = read_little_endian_int(header+0x20);
+	restsize = read_little_endian_int(header+0x24);
+	dummy2 = read_little_endian_int(header+0x28);
+	if (complevel == -1) {	/* 無圧縮rawデータ */
+		/* 適当に決め打ちする */
+		blocksize = 65536;
+		restsize = (datasize % (blocksize * (bps/8))) / (bps/8);
+		blocks = datasize / (blocksize * (bps/8)) + (restsize > 0 ? 1 : 0);
+	}
+	if (blocks <= 0 || blocks > 1000000) {
+		/* 1時間を超える曲ってのはないでしょ*/
+		fprintf(stderr,"too large blocks : %d\n",blocks);
+		return;
+	}
+	/* regular file なら filesize 読み込み */
+	if (filesize == 0 && fstat(fileno(in), &sb)==0 && (sb.st_mode&S_IFMT) == S_IFREG) {
+		int pos = ftell(in);
+		fseek(in, 0, 2);
+		filesize = ftell(in);
+		fseek(in, pos, 0);
+		if (pos+blocks*4 >= filesize) {
+			fprintf(stderr,"offset block is not exist\n");
+			return;
+		}
+	}
+	if (complevel == -1) return;
+	/* offset index 読み込み */
+	offsets = new int[blocks];
+	fread(offsets, blocks, 4, in);
+	for (i=0; i<blocks; i++) {
+		offsets[i] = read_little_endian_int((char*)(offsets+i));
+	}
+	if (feof(in) || ferror(in)) {
+		fprintf(stderr,"invalid stream\n");
+		delete[] offsets;
+		offsets = 0;
+		return;
+	}
+	return;
+}
+void NWAData::Rewind(FILE* in) {
+	curblock = -1;
+	fseek(in, 0x2c, 0);
+	if (offsets) fseek(in, blocks*4, 1);
+}
+int NWAData::CheckHeader(void) {
+	if (complevel != -1 && offsets == 0) return false;
+	/* データそのもののチェック */
+	if (channels != 1 && channels != 2) {
+		fprintf(stderr,"This program only supports mono / stereo data : data have %d channels.\n",channels);
+		return false;
+	}
+	if (bps != 8 && bps != 16) {
+		fprintf(stderr,"This program only supports 8 / 16bit data : data is %d bits\n",bps);
+		return false;
+	}
+	if (complevel == -1) {
+		int byps = bps/8; /* bytes per sample */
+		if (datasize != samplecount*byps) {
+			fprintf(stderr,"invalid datasize : datasize %d != samplecount %d * samplesize %d\n",datasize,samplecount,byps);
+			return false;
+		}
+		if (samplecount != (blocks-1)*blocksize+restsize ) {
+			fprintf(stderr,"total sample count is invalid : samplecount %d != %d*%d+%d(block*blocksize+lastblocksize).\n",samplecount,blocks-1,blocksize,restsize);
+			return false;
+		}
+		else
+			return true;
+	}
+	//if (complevel < 0 || complevel > 2) {
+	if (complevel < 0 || complevel > 5) {
+		fprintf(stderr,"This program only supports -1,0,1,2 compression level : the level of data is %d\n",complevel);
+		return false;
+	}
+	/* 整合性チェック */
+	if (filesize != 0 && filesize != compdatasize) {
+		fprintf(stderr,"file size is invalid : %d != %d\n",filesize,compdatasize);
+		return false;
+	}
+	if (offsets[blocks-1] >= compdatasize) {
+		fprintf(stderr,"the last offset overruns the file.\n");
+		return false;
+	}
+	int byps = bps/8; /* bytes per sample */
+	if (datasize != samplecount*byps) {
+		fprintf(stderr,"invalid datasize : datasize %d != samplecount %d * samplesize %d\n",datasize,samplecount,byps);
+		return false;
+	}
+	if (samplecount != (blocks-1)*blocksize+restsize ) {
+		fprintf(stderr,"total sample count is invalid : samplecount %d != %d*%d+%d(block*blocksize+lastblocksize).\n",samplecount,blocks-1,blocksize,restsize);
+		return false;
+	}
+	tmpdata = new char[blocksize*byps*2]; /* これ以上の大きさはないだろう、、、 */
+	return true;
+}
+
+class NWAInfo_sw2 {
+public:
+	int Channels(void) const{return 2;}
+	int Bps(void) const { return 16;}
+	int CompLevel(void) const { return 2;}
+	int UseRunLength(void) const { return false; }
+};
+int NWAData::Decode(FILE* in, char* data, int& skip_count) {
+	if (complevel == -1) {		/* 無圧縮時の処理 */
+		if (feof(in) || ferror(in)) return -1;
+		if (curblock == -1) {
+			/* 最初のブロックなら、wave header 出力 */
+			memcpy(data, make_wavheader(datasize, channels, bps, freq), 0x2c);
+			curblock++;
+			fseek(in, offset_start + 0x2c, SEEK_SET);
+			return 0x2c;
+		}
+		if (skip_count > blocksize/channels) {
+			skip_count -= blocksize/channels;
+			fseek(in, blocksize*(bps/8), SEEK_CUR);
+			curblock++;
+			return -2;
+		}
+		if (curblock < blocks) {
+			int readsize = blocksize;
+			if (skip_count) {
+				fseek(in, skip_count*channels*(bps/8), SEEK_CUR);
+				readsize -= skip_count * channels;
+				skip_count = 0;
+			}
+			int err = fread(data, 1, readsize * (bps/8), in);
+			curblock++;
+			return err;
+		}
+		return -1;
+	}
+	if (offsets == 0 || tmpdata == 0) return -1;
+	if (blocks == curblock) return 0;
+	if (feof(in) || ferror(in)) return -1;
+	if (curblock == -1) {
+		/* 最初のブロックなら、wave header 出力 */
+		memcpy(data, make_wavheader(datasize, channels, bps, freq), 0x2c);
+		curblock++;
+		return 0x2c;
+	}
+	/* 今回読み込む/デコードするデータの大きさを得る */
+	int curblocksize, curcompsize;
+	if (curblock != blocks-1) {
+		curblocksize = blocksize * (bps/8);
+		curcompsize = offsets[curblock+1] - offsets[curblock];
+		if (curblocksize >= blocksize*(bps/8)*2) return -1; // Fatal error
+	} else {
+		curblocksize = restsize * (bps/8);
+		curcompsize = blocksize*(bps/8)*2;
+	}
+	if (skip_count > blocksize/channels) {
+		skip_count -= blocksize/channels;
+		fseek(in, curcompsize, SEEK_CUR);
+		curblock++;
+		return -2;
+	}
+	/* データ読み込み */
+	fread(tmpdata, 1, curcompsize, in);
+	/* 展開 */
+	if (channels == 2 && bps == 16 && complevel == 2) {
+		NWAInfo_sw2 info;
+		NWADecode(info, tmpdata, data, curcompsize, curblocksize);
+	} else {
+		NWAInfo info(channels, bps, complevel);
+		NWADecode(info, tmpdata, data, curcompsize, curblocksize);
+	}
+	int retsize = curblocksize;
+	if (skip_count) {
+		int skip_c = skip_count * channels * (bps/8);
+		retsize -= skip_c;
+		memmove(data, data+skip_c, skip_c);
+		skip_count = 0;
+	}
+	curblock++;
+	return retsize;
+}
+
+#ifdef USE_MAIN
+
+void conv(FILE* in, FILE* out, int skip_count, int in_size = -1) {
+	NWAData h;
+	h.ReadHeader(in, in_size);
+	h.CheckHeader();
+	int bs = h.BlockLength();
+	char* d = new char[bs];
+	int err;
+	while( (err=h.Decode(in, d, skip_count)) != 0) {
+		if (err == -1) break;
+		if (err == -2) continue;
+		fwrite(d, err, 1, out);
+	}
+	return;
+}
+int main(int argc, char** argv) {
+	int skip_count = 0;
+
+	if (argc > 2 && strcmp(argv[1], "--skip") == 0) {
+		skip_count = atoi(argv[2]);
+		argc -= 2;
+		argv[1] = argv[3];
+		argv[2] = argv[4];
+	}
+	if (argc != 2 && argc != 3) {
+		fprintf(stderr,"usage : nwatowav [inputfile [outputfile]]\n");
+		return -1;
+	}
+	if (strstr(argv[1], ".nwk") != 0 || strstr(argv[1], ".ovk") != 0) {
+		bool is_ovk;
+		int headblk_sz;
+		char* out_ext;
+
+		char* outpath = new char[strlen(argv[1])+10];
+		char buf[1024];
+		memset(buf, 0, 1024);
+		FILE* in = fopen(argv[1], "rb");
+		if (in == 0) {
+			fprintf(stderr,"Cannot open file : %s\n",argv[1]);
+			return -1;
+		}
+		if (strstr(argv[1], ".ovk") != 0) {
+			is_ovk = true;
+			headblk_sz = 16; 
+			out_ext = "ogg";
+		} else {
+			is_ovk = false;
+			headblk_sz = 12;
+			out_ext = "wav";
+		}
+		fread(buf, 1, 4, in);
+		int index = read_little_endian_int(buf);
+		if (index <= 0) { 
+			if (is_ovk)
+				fprintf(stderr,"Invalid Ogg-ovk file : %s : index = %d\n",argv[1],index);
+			else
+				fprintf(stderr,"Invalid Koe-nwk file : %s : index = %d\n",argv[1],index);
+			return -1;
+		}
+		int* tbl_off = new int[index];
+		int* tbl_siz = new int[index];
+		int* tbl_cnt = new int[index];
+		int* tbl_origsiz = new int[index];
+		int i;
+		for (i=0; i<index; i++) {
+			fread(buf, 1, headblk_sz, in);
+			tbl_siz[i] = read_little_endian_int(buf);
+			tbl_off[i] = read_little_endian_int(buf+4);
+			tbl_cnt[i] = read_little_endian_int(buf+8);
+			tbl_origsiz[i] = read_little_endian_int(buf+12);
+		}
+		fseek(in, 0, 2);
+		int fsize = ftell(in);
+		for (i=0; i<index; i++) {
+			if (tbl_off[i] <= 0 || tbl_siz[i] <= 0 || tbl_off[i]+tbl_siz[i] > fsize) {
+				fprintf(stderr,"Invalid table[%d] : cnt %d off %d size %d / %d\n",i,tbl_cnt[i],tbl_off[i],tbl_siz[i],fsize);
+				continue;
+			}
+			if (argc == 2)
+				sprintf(outpath, "%s-%d.%s", argv[1], tbl_cnt[i],out_ext);
+			else
+				sprintf(outpath, "%s-%d.%s", argv[2], tbl_cnt[i],out_ext);
+			FILE* out = fopen(outpath, "wb");
+			if (out == 0) {
+				fprintf(stderr,"Cannot open output file %s\n",outpath);
+				continue;
+			}
+			fprintf(stderr,"Writing file %s...\n",outpath);
+			fseek(in, tbl_off[i], 0);
+			if (is_ovk) { // copy file
+				int sz = tbl_siz[i];
+				char buf[32*1024];
+				while(sz > 32*1024) {
+					fread(buf, 32*1024, 1, in);
+					fwrite(buf, 32*1024, 1, out);
+					sz -= 1024*32;
+				}
+				if (sz > 0) {
+					fread(buf, sz, 1, in);
+					fwrite(buf, sz, 1, out);
+				}
+			} else { // .nwk
+				conv(in, out, 0, tbl_siz[i]);
+			}
+			fclose(out);
+		}
+		fclose(in);
+		return 0;
+	}
+	FILE* in = fopen(argv[1],"rb");
+	if (in == 0) {
+		fprintf(stderr,"Cannot open file : %s\n",argv[1]);
+		return -1;
+	}
+	FILE* out;
+	if (argc != 3 && (!isatty(fileno(stdout)))) {	// wave file is written to stdout if stdout is redirected to a file
+		out = stdout;
+	} else {					// make a new file or use argv[2] for output file name
+		char* outpath = new char[strlen(argv[1])+10];
+		sprintf(outpath, "%s.wav",argv[1]);
+		if (argc == 3) outpath = argv[2];
+		out = fopen(outpath, "wb");
+		if (out == 0) {
+			fprintf(stderr,"Cannot open file : %s\n",outpath);
+			return -1;
+		}
+	}
+	conv(in, out, skip_count);
+	fclose(in);
+	if (out != stdout) fclose(out);
+	return 0;
+}
+#else
+
+#include"wavfile.h"
+
+void NWAFILE::Seek(int count) {
+	if (data == 0) data = new char[block_size];
+	nwa->Rewind(stream);
+	int dmy = 0;
+	nwa->Decode(stream, data, dmy); // skip wav header
+	data_len = 0;
+	skip_count = count;
+}
+NWAFILE::NWAFILE(FILE* _stream) {
+	skip_count = 0;
+	data = 0;
+	stream = _stream;
+	nwa = new NWAData;
+	nwa->ReadHeader(stream);
+	if (!nwa->CheckHeader()) {
+		return;
+	}
+	block_size = nwa->BlockLength();
+	data = new char[block_size];
+	data_len = 0;
+
+	wavinfo.SamplingRate = nwa->freq;
+	wavinfo.Channels = nwa->channels;
+	wavinfo.DataBits = nwa->bps;
+
+	int dmy = 0;
+	data_len = nwa->Decode(stream, data, dmy); // skip wav header
+
+	return;
+}
+NWAFILE::~NWAFILE() {
+	if (stream) fclose(stream);
+	if (data) delete[] data;
+	if (nwa) delete nwa;
+}
+int NWAFILE::Read(char* buf, int blksize, int blklen) {
+	if (data == 0) return -1; // end of file
+
+	if (data_len > blksize * blklen) {
+		int len = blksize * blklen;
+		memcpy(buf, data, len);
+		memmove(data, data+len, data_len-len);
+		data_len -= len;
+		return blklen;
+	}
+	memcpy(buf, data, data_len);
+	int copied_length = data_len;
+	data_len = 0;
+
+	if (stream == 0) {
+		delete[] data;
+		data = 0;
+		return copied_length / blksize;
+	}
+
+	// read
+	do {
+		int err;
+retry:
+		err = nwa->Decode(stream, data, skip_count);
+		if (err == 0 || err == -1) { // eof or error
+			delete[] data;
+			data = 0;
+			return copied_length / blksize;
+		}
+		if (err == -2) goto retry; // EAGAIN
+		data_len = err;
+		if (copied_length + data_len < blklen*blksize) {
+			memcpy(buf+copied_length, data, data_len);
+			copied_length += data_len;
+			goto retry;
+		}
+	} while(0);
+
+	// determine return length
+	int datablks = (data_len+copied_length)/blksize;
+	if (datablks <= 0) return 0;
+	if (datablks > blklen) datablks = blklen;
+	int rest_len = datablks * blksize - copied_length;
+	if (rest_len) {
+		memcpy(buf+copied_length, data, rest_len);
+		memmove(data, data+rest_len, data_len-rest_len);
+		data_len -= rest_len;
+	}
+	return datablks;
+}
+
+char* NWAFILE::ReadAll(FILE* in, int& total_size) {
+	NWAData h;
+	if (in == 0) return 0;
+	h.ReadHeader(in);
+	h.CheckHeader();
+	int bs = h.BlockLength();
+	total_size = h.datasize+0x2c;
+	char* d = new char[total_size + bs*2];
+	int dcur = 0;
+	int err;
+	int skip = 0;
+	while(dcur < total_size+bs && (err=h.Decode(in, d+dcur, skip)) != 0) {
+		if (err == -1) break;
+		if (err == -2) continue;
+		dcur += err;
+	}
+	return d;
+}
+
+#include"music.h"
+
+char* decode_koe_nwa(AvgKoeInfo info, int* data_len) {
+	NWAData h;
+	if (info.stream == 0) return 0;
+	fseek(info.stream, info.offset, 0);
+	h.ReadHeader(info.stream, info.length);
+	if (h.CheckHeader() == false) return 0;
+	int bs = h.BlockLength();
+	int total = h.datasize + 0x2c;
+	char* d = new char[total + bs*2];
+	int dcur = 0;
+	int err;
+	int skip = 0;
+	while(dcur < total+bs && (err=h.Decode(info.stream, d+dcur, skip)) != 0) {
+		if (err == -1) break;
+		if (err == -2) continue;
+		dcur += err;
+	}
+	if (data_len) {
+		*data_len = dcur;
+		if (*data_len > total) *data_len = total;
+	}
+	return d;
+}
+
+#endif