ガレスタさんのDIY日記

電子回路、Web、組み込み、自作エフェクターを語るblog

STM32CubeProgでUARTを使ったフラッシュ書き換えを試す

STM32に関しては久しぶりですが
今回はSTからリリースされたSTM32CubeProgというソフトについてみていこうと思います。

STM32CubeProg

www.st.com

簡単に説明すると

STlink utility とかflash loaderとかdfuツールとかそういうのが一緒になってWindows,Mac ,Linuxに対応したものです。
以前できたことは一通りできます。

というわけで・・・・・

SWD接続とかDFUはいろんな方がやっているので
UARTブートローダーでもやってみようと思います。

UART bootloader

UARTからバイナリをぼこぼこ投げて書き込みできる機能。
特にまあたらしい物でもないからこんなもんで早速やって行きましょう。

今回使うものはSTM32F767 Nucleoを使うのでそれ用の設定で説明していきます。

STM32F767の場合UARTは以下のものが使えます。
f:id:gsmcustomeffects:20181020085513p:plain

んでまあ見てわかるんですが注意が必要でUART3ってSTLINKとつながってるのでそれが使えるやん?思う方もいると思うんですがところがどっこい
f:id:gsmcustomeffects:20181020085624p:plain

ブートローダーで使えるピンと違うとこからUART3を出してるのでSTLINK経由では使えません(このせいでなんでつながらないんだろうと5分位悩みました。
問題が整理できたとこでPC10,11が使えるとのことからこの辺から引っ張ってあげましょう

f:id:gsmcustomeffects:20181020090002p:plain

STLINKのTX RXピンから延ばしてもいいのですがシリアル変換器はこの辺の使ってます
f:id:gsmcustomeffects:20181020090144p:plain

次にbootloaderに遷移するための準備です。
STM32の場合は基本BOOT1とBOOT0の設定でフラッシュブートとsystem bootloaderから起動するか設定できるんですがF7の場合BOOTピン一個になりまして起動アドレス指定ができるようになりました。
f:id:gsmcustomeffects:20181020091839p:plain

初期状態でBOOTをHIGHにするとsystem bootloaderでいけるという感じですね。オプションバイトに関してはCubeProgで書き換えたり今の状態を見たりできるのでその都度確認してみてください。
ピンで言うとここです
f:id:gsmcustomeffects:20181020092138p:plain

ココまで終わるとフローチャート的にはこうなります
f:id:gsmcustomeffects:20181020092315p:plain

というわけでBOOTピンをHIGHにしてリセットしてUARTで接続してみましょう。
つながるとこのようになります。
f:id:gsmcustomeffects:20181020092536p:plain

先ほど示したオプションバイトはこのように確認できます。
f:id:gsmcustomeffects:20181020092623p:plain

書き込みはこの画面からできます
f:id:gsmcustomeffects:20181020093101p:plain

所感

というわけでかなり簡単にフラッシュの書き換えができるというわけです。
近年デバッガが安くなってきており持ってると特に使わない機能なんですが

エフェクターとか開発してると弾き手とかそういう人とやりとりする機会が結構あってアルゴリズム詰めてる時向こうが書き込み機を持ってないときとかに便利です。
あとは製品出荷後にアップデートとかしたいときに使えます。

あとF7ならDFUで書くほうが何もいらないのでいいと思いましたw

おまけ

ブートローダーについてCubeProgが自動でやってくれるので特に必要ありませんがマイコン間とかでファーム書き換えする場合は自前で打つ必要があります。
f:id:gsmcustomeffects:20181020093528p:plain

BOOTピンをHIGHにして該当UARTに0x7Fを送るとUARTブートするみたいです。
それを送るとACKがかえるみたいなのでTeratermで実験してみた。
f:id:gsmcustomeffects:20181020093833p:plain

Teratermを16進表記するとわかりやすいのでココを参考にしてみてください
shuzo-kino.hateblo.jp

デバッグmodeにできたら
信号を送信していきます。

ここでもTeratermのマクロ機能が便利です。

send $7F

とすると0x7Fを送ってくれます。

保存はこのような拡張子にします
f:id:gsmcustomeffects:20181020094134p:plain

次に接続設定です。
f:id:gsmcustomeffects:20181020094223p:plain

パリティEVEN以外は得に変わりはないです。

そうしたら上部メニューからコントロール/マクロを選択 先ほどのiniファイルを選択すると

f:id:gsmcustomeffects:20181020094332p:plain

きちんと79が帰ってきています。

そのあとはマニュアル通りにコマンドを打ってあげると書き込みできます。

ちなみにteratermのマクロはいろいろできるのでプロトコルとかそういった通信テストには便利だと思いますので覚えておくといいでしょう。

資料など

  • AN3155 Application note USART protocol used in the STM32 bootloader
  • UM2237 User manual STM32CubeProgrammer software description
  • AN2606 Application note STM32 microcontroller system memory boot mode

i.MX-RT600なるものが出たらしいよ

f:id:gsmcustomeffects:20181016033829p:plain

f:id:gsmcustomeffects:20181016034159p:plain
ARMの最新コアCortexM33とケイデンスDSPテンシリカHifi4のニコイチSoCみたいですね。

  • CortexM33 300Mhz
  • Cadence® Tensilica® Hi-Fi 4 600MHz

という構成になってるみたいですね。

SRAMが4.5MBとかついてますね
これはデジタルエフェクトにかなりよさそう。

その他I3CというI2Cの次の規格になっているみたいですね。

パッケージは0.5㎜ピッチのBGAでところどころ空きがあるので配線には余裕がありそう

f:id:gsmcustomeffects:20181016034259p:plain

STM32F767 SAIについてメモ1

今回はSerial Audio Interfaceの話です
タイトルはF7となっているがSTがラインナップごとにCubeHALの仕様変えなければ同じように考えることができると思います。

まずSAIについて基礎的なことはこの辺を読んでくださればわかると思います。
http://www.st.com/resource/ja/product_presentation/37.stm32l4-peripheral-serial-audio-interface-wspc-(sai)-wspc-final_jp.pdf

そんでもって何で今回こういう記事を書いているのかというとペリフェラルInitまではCubeMX*1がやってくれるけどその先でHAL_SAI_Receive_ITのような末尾にITがつく割り込み用のAPIに関して何もわかってなかったのでそれを解決する目的からです。

HAL_SAI_Receive_ITについて

STのCubeHAL*2の通信系のペリフェラルは大きく分けて

  • HAL_SAI_Receive(ポーリング)
  • HAL_SAI_Receive_IT(割り込み)
  • HAL_SAI_Receive_DMA(DMA)

の3つのAPIが用意されている。
下の二つがノンブロッキングAPI(他の処理をブロックしない)になっている。
今回は割り込み駆動についてみていきたいので

  • HAL_SAI_Receive_IT(SAI_HandleTypeDef *hsai, uint8_t *pData, uint16_t Size)

を見ていくことになる。
引数に関しては設定などもろもろ含まれている構造体SAI_HandleTypeDef とデータバッファのポインタとサイズが必要。

HAL_StatusTypeDef HAL_SAI_Receive_IT(SAI_HandleTypeDef *hsai, uint8_t *pData, uint16_t Size)
{
  if((pData == NULL) || (Size == 0))
  {
    return  HAL_ERROR;
  }

  if(hsai->State == HAL_SAI_STATE_READY)
  {
    /* Process Locked */
    __HAL_LOCK(hsai);

    hsai->pBuffPtr = pData;
    hsai->XferSize = Size;
    hsai->XferCount = Size;
    hsai->ErrorCode = HAL_SAI_ERROR_NONE;
    hsai->State = HAL_SAI_STATE_BUSY_RX;

    if((hsai->Init.DataSize == SAI_DATASIZE_8) && (hsai->Init.CompandingMode == SAI_NOCOMPANDING))
    {
      hsai->InterruptServiceRoutine = SAI_Receive_IT8Bit;
    }
    else if(hsai->Init.DataSize <= SAI_DATASIZE_16)
    {
      hsai->InterruptServiceRoutine = SAI_Receive_IT16Bit;
    }
    else
    {
      hsai->InterruptServiceRoutine = SAI_Receive_IT32Bit;
    }

    /* Enable TXE and OVRUDR interrupts */
    __HAL_SAI_ENABLE_IT(hsai, SAI_InterruptFlag(hsai, SAI_MODE_IT));

    /* Check if the SAI is already enabled */
    if((hsai->Instance->CR1 & SAI_xCR1_SAIEN) == RESET)
    {
      /* Enable SAI peripheral */
      __HAL_SAI_ENABLE(hsai);
    }

    /* Process Unlocked */
    __HAL_UNLOCK(hsai);
    
    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

冒頭のエラー処理を除くとおおまかに4つのことをやっている。

  1. 構造体メンバに対して値の設定
  2. InterruptServiceRoutine の登録
  3. 割り込みの有効化
  4. SAIモジュールの有効化(無効の場合だけ実行)

これだけ見るとデータの受け渡しやってないやん!?ってなるけどまあ耐えてください。

構造体メンバに対して値の設定

    hsai->pBuffPtr = pData;
    hsai->XferSize = Size;
    hsai->XferCount = Size;
    hsai->ErrorCode = HAL_SAI_ERROR_NONE;
    hsai->State = HAL_SAI_STATE_BUSY_RX;

がその部分にあたりバッファのサイズとかその辺を構造体に対して渡してあげるという感じ

InterruptServiceRoutine の登録

    if((hsai->Init.DataSize == SAI_DATASIZE_8) && (hsai->Init.CompandingMode == SAI_NOCOMPANDING))
    {
      hsai->InterruptServiceRoutine = SAI_Receive_IT8Bit;
    }
    else if(hsai->Init.DataSize <= SAI_DATASIZE_16)
    {
      hsai->InterruptServiceRoutine = SAI_Receive_IT16Bit;
    }
    else
    {
      hsai->InterruptServiceRoutine = SAI_Receive_IT32Bit;
    }

ここが結構重要でRX FIFO -> データバッファ転送を行うための実態を登録する処理をしている。

InterruptServiceRoutineは構造体にて定義されている
f:id:gsmcustomeffects:20180915032512p:plain

SAI_Receive_IT32Bitはstaticで定義されておりここでやっとバッファに対してデータを渡す形になっている。

f:id:gsmcustomeffects:20180915032900p:plain

規定数データの受け渡しが終わったら

  • 割り込みの禁止
  • 割り込みフラグのクリア
  • ステータスの更新(busy -> ready)
  • HAL_SAI_RxCpltCallbackのコール

ここで注意しなければいけないのはHAL_SAI_Receive_ITはISRの登録しかしていないので実際の処理をやっているのはSAI1_IRQHandler内のHAL_SAI_IRQHandler(&hsai_BlockA1);である。
f:id:gsmcustomeffects:20180915212013p:plain

この内部でステータスフラグを確認して何を起爆剤として割り込みに入ったかを判別している。
FIFOの場合は青枠でくくった感じになっており関数ポインタで定義されたInterruptServiceRoutineを呼ぶようにできている。

割り込みの有効化とSAIの有効化

一通りの設定が終了後に__HAL_SAI_ENABLE_ITと__HAL_SAI_ENABLEを読んで一連の動作が終わる。

まとめ

HAL_SAI_Receive_ITは

  • データサイズの登録
  • InterruptServiceRoutineの登録
  • __HAL_SAI_ENABLE_ITにて割り込み有効
  • __HAL_SAI_ENABLEにてペリフェラルを有効

割り込み検出時には

  • 実際のハンドラSAI1_IRQHandlerにてInterruptServiceRoutineをコールして登録した関数を実行

SAI_Receive_IT32Bitが

  • データレジスタから読み出し
  • 規定数読み出し終了後割り込みの無効化、フラグクリア
  • HAL_SAI_RxCpltCallback(受信完了コールバック)をコール

*1:STが出しているマイコンGUIコンフィグツール

*2:STが出しているSDKのことを総じてこう呼んでいる。 CubeF7のようにラインナップごとに名前がついている

i.MX RT1050 SAIメモ3

今回からやっとSDKのほうに入っていく。

割り込みのサンプルコードベースでみていく
まずは構造体のインスタンス生成を行っている

sai_config_t config;
sai_transfer_format_t format;

configは主にプロトコルとクロック供給の設定項目をもつ構造体

typedef struct _sai_config
{
    sai_protocol_t protocol;  /*!< Audio bus protocol in SAI */
    sai_sync_mode_t syncMode; /*!< SAI sync mode, control Tx/Rx clock sync */
#if defined(FSL_FEATURE_SAI_HAS_MCR) && (FSL_FEATURE_SAI_HAS_MCR)
    bool mclkOutputEnable;          /*!< Master clock output enable, true means master clock divider enabled */
#endif                              /* FSL_FEATURE_SAI_HAS_MCR */
    sai_mclk_source_t mclkSource;   /*!< Master Clock source */
    sai_bclk_source_t bclkSource;   /*!< Bit Clock source */
    sai_master_slave_t masterSlave; /*!< Master or slave */
} sai_config_t;

formatはSAIの内部設定が多め
主にサンプル周波数、bit幅、クロック、FIFO閾値

typedef struct _sai_transfer_format
{
    uint32_t sampleRate_Hz;   /*!< Sample rate of audio data */
    uint32_t bitWidth;        /*!< Data length of audio data, usually 8/16/24/32 bits */
    sai_mono_stereo_t stereo; /*!< Mono or stereo */
    uint32_t masterClockHz;   /*!< Master clock frequency in Hz */
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
    uint8_t watermark;       /*!< Watermark value */
#endif                       /* FSL_FEATURE_SAI_FIFO_COUNT */
    uint8_t channel;         /*!< Data channel used in transfer.*/
    sai_protocol_t protocol; /*!< Which audio protocol used */
    bool isFrameSyncCompact; /*!< True means Frame sync length is configurable according to bitWidth, false means frame
                                sync length is 64 times of bit clock. */
} sai_transfer_format_t;
||

その次に出てくるのが
>|c|
SAI_TxGetDefaultConfig(&config);
SAI_TxInit(DEMO_SAI, &config);

という記述だが
defaultのほうはconfig構造体に初期値をセットする処理
initのほうはvoid SAI_TxInit(I2S_Type *base, const sai_config_t *config)という引数構造になっておりレジスタのベースアドレスを与えるのと先ほど使ったコンフィグを使ってSAIのTx側を初期化するAPI

次に使う設定にformatを書き換えていく

    format.bitWidth = DEMO_SAI_BITWIDTH;
    format.channel = DEMO_SAI_CHANNEL;
    format.sampleRate_Hz = kSAI_SampleRate16KHz;
    format.masterClockHz = DEMO_SAI_CLK_FREQ;
    format.protocol = config.protocol;
    format.stereo = kSAI_MonoLeft;
    format.isFrameSyncCompact = false;

この辺はサンプル用にできてるのでfsl_sai.hにenum定義されている値を使って代入してくと便利。
f:id:gsmcustomeffects:20180913010536p:plain

次がCodecの初期化

    CODEC_Init(&codecHandle, &boardCodecConfig);
    CODEC_SetFormat(&codecHandle, format.masterClockHz, format.sampleRate_Hz, format.bitWidth);

ここに関しては別に記事を書いたのでそちらを参照
MCUXpresso SDK:コーディック周りのHALについて - ガレスタさんのDIY日記

次にクロックの値と先ほど設定したformat構造体をセットしに行く
そのあとに割り込みを有効にしSAIのTx割り込みの設定(どのイベントで割り込みするのか)
最後にTxを有効

    mclkSourceClockHz = DEMO_SAI_CLK_FREQ;
    SAI_TxSetFormat(DEMO_SAI, &format, mclkSourceClockHz, format.masterClockHz);
    EnableIRQ(DEMO_SAI_IRQ);
    SAI_TxEnableInterrupts(DEMO_SAI, kSAI_FIFOWarningInterruptEnable | kSAI_FIFOErrorInterruptEnable);
    SAI_TxEnable(DEMO_SAI, true);

割り込みでは
f:id:gsmcustomeffects:20180913011209p:plain
となっているのでTxFIFOが空だったら割り込みして次のデータをFIFOに対して書きに行くって感じですかね。

というわけで

まとめ

  • 主な設定はconfig,format構造体
  • SAI_TxGetDefaultConfigしてとりあえずInit そのあとに自分の設定をたたく
  • mclkSourceClockHz はきちんと確認する
  • SAI_TxSetFormatで設定の上書きができる
  • SAI_TxEnableInterruptにて割り込みの設定

MCUXpresso SDK:コーディック周りのHALについて

オーディオフレームを書くにあたっていろいろ調べていたのでそれのメモがてら書いています。


IMXRT1020のコーディック周りは

  • fsl_codec_common.h
  • fsl_codec_common.c
  • fsl_wm8960.c
  • fsl_wm8960.h

の4つからできています。

WM8960_WriteRegの中でCODEC_I2C_WriteReg使っています。
これはCODEC_I2C_WriteRegを自身のI2CAPIで置き換えることで移植性をあげる目的かと思います。

status_t CODEC_I2C_WriteReg(uint8_t i2cAddr,
                            codec_reg_addr_t addrType,
                            uint32_t reg,
                            codec_reg_width_t regWidth,
                            uint32_t value,
                            codec_i2c_send_func_t i2cSendFunc)
{
    uint8_t data[4];
    uint8_t i;

    i = regWidth;
    while (i--)
    {
        data[i] = (uint8_t)value;
        value >>= 8;
    }

    return i2cSendFunc(i2cAddr, reg, addrType, data, regWidth);
}
status_t WM8960_WriteReg(codec_handle_t *handle, uint8_t reg, uint16_t val)
{
    uint8_t cmd, buff;
    uint8_t retval = 0;

    /* The register address */
    cmd = (reg << 1) | ((val >> 8U) & 0x0001U);
    /* Data */
    buff = val & 0xFF;

    retval = CODEC_I2C_WriteReg(handle->slaveAddress, kCODEC_RegAddr8Bit, cmd, kCODEC_RegWidth8Bit, buff,
                                handle->I2C_SendFunc);

    if (retval == kStatus_Success)
    {
        reg_cache[reg] = val;
    }

    return retval;
}

codec_i2c_send_func_tは関数ポインタでこのように記載されている。

typedef status_t (*codec_i2c_send_func_t)(uint8_t deviceAddress, uint32_t subAddress, uint8_t subaddressSize, const uint8_t *txBuff, uint8_t txBuffSize);

んでもってAPIをつなぐAPICODEC_Init(&codecHandle, &boardCodecConfig);となっているわけだが

status_t CODEC_Init(codec_handle_t *handle, codec_config_t *config)
{
    /* Set the handle information */
    handle->I2C_SendFunc = config->I2C_SendFunc;
    handle->I2C_ReceiveFunc = config->I2C_ReceiveFunc;
    memcpy(&handle->op, &config->op, sizeof(codec_operation_t));
    return handle->op.Init(handle, config->codecConfig);
}

codec_config_tのセットに関してはborad.cの以下で記載されている

codec_config_t boardCodecConfig = {
    .I2C_SendFunc = BOARD_Codec_I2C_Send,
    .I2C_ReceiveFunc = BOARD_Codec_I2C_Receive,
    .op.Init = WM8960_Init,
    .op.Deinit = WM8960_Deinit,
    .op.SetFormat = WM8960_ConfigDataFormat
};

そこから下に入るとBOARD_Codec_I2C_Sendがこうなっている。

status_t BOARD_Codec_I2C_Send(
    uint8_t deviceAddress, uint32_t subAddress, uint8_t subAddressSize, const uint8_t *txBuff, uint8_t txBuffSize)
{
    return BOARD_LPI2C_Send(BOARD_CODEC_I2C_BASEADDR, deviceAddress, subAddress, subAddressSize, (uint8_t *)txBuff,
                            txBuffSize);
}

ここまででやっと上と下がつながる構造になっている。


てなわけでBOARD_Codec_I2C_Sendを書きかえるか新たに同じ引数の形でつなぎ直すかで対応できると思います。

まとめ

  • codecを追加するときはCODEC_I2C_WriteRegを使って送信関数を記述してinitとかをインプリする。
  • I2Cを変えるときはcodec_config_t構造体にて各種関数をつなぐ
  • 最後にCODEC_Initを呼ぶ

Python:ディレイを実装してみる:1

今回はディレイを実装していきます。
いわゆる遅延系エフェクトでやまびこ効果とか言われるやつです。


ディレイの実装には参考文献に示すようにフィードバックの仕方によっていろいろな種類がありますが一般的なフィードバックありミックスありの通常のタイプをやって行きます。

f:id:gsmcustomeffects:20180820231509p:plain

まず時系列信号処理をするにあたってデータを保存するバッファを確保する必要があるのでリングバッファを使ってそれを実現します。
リングバッファの説明は参考文献の二つ目を参照してくれると理解しやすいと思う。

実装

というわけでさっそく実装していく

まずはエフェクトのパラメータであるfeedbackとmixとtimeを定義する。

delay_buffer_idx    = 0                                 #リングバッファのインデックス
feedback            = 0.4                               #フィードバック(0~1)
delay_ms            = 465                               #ディレイタイム[ms]
delay_buf_size      = int(frame_fs * delay_ms / 1000)   #バッファ確保[sample]
level                 = 0.1                               #level(0~1)

frame_fs は処理系によって異なりますが基本48000(48kHz)ぐらいだと思います。
48kHzサンプリングなので48000サンプルで1秒のディレイタイムが確保できる。
今回はPC上での実装なのでメモリはほぼ無限にあるがDSPだったり組み込みでの実装の場合限られた中で実装する必要もあるので頭に入れておくといいです。

次に入出力関係の整備を行う
ここはいつも通り入力バッファと出力バッファを確保しそれと今回使うディレイバッファを確保する。

input_buf    = in_data#wavデータだったり正弦波だったりを格納しておく
output_buf   = np.zeros(frame_size,dtype=np.float)#
delay_buffer = np.zeros(delay_buf_size, dtype=np.float)#パラメータのところで計算したサイズ分だけ確保する。

信号処理部分はコメントに示す通りの流れになる。

"""signal  processing"""
for i in range(frame_size):
    l_x = input_buf[i]*1.0+ delay_buffer[delay_buffer_idx] * level #入力(ゲインは1)+ディレイ
    delay_buffer[delay_buffer_idx] = input_buf[i] + delay_buffer[delay_buffer_idx]*feedback#入力とフェードバック分をバッファに書き込み
    delay_buffer_idx = int((delay_buffer_idx + 1) % delay_buf_size)#リングバッファをすすめる
    output_buf[i] = l_x#出力

これで処理自体は完了となる。
処理としてはメモリに入れて所定サンプル数経過後に原音と加算して出力してるだけなのでかなりコードが短い

最後に処理前後の波形を示しておきます。
f:id:gsmcustomeffects:20180821001507p:plain

音が切れてから残響効果により音が残る波形となっていることがわかる

サンプル音源

元音源

ディレイ

各種設定

"""delay param"""
delay_buffer_idx    = 0                                 #リングバッファのインデックス
feedback            = 0.4                               #フィードバック
delay_ms            = 465                               #ディレイタイム[ms]
delay_buf_size      = int(frame_fs * delay_ms / 1000)   #バッファ確保[sample]
level                 = 0.8                               #ミックス(エフェクトレベル)

コード全体

かなり走り書きなので参考程度にお願いします。

import numpy as np
import matplotlib.pyplot as plt
import create_func as cf
import wave
import audio_func as af
import struct
import scipy

wf = wave.open("GS04.wav", "r")#wav 扱うならお決まりのやつ
num_data = scipy.fromstring(wf.readframes(wf.getnframes()),dtype = "int16") / 32768.0#正規化
frame_fs = wf.getframerate()
frame_size = wf.getnframes()

"""delay param"""
delay_buffer_idx    = 0                                 #リングバッファのインデックス
feedback            = 0.4                               #フィードバック
delay_ms            = 400                              #ディレイタイム[ms]
delay_buf_size      = int(frame_fs * delay_ms / 1000)   #バッファ確保[sample]
level                 = 0.5                               #ミックス(エフェクトレベル)
if(wf.getnchannels() == 2):
    left = num_data[::2]#1 スライス
    right= num_data[1::2]
    in_data = left*0.5 + right*0.5#2ステレオモノラル化
    #1:スライスの説明
    #a[1,2,3,4,5]ていうリストがあったとして
    #a[::2]  -> 1,3,5
    #a[1::2] -> 2,4

    #2:ステレオ->モノラル化
    #もともとギターの録音で左右に同じ音ふってるのでLR分解して半分にして足せば同じようなもん

input_buf    = in_data
output_buf   = np.zeros(frame_size,dtype=np.float)
delay_buffer = np.zeros(delay_buf_size, dtype=np.float)


"""signal  processing"""
for i in range(frame_size):
    l_x = input_buf[i] + delay_buffer[delay_buffer_idx] * level #入力+ディレイ
    delay_buffer[delay_buffer_idx] = input_buf[i] + delay_buffer[delay_buffer_idx]*feedback#入力とフェードバック分をバッファに書き込み
    delay_buffer_idx = int((delay_buffer_idx + 1) % delay_buf_size)#リングバッファをすすめる
    output_buf[i] = l_x#出力

plt.subplot(211)
plt.plot(input_buf[0:frame_size])
plt.subplot(212)
plt.plot(output_buf[0:frame_size])
plt.show()

output = [int(x * 32768.0) for x in output_buf]
output = struct.pack("h" * len(output), *output)
af.play(output,wf.getsampwidth(),1,wf.getframerate()) #再生
#af.save(output, frame_fs,1,"delay2.wav")