summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/.gitignore1
-rw-r--r--tools/Makefile2
-rw-r--r--tools/pcm.c160
-rw-r--r--tools/pokemontools/pcm.py156
-rwxr-xr-xtools/unnamed.py6
5 files changed, 321 insertions, 4 deletions
diff --git a/tools/.gitignore b/tools/.gitignore
index 967af106..cf5f5adb 100644
--- a/tools/.gitignore
+++ b/tools/.gitignore
@@ -1,3 +1,4 @@
scan_includes
gfx
pkmncompress
+pcm
diff --git a/tools/Makefile b/tools/Makefile
index 7ab1d146..6bea053d 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -3,7 +3,7 @@
CC := gcc
CFLAGS := -O3 -std=c99 -Wall -Wextra -Wno-missing-field-initializers
-tools := scan_includes gfx pkmncompress
+tools := scan_includes gfx pkmncompress pcm
all: $(tools)
@:
diff --git a/tools/pcm.c b/tools/pcm.c
new file mode 100644
index 00000000..a14e291d
--- /dev/null
+++ b/tools/pcm.c
@@ -0,0 +1,160 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#define CHUNKID(b1, b2, b3, b4) \
+ (uint32_t)((uint32_t)(b1) | ((uint32_t)(b2) << 8) | \
+ ((uint32_t)(b3) << 16) | ((uint32_t)(b4) << 24))
+
+size_t file_size(FILE *f) {
+ if (fseek(f, 0, SEEK_END) == -1) return 0;
+ long f_size = ftell(f);
+ if (f_size == -1) return 0;
+ if (fseek(f, 0, SEEK_SET) == -1) return 0;
+ return (size_t)f_size;
+}
+
+int32_t get_uint16le(uint8_t *data, size_t size, size_t i) {
+ return i + 2 >= size ? -1 :
+ (int32_t)data[i] | ((int32_t)data[i+1] << 8);
+}
+
+int64_t get_uint32le(uint8_t *data, size_t size, size_t i) {
+ return i + 4 >= size ? -1 :
+ (int64_t)data[i] | ((int64_t)data[i+1] << 8) |
+ ((int64_t)data[i+2] << 16) | ((int64_t)data[i+3] << 24);
+}
+
+uint8_t *wav2pcm(uint8_t *wavdata, size_t wavsize, size_t *pcmsize) {
+ int64_t fourcc = get_uint32le(wavdata, wavsize, 0);
+ if (fourcc != CHUNKID('R', 'I', 'F', 'F')) {
+ fputs("WAV file does not start with 'RIFF'\n", stderr);
+ return NULL;
+ }
+
+ int64_t waveid = get_uint32le(wavdata, wavsize, 8);
+ if (waveid != CHUNKID('W', 'A', 'V', 'E')) {
+ fputs("RIFF chunk does not start with 'WAVE'\n", stderr);
+ return NULL;
+ }
+
+ size_t sample_offset = 0;
+ int64_t num_samples = 0;
+
+ size_t riffsize = (size_t)get_uint32le(wavdata, wavsize, 4) + 8;
+ for (size_t i = 12; i < riffsize;) {
+ int64_t chunkid = get_uint32le(wavdata, wavsize, i);
+ int64_t chunksize = get_uint32le(wavdata, wavsize, i+4);
+ i += 8;
+ if (chunksize == -1) {
+ fputs("failed to read sub-chunk size\n", stderr);
+ return NULL;
+ }
+
+ // require 22050 Hz 8-bit PCM WAV audio
+ if (chunkid == CHUNKID('f', 'm', 't', ' ')) {
+ int32_t audio_format = get_uint16le(wavdata, wavsize, i);
+ if (audio_format != 1) {
+ fputs("WAV data is not PCM format\n", stderr);
+ return NULL;
+ }
+ int32_t num_channels = get_uint16le(wavdata, wavsize, i+2);
+ if (num_channels != 1) {
+ fputs("WAV data is not mono\n", stderr);
+ return NULL;
+ }
+ int64_t sample_rate = get_uint32le(wavdata, wavsize, i+4);
+ if (sample_rate != 22050) {
+ fputs("WAV data is not 22050 Hz\n", stderr);
+ return NULL;
+ }
+ int32_t bits_per_sample = get_uint16le(wavdata, wavsize, i+14);
+ if (bits_per_sample != 8) {
+ fputs("WAV data is not 8-bit\n", stderr);
+ return NULL;
+ }
+ }
+
+ else if (chunkid == CHUNKID('d', 'a', 't', 'a')) {
+ sample_offset = i;
+ num_samples = chunksize;
+ break;
+ }
+
+ i += (size_t)chunksize;
+ }
+
+ if (!num_samples) {
+ fputs("WAV data has no PCM samples\n", stderr);
+ return NULL;
+ }
+
+ // pack 8 WAV samples per PCM byte, clamping each to 0 or 1
+ *pcmsize = (size_t)((num_samples + 7) / 8);
+ uint8_t *pcmdata = malloc(*pcmsize);
+ for (int64_t i = 0; i < num_samples; i += 8) {
+ uint8_t v = 0;
+ for (int64_t j = 0; j < 8 && i + j < num_samples; j++) {
+ v |= (wavdata[sample_offset + i + j] > 0x80) << (7 - j);
+ }
+ pcmdata[i / 8] = v;
+ }
+
+ return pcmdata;
+}
+
+int main(int argc, char *argv[]) {
+ if (argc != 3) {
+ fprintf(stderr, "Usage: %s infile.wav outfile.pcm\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ char *wavname = argv[1];
+ char *pcmname = argv[2];
+
+ FILE *wavfile = fopen(wavname, "rb");
+ if (!wavfile) {
+ fprintf(stderr, "failed to open for reading: '%s'\n", wavname);
+ return EXIT_FAILURE;
+ }
+
+ size_t wavsize = file_size(wavfile);
+ if (!wavsize) {
+ fclose(wavfile);
+ fprintf(stderr, "failed to get file size: '%s'\n", wavname);
+ return EXIT_FAILURE;
+ }
+
+ uint8_t *wavdata = malloc(wavsize);
+ size_t readsize = fread(wavdata, 1, wavsize, wavfile);
+ fclose(wavfile);
+ if (readsize != wavsize) {
+ fprintf(stderr, "failed to read: '%s'\n", wavname);
+ return EXIT_FAILURE;
+ }
+
+ size_t pcmsize;
+ uint8_t *pcmdata = wav2pcm(wavdata, wavsize, &pcmsize);
+ free(wavdata);
+ if (!pcmdata) {
+ fprintf(stderr, "failed to convert: '%s'\n", wavname);
+ return EXIT_FAILURE;
+ }
+
+ FILE *pcmfile = fopen(pcmname, "wb");
+ if (!pcmfile) {
+ fprintf(stderr, "failed to open for writing: '%s'\n", pcmname);
+ return EXIT_FAILURE;
+ }
+
+ size_t writesize = fwrite(pcmdata, 1, pcmsize, pcmfile);
+ free(pcmdata);
+ fclose(pcmfile);
+ if (writesize != pcmsize) {
+ fprintf(stderr, "failed to write: '%s'\n", pcmname);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/tools/pokemontools/pcm.py b/tools/pokemontools/pcm.py
new file mode 100644
index 00000000..428d5730
--- /dev/null
+++ b/tools/pokemontools/pcm.py
@@ -0,0 +1,156 @@
+# pcm.py
+# Converts between .wav files and 1-bit pcm data. (pcm = pulse-code modulation)
+
+import argparse
+import os
+import struct
+import wave
+
+
+BASE_SAMPLE_RATE = 22050
+
+def convert_to_wav(filenames=[]):
+ """
+ Converts a file containing 1-bit pcm data into a .wav file.
+ """
+ for filename in filenames:
+ with open(filename, 'rb') as pcm_file:
+ # Generate array of on/off pcm values.
+ samples = []
+ byte = pcm_file.read(1)
+ while byte != "":
+ byte = struct.unpack('B', byte)[0]
+ for i in range(8):
+ bit_index = 7 - i
+ value = (byte >> bit_index) & 1
+ samples.append(value)
+ byte = pcm_file.read(1)
+
+ # Write a .wav file using the pcm data.
+ name, extension = os.path.splitext(filename)
+ wav_filename = name + '.wav'
+ wave_file = wave.open(wav_filename, 'w')
+ wave_file.setframerate(BASE_SAMPLE_RATE)
+ wave_file.setnchannels(1)
+ wave_file.setsampwidth(1)
+
+ for value in samples:
+ if value > 0:
+ value = 0xff
+
+ packed_value = struct.pack('B', value)
+ wave_file.writeframesraw(packed_value)
+
+ wave_file.close()
+
+
+def convert_to_pcm(filenames=[]):
+ """
+ Converts a .wav file into 1-bit pcm data.
+ Samples in the .wav file are simply clamped to on/off.
+
+ This currently works correctly on .wav files with the following attributes:
+ 1. Sample Width = 1 or 2 bytes (Some wave files use 3 bytes per sample...)
+ 2. Arbitrary sample sample_rate
+ 3. Mono or Stereo (1 or 2 channels)
+ """
+ for filename in filenames:
+ samples, average_sample = get_wav_samples(filename)
+
+ # Generate a list of clamped samples
+ clamped_samples = []
+ for sample in samples:
+ # Clamp the raw sample to on/off
+ if sample < average_sample:
+ clamped_samples.append(0)
+ else:
+ clamped_samples.append(1)
+
+ # The pcm data must be a multiple of 8, so pad the clamped samples with 0.
+ while len(clamped_samples) % 8 != 0:
+ clamped_samples.append(0)
+
+ # Pack the 1-bit samples together.
+ packed_samples = bytearray()
+ for i in range(0, len(clamped_samples), 8):
+ # Read 8 pcm values to pack one byte.
+ packed_value = 0
+ for j in range(8):
+ packed_value <<= 1
+ packed_value += clamped_samples[i + j]
+ packed_samples.append(packed_value)
+
+ # Open the output .pcm file, and write all 1-bit samples.
+ name, extension = os.path.splitext(filename)
+ pcm_filename = name + '.pcm'
+ with open(pcm_filename, 'wb') as out_file:
+ out_file.write(packed_samples)
+
+
+def get_wav_samples(filename):
+ """
+ Reads the given .wav file and returns a list of its samples after re-sampling
+ to BASE_SAMPLE_RATE.
+ Also returns the average sample amplitude.
+ """
+ wav_file = wave.open(filename, 'r')
+ sample_width = wav_file.getsampwidth()
+ sample_count = wav_file.getnframes()
+ sample_rate = wav_file.getframerate()
+ num_channels = wav_file.getnchannels()
+
+ samples = bytearray(wav_file.readframes(sample_count))
+
+ # Unpack the values based on the sample byte width.
+ unpacked_samples = []
+ for i in range(0, len(samples), sample_width):
+ if sample_width == 1:
+ fmt = 'B'
+ elif sample_width == 2:
+ fmt = 'h'
+ else:
+ # todo: support 3-byte sample width
+ raise (Exception, "Unsupported sample width: " + str(sample_width))
+
+ value = struct.unpack(fmt, samples[i:i + sample_width])[0]
+ unpacked_samples.append(value)
+
+ # Only keep the samples from the first audio channel.
+ unpacked_samples = unpacked_samples[::num_channels]
+
+ # Approximate the BASE_SAMPLE_RATE.
+ # Also find the average amplitude of the samples.
+ resampled_samples = []
+ total_value = 0
+ interval = float(sample_rate) / BASE_SAMPLE_RATE
+ index = 0
+ while index < sample_count:
+ sample = unpacked_samples[int(index)]
+ total_value += sample
+
+ resampled_samples.append(sample)
+ index += interval
+
+ average_sample = float(total_value) / len(resampled_samples)
+
+ return resampled_samples, average_sample
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument('mode')
+ ap.add_argument('filenames', nargs='*')
+ args = ap.parse_args()
+
+ method = {
+ 'wav': convert_to_wav,
+ 'pcm': convert_to_pcm,
+ }.get(args.mode, None)
+
+ if method == None:
+ raise (Exception, "Unknown conversion method!")
+
+ method(args.filenames)
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/unnamed.py b/tools/unnamed.py
index c5544437..d3a8b6bf 100755
--- a/tools/unnamed.py
+++ b/tools/unnamed.py
@@ -40,8 +40,8 @@ objects = None
if args.rootdir:
for line in Popen(["make", "-C", args.rootdir, "-s", "-p", "DEBUG=1"],
stdout=PIPE).stdout.read().decode().split("\n"):
- if line.startswith("pokered_obj := "):
- objects = line[15:].strip().split()
+ if line.startswith("rom_obj := "):
+ objects = line[11:].strip().split()
break
else:
print("Error: Object files not found!", file=stderr)
@@ -63,7 +63,7 @@ for line in args.symfile:
symbols.add(symbol)
# If no object files were provided, just print what we know and exit
-print("Unnamed pokered symbols: %d (%.2f%% complete)" % (len(symbols),
+print("Unnamed pokeyellow symbols: %d (%.2f%% complete)" % (len(symbols),
(symbols_total - len(symbols)) / symbols_total * 100))
if not objects:
for sym in symbols: