You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
IoT-For-Beginners/translations/ja/6-consumer/lessons/1-speech-recognition/wio-terminal-audio.md

29 KiB

音声をキャプチャする - Wio Terminal

このレッスンでは、Wio Terminalで音声をキャプチャするコードを書きます。音声キャプチャは、Wio Terminalの上部にあるボタンの1つで制御されます。

デバイスをプログラムして音声をキャプチャする

マイクから音声をキャプチャするには、C++コードを使用します。Wio Terminalには192KBのRAMしかないため、数秒以上の音声をキャプチャするには不十分です。ただし、4MBのフラッシュメモリがあるため、これを使用してキャプチャした音声を保存できます。

内蔵マイクはアナログ信号をキャプチャし、それをWio Terminalが使用できるデジタル信号に変換します。音声をキャプチャする際には、データを正確なタイミングでキャプチャする必要があります。例えば、16KHzで音声をキャプチャする場合、1秒間に16,000回、等間隔でサンプルを取得する必要があります。この処理をコードで行う代わりに、ダイレクトメモリアクセスコントローラDMACを使用できます。これは、プロセッサで実行中のコードを中断することなく、信号を取得してメモリに書き込む回路です。

DMAについてさらに詳しくは、Wikipediaのダイレクトメモリアクセスのページをご覧ください。

マイクからの音声がADCを通り、DMACに送られる。このデータは1つのバッファに書き込まれ、バッファが満杯になると処理され、DMACは2つ目のバッファに書き込む

DMACは、ADCからの音声を固定間隔でキャプチャできます。例えば、16KHzの音声の場合、1秒間に16,000回キャプチャします。このキャプチャしたデータは、事前に割り当てられたメモリバッファに書き込まれ、バッファが満杯になるとコードで処理できるようになります。このメモリの使用により音声キャプチャが遅れる可能性がありますが、複数のバッファを設定することで対応できます。DMACはバッファ1に書き込み、満杯になるとコードに通知してバッファ1を処理させ、その間にDMACはバッファ2に書き込みます。バッファ2が満杯になるとコードに通知し、再びバッファ1に書き込みます。このようにして、各バッファを満杯になる前に処理できれば、データを失うことはありません。

各バッファがキャプチャされると、それをフラッシュメモリに書き込むことができます。フラッシュメモリに書き込むには、定義されたアドレスを使用して、どこに書き込むか、どのくらいのサイズで書き込むかを指定する必要があります。これは、メモリ内のバイト配列を更新するのと似ています。フラッシュメモリには粒度granularityがあり、消去や書き込み操作は固定サイズで行われ、そのサイズに整列する必要があります。例えば、粒度が4096バイトの場合、アドレス4200で消去をリクエストすると、アドレス4096から8192までのデータがすべて消去される可能性があります。このため、音声データをフラッシュメモリに書き込む際には、正しいサイズのチャンクで書き込む必要があります。

タスク - フラッシュメモリを設定する

  1. PlatformIOを使用して新しいWio Terminalプロジェクトを作成します。このプロジェクトをsmart-timerと名付け、setup関数にシリアルポートを設定するコードを追加します。

  2. フラッシュメモリにアクセスするために、以下のライブラリ依存関係をplatformio.iniファイルに追加します:

    lib_deps =
        seeed-studio/Seeed Arduino FS @ 2.1.1
        seeed-studio/Seeed Arduino SFUD @ 2.0.2
    
  3. main.cppファイルを開き、フラッシュメモリライブラリのインクルードディレクティブをファイルの先頭に追加します:

    #include <sfud.h>
    #include <SPI.h>
    

    🎓 SFUDはSerial Flash Universal Driverの略で、すべてのフラッシュメモリチップで動作するように設計されたライブラリです。

  4. setup関数に以下のコードを追加して、フラッシュストレージライブラリを設定します:

    while (!(sfud_init() == SFUD_SUCCESS))
        ;
    
    sfud_qspi_fast_read_enable(sfud_get_device(SFUD_W25Q32_DEVICE_INDEX), 2);
    

    このコードは、SFUDライブラリが初期化されるまでループし、その後高速読み取りを有効にします。内蔵フラッシュメモリは、Queued Serial Peripheral InterfaceQSPIを使用してアクセスできます。QSPIは、プロセッサの使用を最小限に抑えながら、キューを介して連続的にアクセスできるSPIコントローラの一種です。これにより、フラッシュメモリへの読み書きが高速化されます。

  5. srcフォルダにflash_writer.hという新しいファイルを作成します。

  6. このファイルの先頭に以下を追加します:

    #pragma once
    
    #include <Arduino.h>
    #include <sfud.h>
    

    これには必要なヘッダーファイルが含まれており、フラッシュメモリとやり取りするためのSFUDライブラリのヘッダーファイルも含まれています。

  7. この新しいヘッダーファイルにFlashWriterというクラスを定義します:

    class FlashWriter
    {
    public:
    
    private:
    };
    
  8. privateセクションに以下のコードを追加します:

    byte *_sfudBuffer;
    size_t _sfudBufferSize;
    size_t _sfudBufferPos;
    size_t _sfudBufferWritePos;
    
    const sfud_flash *_flash;
    

    このコードは、フラッシュメモリに書き込む前にデータを保存するためのバッファを定義します。バイト配列_sfudBufferにデータを書き込み、これが満杯になるとフラッシュメモリにデータを書き込みます。_sfudBufferPosフィールドは、このバッファ内の現在の書き込み位置を格納し、_sfudBufferWritePosはフラッシュメモリ内の書き込み位置を格納します。_flashは書き込み先のフラッシュメモリへのポインタです。一部のマイクロコントローラには複数のフラッシュメモリチップがあります。

  9. このクラスを初期化するための以下のメソッドをpublicセクションに追加します:

    void init()
    {
        _flash = sfud_get_device_table() + 0;
        _sfudBufferSize = _flash->chip.erase_gran;
        _sfudBuffer = new byte[_sfudBufferSize];
        _sfudBufferPos = 0;
        _sfudBufferWritePos = 0;
    }
    

    このメソッドは、Wio Terminalのフラッシュメモリを設定し、フラッシュメモリの粒度に基づいてバッファを設定します。これはコンストラクタではなくinitメソッドに含まれています。なぜなら、フラッシュメモリがsetup関数で設定された後に呼び出す必要があるからです。

  10. publicセクションに以下のコードを追加します:

    void writeSfudBuffer(byte b)
    {
        _sfudBuffer[_sfudBufferPos++] = b;
        if (_sfudBufferPos == _sfudBufferSize)
        {
            sfud_erase_write(_flash, _sfudBufferWritePos, _sfudBufferSize, _sfudBuffer);
            _sfudBufferWritePos += _sfudBufferSize;
            _sfudBufferPos = 0;
        }
    }
    
    void writeSfudBuffer(byte *b, size_t len)
    {
        for (size_t i = 0; i < len; ++i)
        {
            writeSfudBuffer(b[i]);
        }
    }
    
    void flushSfudBuffer()
    {
        if (_sfudBufferPos > 0)
        {
            sfud_erase_write(_flash, _sfudBufferWritePos, _sfudBufferSize, _sfudBuffer);
            _sfudBufferWritePos += _sfudBufferSize;
            _sfudBufferPos = 0;
        }
    }
    

    このコードは、フラッシュストレージシステムにバイトを書き込むためのメソッドを定義します。これは、フラッシュメモリに適したサイズのメモリ内バッファに書き込み、このバッファが満杯になるとフラッシュメモリに書き込みます。既存のデータはその場所で消去されます。また、flushSfudBufferメソッドは、不完全なバッファを書き込むためのものです。キャプチャされたデータはフラッシュメモリの粒度の正確な倍数ではないため、データの最後の部分をフラッシュメモリに書き込む必要があります。

    💁 データの最後の部分には不要なデータが追加されますが、必要なデータだけを読み取るため問題ありません。

タスク - 音声キャプチャを設定する

  1. srcフォルダにconfig.hという新しいファイルを作成します。

  2. このファイルの先頭に以下を追加します:

    #pragma once
    
    #define RATE 16000
    #define SAMPLE_LENGTH_SECONDS 4
    #define SAMPLES RATE * SAMPLE_LENGTH_SECONDS
    #define BUFFER_SIZE (SAMPLES * 2) + 44
    #define ADC_BUF_LEN 1600
    

    このコードは、音声キャプチャ用の定数を設定します。

    定数 説明
    RATE 16000 音声のサンプルレート。16,000は16KHz
    SAMPLE_LENGTH_SECONDS 4 キャプチャする音声の長さ。これは4秒に設定されています。より長い音声を録音するには、この値を増やしてください。
    SAMPLES 64000 キャプチャされる音声サンプルの総数。この値はサンプルレート×秒数で設定されます
    BUFFER_SIZE 128044 音声をキャプチャするためのバッファサイズ。音声はWAVファイルとしてキャプチャされ、44バイトのヘッダーと128,000バイトの音声データ各サンプルは2バイトで構成されます
    ADC_BUF_LEN 1600 DMACから音声をキャプチャするために使用するバッファのサイズ

    💁 4秒がタイマーをリクエストするには短すぎると感じた場合、SAMPLE_LENGTH_SECONDSの値を増やすことで、他の値も再計算されます。

  3. srcフォルダにmic.hという新しいファイルを作成します。

  4. このファイルの先頭に以下を追加します:

    #pragma once
    
    #include <Arduino.h>
    
    #include "config.h"
    #include "flash_writer.h"
    

    これには必要なヘッダーファイルが含まれており、config.hFlashWriterのヘッダーファイルも含まれています。

  5. 以下を追加して、マイクからキャプチャするMicクラスを定義します:

    class Mic
    {
    public:
        Mic()
        {
            _isRecording = false;
            _isRecordingReady = false;
        }
    
        void startRecording()
        {
            _isRecording = true;
            _isRecordingReady = false;
        }
    
        bool isRecording()
        {
            return _isRecording;
        }
    
        bool isRecordingReady()
        {
            return _isRecordingReady;
        }
    
    private:
        volatile bool _isRecording;
        volatile bool _isRecordingReady;
        FlashWriter _writer;
    };
    
    Mic mic;
    

    このクラスには現在、録音が開始されたかどうか、録音が使用可能かどうかを追跡するためのフィールドがいくつか含まれています。DMACが設定されると、メモリバッファに継続的に書き込みます。そのため、_isRecordingフラグはこれらを処理するか無視するかを決定します。必要な4秒間の音声がキャプチャされると、_isRecordingReadyフラグが設定されます。_writerフィールドは、音声データをフラッシュメモリに保存するために使用されます。

    グローバル変数としてMicクラスのインスタンスが宣言されます。

  6. Micクラスのprivateセクションに以下のコードを追加します:

    typedef struct
    {
        uint16_t btctrl;
        uint16_t btcnt;
        uint32_t srcaddr;
        uint32_t dstaddr;
        uint32_t descaddr;
    } dmacdescriptor;
    
    // Globals - DMA and ADC
    volatile dmacdescriptor _wrb[DMAC_CH_NUM] __attribute__((aligned(16)));
    dmacdescriptor _descriptor_section[DMAC_CH_NUM] __attribute__((aligned(16)));
    dmacdescriptor _descriptor __attribute__((aligned(16)));
    
    void configureDmaAdc()
    {
        // Configure DMA to sample from ADC at a regular interval (triggered by timer/counter)
        DMAC->BASEADDR.reg = (uint32_t)_descriptor_section;                    // Specify the location of the descriptors
        DMAC->WRBADDR.reg = (uint32_t)_wrb;                                    // Specify the location of the write back descriptors
        DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);           // Enable the DMAC peripheral
        DMAC->Channel[1].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(TC5_DMAC_ID_OVF) | // Set DMAC to trigger on TC5 timer overflow
                                        DMAC_CHCTRLA_TRIGACT_BURST;             // DMAC burst transfer
    
        _descriptor.descaddr = (uint32_t)&_descriptor_section[1];                    // Set up a circular descriptor
        _descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg;                           // Take the result from the ADC0 RESULT register
        _descriptor.dstaddr = (uint32_t)_adc_buf_0 + sizeof(uint16_t) * ADC_BUF_LEN; // Place it in the adc_buf_0 array
        _descriptor.btcnt = ADC_BUF_LEN;                                             // Beat count
        _descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                            // Beat size is HWORD (16-bits)
                                DMAC_BTCTRL_DSTINC |                                    // Increment the destination address
                                DMAC_BTCTRL_VALID |                                     // Descriptor is valid
                                DMAC_BTCTRL_BLOCKACT_SUSPEND;                           // Suspend DMAC channel 0 after block transfer
        memcpy(&_descriptor_section[0], &_descriptor, sizeof(_descriptor));          // Copy the descriptor to the descriptor section
    
        _descriptor.descaddr = (uint32_t)&_descriptor_section[0];                    // Set up a circular descriptor
        _descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg;                           // Take the result from the ADC0 RESULT register
        _descriptor.dstaddr = (uint32_t)_adc_buf_1 + sizeof(uint16_t) * ADC_BUF_LEN; // Place it in the adc_buf_1 array
        _descriptor.btcnt = ADC_BUF_LEN;                                             // Beat count
        _descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                            // Beat size is HWORD (16-bits)
                                DMAC_BTCTRL_DSTINC |                                    // Increment the destination address
                                DMAC_BTCTRL_VALID |                                     // Descriptor is valid
                                DMAC_BTCTRL_BLOCKACT_SUSPEND;                           // Suspend DMAC channel 0 after block transfer
        memcpy(&_descriptor_section[1], &_descriptor, sizeof(_descriptor));          // Copy the descriptor to the descriptor section
    
        // Configure NVIC
        NVIC_SetPriority(DMAC_1_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC1 to 0 (highest)
        NVIC_EnableIRQ(DMAC_1_IRQn);      // Connect DMAC1 to Nested Vector Interrupt Controller (NVIC)
    
        // Activate the suspend (SUSP) interrupt on DMAC channel 1
        DMAC->Channel[1].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;
    
        // Configure ADC
        ADC1->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN12_Val; // Set the analog input to ADC0/AIN2 (PB08 - A4 on Metro M4)
        while (ADC1->SYNCBUSY.bit.INPUTCTRL)
            ;                              // Wait for synchronization
        ADC1->SAMPCTRL.bit.SAMPLEN = 0x00; // Set max Sampling Time Length to half divided ADC clock pulse (2.66us)
        while (ADC1->SYNCBUSY.bit.SAMPCTRL)
            ;                                         // Wait for synchronization
        ADC1->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV128; // Divide Clock ADC GCLK by 128 (48MHz/128 = 375kHz)
        ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT |    // Set ADC resolution to 12 bits
                            ADC_CTRLB_FREERUN;          // Set ADC to free run mode
        while (ADC1->SYNCBUSY.bit.CTRLB)
            ;                       // Wait for synchronization
        ADC1->CTRLA.bit.ENABLE = 1; // Enable the ADC
        while (ADC1->SYNCBUSY.bit.ENABLE)
            ;                       // Wait for synchronization
        ADC1->SWTRIG.bit.START = 1; // Initiate a software trigger to start an ADC conversion
        while (ADC1->SYNCBUSY.bit.SWTRIG)
            ; // Wait for synchronization
    
        // Enable DMA channel 1
        DMAC->Channel[1].CHCTRLA.bit.ENABLE = 1;
    
        // Configure Timer/Counter 5
        GCLK->PCHCTRL[TC5_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |     // Enable peripheral channel for TC5
                                            GCLK_PCHCTRL_GEN_GCLK1; // Connect generic clock 0 at 48MHz
    
        TC5->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ; // Set TC5 to Match Frequency (MFRQ) mode
        TC5->COUNT16.CC[0].reg = 3000 - 1;            // Set the trigger to 16 kHz: (4Mhz / 16000) - 1
        while (TC5->COUNT16.SYNCBUSY.bit.CC0)
            ; // Wait for synchronization
    
        // Start Timer/Counter 5
        TC5->COUNT16.CTRLA.bit.ENABLE = 1; // Enable the TC5 timer
        while (TC5->COUNT16.SYNCBUSY.bit.ENABLE)
            ; // Wait for synchronization
    }
    
    uint16_t _adc_buf_0[ADC_BUF_LEN];
    uint16_t _adc_buf_1[ADC_BUF_LEN];
    

    このコードは、DMACを設定するconfigureDmaAdcメソッドを定義します。DMACをADCに接続し、2つの異なる交互バッファ_adc_buf_0_adc_buf_1)を設定します。

    💁 マイクロコントローラ開発の欠点の1つは、ハードウェアと直接やり取りするために必要なコードの複雑さです。コードは非常に低レベルで動作し、ハードウェアと直接やり取りします。シングルボードコンピュータやデスクトップコンピュータで書くコードよりも複雑です。オペレーティングシステムが支援してくれるわけではないためです。一部のライブラリを使用することでこれを簡略化できますが、それでも多くの複雑さが残ります。

  7. このコードの下に以下を追加します:

    // WAV files have a header. This struct defines that header
    struct wavFileHeader
    {
        char riff[4];         /* "RIFF"                                  */
        long flength;         /* file length in bytes                    */
        char wave[4];         /* "WAVE"                                  */
        char fmt[4];          /* "fmt "                                  */
        long chunk_size;      /* size of FMT chunk in bytes (usually 16) */
        short format_tag;     /* 1=PCM, 257=Mu-Law, 258=A-Law, 259=ADPCM */
        short num_chans;      /* 1=mono, 2=stereo                        */
        long srate;           /* Sampling rate in samples per second     */
        long bytes_per_sec;   /* bytes per second = srate*bytes_per_samp */
        short bytes_per_samp; /* 2=16-bit mono, 4=16-bit stereo          */
        short bits_per_samp;  /* Number of bits per sample               */
        char data[4];         /* "data"                                  */
        long dlength;         /* data length in bytes (filelength - 44)  */
    };
    
    void initBufferHeader()
    {
        wavFileHeader wavh;
    
        strncpy(wavh.riff, "RIFF", 4);
        strncpy(wavh.wave, "WAVE", 4);
        strncpy(wavh.fmt, "fmt ", 4);
        strncpy(wavh.data, "data", 4);
    
        wavh.chunk_size = 16;
        wavh.format_tag = 1; // PCM
        wavh.num_chans = 1;  // mono
        wavh.srate = RATE;
        wavh.bytes_per_sec = (RATE * 1 * 16 * 1) / 8;
        wavh.bytes_per_samp = 2;
        wavh.bits_per_samp = 16;
        wavh.dlength = RATE * 2 * 1 * 16 / 2;
        wavh.flength = wavh.dlength + 44;
    
        _writer.writeSfudBuffer((byte *)&wavh, 44);
    }
    

    このコードは、44バイトのメモリを占有するWAVヘッダーを構造体として定義します。このヘッダーには、音声ファイルのレート、サイズ、チャンネル数に関する詳細が書き込まれます。このヘッダーはフラッシュメモリに書き込まれます。

  8. このコードの下に、音声バッファが処理可能になったときに呼び出されるメソッドを宣言する以下のコードを追加します:

    void audioCallback(uint16_t *buf, uint32_t buf_len)
    {
        static uint32_t idx = 44;
    
        if (_isRecording)
        {
            for (uint32_t i = 0; i < buf_len; i++)
            {
                int16_t audio_value = ((int16_t)buf[i] - 2048) * 16;
    
                _writer.writeSfudBuffer(audio_value & 0xFF);
                _writer.writeSfudBuffer((audio_value >> 8) & 0xFF);
            }
    
            idx += buf_len;
    
            if (idx >= BUFFER_SIZE)
            {
                _writer.flushSfudBuffer();
                idx = 44;
                _isRecording = false;
                _isRecordingReady = true;
            }
        }
    }
    

    音声バッファは、ADCからの音声を含む16ビット整数の配列です。ADCは12ビットの符号なし値0-1023を返すため、これを16ビットの符号付き値に変換し、さらに2バイトに変換して生のバイナリデータとして保存します。

    これらのバイトはフラッシュメモリバッファに書き込まれます。書き込みはインデックス44から開始されます。これは、WAVファイルヘッダーとして書き込まれた44バイトのオフセットです。必要な音声長のバイトがすべてキャプチャされると、残りのデータがフラッシュメモリに書き込まれます。

  9. Micクラスのpublicセクションに以下のコードを追加します:

    void dmaHandler()
    {
        static uint8_t count = 0;
    
        if (DMAC->Channel[1].CHINTFLAG.bit.SUSP)
        {
            DMAC->Channel[1].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;
            DMAC->Channel[1].CHINTFLAG.bit.SUSP = 1;
    
            if (count)
            {
                audioCallback(_adc_buf_0, ADC_BUF_LEN);
            }
            else
            {
                audioCallback(_adc_buf_1, ADC_BUF_LEN);
            }
    
            count = (count + 1) % 2;
        }
    }
    

    このコードは、DMACがバッファを処理するようコードに通知するために呼び出されます。処理すべきデータがあるかどうかを確認し、該当するバッファをaudioCallbackメソッドに渡します。

  10. クラスの外部で、Mic mic;の宣言の後に以下のコードを追加します:

    void DMAC_1_Handler()
    {
        mic.dmaHandler();
    }
    

    DMAC_1_Handlerは、バッファが処理可能になったときにDMACによって呼び出されます。この関数は名前で見つけられるため、存在するだけで呼び出されます。

  11. Micクラスのpublicセクションに以下の2つのメソッドを追加します

    void init()
    {
        analogReference(AR_INTERNAL2V23);
    
        _writer.init();
    
        initBufferHeader();
        configureDmaAdc();
    }
    
    void reset()
    {
        _isRecordingReady = false;
        _isRecording = false;
    
        _writer.reset();
    
        initBufferHeader();
    }
    

    initメソッドはMicクラスを初期化するコードを含みます。このメソッドは、マイクピンの正しい電圧を設定し、フラッシュメモリライターを設定し、WAVファイルヘッダーを書き込み、DMACを設定します。resetメソッドは、音声がキャプチャされて使用された後にフラッシュメモリをリセットし、ヘッダーを書き直します。

タスク - 音声をキャプチャする

  1. main.cppファイルにmic.hヘッダーファイルのインクルードディレクティブを追加します:

    #include "mic.h"
    
  2. setup関数で、Cボタンを初期化します。このボタンが押されると音声キャプチャが開始され、4秒間続きます

    pinMode(WIO_KEY_C, INPUT_PULLUP);
    
  3. このコードの下に、マイクを初期化し、音声キャプチャの準備ができたことをコンソールに出力します:

    mic.init();
    
    Serial.println("Ready.");
    
  4. loop関数の上に、キャプチャされた音声を処理する関数を定義します。この関数は現在何もしませんが、後のレッスンで音声をテキストに変換するために使用されます:

    void processAudio()
    {
    
    }
    
  5. loop関数に以下を追加します:

    void loop()
    {
        if (digitalRead(WIO_KEY_C) == LOW && !mic.isRecording())
        {
            Serial.println("Starting recording...");
            mic.startRecording();
        }
    
        if (!mic.isRecording() && mic.isRecordingReady())
        {
            Serial.println("Finished recording");
    
            processAudio();
    
            mic.reset();
        }
    }
    

    このコードはCボタンをチェックし、録音が開始されていない場合にボタンが押されたら、Micクラスの_isRecordingフィールドをtrueに設定します。これにより、MicクラスのaudioCallbackメソッドが4秒間音声を保存します。4秒間の音声がキャプチャされると、_isRecordingフィールドがfalseに設定され、_isRecordingReadyフィールドがtrueに設定されます。これがloop関数でチェックされ、trueの場合にprocessAudio関数が呼び出され、その後マイククラスがリセットされます。

  6. このコードをビルドし、Wio Terminalにアップロードしてシリアルモニタでテストします。Cボタン左側で電源スイッチに最も近いボタンを押して話しかけると、4秒間の音声がキャプチャされます。

    --- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
    --- More details at http://bit.ly/pio-monitor-filters
    --- Miniterm on /dev/cu.usbmodem1101  9600,8,N,1 ---
    --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
    Ready.
    Starting recording...
    Finished recording
    

💁 このコードは code-record/wio-terminal フォルダーにあります。 😀 あなたの音声録音プログラムは大成功でした!

免責事項:
この文書は、AI翻訳サービス Co-op Translator を使用して翻訳されています。正確性を追求しておりますが、自動翻訳には誤りや不正確な部分が含まれる可能性があることをご承知おきください。元の言語で記載された文書が公式な情報源とみなされるべきです。重要な情報については、専門の人間による翻訳を推奨します。この翻訳の使用に起因する誤解や誤認について、当方は一切の責任を負いません。