summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoryenatch <yenatch@gmail.com>2017-07-07 23:25:06 -0400
committeryenatch <yenatch@gmail.com>2017-07-08 11:41:43 -0400
commit5ce2de752bd908de2bf9dcaedfa68a1d194700db (patch)
tree500030f9a8a511f55ff09f59a7ebf8ba3ec7804c
parent7af1ac9dedd5fcef8eabfe39a10d72f17418a6a4 (diff)
Add C wav to pcm tool
-rwxr-xr-xMakefile4
-rw-r--r--tools/Makefile3
-rw-r--r--tools/pcm.c255
3 files changed, 258 insertions, 4 deletions
diff --git a/Makefile b/Makefile
index 1108128..06aea2e 100755
--- a/Makefile
+++ b/Makefile
@@ -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 $@ $<
diff --git a/tools/Makefile b/tools/Makefile
index c3c3d35..e5df0a9 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -1,10 +1,11 @@
.PHONY: all clean
CC := gcc
-CFLAGS := -std=c99
+CFLAGS := -std=c99 -Wall -Wextra
tools := \
gfx \
+ pcm \
scan_includes
all: $(tools)
diff --git a/tools/pcm.c b/tools/pcm.c
new file mode 100644
index 0000000..b10ef54
--- /dev/null
+++ b/tools/pcm.c
@@ -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);
+ }
+}