diff options
author | yenatch <yenatch@gmail.com> | 2017-07-07 23:25:06 -0400 |
---|---|---|
committer | yenatch <yenatch@gmail.com> | 2017-07-08 11:41:43 -0400 |
commit | 5ce2de752bd908de2bf9dcaedfa68a1d194700db (patch) | |
tree | 500030f9a8a511f55ff09f59a7ebf8ba3ec7804c | |
parent | 7af1ac9dedd5fcef8eabfe39a10d72f17418a6a4 (diff) |
Add C wav to pcm tool
-rwxr-xr-x | Makefile | 4 | ||||
-rw-r--r-- | tools/Makefile | 3 | ||||
-rw-r--r-- | tools/pcm.c | 255 |
3 files changed, 258 insertions, 4 deletions
@@ -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); + } +} |