diff --git a/firmware/.gitignore b/firmware/.gitignore index 9c48b36..055c62a 100644 --- a/firmware/.gitignore +++ b/firmware/.gitignore @@ -4,4 +4,6 @@ .vscode/launch.json .vscode/ipch src/gfx_png.h +src/audio_sample.h gfx/ +audio/ diff --git a/firmware/README.md b/firmware/README.md index 570d1b5..f209465 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -11,3 +11,15 @@ move to `gfx` folder, then: ``` python3 scripts/gfx_convert.py ``` + +convert audio + +``` +ffmpeg -i badapple.webm -t 30 -ar 11000 output.wav +``` + +move to `audio` folder, then: + +``` +python3 scripts/audio_convert.py +``` diff --git a/firmware/scripts/audio_convert.py b/firmware/scripts/audio_convert.py new file mode 100644 index 0000000..e6d1bee --- /dev/null +++ b/firmware/scripts/audio_convert.py @@ -0,0 +1,57 @@ +import os +import sys +import soundfile as sf +import samplerate + +# Adapted from https://github.com/rgrosset/pico-pwm-audio + +print("loading file...") + +soundfile = 'audio/output.wav' +data_in, datasamplerate = sf.read(soundfile) +# This means stereo so extract one channel 0 +if len(data_in.shape)>1: + data_in = data_in[:,0] + +print("resampling...") + +converter = 'sinc_best' # or 'sinc_fastest', ... +desired_sample_rate = 11000.0 +ratio = desired_sample_rate/datasamplerate +data_out = samplerate.resample(data_in, ratio, converter) + +print("analyzing...") + +maxValue = max(data_out) +minValue = min(data_out) +print("length", len(data_out)) +print("max value", max(data_out)) +print("min value", min(data_out)) +vrange = (maxValue - minValue) +print("value range", vrange) + +print("normalizing...") + +# normalize to 0-1 +normalized = [int((v-minValue)/vrange*255) for v in data_out] + +print("generating header...") + +m68code = "/* File "+soundfile+ "\r\n * Sample rate "+str(int(desired_sample_rate)) +" Hz\r\n */\r\n" +m68code += "#define WAV_DATA_LENGTH "+str(len(data_out))+" \r\n\r\n" +m68code += "static const uint8_t WAV_DATA[] = {\r\n " + +m68code += ','.join(str(v) for v in normalized) + +# keep track of first and last values to avoid +# blip when the loop restarts.. make the end value +# the average of the first and last. +end_value = int( (normalized[0] + normalized[len(normalized) - 1]) / 2) +m68code+=","+str(end_value)+'\n};\n' + +print("writing output...") + +with open("src/audio_sample.h", "w") as f: + f.write(m68code) + +print("done!") diff --git a/firmware/src/audio.cpp b/firmware/src/audio.cpp new file mode 100644 index 0000000..a7dec04 --- /dev/null +++ b/firmware/src/audio.cpp @@ -0,0 +1,62 @@ +#include +#include "hardware/irq.h" // interrupts +#include "hardware/pwm.h" // pwm +#include "hardware/sync.h" // wait for interrupt +#include "hardware/gpio.h" + +#include "audio_sample.h" + +// Adapted from https://github.com/rgrosset/pico-pwm-audio + +#define AUDIO_PIN 23 +int wav_position = 0; + +/* + * PWM Interrupt Handler which outputs PWM level and advances the + * current sample. + * + * We repeat the same value for 8 cycles this means sample rate etc + * adjust by factor of 8 (this is what bitshifting <<3 is doing) + * + */ +void pwm_interrupt_handler() { + pwm_clear_irq(pwm_gpio_to_slice_num(AUDIO_PIN)); + if (wav_position < (WAV_DATA_LENGTH<<3) - 1) { + // set pwm level + // allow the pwm value to repeat for 8 cycles this is >>3 + pwm_set_gpio_level(AUDIO_PIN, WAV_DATA[wav_position>>3]); + wav_position++; + } else { + // reset to start + wav_position = 0; + } +} + +// 11 KHz is fine for speech. Phone lines generally sample at 8 KHz +#define SYS_CLOCK 125000000.0f +#define AUDIO_WRAP 250.0f +#define AUDIO_RATE 11000.0f +#define AUDIO_CLK_DIV (SYS_CLOCK / AUDIO_WRAP / 8 / AUDIO_RATE) + +void init_audio() { + gpio_set_function(AUDIO_PIN, GPIO_FUNC_PWM); + + int audio_pin_slice = pwm_gpio_to_slice_num(AUDIO_PIN); + + // Setup PWM interrupt to fire when PWM cycle is complete + pwm_clear_irq(audio_pin_slice); + pwm_set_irq_enabled(audio_pin_slice, true); + + // set the handle function above + irq_set_exclusive_handler(PWM_IRQ_WRAP, pwm_interrupt_handler); + irq_set_enabled(PWM_IRQ_WRAP, true); + + // Setup PWM for audio output + pwm_config config = pwm_get_default_config(); + + pwm_config_set_clkdiv(&config, AUDIO_CLK_DIV); + pwm_config_set_wrap(&config, 250); + pwm_init(audio_pin_slice, 0, &config, true); + + pwm_set_gpio_level(AUDIO_PIN, 0); +} diff --git a/firmware/src/audio.h b/firmware/src/audio.h new file mode 100644 index 0000000..f73f774 --- /dev/null +++ b/firmware/src/audio.h @@ -0,0 +1 @@ +void init_audio(); diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index ff0b7d1..6bdfcdd 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -4,6 +4,7 @@ #include "pico/multicore.h" #include "hardware/gpio.h" #include "mbed_wait_api.h" +#include "audio.h" #define Serial Serial1 @@ -60,6 +61,8 @@ void setup() { Serial.begin(115200); Serial.println("Hello worldd!"); + + init_audio(); memset(framebuffer, 0, sizeof(framebuffer)); // disable output