diff --git a/plip_voice/main/CMakeLists.txt b/plip_voice/main/CMakeLists.txt index 9c79a3e..20ed31c 100644 --- a/plip_voice/main/CMakeLists.txt +++ b/plip_voice/main/CMakeLists.txt @@ -7,6 +7,9 @@ idf_component_register( "cmd_exec.c" "hook_client.c" "phone.c" + "tones.c" + "dialer.c" + "conversation.c" INCLUDE_DIRS "." PRIV_REQUIRES driver @@ -14,6 +17,7 @@ idf_component_register( esp_http_client esp_http_server esp_netif + esp_timer esp_wifi json nvs_flash diff --git a/plip_voice/main/tones.c b/plip_voice/main/tones.c new file mode 100644 index 0000000..80f0604 --- /dev/null +++ b/plip_voice/main/tones.c @@ -0,0 +1,125 @@ +/* + * tones.c — France Télécom telephone tones (dial / busy / ringback). + * + * Generates 440 Hz sinusoid with on/off cadence written directly to the + * I2S speaker handle (stereo — ES8388 requires L+R pairs). + * + * Cadences (all at 440 Hz): + * DIAL : continuous + * BUSY : 0.5 s on / 0.5 s off (1 s cycle) + * RINGBACK : 1.5 s on / 3.5 s off (5 s cycle) + */ + +#include "tones.h" +#include "audio.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/i2s_std.h" +#include "esp_log.h" + +#include +#include +#include + +#define TAG "tones" + +typedef enum { TONE_NONE, TONE_DIAL, TONE_BUSY, TONE_RINGBACK } tone_mode_t; + +static volatile tone_mode_t s_mode = TONE_NONE; +static TaskHandle_t s_task = NULL; + +#define SR 16000 /* sample rate (must match audio_init) */ +#define FRAME 320 /* 20 ms @ 16 kHz */ + +/* + * Fill `n` stereo frames (L=R = 440 Hz sine) into buf. + * buf must hold n * 2 int16_t (stereo interleaved). + * ph: running phase accumulator (mono sample index). + */ +static void fill_440_stereo(int16_t *buf, int n, int *ph) +{ + for (int i = 0; i < n; i++) { + int16_t v = (int16_t)(8000.0f * sinf(2.0f * (float)M_PI * 440.0f * (*ph) / SR)); + buf[i * 2] = v; /* L */ + buf[i * 2 + 1] = v; /* R */ + (*ph)++; + } +} + +static void tone_task(void *a) +{ + (void)a; + /* Stereo buffer: FRAME mono samples × 2 channels × 2 bytes = FRAME*4 bytes */ + int16_t buf[FRAME * 2]; + int ph = 0; + int64_t t = 0; /* ms elapsed in current tone state */ + + for (;;) { + tone_mode_t m = s_mode; + if (m == TONE_NONE) { + vTaskDelay(pdMS_TO_TICKS(20)); + t = 0; + ph = 0; + continue; + } + + bool on = true; + if (m == TONE_BUSY) { + on = (t % 1000) < 500; /* 500ms on / 500ms off */ + } else if (m == TONE_RINGBACK) { + on = (t % 5000) < 1500; /* 1.5s on / 3.5s off */ + } + /* TONE_DIAL: always on */ + + if (on) { + fill_440_stereo(buf, FRAME, &ph); + } else { + memset(buf, 0, sizeof(buf)); + /* Keep phase accumulator advancing so pitch is smooth on resumption */ + ph += FRAME; + } + + size_t written = 0; + i2s_channel_write(audio_spk_handle(), buf, sizeof(buf), &written, pdMS_TO_TICKS(500)); + t += 20; /* each frame = 20 ms */ + } +} + +static void ensure_task(void) +{ + if (!s_task) { + xTaskCreate(tone_task, "tones", 3072, NULL, 5, &s_task); + ESP_LOGI(TAG, "tone_task created"); + } +} + +void tones_dialtone_start(void) +{ + ESP_LOGI(TAG, "dial tone start"); + audio_pa_set(true); + ensure_task(); + s_mode = TONE_DIAL; +} + +void tones_busy_start(void) +{ + ESP_LOGI(TAG, "busy tone start"); + audio_pa_set(true); + ensure_task(); + s_mode = TONE_BUSY; +} + +void tones_ringback_start(void) +{ + ESP_LOGI(TAG, "ringback tone start"); + audio_pa_set(true); + ensure_task(); + s_mode = TONE_RINGBACK; +} + +void tones_stop(void) +{ + ESP_LOGI(TAG, "tones stop"); + s_mode = TONE_NONE; +} diff --git a/plip_voice/main/tones.h b/plip_voice/main/tones.h new file mode 100644 index 0000000..5444a97 --- /dev/null +++ b/plip_voice/main/tones.h @@ -0,0 +1,7 @@ +#pragma once +#include "esp_err.h" + +void tones_dialtone_start(void); /* 440 Hz continuous */ +void tones_busy_start(void); /* 440 Hz 0.5s on/off */ +void tones_ringback_start(void); /* 440 Hz 1.5s/3.5s */ +void tones_stop(void);