diff --git a/main/cmd.c b/main/cmd.c index d23c99d..d8269a5 100644 --- a/main/cmd.c +++ b/main/cmd.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" diff --git a/main/i2s_out.c b/main/i2s_out.c index b122d43..c870834 100644 --- a/main/i2s_out.c +++ b/main/i2s_out.c @@ -6,7 +6,6 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/i2s_std.h" -#include "driver/gpio.h" #include "esp_log.h" #define TAG "i2s_out" @@ -21,12 +20,26 @@ static i2s_chan_handle_t tx_handle; void i2s_out_init(void) { i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); - chan_cfg.dma_frame_num = BUF_FRAMES; ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL)); i2s_std_config_t std_cfg = { - .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE), - .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .clk_cfg = { + .sample_rate_hz = SAMPLE_RATE, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_512, + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_32BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_32BIT, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = 32, + .ws_pol = false, + .bit_shift = false, + .left_align = true, + .big_endian = false, + .bit_order_lsb = false, + }, .gpio_cfg = { .mclk = I2S_GPIO_UNUSED, .bclk = PIN_BCLK, @@ -43,27 +56,24 @@ void i2s_out_init(void) ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg)); ESP_ERROR_CHECK(i2s_channel_enable(tx_handle)); - ESP_LOGI(TAG, "I2S TX initialized: 48kHz 16-bit stereo, BCLK=%d WS=%d DOUT=%d", + ESP_LOGI(TAG, "I2S TX: 48kHz 24-bit stereo, BCLK=%d WS=%d DOUT=%d", PIN_BCLK, PIN_WS, PIN_DOUT); } static void i2s_out_task(void *arg) { - int16_t buf[BUF_FRAMES * 2]; + int32_t buf[BUF_FRAMES * 2]; size_t bytes_written; DBG("i2s_out_task started"); for (;;) { - for (int i = 0; i < BUF_FRAMES; i++) { - buf[i * 2] = waveform_next_sample(0); - buf[i * 2 + 1] = waveform_next_sample(1); - } + waveform_fill_buffer(buf, BUF_FRAMES); i2s_channel_write(tx_handle, buf, sizeof(buf), &bytes_written, portMAX_DELAY); } } void i2s_out_start_task(void) { - xTaskCreatePinnedToCore(i2s_out_task, "i2s_out", 4096, NULL, 10, NULL, 1); + xTaskCreatePinnedToCore(i2s_out_task, "i2s_out", 8192, NULL, 10, NULL, 1); } diff --git a/main/waveform.c b/main/waveform.c index c9c41d6..86c5e58 100644 --- a/main/waveform.c +++ b/main/waveform.c @@ -10,64 +10,112 @@ #define M_PI 3.14159265358979323846f #endif +#define LUT_SIZE 4096 +#define LUT_SHIFT 20 /* 32 - 12 = 20, maps phase to 12-bit index */ + +#define RING_FRAMES 4800 /* 100ms at 48kHz */ +#define RING_SAMPLES (RING_FRAMES * 2) + +/* left-justified 24-bit amplitude constants */ +#define AMP_LJ ((int32_t)AMPLITUDE_24BIT << 8) +#define AMP_LJ_N (-AMP_LJ) + +static int32_t sine_lut[LUT_SIZE]; + static channel_state_t channels[2]; +static int32_t ring[RING_SAMPLES]; +static int ring_pos; static SemaphoreHandle_t mutex; +static int32_t generate_sample(const channel_state_t *ch, uint32_t phase) +{ + switch (ch->type) { + case WAVE_SINE: + return sine_lut[phase >> LUT_SHIFT]; + + case WAVE_SQUARE: + return (phase < (uint32_t)(ch->duty * 4294967296.0f)) ? AMP_LJ : AMP_LJ_N; + + case WAVE_SAW: { + /* symmetric triangle: -amp at 0, +amp at mid, -amp at max */ + int32_t p; + if (phase < 0x80000000U) { + /* first half: -amp to +amp */ + p = (int32_t)((int64_t)phase * 2 * AMP_LJ / (int64_t)0x80000000U) - AMP_LJ; + } else { + /* second half: +amp to -amp */ + uint32_t ph2 = phase - 0x80000000U; + p = AMP_LJ - (int32_t)((int64_t)ph2 * 2 * AMP_LJ / (int64_t)0x80000000U); + } + return p; + } + + default: + return 0; + } +} + +static void regenerate_ring(void) +{ + uint32_t phase[2] = {0, 0}; + uint32_t inc[2]; + + for (int c = 0; c < 2; c++) { + if (channels[c].type != WAVE_NONE && channels[c].freq > 0.0f) + inc[c] = (uint32_t)((double)channels[c].freq / SAMPLE_RATE * 4294967296.0); + else + inc[c] = 0; + } + + for (int i = 0; i < RING_FRAMES; i++) { + ring[i * 2] = generate_sample(&channels[0], phase[0]); + ring[i * 2 + 1] = generate_sample(&channels[1], phase[1]); + phase[0] += inc[0]; + phase[1] += inc[1]; + } + + ring_pos = 0; +} + void waveform_init(void) { mutex = xSemaphoreCreateMutex(); memset(channels, 0, sizeof(channels)); channels[0].duty = 0.5f; channels[1].duty = 0.5f; - DBG("waveform_init complete"); + + for (int i = 0; i < LUT_SIZE; i++) + sine_lut[i] = (int32_t)(AMPLITUDE_24BIT * sinf(2.0f * M_PI * i / LUT_SIZE)) << 8; + + memset(ring, 0, sizeof(ring)); + ring_pos = 0; + + DBG("waveform_init complete, LUT %d entries", LUT_SIZE); } void waveform_set(int ch, wave_type_t type, float freq, float duty) { if (ch < 0 || ch > 1) return; xSemaphoreTake(mutex, portMAX_DELAY); - channels[ch].type = type; - channels[ch].freq = freq; - channels[ch].duty = duty; - channels[ch].phase = 0.0f; + channels[ch].type = type; + channels[ch].freq = freq; + channels[ch].duty = duty; + regenerate_ring(); xSemaphoreGive(mutex); DBG("waveform_set ch=%d type=%d freq=%.1f duty=%.2f", ch, type, freq, duty); } -static inline int16_t generate(channel_state_t *s) +void waveform_fill_buffer(int32_t *buf, int frames) { - float p = s->phase; - float v; + int samples = frames * 2; - switch (s->type) { - case WAVE_SINE: - v = sinf(2.0f * M_PI * p); - break; - case WAVE_SQUARE: - v = (p < s->duty) ? 1.0f : -1.0f; - break; - case WAVE_SAW: - if (p < 0.5f) - v = p * 4.0f - 1.0f; - else - v = 3.0f - p * 4.0f; - break; - default: - return 0; - } - - s->phase += s->freq / (float)SAMPLE_RATE; - if (s->phase >= 1.0f) - s->phase -= 1.0f; - - return (int16_t)(AMPLITUDE * v); -} - -int16_t waveform_next_sample(int ch) -{ - if (ch < 0 || ch > 1) return 0; xSemaphoreTake(mutex, portMAX_DELAY); - int16_t s = generate(&channels[ch]); + int pos = ring_pos; + for (int i = 0; i < samples; i++) { + buf[i] = ring[pos]; + if (++pos >= RING_SAMPLES) + pos = 0; + } + ring_pos = pos; xSemaphoreGive(mutex); - return s; } diff --git a/main/waveform.h b/main/waveform.h index 7bd473e..2e3a90e 100644 --- a/main/waveform.h +++ b/main/waveform.h @@ -2,8 +2,8 @@ #include -#define SAMPLE_RATE 48000 -#define AMPLITUDE 16384 +#define SAMPLE_RATE 48000 +#define AMPLITUDE_24BIT 0x3FFFFF typedef enum { WAVE_NONE = 0, @@ -16,9 +16,8 @@ typedef struct { wave_type_t type; float freq; float duty; - float phase; } channel_state_t; -void waveform_init(void); -void waveform_set(int ch, wave_type_t type, float freq, float duty); -int16_t waveform_next_sample(int ch); +void waveform_init(void); +void waveform_set(int ch, wave_type_t type, float freq, float duty); +void waveform_fill_buffer(int32_t *buf, int frames);