File:  [Isaki's NoNo m68k/m88k emulator] / nono / util / adpcmconv / adpcmconv.cpp
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:53 2026 UTC (3 months ago) by root
Branches: MAIN, Isaki
CVS tags: v027, v026, HEAD
nono 1.6.0

//
// nono
// Copyright (C) 2025 nono project
// Licensed under nono-license.txt
//

// ADPCM <-> PCM Converter

#include "header.h"
#include <vector>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

//--- ここから adpcm.h 相当 ---

class ADPCMDevice
{
 public:
	uint32 pcm2adpcm_step(int16 pcm);
	int16 adpcm2pcm_step(uint32 data);
 private:
	inline uint32 adpcm_encode(int32 dn);
	inline int32  adpcm_decode(uint32 data, int32 xn);
	inline void adpcm_calcstep(uint32 data);

	int32 xprev {};
	int stepidx {};
	static const int   adpcm_stepadj[16];
	static const int32 adpcm_stepsize[49];
};

//--- ここまで adpcm.h 相当 ---
//--- ここから adpcm.cpp 相当 ---

inline uint32
ADPCMDevice::adpcm_encode(int32 dn)
{
	uint32 Ln = 0;

	int ss = adpcm_stepsize[stepidx];

	if (dn < 0) {
		Ln = 0x8;
		dn = -dn;
	}
	if (dn >= ss) {
		Ln |= 0x4;
		dn -= ss;
	}
	ss /= 2;
	if (dn >= ss) {
		Ln |= 0x2;
		dn -= ss;
	}
	ss /= 2;
	if (dn >= ss) {
		Ln |= 0x1;
	}
	return Ln;
}

inline int32
ADPCMDevice::adpcm_decode(uint32 data, int32 xn)
{
	int b3 = (data & 8) ? 1 : 0;
	int b2 = (data & 4) ? 1 : 0;
	int b1 = (data & 2) ? 1 : 0;
	int b0 = (data & 1);

	int32 ss = adpcm_stepsize[stepidx];
	int32 dn = (ss * b2) + (ss / 2 * b1) + (ss / 4 * b0) + ss / 8;
	if (b3) {
		dn = -dn;
	}

	xn += dn;

	// Saturate in 12 bits.
	if (__predict_false(xn > 2047)) {
		xn = 2047;
	} else if (__predict_false(xn < -2047)) {
		xn = -2047;
	}

	return xn;
}

inline void
ADPCMDevice::adpcm_calcstep(uint32 data)
{
	stepidx += adpcm_stepadj[data];
	if (__predict_false(stepidx < 0)) {
		stepidx = 0;
	} else if (__predict_false(stepidx > 48)) {
		stepidx = 48;
	}
}

uint32
ADPCMDevice::pcm2adpcm_step(int16 pcm)
{
	// Input of this algorithm is 12 bit.
	int32 xn = pcm / 16;
	int32 dn = xn - xprev;
	uint32 Ln = adpcm_encode(dn);

	// next はこの Ln をデコードして PCM にした値
	xprev = adpcm_decode(Ln, xprev);
	adpcm_calcstep(Ln);

	return Ln;
}

int16
ADPCMDevice::adpcm2pcm_step(uint32 data)
{
	int32 xn = adpcm_decode(data, xprev);

	// next
	xprev = xn;
	adpcm_calcstep(data);

	// このアルゴリズムの出力は(下詰めで) 12 bit。
	// ただし MSM6258 の出力 DAC は 10 bit 分しかない。
	xn /= 4;
	// その 10 bit を 16 bit PCM にする。
	xn *= 64;
	return xn;
}

/*static*/ const int
ADPCMDevice::adpcm_stepadj[16] = {
	-1, -1, -1, -1, 2, 4, 6, 8,
	-1, -1, -1, -1, 2, 4, 6, 8,
};

/*static*/ const int32
ADPCMDevice::adpcm_stepsize[49] = {
	 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, 876, 963, 1060, 1166, 1282, 1411, 1552,
};

//--- ここまで adpcm.cpp 相当 ---

#define RIFF(a,b,c,d)	(((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
#define VERBOSE(fmt...)	do { if (verbose) printf(fmt); } while (0)

struct chunk_header {
	uint32 chunktype;
	uint32 chunksize;
};

struct fmt_chunk {
	uint16 fmtid;
	uint16 nchannels;
	uint32 frequency;
	uint32 byterate;
	uint16 framesize;
	uint16 bitdepth;
} __packed;

static void usage();
static bool is_wavfile(const char *);
static void encode_file(const char *, const char *);
static void read_fmt_chunk(int ifd, const char *infile);
static void read_data_chunk(int ifd, int ofd, uint32 datasize,
	const char *infile, const char *outfile);
static void decode_file(const char *, const char *);

static const char wav_magic[12] = {
	'R', 'I', 'F', 'F', 0, 0, 0, 0, 'W', 'A', 'V', 'E'
};

static const int freqtable[] = {
	0,
	3906,
	5208,
	7812,
	10416,
	15625,
};

static ADPCMDevice *dev;
static int freq;
static bool verbose;

int
main(int ac, char *av[])
{
	enum {
		CMD_AUTO,
		CMD_ENCODE,
		CMD_DECODE,
	} cmd {};
	int c;
	int f = 0;

	while ((c = getopt(ac, av, "def:v")) != -1) {
		switch (c) {
		 case 'd':
			cmd = CMD_DECODE;
			break;
		 case 'e':
			cmd = CMD_ENCODE;
			break;
		 case 'f':
		 {
			f = atoi(optarg);
			if (f < 0 || f > countof(freqtable)) {
				usage();
			}
			break;
		 }
		 case 'v':
			verbose = true;
			break;
		 default:
			usage();
		}
	}
	ac -= optind;
	av += optind;

	if (ac < 2) {
		usage();
	}
	const char *infile  = av[0];
	const char *outfile = av[1];

	if (cmd == CMD_AUTO) {
		if (is_wavfile(infile)) {
			cmd = CMD_ENCODE;
			VERBOSE("Input file looks WAV\n");
		} else {
			cmd = CMD_DECODE;
			VERBOSE("Input file looks ADPCM\n");
		}
	}
	assert(cmd == CMD_ENCODE || cmd == CMD_DECODE);

	dev = new ADPCMDevice();

	if (cmd == CMD_DECODE) {
		// ADPCM -> PCM
		if (f == 0) {
			f = 5;
		}
		freq = freqtable[f];
		decode_file(infile, outfile);
	} else {
		// PCM -> ADPCM
		encode_file(infile, outfile);
	}

	return 0;
}

static void
usage()
{
	printf("%s [-e] <PCMfile> <out.adpcm>\n", getprogname());
	printf("%s [-d] [-f<n> ] <ADPCMfile> <out.PCM>\n", getprogname());
	printf("  -f1: 3.9 kHz,   -f2: 5.2 kHz,   -f3: 7.8 kHz,");
	printf("  -f4: 10.4 kHz,  -f5: 15.6 kHz\n");
	exit(1);
}

// 入力ファイルが .WAV ファイルっぽければ true を返す。
static bool
is_wavfile(const char *infile)
{
	char buf[12];
	int fd;

	fd = open(infile, O_RDONLY);
	if (fd < 0) {
		err(1, "%s: open %s", __func__, infile);
	}

	auto n = read(fd, buf, sizeof(buf));
	close(fd);
	if (n < 0) {
		err(1, "%s: read %s", __func__, infile);
	}
	if (n < sizeof(buf)) {
		return false;
	}
	memset(buf + 4, 0, 4);
	if (memcmp(buf, wav_magic, sizeof(wav_magic)) != 0) {
		return false;
	}

	return true;
}

// PCM -> ADPCM。
// 入力の WAV ファイルは 16bit, 1チャンネル固定。
// サンプリングレートは気にせずデータを列挙するだけ。
static void
encode_file(const char *infile, const char *outfile)
{
	char buf[12];
	ssize_t n;

	int ifd = open(infile, O_RDONLY);
	if (ifd < 0) {
		err(1, "%s: open", infile);
	}
	int ofd = creat(outfile, 0644);
	if (ofd < 0) {
		err(1, "%s: creat", outfile);
	}

	// RIFF ヘッダ。
	n = read(ifd, buf, sizeof(wav_magic));
	if (n < 0) {
		err(1, "%s: reading header", infile);
	}
	if (n < sizeof(wav_magic)) {
		errx(1, "%s: file too short", infile);
	}
	if (memcmp(buf + 0, wav_magic + 0, 4) != 0 ||
		memcmp(buf + 8, wav_magic + 8, 4) != 0) {
		errx(1, "%s: Invalid WAV magic", infile);
	}

	// チャンクが続く。
	for (bool done = false; done == false; ) {
		n = read(ifd, buf, sizeof(chunk_header));
		if (n < 0) {
			err(1, "%s: reading chunk header", infile);
		}
		if (n == 0) {
			break;
		}
		if (n < sizeof(chunk_header)) {
			errx(1, "%s: file too short", infile);
		}

		const chunk_header *ch = (const chunk_header *)&buf[0];
		uint32 chunktype = be32toh(ch->chunktype);
		uint32 chunksize = le32toh(ch->chunksize);

		uint32 nextpos = lseek(ifd, 0, SEEK_CUR);
		nextpos += chunksize;

		VERBOSE("Chunk '%c%c%c%c' len=%u\n",
			(chunktype >> 24),
			(chunktype >> 16) & 0xff,
			(chunktype >>  8) & 0xff,
			(chunktype & 0xff),
			chunksize);
		switch (chunktype) {
		 case RIFF('f', 'm', 't', ' '):
			read_fmt_chunk(ifd, infile);
			break;
		 case RIFF('d', 'a', 't', 'a'):
			read_data_chunk(ifd, ofd, chunksize, infile, outfile);
			// data チャンク処理したら後は無視して終わる?
			done = true;
			break;
		 default:
			// 知らないチャンクは読み飛ばす。
			break;
		}
		lseek(ifd, nextpos, SEEK_SET);
	}
	close(ifd);
	close(ofd);
}

// ifd から fmt チャンクを読み込んで、パラメータをチェックするだけ。
static void
read_fmt_chunk(int ifd, const char *infile)
{
	struct fmt_chunk fmt;

	auto n = read(ifd, &fmt, sizeof(fmt));
	if (n < 0) {
		err(1, "%s: read fmt chunk", infile);
	}
	if (n < sizeof(fmt)) {
		errx(1, "%s: file too short", infile);
	}

	// 16bit, 1channel, PCM のみサポート。
	uint32 format		= le16toh(fmt.fmtid);
	uint32 nchannels	= le16toh(fmt.nchannels);
	uint32 bitdepth		= le16toh(fmt.bitdepth);
	if (format != 1/*PCM*/) {
		errx(1, "%s: Unknown WAV format 0x%x", infile, format);
	}
	if (nchannels != 1) {
		errx(1, "%s: Only monaural is supported", infile);
	}
	if (bitdepth != 16) {
		errx(1, "%s: Only 16 bit is supported", infile);
	}
}

// ifd から data チャンクを読み込んで ADPCM にして ofd に書き出す。
static void
read_data_chunk(int ifd, int ofd, uint32 datasize,
	const char *infile, const char *outfile)
{
	uint32 srcsize;
	uint32 dstsize;

	// 奇数バイトのはずはないけど、一応。
	srcsize = datasize;
	if ((srcsize & 1U) != 0) {
		srcsize += 1;
		VERBOSE("srcsize ODD! %u\n", srcsize);
	}
	VERBOSE("srcsize = %u\n", srcsize);
	// 奇数サンプルなら dst は偶数サンプルになるよう切り上げる。
	dstsize = srcsize;
	if ((dstsize & 2U) != 0) {
		dstsize += 2;
		VERBOSE("dst sample count ODD! %u\n", dstsize);
	}
	dstsize /= 4;
	VERBOSE("dstsize = %u [bytes]\n", dstsize);

	std::vector<uint8> src;
	std::vector<uint8> dst;
	try {
		src.resize(srcsize);
		dst.resize(dstsize);
	} catch (...) {
		errno = ENOMEM;
		err(1, "data chunk %u", datasize);
	}

	auto n = read(ifd, src.data(), datasize);
	if (n < 0) {
		err(1, "%s: read data", infile);
	}
	if (n < src.size()) {
		errx(1, "%s: file too short", infile);
	}

	// 2サンプルずつ処理できる。
	const int16 *s = (const int16 *)src.data();
	uint8 *d = dst.data();
	for (const uint8 *end = d + dstsize; d < end; ) {
		uint32 v0 = dev->pcm2adpcm_step(*s++);
		uint32 v1 = dev->pcm2adpcm_step(*s++);
		*d++ = v0 | (v1 << 4);
	}

	write(ofd, dst.data(), dst.size());
}

// ADPCM -> PCM
static void
decode_file(const char *infile, const char *outfile)
{
	struct stat st;
	std::vector<uint8> src;
	std::vector<int16> dst;
	uint8 wavhdr[12];
	chunk_header hdr;
	fmt_chunk fmt;
	ssize_t n;

	int ifd = open(infile, O_RDONLY);
	if (ifd < 0) {
		err(1, "%s: open", infile);
	}
	if (fstat(ifd, &st) < 0) {
		err(1, "%s: fstat", infile);
	}
	// 一気に確保。
	try {
		src.resize(st.st_size);
		dst.resize(st.st_size * 2);
	} catch (...) {
		errno = ENOMEM;
		err(1, "buf(%zd)", st.st_size);
	}
	// 出力データ部のバイト数。
	size_t databytes = dst.size() * sizeof(int16);

	// 全体を読み込む。
	n = read(ifd, src.data(), src.size());
	if (n < 0) {
		err(1, "%s: read data", infile);
	}
	if (n < src.size()) {
		errx(1, "%s: file too short", infile);
	}

	// 出力ファイル。
	int ofd = creat(outfile, 0644);
	if (ofd < 0) {
		err(1, "%s: creat", outfile);
	}
	// WAV ヘッダ。
	memcpy(wavhdr, wav_magic, sizeof(wav_magic));
	*(uint32 *)(wavhdr + 4) = htole32(
		sizeof(wavhdr) +
		sizeof(hdr) + sizeof(fmt) +
		sizeof(hdr) + databytes);
	n = write(ofd, wavhdr, sizeof(wavhdr));
	if (n < 0) {
		err(1, "%s: write", outfile);
	}

	// fmt チャンク。
	hdr.chunktype	= htobe32(RIFF('f', 'm', 't', ' '));
	hdr.chunksize	= htole32(16);
	n = write(ofd, &hdr, sizeof(hdr));
	if (n < 0) {
		err(1, "%s: write", outfile);
	}
	fmt.fmtid		= htole16(1);	// PCM
	fmt.nchannels	= htole16(1);
	fmt.frequency	= htole32(freq);
	fmt.byterate	= htole32(freq * 1 * (16 / 8));
	fmt.framesize	= htole16(1 * (16 / 8));
	fmt.bitdepth	= htole16(16);
	n = write(ofd, &fmt, sizeof(fmt));
	if (n < 0) {
		err(1, "%s: write", outfile);
	}

	// data チャンクのヘッダ部。
	hdr.chunktype	= htobe32(RIFF('d', 'a', 't', 'a'));
	hdr.chunksize	= databytes;
	n = write(ofd, &hdr, sizeof(hdr));
	if (n < 0) {
		err(1, "%s: write", outfile);
	}

	const uint8 *s = src.data();
	int16 *d = dst.data();
	for (const uint8 *end = s + src.size(); s < end; ) {
		uint8 data = *s++;
		int16 pcm0 = dev->adpcm2pcm_step(data & 0x0f);
		int16 pcm1 = dev->adpcm2pcm_step(data >> 4);
		*d++ = pcm0;
		*d++ = pcm1;
	}
	write(ofd, dst.data(), dst.size() * sizeof(int16));

	close(ifd);
	close(ofd);
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.