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}