【自作エフェクター】ESP32でディジタルエフェクターをつくる その5

電子工作
ESP32_PCM1808_PCM5102

ソフト設計

それでは、具体的なソフト設計に行きます。

今回は ESP32 を使って、PCM1808 で取り込んだ音声をリアルタイムで加工し、PCM5102 に出力する構成にしています。とりあえず音が出るところまで動作したので、コードをブロックごとに整理しながら解説していきます。

I2S の基本設定について

まずは I2S の基本設定です。ESP32 の I2S は設定項目が多く、毎回どこかを忘れてしまうので、こうしてメモしておくと後で助かります。

// --- I2S の基本設定 ---
#define I2S_NUM         I2S_NUM_0
#define I2S_SAMPLE_RATE 22050                 // サンプルレート(軽め)
#define I2S_BITS_PER_SAMPLE I2S_BITS_PER_SAMPLE_32BIT

ピン設定(PCM1808 / PCM5102)

ADC と DAC のピン設定は以下の通りです。

// --- ピン設定(PCM1808 / PCM5102 用)---
#define I2S_PIN_BCK     14  // BCK(ビットクロック)
#define I2S_PIN_WS      25  // LRCK(ワードセレクト)
#define I2S_PIN_DATA_IN 15  // PCM1808 → ESP32(ADC)
#define I2S_PIN_DATA_OUT 22 // ESP32 → PCM5102(DAC)

最初はここを間違えていて音が出ず、しばらく悩みました。ハードとソフトの境界は、こうした小さなミスが起きやすいです。

ディストーション処理(クリッピング)

歪み処理は、単純なクリッピング方式を採用しています。サンプルを増幅し、閾値を超えた部分を切り落とすだけのシンプルなものです。

// ------------------------------------------------------------
// ディストーション処理:単純なクリッピング
// ------------------------------------------------------------
int32_t applyDistortion(int32_t sample, int32_t threshold) {
    if (sample > threshold) {
        return threshold;          // 上側クリップ
    } else if (sample < -threshold) {
        return -threshold;         // 下側クリップ
    } else {
        return sample;             // 範囲内ならそのまま
    }
}

増幅率(gain)は 20 倍にしていますが、これはかなり強めです。音は出ますが、ギターらしいニュアンスはほとんど残らないため、今後調整したい部分です。

リバーブ処理(短い遅延)

リバーブは、短い遅延バッファを混ぜるだけの簡易的なものです。

// ------------------------------------------------------------
// リバーブ処理:短い遅延バッファを足すだけの簡易版
// ------------------------------------------------------------
void applyReverb(int32_t* data, size_t length, int32_t* reverb_buffer) {
    for (size_t i = 0; i < length; i++) {
        int32_t reverb_sample = reverb_buffer[i % REVERB_DELAY]; // 過去のサンプル
        reverb_buffer[i % REVERB_DELAY] = data[i];               // 現在のサンプルを保存
        data[i] = data[i] + reverb_sample / 2;                   // 混ぜる(簡易リバーブ)
    }
}

setup():I2S の初期化

I2S の初期化部分です。設定項目が多いので、毎回ここでつまずきます。

void setup() {

    // --- I2S の動作設定 ---
    i2s_config_t i2s_config = {
        .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX), // 送受信両方
        .sample_rate = I2S_SAMPLE_RATE,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
        .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, // モノラル(右チャンネル)
        .communication_format = I2S_COMM_FORMAT_I2S,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
        .dma_buf_count = 8,   // DMA バッファ数
        .dma_buf_len = 64,    // DMA バッファ長
        .use_apll = true,     // APLL 使用(クロック安定)
        .tx_desc_auto_clear = true,
        .fixed_mclk = 0
    };

    // --- ピン設定 ---
    i2s_pin_config_t pin_config = {
        .bck_io_num = I2S_PIN_BCK,
        .ws_io_num = I2S_PIN_WS,
        .data_out_num = I2S_PIN_DATA_OUT,
        .data_in_num = I2S_PIN_DATA_IN
    };

    // --- I2S ドライバ初期化 ---
    i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
    i2s_set_pin(I2S_NUM, &pin_config);
    i2s_zero_dma_buffer(I2S_NUM);

    // --- クロック設定(モノラル) ---
    i2s_set_clk(I2S_NUM, I2S_SAMPLE_RATE, I2S_BITS_PER_SAMPLE_32BIT, I2S_CHANNEL_MONO);
}

APLL を有効にするとクロックが安定するようなので、true にしています。

loop():リアルタイム処理

ここで実際の音声処理が行われます。

void loop() {
    size_t bytes_read, bytes_written;
    int32_t i2s_data[64];                         // I2S 受信バッファ
    static int32_t reverb_buffer[REVERB_DELAY] = {0}; // リバーブ用リングバッファ

    int32_t threshold = 0x0FFFFFFF; // クリッピング閾値(仮)
    float gain = 20.0;              // 増幅率(強め)

    // --- PCM1808 から音声データを読み込み ---
    i2s_read(I2S_NUM, (char*)i2s_data, sizeof(i2s_data), &bytes_read, portMAX_DELAY);

    // --- ディストーション処理 ---
    for (int i = 0; i < bytes_read / sizeof(int32_t); i++) {
        i2s_data[i] = (int32_t)(i2s_data[i] * gain);          // 増幅
        i2s_data[i] = applyDistortion(i2s_data[i], threshold); // クリッピング
    }

    // --- リバーブ処理 ---
    applyReverb(i2s_data, bytes_read / sizeof(int32_t), reverb_buffer);

    // --- PCM5102 へ出力 ---
    i2s_write(I2S_NUM, (const char*)i2s_data, bytes_read, &bytes_written, portMAX_DELAY);
}

とりあえず音は出ています。歪みもリバーブも、それっぽい音にはなっていますが、まだ「エフェクター」というより「実験用の音」という印象です。

全コードまとめ

#include <Arduino.h>
#include <driver/i2s.h>

// --- I2S の基本設定 ---
#define I2S_NUM         I2S_NUM_0
#define I2S_SAMPLE_RATE 22050                 // サンプルレート(軽め)
#define I2S_BITS_PER_SAMPLE I2S_BITS_PER_SAMPLE_32BIT

// --- ピン設定(PCM1808 / PCM5102 用) ---
#define I2S_PIN_BCK     14  // BCK(ビットクロック)
#define I2S_PIN_WS      25  // LRCK(ワードセレクト)
#define I2S_PIN_DATA_IN 15  // PCM1808 → ESP32(ADC)
#define I2S_PIN_DATA_OUT 22 // ESP32 → PCM5102(DAC)

// --- エフェクト用 ---
#define REVERB_DELAY    100 // リバーブ遅延(サンプル数)

// ------------------------------------------------------------
// ディストーション処理:単純なクリッピング
// ------------------------------------------------------------
int32_t applyDistortion(int32_t sample, int32_t threshold) {
    if (sample > threshold) {
        return threshold;          // 上側クリップ
    } else if (sample < -threshold) {
        return -threshold;         // 下側クリップ
    } else {
        return sample;             // 範囲内ならそのまま
    }
}

// ------------------------------------------------------------
// リバーブ処理:短い遅延バッファを足すだけの簡易版
// ------------------------------------------------------------
void applyReverb(int32_t* data, size_t length, int32_t* reverb_buffer) {
    for (size_t i = 0; i < length; i++) {
        int32_t reverb_sample = reverb_buffer[i % REVERB_DELAY]; // 過去のサンプル
        reverb_buffer[i % REVERB_DELAY] = data[i];               // 現在のサンプルを保存
        data[i] = data[i] + reverb_sample / 2;                   // 混ぜる(簡易リバーブ)
    }
}

void setup() {

    // --- I2S の動作設定 ---
    i2s_config_t i2s_config = {
        .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX), // 送受信両方
        .sample_rate = I2S_SAMPLE_RATE,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
        .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, // モノラル(右チャンネル)
        .communication_format = I2S_COMM_FORMAT_I2S,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
        .dma_buf_count = 8,   // DMA バッファ数
        .dma_buf_len = 64,    // DMA バッファ長
        .use_apll = true,     // APLL 使用(クロック安定)
        .tx_desc_auto_clear = true,
        .fixed_mclk = 0
    };

    // --- ピン設定 ---
    i2s_pin_config_t pin_config = {
        .bck_io_num = I2S_PIN_BCK,
        .ws_io_num = I2S_PIN_WS,
        .data_out_num = I2S_PIN_DATA_OUT,
        .data_in_num = I2S_PIN_DATA_IN
    };

    // --- I2S ドライバ初期化 ---
    i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
    i2s_set_pin(I2S_NUM, &pin_config);
    i2s_zero_dma_buffer(I2S_NUM);

    // --- クロック設定(モノラル) ---
    i2s_set_clk(I2S_NUM, I2S_SAMPLE_RATE, I2S_BITS_PER_SAMPLE_32BIT, I2S_CHANNEL_MONO);
}

void loop() {
    size_t bytes_read, bytes_written;
    int32_t i2s_data[64];                         // I2S 受信バッファ
    static int32_t reverb_buffer[REVERB_DELAY] = {0}; // リバーブ用リングバッファ

    int32_t threshold = 0x0FFFFFFF; // クリッピング閾値(仮)
    float gain = 20.0;              // 増幅率(強め)

    // --- PCM1808 から音声データを読み込み ---
    i2s_read(I2S_NUM, (char*)i2s_data, sizeof(i2s_data), &bytes_read, portMAX_DELAY);

    // --- ディストーション処理 ---
    for (int i = 0; i < (int)(bytes_read / sizeof(int32_t)); i++) {
        i2s_data[i] = (int32_t)(i2s_data[i] * gain);           // 増幅
        i2s_data[i] = applyDistortion(i2s_data[i], threshold); // クリッピング
    }

    // --- リバーブ処理 ---
    applyReverb(i2s_data, bytes_read / sizeof(int32_t), reverb_buffer);

    // --- PCM5102 へ出力 ---
    i2s_write(I2S_NUM, (const char*)i2s_data, bytes_read, &bytes_written, portMAX_DELAY);
}

今回はここまで。続く。

コメント

タイトルとURLをコピーしました