2013/10/05

Audacity built-in effects
Effect.hを使ったステレオ処理

Audacity built-in effects をEffect.hを継承して作ってみる。Effect.hはエフェクトの基本となるクラスなので、制約が少なく自由度が高いが、いろいろ面倒な手続きも必要。SimpleMono.hを使うような手軽さはない。
今回は左右のチャンネルが影響しあうエフェクトを作るための土台を作ってみた。 Audacityの標準エフェクトを調べて作っているのだが、予想以上にトラックの扱いが複雑だった。通常のwavファイルのステレオ処理の方がよっぽどシンプル。ただ Audacity の built-in effects として使うなら、この面倒な手続きを避けては通れない。
プログラムの流れは、ステレオトラックとして認識し、そして左右のチェンネルをバッファに保存して、加工し、最後にバッファから元の場所に戻す。流れとしては素直なのだが、トラックを順に処理していくAudacityで左右チャンネルを同時に扱うというのは意外と大変なのだ。 もともとAudacityのエフェクトは左右のチェンネルが影響するようなエフェクトは殆ど無い。唯一かと思えるのがReverbなのだが、これがなかなか複雑な構造で全体像が見えにくい。まぁそれでも数時間で何とか扱えるようにはなった。

ここまで来れば、あとは好きなようにエフェクトを作ることができる。 今回チェック用に作ったのが下記プログラムで、左右のチェンネルがお互い影響を受けるようにしてみた。内容は、それぞれのチェンネルが他方のチェンネルを任意の比率で混ぜる。パラメータは1.0が100%としている。

適用例として、左チャンネル440Hzと右チャンネル880Hzの合成をやっている。それぞれのチャンネルに100%でブレンド。結果は左右チャンネル同じ波形となる。モノラルの場合は何もしないで終了するようにした。

ソースコードは結構長くなってしまった。
/* NamagiAmp.h 131004 Namagi */
#ifndef __AUDACITY_EFFECT_AMP__
#define __AUDACITY_EFFECT_AMP__
#include <wx/dialog.h>
#include <wx/intl.h>
#include <wx/valtext.h>
#include "Effect.h"
class WaveTrack;
/*****************************************************/
class EffectNamagiAmp:public Effect{
 public:
   EffectNamagiAmp();
   wxString GetEffectName(){return _("NamagiAmp...");}
   wxString GetEffectAction(){return _("NamagiAmp...");}
   wxString GetEffectDescription();
   bool TransferParameters(Shuttle & shuttle);  
 protected:
   bool PromptUser();
   bool Process();
   void Create(double rate, bool isStereo);
   bool ProcessOneBlock(sampleCount len,
      float *const *chans);
   bool ProcessOneTrack(size_t n,
      WaveTrack *track, 
      WaveTrack *track2, 
      wxString const & msg);
   double mCurT0, mCurT1;
   size_t ichannels;
   size_t ochannels;
   float gainL;
   float gainR;
 friend class NamagiAmpDialog;
};
//-------------------------------------------------------
// NamagiAmpDialog
//-------------------------------------------------------
class NamagiAmpDialog:public EffectDialog {
 public:
   NamagiAmpDialog(EffectNamagiAmp *effect,wxWindow *parent);
   void PopulateOrExchange(ShuttleGui & S);
   bool TransferDataToWindow();
   bool TransferDataFromWindow();
 private:
   void OnPreview( wxCommandEvent &event );
   bool m_bLoopDetect;
   EffectNamagiAmp *m_pEffect;  
   wxTextCtrl *m_pTextCtrl_gainL;
   wxTextCtrl *m_pTextCtrl_gainR; 
 public:
   float gainLDialog;
   float gainRDialog;
 private:
   DECLARE_EVENT_TABLE()
};
#endif


/* NamagiAmp.cpp 131004 */
#include "NamagiAmp.h"
using std::min;
using std::max;
/*************************************************************/
//初期化
EffectNamagiAmp::EffectNamagiAmp()
{
   gainL = float(0.5);
   gainR = float(0.5);
}
/*************************************************************/
wxString EffectNamagiAmp::GetEffectDescription(){
   return wxString::Format(_("effect: %s gainL = %f gainR = %f"),
                this->GetEffectName().c_str(), gainL,gainR);
}
/*************************************************************/
bool EffectNamagiAmp::PromptUser()
{
   NamagiAmpDialog dlog(this, mParent);
   dlog.gainLDialog = gainL;
   dlog.gainRDialog = gainR;
   dlog.CentreOnParent();
   dlog.ShowModal();
   if (dlog.GetReturnCode() == wxID_CANCEL) return false;
   gainL = dlog.gainLDialog;
   gainR = dlog.gainRDialog;
   return true;
}
/*************************************************************/
bool EffectNamagiAmp::TransferParameters( Shuttle & shuttle )
{
 shuttle.TransferFloat(wxT("gainL"),gainL,0.5);
 shuttle.TransferFloat(wxT("gainR"),gainL,0.5);
 return true;
}
/*************************************************************/
void EffectNamagiAmp::Create(double rate, bool isStereo)
{
 ichannels = ochannels   = 1 + isStereo;
}
/*************************************************************/
//エフェクト処理
bool EffectNamagiAmp::ProcessOneTrack(
    size_t n,
    WaveTrack *track,
    WaveTrack *track2,
    wxString const & msg)
{
 sampleCount begin = track->TimeToLongSamples(mCurT0);
 sampleCount pos = begin;
 sampleCount end = track->TimeToLongSamples(mCurT1);
 //バッファ宣言 3トラック分
 float * buffers[3];
 //バッファ確保 モノラル or 左チャンネル
 buffers[0] = new float[track->GetMaxBlockSize()];
 //ステレオなら右チャンネル用バッファ確保 
 buffers[1] = track2? new float[track->GetMaxBlockSize()] : 0;
 //ステレオなら一時保存用バッファ確保(左チャンネルを保存)
 buffers[2] = track2? new float[track->GetMaxBlockSize()] : 0;
 bool cancelled = false;
 Create(track->GetRate(), track2 != 0);
 //処理開始
 while (!cancelled && pos < end) {
  sampleCount block = track->GetBestBlockSize(pos);
  block = min(block, end - pos);
  //モノラル or 左チャンネルをバッファへコピー
  track->Get((samplePtr) buffers[0],floatSample, pos, block);
  //ステレオなら 右チャンネルをコピー
  if (track2){
    track2->Get((samplePtr) buffers[1],floatSample, pos, block);
    //左チャンネルも一時保存
    track->Get((samplePtr) buffers[2],floatSample, pos, block);
    //エフェクト処理
    for(sampleCount i=0; i<end; i++){
      //左チャンネル処理buffers[0]に保存
      buffers[0][i] += (float)(buffers[1][i] * gainL);
    }
    for(sampleCount i=0; i<end; i++){
      //右チャンネル処理buffers[1]に保存
      buffers[1][i] += (float)(buffers[2][i] * gainR);
    }
  }
  //バッファから書き出し
  track->Set((samplePtr) buffers[0],floatSample, pos, block);
  if (track2)
   track2->Set((samplePtr) buffers[1],floatSample, pos, block);
  pos += block;
  cancelled = TrackProgress(n, (1. * pos - begin) /
     (1. * end - begin) * .5 + .5, msg);
 }
 //バッファ消去
 delete[] buffers[0];
 delete[] buffers[1];
 delete[] buffers[2];
 return !cancelled;
}
/*************************************************************/
//前処理 変数を定義し、ProcessOneを呼び出す
bool EffectNamagiAmp::Process()
{
 CopyInputTracks();
 SelectedTrackListOfKindIterator iter(Track::Wave,
         mOutputTracks);
 WaveTrack *track = (WaveTrack *)iter.First();
 bool success = true;
 for (int n = 0;
   success && track;
   track = (WaveTrack *)iter.Next(), ++n){
  double trackStart = track->GetStartTime();
  double trackEnd = track->GetEndTime();
  mCurT0 = mT0 < trackStart? trackStart: mT0;
  mCurT1 = mT1 > trackEnd? trackEnd: mT1;
  if (mCurT1 > mCurT0) {
   wxString msg(_("Processing: ") + track->GetName());
   if (track->GetLinked())
    //ステレオの場合実行
    success = ProcessOneTrack(++n, track,
      (WaveTrack *)iter.Next(), msg);
   else
    //モノラルの場合実行
    success = ProcessOneTrack(n, track, 0, msg);
  }
 }
 ReplaceProcessedTracks(success);
 return success;
}
//------------------------------------------------------------
// NamagiAmpDialog
//------------------------------------------------------------
BEGIN_EVENT_TABLE(NamagiAmpDialog, EffectDialog)
    EVT_BUTTON(ID_EFFECT_PREVIEW, NamagiAmpDialog::OnPreview)
END_EVENT_TABLE()
/*************************************************************/
NamagiAmpDialog::NamagiAmpDialog(EffectNamagiAmp * effect,
                                         wxWindow * parent)
: EffectDialog(parent, _("NamagiAmp"))
{
   m_bLoopDetect = false;
   m_pEffect = effect;
   m_pTextCtrl_gainL = NULL;
   m_pTextCtrl_gainR = NULL;
   gainLDialog = float(0.5); 
   gainRDialog = float(0.5); 
   Init();
}
/*************************************************************/
//レイアウト
void NamagiAmpDialog::PopulateOrExchange(ShuttleGui & S)
{
   S.AddSpace(0, 5); 
   S.StartMultiColumn(2, wxALIGN_CENTER);
   {
      m_pTextCtrl_gainL = S.AddTextBox(_("L cross:"),
                                       wxT("0.5"),
                                       10);
      m_pTextCtrl_gainL->SetValidator(
             wxTextValidator(wxFILTER_NUMERIC));
 
      m_pTextCtrl_gainR = S.AddTextBox(_("R cross:"),
                                       wxT("0.5"),
                                       10);
      m_pTextCtrl_gainR->SetValidator(
             wxTextValidator(wxFILTER_NUMERIC));
   }
   S.EndMultiColumn();
}
/*************************************************************/
bool NamagiAmpDialog::TransferDataToWindow()
{
   m_bLoopDetect = true;
   wxString str;
   if (m_pTextCtrl_gainL) {
      str.Printf(wxT("%g"), gainLDialog);
      m_pTextCtrl_gainL->SetValue(str);
   }
   if (m_pTextCtrl_gainR) {
      str.Printf(wxT("%g"), gainRDialog);
      m_pTextCtrl_gainR->SetValue(str);
   }
   m_bLoopDetect = false;
   return true;
}
/*************************************************************/
bool NamagiAmpDialog::TransferDataFromWindow()
{
   double newValue;
   wxString str;
   if (m_pTextCtrl_gainL) {
      str = m_pTextCtrl_gainL->GetValue();
      str.ToDouble(&newValue);
      gainLDialog = (float)(newValue);
   }
   if (m_pTextCtrl_gainR) {
      str = m_pTextCtrl_gainR->GetValue();
      str.ToDouble(&newValue);
      gainRDialog = (float)(newValue);
   }
   return true;
}
/*************************************************************/
void NamagiAmpDialog::OnPreview(wxCommandEvent & WXUNUSED(event))
{
   TransferDataFromWindow();
   float oldgainL = m_pEffect->gainL;
   float oldgainR = m_pEffect->gainR;
   m_pEffect->gainL = gainLDialog;
   m_pEffect->gainR = gainRDialog;
   m_pEffect->Preview();
   m_pEffect->gainL = oldgainL;
   m_pEffect->gainR = oldgainR;
}



Audacity 関係の記事