|
| 1 | +#include <stdio.h> |
| 2 | +#include <math.h> |
| 3 | +#include "freertos/FreeRTOS.h" |
| 4 | +#include "freertos/task.h" |
| 5 | +#include "esp_log.h" |
| 6 | +#include "esp_system.h" |
| 7 | +#include "lvgl.h" |
| 8 | +#include "bsp/esp-bsp.h" |
| 9 | +#include "esp_timer.h" |
| 10 | +#include "driver/i2s.h" |
| 11 | +#include "esp_ota_ops.h" |
| 12 | + |
| 13 | +#define TAG "SynthPiano" |
| 14 | +#define SAMPLE_RATE 44100 |
| 15 | +#define DEFAULT_VOLUME 90 |
| 16 | + |
| 17 | +static lv_obj_t *octave_label; |
| 18 | +static int current_octave = 4; |
| 19 | +static int last_note_index = -1; |
| 20 | +static int last_octave_event = -1; |
| 21 | +static esp_codec_dev_handle_t spk_codec_dev = NULL; |
| 22 | +static QueueHandle_t tone_queue; |
| 23 | + |
| 24 | +typedef struct { |
| 25 | + float frequency; |
| 26 | + int duration_ms; |
| 27 | +} tone_t; |
| 28 | + |
| 29 | +static void update_display() { |
| 30 | + char buffer[16]; |
| 31 | + snprintf(buffer, sizeof(buffer), "Octave: %d", current_octave); |
| 32 | + bsp_display_lock(0); |
| 33 | + lv_label_set_text(octave_label, buffer); |
| 34 | + bsp_display_unlock(); |
| 35 | +} |
| 36 | + |
| 37 | +static void play_tone(float frequency, int duration_ms) { |
| 38 | + const int sample_rate = SAMPLE_RATE; |
| 39 | + const int num_samples = (sample_rate * duration_ms) / 1000; |
| 40 | + const float amplitude = 1.0; |
| 41 | + |
| 42 | + int16_t *samples = malloc(num_samples * sizeof(int16_t)); |
| 43 | + if (!samples) { |
| 44 | + ESP_LOGE(TAG, "Failed to allocate memory for samples"); |
| 45 | + return; |
| 46 | + } |
| 47 | + |
| 48 | + for (int i = 0; i < num_samples; ++i) { |
| 49 | + samples[i] = (int16_t)(amplitude * INT16_MAX * sinf(2.0f * M_PI * frequency * i / sample_rate)); |
| 50 | + } |
| 51 | + |
| 52 | + esp_codec_dev_write(spk_codec_dev, samples, num_samples * sizeof(int16_t)); |
| 53 | + vTaskDelay(pdMS_TO_TICKS(duration_ms)); // Ensure the tone plays for the specified duration |
| 54 | + |
| 55 | + free(samples); |
| 56 | +} |
| 57 | + |
| 58 | +static void tone_task(void *param) { |
| 59 | + tone_t tone; |
| 60 | + while (1) { |
| 61 | + if (xQueueReceive(tone_queue, &tone, portMAX_DELAY)) { |
| 62 | + play_tone(tone.frequency, tone.duration_ms); |
| 63 | + } |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +static void note_event_cb(lv_event_t *e) { |
| 68 | + lv_obj_t *btn = lv_event_get_target(e); |
| 69 | + const char *txt = lv_btnmatrix_get_btn_text(btn, lv_btnmatrix_get_selected_btn(btn)); |
| 70 | + |
| 71 | + float frequencies[12] = { |
| 72 | + 261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99, 392.00, 415.30, 440.00, 466.16, 493.88 |
| 73 | + }; |
| 74 | + int note_index = atoi(txt); |
| 75 | + if (note_index == last_note_index) { |
| 76 | + return; // Ignore repeated events for the same note |
| 77 | + } |
| 78 | + last_note_index = note_index; |
| 79 | + |
| 80 | + if (note_index >= 0 && note_index < 12) { |
| 81 | + float frequency = frequencies[note_index] * powf(2.0f, current_octave - 4); |
| 82 | + tone_t tone = { .frequency = frequency, .duration_ms = 250 }; |
| 83 | + xQueueSend(tone_queue, &tone, portMAX_DELAY); // Send tone to queue |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +static void octave_event_cb(lv_event_t *e) { |
| 88 | + lv_obj_t *btn = lv_event_get_target(e); |
| 89 | + const char *txt = lv_btnmatrix_get_btn_text(btn, lv_btnmatrix_get_selected_btn(btn)); |
| 90 | + |
| 91 | + if ((last_octave_event == 0 && strcmp(txt, "Up") == 0) || (last_octave_event == 1 && strcmp(txt, "Down") == 0)) { |
| 92 | + return; // Ignore repeated events for the same octave button |
| 93 | + } |
| 94 | + |
| 95 | + if (strcmp(txt, "Up") == 0) { |
| 96 | + last_octave_event = 0; |
| 97 | + current_octave = (current_octave < 8) ? current_octave + 1 : current_octave; |
| 98 | + } else if (strcmp(txt, "Down") == 0) { |
| 99 | + last_octave_event = 1; |
| 100 | + current_octave = (current_octave > 0) ? current_octave - 1 : current_octave; |
| 101 | + } |
| 102 | + update_display(); |
| 103 | +} |
| 104 | + |
| 105 | +void app_audio_init(void) |
| 106 | +{ |
| 107 | + /* Initialize speaker */ |
| 108 | + spk_codec_dev = bsp_audio_codec_speaker_init(); |
| 109 | + assert(spk_codec_dev); |
| 110 | + /* Speaker output volume */ |
| 111 | + esp_codec_dev_set_out_vol(spk_codec_dev, DEFAULT_VOLUME); |
| 112 | + |
| 113 | + esp_codec_dev_open(spk_codec_dev, &(esp_codec_dev_sample_info_t){ |
| 114 | + .sample_rate = SAMPLE_RATE, |
| 115 | + .channel = 1, |
| 116 | + .bits_per_sample = 16, |
| 117 | + }); |
| 118 | +} |
| 119 | + |
| 120 | +void reset_to_factory_app() { |
| 121 | + // Get the partition structure for the factory partition |
| 122 | + const esp_partition_t *factory_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL); |
| 123 | + if (factory_partition != NULL) { |
| 124 | + if (esp_ota_set_boot_partition(factory_partition) == ESP_OK) { |
| 125 | + printf("Set boot partition to factory, restarting now.\n"); |
| 126 | + } else { |
| 127 | + printf("Failed to set boot partition to factory.\n"); |
| 128 | + } |
| 129 | + } else { |
| 130 | + printf("Factory partition not found.\n"); |
| 131 | + } |
| 132 | + |
| 133 | + fflush(stdout); |
| 134 | +} |
| 135 | + |
| 136 | +void app_main(void) { |
| 137 | + // Reset to factory app for the next boot. |
| 138 | + // It should return to graphical bootloader. |
| 139 | + reset_to_factory_app(); |
| 140 | + |
| 141 | + // Initialize the BSP |
| 142 | + bsp_i2c_init(); |
| 143 | + bsp_display_start(); |
| 144 | + lv_init(); |
| 145 | + app_audio_init(); |
| 146 | + |
| 147 | + // Create a label for the octave |
| 148 | + octave_label = lv_label_create(lv_scr_act()); |
| 149 | + lv_label_set_text(octave_label, "Octave: 4"); |
| 150 | + lv_obj_align(octave_label, LV_ALIGN_TOP_LEFT, 0, 10); |
| 151 | + |
| 152 | + // Create a button matrix for the octave control |
| 153 | + static const char *octave_map[] = { |
| 154 | + "Up", "\n", |
| 155 | + "Down", "" |
| 156 | + }; |
| 157 | + |
| 158 | + lv_obj_t *octave_btnm = lv_btnmatrix_create(lv_scr_act()); |
| 159 | + lv_btnmatrix_set_map(octave_btnm, octave_map); |
| 160 | + lv_obj_set_size(octave_btnm, 150, 60); |
| 161 | + lv_obj_align(octave_btnm, LV_ALIGN_TOP_RIGHT, 0, 0); |
| 162 | + lv_obj_add_event_cb(octave_btnm, octave_event_cb, LV_EVENT_VALUE_CHANGED, NULL); |
| 163 | + |
| 164 | + // Create a button matrix for the notes |
| 165 | + static const char *note_map[] = { |
| 166 | + "0", "1", "2", "3", "\n", |
| 167 | + "4", "5", "6", "7", "\n", |
| 168 | + "8", "9", "10", "11", "" |
| 169 | + }; |
| 170 | + |
| 171 | + lv_obj_t *note_btnm = lv_btnmatrix_create(lv_scr_act()); |
| 172 | + lv_btnmatrix_set_map(note_btnm, note_map); |
| 173 | + lv_obj_set_size(note_btnm, 320, 150); |
| 174 | + lv_obj_align(note_btnm, LV_ALIGN_CENTER, 0, 30); |
| 175 | + lv_obj_add_event_cb(note_btnm, note_event_cb, LV_EVENT_VALUE_CHANGED, NULL); |
| 176 | + |
| 177 | + // Create the tone queue |
| 178 | + tone_queue = xQueueCreate(10, sizeof(tone_t)); |
| 179 | + assert(tone_queue != NULL); |
| 180 | + |
| 181 | + // Create the tone task |
| 182 | + xTaskCreate(tone_task, "tone_task", 2048, NULL, 5, NULL); |
| 183 | + |
| 184 | + bsp_display_backlight_on(); |
| 185 | + |
| 186 | + while (1) { |
| 187 | + vTaskDelay(pdMS_TO_TICKS(1000)); |
| 188 | + } |
| 189 | +} |
0 commit comments