2013/10/06

Audacity built-in effects
基礎的なDelayを作ってみる

今回作ったディレイの内容は下のブロック図と同等。以前LADSPAで作ったディレイとほとんど同じ。ただDryはボリュームではなくチェックボタンによるON/OFFだけにした。またアルゴリズムは新たに考えて実装してみた。簡単なディレイではあるが、いろいろな考え方で作ることができるので、頭の体操にはよいと思う。試しに子供にもアルゴリズムを考えさせたら、数分で書き上げてしまった。今回載せているプログラム内容とは違うが、なかなか素晴らしいアルゴリズムだった。

このディレイはディレイタップ長のリングバッファを使ったオーソドックスなもの。ちなみにAudacityのEchoというエフェクトを覗いてみたが、随分と考え方が違うと思えたよ。人の真似をするのは面白くないので、今後もアルゴリズムはすべてオリジナルにしたい。

インターフェイスはシンプルで、数値を入力するタイプ。DecayはdBにした。Audacityの多くのエフェクトはリニア単位でどうも使いにくい。一般的にはdBだと思うのだが。

下は適用例。上段がオリジナルで下段がディレイを適用したもの。

ディレイの考え方はいろいろ。個人的にフィードバックは数ではなく、無限に繰り返すものとしたい。AudacityのNyquistは数指定が基本のようだ。これには違和感を感じる。また標準エフェクトのEchoはDecayとfeedbackが一緒に扱われてしまっている。あとDryをOFFにできないものも多い。以前Javaで作ったディレイは、欲しい機能を吟味したもの。今回も同じ内容で作ろうかと思って中見たら、複雑だったので断念。もう少しAudacityの仕様について学習が必要だわ。慣れてきたら、ステレオ対応やら、HPF、LPFを追加して実用的にしようと思う。
/* NamagiDelay.h 131006 */
#ifndef __AUDACITY_EFFECT_DELAY__
#define __AUDACITY_EFFECT_DELAY__
#include <wx/dialog.h>
#include <wx/checkbox.h>
#include <wx/intl.h>
#include "SimpleMono.h"
#include <wx/valtext.h>
class wxString;
class wxStaticText;
class WaveTrack;

class EffectNamagiDelay:public EffectSimpleMono{
 public:
   EffectNamagiDelay();
   virtual wxString GetEffectName(){
      return wxString(_("Namagi Delay..."));
   }
   virtual wxString GetEffectIdentifier(){
      return wxString(wxT("Namagi Delay"));
   }
   virtual wxString GetEffectAction(){
      return wxString(_("Namagi Delay..."));
   }
   virtual wxString GetEffectDescription(); 
   virtual bool PromptUser();
   virtual bool TransferParameters(Shuttle & shuttle);

 protected:
   virtual bool ProcessSimpleMono(float *buffer,
                                 sampleCount len);
 private:
   float delay;
   float decay;
   float feedback;
   double decayLinear;
   bool wetOnly;
   
 friend class NamagiDelayDialog;
};
//-------------------------------------------------------
// NamagiDelayDialog
//-------------------------------------------------------
class NamagiDelayDialog:public EffectDialog {
 public:
   NamagiDelayDialog(EffectNamagiDelay * effect,
                               wxWindow * parent);
   void PopulateOrExchange(ShuttleGui & S);
   bool TransferDataToWindow();
   bool TransferDataFromWindow();
   float delayDialog;
   float decayDialog;
   float feedbackDialog;
   bool wetOnlyDialog;
   
 private:
   void OnPreview( wxCommandEvent &event );
   bool m_bLoopDetect;
   EffectNamagiDelay *m_pEffect;
   wxTextCtrl *m_pTextCtrl_delay;
   wxTextCtrl *m_pTextCtrl_decay;
   wxTextCtrl *m_pTextCtrl_feedback;
   wxCheckBox *mWetOnlyWidget;
   
   DECLARE_EVENT_TABLE()
};
#endif

GUIはよく分かっていないので、ソースには余計なものが入ってます。
/* NamagiDelay.cpp 131006 */
#include <wx/textctrl.h>
#include <wx/valtext.h>
#include "NamagiDelay.h"
/*************************************************************/
//初期化
EffectNamagiDelay::EffectNamagiDelay()
{
  delay = float(200.0);   //msec
  decay = float(-6);      //dB
  feedback = float(0.5);  //-1 to 1
  wetOnly = false;
}
/*************************************************************/
wxString EffectNamagiDelay::GetEffectDescription(){
  return wxString::Format(_(
      "effect: %s delay= %f decay= %f feedback= %f"),
    this->GetEffectName().c_str(),delay,decay,feedback);
}
/*************************************************************/
bool EffectNamagiDelay::PromptUser()
{
  NamagiDelayDialog dlog(this, mParent);
  dlog.delayDialog = delay;
  dlog.decayDialog = decay;
  dlog.feedbackDialog = feedback;
  dlog.CentreOnParent();
  dlog.ShowModal();
  if (dlog.GetReturnCode() == wxID_CANCEL) return false;
  delay = dlog.delayDialog;
  decay = dlog.decayDialog;
  feedback = dlog.feedbackDialog;
  wetOnly = dlog.wetOnlyDialog;
  return true;
}
/*************************************************************/
bool EffectNamagiDelay::TransferParameters( Shuttle & shuttle )
{
  shuttle.TransferFloat(wxT("delay"),delay,200.0);
  shuttle.TransferFloat(wxT("decay"),decay,-6);
  shuttle.TransferFloat(wxT("feedback"),feedback,0.5);
  return true;
}
/*************************************************************/
//処理 Delay本体
//SimpleMono.hにある mCurRate は pOutWaveTrack->GetRate();
bool EffectNamagiDelay::ProcessSimpleMono(float *buffer,sampleCount len)
{
  //dB to Linear double
  decayLinear = pow(10.0, decay/20.0);
  //delay tap size
  sampleCount blockSize = (sampleCount)(1 + mCurRate * (delay/1000.0));
  //blockSizeと選択範囲の調査 無効なら終了
  if(blockSize < 1 || blockSize > len) return true;
  sampleCount i;
  sampleCount w = 0;
  sampleCount r = 1;
  //リングバッファ作成
  float *bufferRing = new float[blockSize];
  //クリーンナップ
  for(i=0; i<blockSize; i++){
    bufferRing[i] = 0.0;
  }
  //Delay本体
  for(i=0; i<len; i++){
    bufferRing[w] = buffer[i] + bufferRing[r] * feedback;
    if(wetOnly) buffer[i] = buffer[i] + bufferRing[r] * decayLinear;
    else buffer[i] = bufferRing[r] * decayLinear;
    w++;
    r++;
    if(r == blockSize) r = 0;
    if(w == blockSize) w = 0;
  }
  delete[]bufferRing;
  return true;
}
//------------------------------------------------------------
// NamagiDelayDialog
//------------------------------------------------------------
/************************************************************/
enum {ID_START = 10000,
  ID_WETONLY_WIDGET,
  ID_END};
/*************************************************************/
BEGIN_EVENT_TABLE(NamagiDelayDialog, EffectDialog)
  EVT_BUTTON(ID_EFFECT_PREVIEW, NamagiDelayDialog::OnPreview)
END_EVENT_TABLE()
/*************************************************************/
NamagiDelayDialog::NamagiDelayDialog(EffectNamagiDelay *effect,
                                              wxWindow *parent)
: EffectDialog(parent, _("NamagiDelay"))
{
  m_bLoopDetect = false;
  m_pEffect = effect;
  m_pTextCtrl_delay = NULL;
  m_pTextCtrl_decay = NULL;
  m_pTextCtrl_feedback = NULL;
  mWetOnlyWidget = NULL;
  delayDialog = float(200.0);
  decayDialog = float(-6); 
  feedbackDialog = float(0.8);
  wetOnlyDialog = bool(false);
  Init();
}
/*************************************************************/
//レイアウト
void NamagiDelayDialog::PopulateOrExchange(ShuttleGui & S)
{
  S.AddSpace(0, 5);
  S.StartMultiColumn(2, wxALIGN_CENTER);
  {
  m_pTextCtrl_delay = S.AddTextBox(_("Delay(msec):"),
                    wxT("200.0"),
                    10);
  m_pTextCtrl_delay->SetValidator(wxTextValidator(wxFILTER_NUMERIC));

  m_pTextCtrl_decay = S.AddTextBox(_("Decay(dB):"),
                    wxT("-6"),
                    10);
  m_pTextCtrl_decay->SetValidator(wxTextValidator(wxFILTER_NUMERIC));

  m_pTextCtrl_feedback = S.AddTextBox(_("Feedback(-1to1):"),
                    wxT("0.5"),
                    10);
  m_pTextCtrl_feedback->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
  }
  S.EndMultiColumn();
  S.StartHorizontalLay(wxCENTER, false); 
  {
  mWetOnlyWidget = S.Id(ID_WETONLY_WIDGET).AddCheckBox(_("Wet Only"),
                                                      wxT("false"));
  }
  S.EndHorizontalLay();
}
/*************************************************************/
bool NamagiDelayDialog::TransferDataToWindow()
{
  m_bLoopDetect = true;
  wxString str;
  if (m_pTextCtrl_delay) {
    str.Printf(wxT("%g"), delayDialog);
    m_pTextCtrl_delay->SetValue(str);
  }
  if (m_pTextCtrl_decay) {
    str.Printf(wxT("%g"), decayDialog);
    m_pTextCtrl_decay->SetValue(str);
  }
  if (m_pTextCtrl_feedback) {
    str.Printf(wxT("%g"), feedbackDialog);
    m_pTextCtrl_feedback->SetValue(str);
  }
  //mWetOnlyWidget->SetValue(wetOnlyDialog);
  m_bLoopDetect = false;
  return true;
}
/*************************************************************/
bool NamagiDelayDialog::TransferDataFromWindow()
{
  double newValue;
  wxString str;
  if (m_pTextCtrl_delay) {
    str = m_pTextCtrl_delay->GetValue();
    str.ToDouble(&newValue);
    delayDialog = (float)(newValue);
  }
  if (m_pTextCtrl_decay) {
    str = m_pTextCtrl_decay->GetValue();
    str.ToDouble(&newValue);
    decayDialog = (float)(newValue);
  }
  if (m_pTextCtrl_feedback) {
    str = m_pTextCtrl_feedback->GetValue();
    str.ToDouble(&newValue);
    feedbackDialog = (float)(newValue);
  }
  wetOnlyDialog = !mWetOnlyWidget->GetValue();
  return true;
}
/*************************************************************/
void NamagiDelayDialog::OnPreview(wxCommandEvent & WXUNUSED(event))
{
  TransferDataFromWindow();
  float olddelay = m_pEffect->delay;
  float olddecay = m_pEffect->decay;
  float oldfeedback = m_pEffect->feedback;
  bool oldwetOnly = m_pEffect->wetOnly;
  m_pEffect->delay = delayDialog;
  m_pEffect->decay = decayDialog;
  m_pEffect->feedback = feedbackDialog;
  m_pEffect->wetOnly = wetOnlyDialog;
  m_pEffect->Preview();
  m_pEffect->delay = olddelay;
  m_pEffect->decay = olddecay;
  m_pEffect->feedback = oldfeedback;
  m_pEffect->wetOnly = oldwetOnly;
}


Audacity 関係の記事