mirror of
https://github.com/pret/pokepinball.git
synced 2026-08-22 02:24:10 -05:00
Add C wav to pcm tool
This commit is contained in:
4
Makefile
4
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 $@ $<
|
||||
|
||||
@@ -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
255
tools/pcm.c
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user