1/////////////////////////////////////////////////////////////////////////////////////////
2// Filename: main.c
3// Filetype: javascript
4// Author: Louis Dalibard
5// Created On: May 29th 2022 @ 11:18PM
6// Description: pTuna : α 1.01
7/////////////////////////////////////////////////////////////////////////////////////////
8
9#include <stdio.h>
10#include <stdint.h>
11#include <string.h>
12#include <stdlib.h>
13#include <math.h>
14
15#include "pico/stdlib.h"
16#include "pico/pdm_microphone.h"
17#include "pico/multicore.h"
18#include "hardware/i2c.h"
19
20#include "libssd1306/ssd1306.h"
21
22#include "bitmaps/splashScreen.h"
23#include "bitmaps/mainUI.h"
24#include "bitmaps/overclockFail.h"
25
26#include "notes/notes.h"
27
28#include "kiss/kiss_fftr.h"
29
30#define SPLASHSCREEN_DURATION 1200 //ms
31#define C0_FREQUENCY 16.3515978313 //hz C8(4186.01hz) / 4186.01/(2^8) (440/(2^(9/12+4)) would be more exact though)
32#define CUTOFF_FREQUENCY 8.17580078125 //hz
33#define SEMITONE_OFFSET_SENSITIVITY 1.24 // cents per pixel (64-2)/50
34#define MIC_BUFFER_SIZE 512 // samples
35#define CUMUL_BUFFER_SIZE 8 // multiplier, int
36#define MIC_SAMPLERATE 4096 // hz
37#define MIC_GAIN 1.0 // multiplier
38#define BOOT_ANIMATION_FRAMES 60
39#define SHOW_BOOT_ANIMATION false
40
41#define OSCILLOSCOPE_SAMPLES 50
42#define OSCILLOSCOPE_GAIN 15
43
44#define FLAG_VALUE 123
45
46// configuration
47const struct pdm_microphone_config config = {
48 .gpio_data = 2,
49 .gpio_clk = 3,
50 .pio = pio0,
51 .pio_sm = 0,
52 .sample_rate = MIC_SAMPLERATE,
53 .sample_buffer_size = MIC_BUFFER_SIZE,
54};
55
56// variables
57int16_t sample_buffer[MIC_BUFFER_SIZE];
58int16_t sample_buffer_cumul[MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE];
59volatile int samples_read = 0;
60float freqs[MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE];
61
62// functions
63void setup_gpios(void);
64void setup_microphone(void);
65void splashscreen(ssd1306_t disp);
66void overclockFail(ssd1306_t disp);
67void showMainUI(ssd1306_t disp);
68double U64ToDoubleConverter(uint64_t val);
69double determineNote(double frequency, char * output_note_string, char * output_octave_string);
70void drawGuitarStringIndicator(ssd1306_t* disp, int stringIndex);
71void drawOscilloscope(ssd1306_t* disp, float max_freq);
72void cumulSamples(void);
73
74int main() {
75 // Overclock Pico
76 bool overclockingSuccess = true;
77 if (!set_sys_clock_khz(150000, true)) {
78 set_sys_clock_khz(125000, true);
79 overclockingSuccess = false;
80 }
81 // STDIO
82 stdio_init_all();
83
84 printf("configuring pins...\n");
85 setup_gpios();
86 printf("display init...\n");
87 ssd1306_t disp;
88 disp.external_vcc = false;
89 ssd1306_init( & disp, 128, 64, 0x3C, i2c0);
90 if (!overclockingSuccess) {
91 printf("overclock failed...\n");
92 overclockFail(disp);
93 return 0;
94 }
95 splashscreen(disp);
96 // initialize the PDM microphone
97 printf("microphone init...\n");
98 setup_microphone();
99 // Start main UI.
100 showMainUI(disp);
101 return 0;
102}
103
104void on_pdm_samples_ready(void) {
105 // callback from library when all the samples in the library
106 // internal sample buffer are ready for reading
107 samples_read = pdm_microphone_read(sample_buffer, MIC_BUFFER_SIZE);
108}
109
110void setup_microphone(void) {
111 // set callback that is called when all the samples in the library
112 // internal sample buffer are ready for reading
113 if (pdm_microphone_init( & config) < 0) {
114 printf("PDM microphone initialization failed!\n");
115 }
116 pdm_microphone_set_samples_ready_handler(on_pdm_samples_ready);
117 // start capturing data from the PDM microphone
118 if (pdm_microphone_start() < 0) {
119 printf("PDM microphone start failed!\n");
120 }
121}
122
123void setup_gpios(void) {
124 i2c_init(i2c0, 400000);
125 gpio_set_function(17, GPIO_FUNC_I2C);
126 gpio_set_function(16, GPIO_FUNC_I2C);
127 gpio_pull_up(17);
128 gpio_pull_up(16);
129}
130
131void splashscreen(ssd1306_t disp) {
132 ssd1306_clear( & disp);
133 printf("Showing splash screen!\n");
134 ssd1306_bmp_show_image( & disp, splash_screen_image_data, splash_screen_image_size);
135 ssd1306_show( & disp);
136 sleep_ms(SPLASHSCREEN_DURATION);
137}
138
139void overclockFail(ssd1306_t disp) {
140 ssd1306_clear( & disp);
141 printf("Showing overclock fail screen!\n");
142 ssd1306_bmp_show_image( & disp, overclockfail_screen_image_data, overclockfail_screen_image_size);
143 ssd1306_show( & disp);
144}
145
146double U64ToDoubleConverter(uint64_t val) {
147 double convertedValue = 0.0;
148 memcpy( & convertedValue, & val, sizeof(convertedValue));
149 return convertedValue;
150}
151
152double determineNote(double frequency, char * output_note_string, char * output_octave_string) {
153 if (frequency < CUTOFF_FREQUENCY) {
154 strcpy(output_note_string, "L");
155 strcpy(output_octave_string, "-");
156 return (0);
157 } else {
158 double octaves_over_C0 = log2l(frequency / C0_FREQUENCY);
159 double semitones_over_C0 = octaves_over_C0 * 12.0;
160 int closest_whole_semitones_over_C0 = round(semitones_over_C0);
161 char * closestnote = (char * ) malloc(2 * sizeof(char));
162 char * octaveString = (char * ) malloc(2 * sizeof(char));
163 strcpy(closestnote, notes_in_an_octave[closest_whole_semitones_over_C0 % 12]);
164 sprintf(octaveString, "%i", (int)(floor(round(semitones_over_C0) / 12)));
165 strcpy(output_note_string, closestnote);
166 strcpy(output_octave_string, octaveString);
167 free(closestnote); // no memory leaks here 😅...
168 free(octaveString); // no memory leaks here 😅...
169 return (semitones_over_C0 - closest_whole_semitones_over_C0);
170 }
171}
172
173void drawGuitarStringIndicator(ssd1306_t* disp, int stringIndex) {
174 ssd1306_draw_square(disp, 3 + stringIndex * 5, 35, 3, 3); // 3, 35
175}
176
177void drawOscilloscope(ssd1306_t* disp, float max_freq){
178 int samples_to_read = OSCILLOSCOPE_SAMPLES;
179 if (max_freq != 0){
180 samples_to_read = round(2/max_freq*MIC_SAMPLERATE);
181 }
182 if (samples_to_read<=MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE){
183 float sample_offset = (float)samples_to_read/OSCILLOSCOPE_SAMPLES;
184 int16_t max_value = 1;
185 for (int i = 0; i < OSCILLOSCOPE_SAMPLES; ++i) {
186 if (abs(sample_buffer_cumul[(int)round(sample_offset*i)+MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE-samples_to_read]) > max_value) {
187 max_value=abs(sample_buffer_cumul[(int)round(sample_offset*i)+MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE-samples_to_read]);
188 }
189 }
190 for (int i = 0; i < OSCILLOSCOPE_SAMPLES; ++i) {
191 ssd1306_draw_pixel(disp,125-i,35+round(((float)sample_buffer_cumul[(int)round(sample_offset*i)])/((float)max_value)*OSCILLOSCOPE_GAIN));
192 }
193 }
194 }
195
196void displayTunerResults(ssd1306_t disp, char * output_note_letter, char * output_note_string, char * output_octave_string, char * output_cents_offset_string, double note_semitones_offset, float max_freq) {
197 // Draw UI background skin
198 ssd1306_bmp_show_image( & disp, main_ui_image_data, main_ui_image_size);
199 // Display closest note
200 ssd1306_draw_string( & disp, 59, 24, 2, output_note_letter); // (128-10)/2, (64-16)/2
201 ssd1306_draw_string( & disp, 69, 20, 1, & (output_note_string[1])); // (128-10)/2+10+4, (64-16)/2-4
202 ssd1306_draw_string( & disp, 69, 36, 1, output_octave_string); // (128-10)/2+10, (64-16)/2+12
203 // Draw the offset bar
204 if (note_semitones_offset < 0) {
205 sprintf(output_cents_offset_string, "%dc", (int)(round(note_semitones_offset * 100)));
206 ssd1306_draw_string( & disp, 28, 48, 1, output_cents_offset_string); // 128/2-4*5-16, 64-2-6-8
207 ssd1306_draw_square( & disp, (int)(round(64 + (note_semitones_offset * 100 * SEMITONE_OFFSET_SENSITIVITY))), 56, (int)(round(fabs(note_semitones_offset * 100 * SEMITONE_OFFSET_SENSITIVITY))), 6); // 128/2, 64-2-6
208 } else {
209 sprintf(output_cents_offset_string, "+%dc", (int)(round(note_semitones_offset * 100)));
210 ssd1306_draw_string( & disp, 80, 48, 1, output_cents_offset_string); // 128/2+16, 64-2-6-8
211 ssd1306_draw_square( & disp, 64, 56, (int)(round(fabs(note_semitones_offset * 100 * SEMITONE_OFFSET_SENSITIVITY))), 6); // 128/2, 64-2-6
212 }
213 if (strcmp(output_octave_string, "2") == 0) {
214 if (strcmp(output_note_string, "E") == 0) {
215 drawGuitarStringIndicator(&disp, 0);
216 }
217 if (strcmp(output_note_string, "A") == 0) {
218 drawGuitarStringIndicator(&disp, 1);
219 }
220 }
221 if (strcmp(output_octave_string, "3") == 0) {
222 if (strcmp(output_note_string, "D") == 0) {
223 drawGuitarStringIndicator(&disp, 2);
224 }
225 if (strcmp(output_note_string, "G") == 0) {
226 drawGuitarStringIndicator(&disp, 3);
227 }
228 if (strcmp(output_note_string, "B") == 0) {
229 drawGuitarStringIndicator(&disp, 4);
230 }
231 }
232 if (strcmp(output_octave_string, "4") == 0) {
233 if (strcmp(output_note_string, "E") == 0) {
234 drawGuitarStringIndicator(&disp, 5);
235 }
236 }
237 drawOscilloscope(&disp,max_freq);
238 // Display result
239 ssd1306_show( & disp);
240 ssd1306_clear( & disp);
241}
242
243double signnum_c(double x) {
244 if (x > 0.0) return 1.0;
245 if (x < 0.0) return -1.0;
246 return x;
247}
248
249void cumulSamples(){
250 multicore_fifo_push_blocking(FLAG_VALUE);
251 uint32_t g = multicore_fifo_pop_blocking();
252 if (g != FLAG_VALUE){
253 printf("Hmm, that's not right on core 1!\n");
254 } else {
255 printf("Its all gone well on core 1!");
256 }
257 for(;;){
258 while (samples_read == 0) {}
259 int sample_count = samples_read;
260 memcpy (&sample_buffer_cumul[0],&sample_buffer_cumul[MIC_BUFFER_SIZE],MIC_BUFFER_SIZE*sizeof(int16_t)*(CUMUL_BUFFER_SIZE-1));
261 memcpy (&sample_buffer_cumul[MIC_BUFFER_SIZE*(CUMUL_BUFFER_SIZE-1)],&sample_buffer[0],MIC_BUFFER_SIZE*sizeof(int16_t));
262 printf("blocking core 1!");
263 multicore_fifo_push_blocking(FLAG_VALUE);
264 printf("core 1 unblocked!");
265 }
266}
267
268void showMainUI(ssd1306_t disp) {
269 // FFT setup start.
270 // calculate frequencies of each bin
271 float f_max = MIC_SAMPLERATE;
272 float f_res = f_max / (MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE);
273 for (int i = 0; i < (MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE); i++) {
274 freqs[i] = f_res * i;
275 }
276 kiss_fft_scalar fft_in[(MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE)]; // kiss_fft_scalar is a float
277 kiss_fft_cpx fft_out[(MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE)];
278 kiss_fftr_cfg cfg = kiss_fftr_alloc((MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE), false, 0, 0);
279 // FFT setup end.
280 ssd1306_clear( & disp);
281 char * old_output_note_string = (char * ) malloc(6 * sizeof(char));
282 char * old_output_octave_string = (char * ) malloc(6 * sizeof(char));
283 double old_note_semitones_offset = 0;
284 bool is_this_the_first_output_note = true;
285 // Allocate memory for temporary variables
286 char * output_note_letter = (char * ) malloc(1 * sizeof(char));
287 char * output_cents_offset_string = (char * ) malloc(4 * sizeof(char));
288 char * output_note_string = (char * ) malloc(6 * sizeof(char));
289 char * output_octave_string = (char * ) malloc(6 * sizeof(char));
290 double note_semitones_offset = 0.0;
291 // Boot animation
292 if (SHOW_BOOT_ANIMATION) {
293 double boot_animation_position = -0.5;
294 double boot_velocity = 0.0;
295 for (int i = 0; i < BOOT_ANIMATION_FRAMES; ++i) {
296 displayTunerResults(disp, "L+", "L", "0", output_cents_offset_string, boot_animation_position, 0);
297 boot_velocity += -signnum_c(boot_animation_position) / (boot_animation_position * boot_animation_position + 0.1) / 2000.0;
298 boot_animation_position += boot_velocity;
299 }
300 }
301 multicore_launch_core1(cumulSamples);
302 uint32_t g = multicore_fifo_pop_blocking();
303
304 if (g != FLAG_VALUE){
305 printf("Hmm, that's not right on core 0!\n");
306 }
307 else {
308 multicore_fifo_push_blocking(FLAG_VALUE);
309 printf("It's all gone well on core 0!");
310 }
311 for (;;) {
312 printf("blocking core 0!");
313 multicore_fifo_pop_blocking();
314 printf("core 0 unblocked!");
315 // FFT calculations (Dear future self, it's 4AM as I'm writing this. Please forgive me for any bugs, memory leaks or thermonuclear war (see what I did there?))
316 // fill fourier transform input while subtracting DC component
317 uint64_t sum = 0;
318 for (int i = 0; i < (MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE); i++) {
319 sum += sample_buffer_cumul[i] * MIC_GAIN;
320 }
321 float avg = ((float)(sum)) / ((double) (MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE));
322
323 for (int i = 0; i < (MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE); i++) {
324 fft_in[i] = sample_buffer_cumul[i] * MIC_GAIN - avg;
325 }
326 // compute fast fourier transform
327 kiss_fftr(cfg, fft_in, fft_out);
328
329 // compute power and calculate max freq component
330 float max_power = 0;
331 int max_idx = 0;
332 // any frequency bin over MIC_BUFFER_SIZE/2 is aliased (nyquist sampling theorum)
333 for (int i = 0; i < (MIC_BUFFER_SIZE*CUMUL_BUFFER_SIZE) / 2; i++) {
334 float power = fft_out[i].r * fft_out[i].r + fft_out[i].i * fft_out[i].i;
335 if (power > max_power) {
336 max_power = power;
337 max_idx = i;
338 }
339 }
340
341 float max_freq = freqs[max_idx];
342 printf("Greatest Frequency Component: %0.1f Hz\n", max_freq);
343 // Determine played note and offset
344 note_semitones_offset = determineNote(max_freq, output_note_string, output_octave_string);
345 if (is_this_the_first_output_note) {
346 is_this_the_first_output_note = false;
347 sprintf(old_output_note_string, "%s", output_note_string);
348 sprintf(old_output_octave_string, "%s", output_octave_string);
349 old_note_semitones_offset = note_semitones_offset;
350 } else {
351 if (strcmp(output_note_string, "L") == 0) {
352 sprintf(output_note_string, "%s", old_output_note_string);
353 sprintf(output_octave_string, "%s", old_output_octave_string);
354 note_semitones_offset = old_note_semitones_offset;
355 } else {
356 sprintf(old_output_note_string, "%s", output_note_string);
357 sprintf(old_output_octave_string, "%s", output_octave_string);
358 old_note_semitones_offset = note_semitones_offset;
359 }
360 }
361 sprintf(output_note_letter, "%c", output_note_string[0]);
362 displayTunerResults(disp, output_note_letter, output_note_string, output_octave_string, output_cents_offset_string, note_semitones_offset, max_freq);
363 }
364}