Add C wav to pcm tool

This commit is contained in:
yenatch
2017-07-07 23:25:06 -04:00
parent 7af1ac9ded
commit 5ce2de752b
3 changed files with 258 additions and 4 deletions

View File

@@ -9,8 +9,6 @@
ROM := pokepinball.gbc
OBJS := main.o wram.o sram.o
PYTHON := python
PRET := pokemon-reverse-engineering-tools/pokemontools
MD5 := md5sum -c --quiet
all: $(ROM) compare
@@ -51,4 +49,4 @@ clean: tidy
rgbgfx -d1 -o $@ $<
%.pcm: %.wav
$(PYTHON) $(PRET)/pcm.py pcm $<
tools/pcm -o $@ $<

View File

@@ -1,10 +1,11 @@
.PHONY: all clean
CC := gcc
CFLAGS := -std=c99
CFLAGS := -std=c99 -Wall -Wextra
tools := \
gfx \
pcm \
scan_includes
all: $(tools)

255
tools/pcm.c Normal file
View File

@@ -0,0 +1,255 @@
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <stdint.h>
#include "common.h"
static void usage(void) {
fprintf(stderr, "Usage: pcm [-h] [-s sample_rate] [-o outfile] infile\n");
}
struct Options {
char *outfile;
int sample_rate;
int help;
};
struct Options Options = {
.sample_rate = 22050
};
struct Riff {
char tag[5];
uint32_t size;
char wave_tag[5];
uint8_t *data;
};
struct Chunk {
char tag[5];
uint32_t size;
uint8_t *data;
};
struct Wav {
uint16_t format;
uint16_t num_channels;
uint32_t sample_rate;
uint32_t data_rate;
uint16_t sample_width;
uint16_t bits_per_sample;
// extensions
uint16_t extension_size;
uint16_t valid_bits_per_sample;
uint32_t channel_mask;
// results
int samples_size;
uint8_t *samples;
};
struct Pcm {
int size;
uint8_t *data;
};
#define READ_U16(ptr, i) \
ptr[i] << 0 \
| ptr[i+1] << 8
#define READ_U32(ptr, i) \
ptr[i] << 0 \
| ptr[i+1] << 8 \
| ptr[i+2] << 16 \
| ptr[i+3] << 24
enum {
WAVE_FORMAT_PCM = 1,
};
void read_wav(char *filename, struct Wav *wav) {
int size = 0;
uint8_t *data = read_u8(filename, &size);
struct Riff riff = {0};
int pos = 0;
memcpy(riff.tag, &data[pos], 4); pos += 4;
riff.size = READ_U32(data, pos); pos += 4;
int riff_end = pos + riff.size;
memcpy(riff.wave_tag, &data[pos], 4); pos += 4;
if (strcmp(riff.tag, "RIFF")) {
fprintf(stderr, "'%s': unknown tag '%s' at 0x%x (expected 'RIFF')\n",
filename, riff.tag, 4);
exit(1);
}
if (strcmp(riff.wave_tag, "WAVE")) {
fprintf(stderr, "'%s': unknown tag '%s' at 0x%x (expected 'WAVE')\n",
filename, riff.wave_tag, 8);
exit(1);
}
while (pos < riff_end) {
struct Chunk chunk = {0};
memcpy(chunk.tag, &data[pos], 4); pos += 4;
chunk.size = READ_U32(data, pos); pos += 4;
int start_pos = pos;
if (!strcmp(chunk.tag, "fmt ")) {
wav->format = READ_U16(data, pos); pos += 2;
wav->num_channels = READ_U16(data, pos); pos += 2;
wav->sample_rate = READ_U32(data, pos); pos += 4;
wav->data_rate = READ_U32(data, pos); pos += 4;
wav->sample_width = READ_U16(data, pos); pos += 2;
wav->bits_per_sample = READ_U16(data, pos); pos += 2;
if (wav->format != WAVE_FORMAT_PCM) {
/*
wav->extension_size = READ_U16(data, pos); pos += 2;
wav->valid_bits_per_sample = READ_U16(data, pos); pos += 2;
wav->channel_mask = READ_U32(data, pos); pos += 4;
*/
fprintf(stderr, "unsupported wave format: 0x%04x\n", wav->format);
exit(1);
}
} else if (!strcmp(chunk.tag, "data")) {
wav->samples = &data[pos];
wav->samples_size = chunk.size;
pos += chunk.size;
} else {
pos += chunk.size;
}
if (pos != start_pos + (int)chunk.size) {
fprintf(stderr, "BUG: failed to read '%s' chunk at 0x%x (0x%x/0x%x bytes)\n",
chunk.tag, start_pos, pos - start_pos, chunk.size - start_pos);
exit(1);
}
}
if (pos > riff_end) {
fprintf(stderr, "BUG: read past end of 'RIFF' chunk (0x%x/0x%x bytes)\n",
pos, riff_end);
}
if (pos > size) {
fprintf(stderr, "BUG: read past end of file (0x%x/0x%x bytes)\n",
pos, size);
}
// only use the first channel
int sample_distance = wav->sample_width * wav->num_channels;
// approximate the desired sample rate
float interval = ((float)wav->sample_rate) / Options.sample_rate;
if (interval == 0) {
fprintf(stderr, "input sample rate too low or output sample rate too high (in: %dHz, out: %dHz)\n",
wav->sample_rate, Options.sample_rate);
exit(1);
}
int num_samples = wav->samples_size / sample_distance;
int new_samples_size = num_samples / interval;
uint8_t *new_samples = malloc(new_samples_size);
int new_num_samples = 0;
float interval_i = 0;
int i = 0;
while (i < num_samples) {
uint8_t sample = 0;
int index = i * sample_distance;
if (wav->sample_width == 1) {
sample = wav->samples[index];
} else if (wav->sample_width == 2) {
sample = wav->samples[index] + (wav->samples[index + 1] << 8);
} else {
fprintf(stderr, "unsupported sample width: %d\n", wav->sample_width);
exit(1);
}
new_samples[new_num_samples++] = sample;
interval_i += interval;
i = (int)interval_i;
}
wav->samples = new_samples;
wav->samples_size = new_num_samples;
free(data);
}
void wav_to_pcm(struct Wav *wav, struct Pcm *pcm) {
pcm->size = wav->samples_size / 8;
pcm->data = calloc(pcm->size, 1);
long total = 0;
for (int i = 0; i < wav->samples_size; i++) {
total += wav->samples[i];
}
float average = (double)total / wav->samples_size;
for (int i = 0; i < wav->samples_size; i++) {
int bit = 0;
if (wav->samples[i] >= average) {
bit = 1;
}
pcm->data[i / 8] |= bit << (7 - (i % 8));
}
}
int main(int argc, char *argv[]) {
struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"--sample-rate", required_argument, 0, 's'},
{0}
};
while (1) {
int opt = getopt_long(argc, argv, "ho:", long_options);
if (opt == -1) {
break;
}
switch (opt) {
case 'h':
Options.help = 1;
break;
case 'o':
Options.outfile = optarg;
break;
case 's':
Options.sample_rate = strtoul(optarg, NULL, 0);
break;
default:
usage();
exit(1);
break;
}
}
argc -= optind;
argv += optind;
if (Options.help) {
usage();
return 0;
}
if (argc < 1) {
usage();
exit(1);
}
char *infile = argv[0];
struct Wav wav = {0};
struct Pcm pcm = {0};
read_wav(infile, &wav);
wav_to_pcm(&wav, &pcm);
if (Options.outfile) {
write_u8(Options.outfile, pcm.data, pcm.size);
}
}