diff --git a/bin_samples/Trumpet.bin b/bin_samples/Trumpet.bin new file mode 100644 index 0000000..c31be73 Binary files /dev/null and b/bin_samples/Trumpet.bin differ diff --git a/include/wavetable.h b/include/wavetable.h index 22417c2..6bd4fe4 100644 --- a/include/wavetable.h +++ b/include/wavetable.h @@ -1,7 +1,7 @@ #pragma once #include -typedef enum { WAVEFORM_SINE, WAVEFORM_SAW, WAVEFORM_SQUARE, WAVEFORM_TRIANGLE } Waveform; +typedef enum { WAVEFORM_SINE, WAVEFORM_SAW, WAVEFORM_SQUARE, WAVEFORM_TRIANGLE, WAVEFORM_CUSTOM } Waveform; typedef struct { float *data; @@ -11,3 +11,5 @@ typedef struct { Wavetable *Wavetable_create(Waveform type, size_t length); void Wavetable_destroy(Wavetable *wt); + +int Wavetable_load(Wavetable *wt, const char *filename); \ No newline at end of file diff --git a/report/wavetable_trumpet.wav b/report/wavetable_trumpet.wav new file mode 100644 index 0000000..d132e64 Binary files /dev/null and b/report/wavetable_trumpet.wav differ diff --git a/resampler/resampler.py b/resampler/resampler.py new file mode 100644 index 0000000..e4b953d --- /dev/null +++ b/resampler/resampler.py @@ -0,0 +1,161 @@ +import numpy as np +import librosa +import librosa.display +import scipy.signal +import argparse +import struct +import os +import matplotlib.pyplot as plt + +def normalize_audio(audio): + """ + Normalizes the audio array so that its maximum absolute value is 1. + """ + max_val = np.max(np.abs(audio)) + if max_val > 0: + return audio / max_val + return audio + +def extract_wavetable(audio_file, target_freq=440.0, table_size=1024): + """ + Loads an audio file, normalizes it, trims silence, extracts one period of a tone at the + target frequency, and resamples that cycle into a wavetable of the given table_size. + + Parameters: + audio_file (str): Path to the input audio file. + target_freq (float): Expected frequency of the tone in Hz. + table_size (int): Desired wavetable size. + + Returns: + wavetable (np.ndarray): The wavetable with table_size samples. + sr (int): The sample rate of the audio file. + """ + # Load the audio file (mono) and normalize immediately + y, sr = librosa.load(audio_file, sr=None, mono=True) + y = normalize_audio(y) + + # Trim silence from beginning and end (adjust top_db if needed) + y_trimmed, _ = librosa.effects.trim(y, top_db=20) + + # Calculate the period length in samples for the target frequency + period_length = sr / target_freq + period_samples = int(round(period_length)) + + # Extract a cycle from the center of the trimmed audio + center = len(y_trimmed) // 2 + start = center - period_samples // 2 + end = start + period_samples + + if start < 0 or end > len(y_trimmed): + raise ValueError("Not enough samples in the trimmed audio to extract one period.") + + cycle = y_trimmed[start:end] + + # Resample the extracted cycle to the desired table size (1024) + wavetable = scipy.signal.resample(cycle, table_size) + + # Normalize again + wavetable = normalize_audio(wavetable) + + return wavetable, sr + +def save_wavetable_to_binary(wavetable, filename): + """ + Saves the wavetable to a binary file. + + The file format is: + - 4 bytes: unsigned int (little-endian) representing the number of samples. + - 4 bytes per sample: float32 samples. + + Parameters: + wavetable (np.ndarray): The wavetable array. + filename (str): The output binary file name. + """ + # Ensure the wavetable is float32 + wavetable = wavetable.astype(np.float32) + + # Write format + with open(filename, "wb") as f: + # First thing is the table length + f.write(struct.pack("wts[WAVEFORM_SINE] = *Wavetable_create(WAVEFORM_SINE, TABLE_SIZE); state->wts[WAVEFORM_SAW] = *Wavetable_create(WAVEFORM_SAW, TABLE_SIZE); state->wts[WAVEFORM_SQUARE] = *Wavetable_create(WAVEFORM_SQUARE, TABLE_SIZE); - state->wts[WAVEFORM_TRIANGLE] = *Wavetable_create(WAVEFORM_TRIANGLE, TABLE_SIZE); + // state->wts[WAVEFORM_TRIANGLE] = *Wavetable_create(WAVEFORM_TRIANGLE, TABLE_SIZE); + state->wts[WAVEFORM_TRIANGLE] = *Wavetable_create(WAVEFORM_CUSTOM, TABLE_SIZE); + Wavetable_load(&state->wts[WAVEFORM_TRIANGLE], "Trumpet.bin"); Lowpass_init(&state->lpf); return state; diff --git a/src/wavetable.c b/src/wavetable.c index f9ba552..72986dc 100644 --- a/src/wavetable.c +++ b/src/wavetable.c @@ -2,6 +2,7 @@ #include #include #include +#include Wavetable *Wavetable_create(Waveform type, size_t length) { assert(length > 0); @@ -45,3 +46,26 @@ void Wavetable_destroy(Wavetable *wt) { free(wt->data); free(wt); } + +int Wavetable_load(Wavetable *wt, const char *filename) { + FILE *f = fopen(filename, "rb"); + if (!f) return -1; + uint32_t length; + if (fread(&length, sizeof(uint32_t), 1, f) != 1) { + fclose(f); + return -1; + } + wt->length = length; + wt->data = (float*)malloc(length * sizeof(float)); + if (!wt->data) { + fclose(f); + return -1; + } + if (fread(wt->data, sizeof(float), length, f) != length) { + free(wt->data); + fclose(f); + return -1; + } + fclose(f); + return 0; +} \ No newline at end of file