VSTとAudioUnitは日本語ドキュメントが少ないので、まとめておきます。
VST
VSTとは
VSTは、
Steinberg's Virtual Studio Technology
の略です。
名前の通り、Steinberg(スタインバーグ)社が提供している仮想音響技術のことです。
仕組みは、C++のダイナミックライブラリを使い、 ホストアプリケーションと呼ばれるアプリケーションに動的に組み込まれ、リアルタイム/非リアルタイムにフィルタや仮想楽器をレンダリングすることができます。
クロスプラットフォームな技術ですが、C++をプラットフォームごとにコンパイルする必要があり、特にGUIには注意する必要があります。
なお、楽器(シンセサイザー)として機能するVSTは、特に VSTiと呼ばれます。
SDKのダウンロード
- Developer Site (Steinberg)
http://www.steinberg.net/en/company/developers.html
開発者アカウント登録
以下のURLで開発者として登録をする。
注:登録する時にユーザ名を登録するが、どうもメールアドレスをログインユーザ名として使う?
既にアカウントを持っている場合
以下のURLでアカウントを拡張する。
ダウンロードページに戻る
以下のサイトに再度アクセスすると、ダウンロード出来るようになる。後は指示に従えばOK。(ちなみにASIO SDKもダウンロードできる。)
SDKを展開してみる
ダウンロードしたzipファイルを展開すると、「VST3 SDK」というフォルダができる。中身は以下のようになっている。
base
―― mac
―― source
―― win
bin
―― Mac OS X
―― Windows 32 bit
―― Windows 64 bit
doc
―― artwork
―― base
―― basemodule
―― doxygfx
―― gfx
―― vstexamples
―― vstinterfaces
―― vstsdk
pluginterfaces
―― base
―― gui
―― test
―― vst
―― vst2.x
public.sdk
―― samples
―――― vst
―――――― InterAppAudio
―――――― adelay
―――――― again
―――――― common
―――――― hostchecker
―――――― mac
―――――― mda―vst3
―――――― note_expression_synth
―――――― note_expression_text
―――――― pitchnames
―――――― validator
vstgui.sf
―― drawtest
―― libpng
―― vstgui
―― zlib
vstgui4
―― vstgui
―――― Documentation
―――― doxygen
―――― ide
―――――― codeblocks
―――――― visualstudio
―――――― xcode4
―――― lib
―――――― animation
―――――― controls
―――――― platform
―――――――― mac
―――――――――― carbon
―――――――――― cocoa
―――――――――― ios
―――――――― win32
―――――――――― direct2d
―――― plugin―bindings
―――― tests
―――― tutorial
―――― uidescription
内容
内容を見てみると、サンプルコードやGUI、IDEに関するリソースも提供されています。また、doxygenなどによるドキュメントも入っているようです。
サンプルコード
上記のサンプルコードから、「pitchnames」というプロジェクトのコードを部分的に紹介します。
pithnames.h
// Project : VST SDK
// Version : 3.6.0
//
// Category : Examples
// Filename : public.sdk/samples/vst/pitchnames/source/pitchnames.h
// Created by : Steinberg, 12/2010
/* ============ */
/* === 中略 === */
/* ============ */
#ifndef __pitchnames__
#define __pitchnames__
#include "public.sdk/source/vst/vsteditcontroller.h"
#include "public.sdk/source/vst/vstaudioeffect.h"
#include "base/source/fstring.h"
#include "base/source/tdictionary.h"
#include "vstgui/plugin-bindings/vst3editor.h"
namespace Steinberg {
namespace Vst {
//-----------------------------------------------------------------------------
class PitchNamesController : public EditControllerEx1, public VSTGUI::VST3EditorDelegate
{
public:
tresult PLUGIN_API initialize (FUnknown* context);
virtual tresult PLUGIN_API getUnitByBus (MediaType type, BusDirection dir, int32 busIndex, int32 channel, UnitID& unitId /*out*/);
virtual IPlugView* PLUGIN_API createView (FIDString name);
VSTGUI::CView* createCustomView (VSTGUI::UTF8StringPtr name, const VSTGUI::UIAttributes& attributes, VSTGUI::IUIDescription* description, VSTGUI::VST3Editor* editor);
static FUnknown* createInstance(void*) { return (IEditController*)new PitchNamesController (); }
static FUID cid;
protected:
TDictionary<int16, String> pitchNames;
};
//-----------------------------------------------------------------------------
class PitchNamesProcessor : public AudioEffect
{
public:
PitchNamesProcessor ();
tresult PLUGIN_API initialize (FUnknown* context);
tresult PLUGIN_API process (ProcessData& data);
static FUnknown* createInstance(void*) { return (IAudioProcessor*)new PitchNamesProcessor (); }
static FUID cid;
};
}} // namespaces
#endif
まず、 #include を見てみましょう。
#include "public.sdk/source/vst/vsteditcontroller.h"
#include "public.sdk/source/vst/vstaudioeffect.h"
#include "base/source/fstring.h"
#include "base/source/tdictionary.h"
#include "vstgui/plugin-bindings/vst3editor.h"
単純化すると(イメージのコードです。動きません。)、
include "vsteditcontroller";
include "vstaudioeffect";
include "base -> source -> fstring";
include "base -> source -> tdictionary";
include "vstgui -> vst3editor";
見る感じ、 VSTEditController というものが大事のようですね。
このPitchNamesというのはエフェクトのようで、 VSTAudioEffect をインクルードするとエフェクトが作成できるようですね。
PitchNamesControllerクラスがGUIで、 PitchNamesProcessorが音声を生成しています。特に processメソッドが重要です。ここに生成処理を記述します。
では次に本体を見てみましょう。
pitchnames.cpp
// Project : VST SDK
// Version : 3.6.0
//
// Category : Examples
// Filename : public.sdk/samples/vst/pitchnames/source/pitchnames.h
// Created by : Steinberg, 12/2010
/* ============ */
/* === 中略 === */
/* ============ */
#include "pitchnames.h"
#include "pitchnamesdatabrowsersource.h"
#include "pluginterfaces/base/ustring.h"
using namespace VSTGUI;
/* ============ */
/* === 中略 === */
/* ============ */
namespace Steinberg {
namespace Vst {
/* ============ */
/* === 中略 === */
/* ============ */
tresult PLUGIN_API PitchNamesController::initialize (FUnknown* context)
{
tresult result = EditControllerEx1::initialize (context);
if (result == kResultOk)
{
#if MULTI_CHANNEL_SCENARIO
addUnit (new Unit (String ("Root"), kRootUnitId, kNoParentUnitId));
addUnit (new Unit (String ("Channel 0"), kChannel0UnitId, kRootUnitId, kProgramList0ID));
addUnit (new Unit (String ("Channel 1"), kChannel1UnitId, kRootUnitId, kProgramList1ID));
ProgramListWithPitchNames* list = new ProgramListWithPitchNames (String ("ProgramList 0"), kProgramList0ID, kChannel0UnitId);
list->addProgram (String ("Init 1"));
list->setPitchName (0, 0, String ("Channel 0 First Item"));
addProgramList (list);
parameters.addParameter (list->getParameter ());
list = new ProgramListWithPitchNames (String ("ProgramList 1"), kProgramList1ID, kChannel1UnitId);
list->addProgram (String ("Init 1"));
list->setPitchName (0, 0, String ("Channel 1 First Item"));
addProgramList (list);
parameters.addParameter (list->getParameter ());
/* ============ */
/* === 中略 === */
/* ============ */
}
/* ============ */
/* === 中略 === */
/* ============ */
//-----------------------------------------------------------------------------
IPlugView* PLUGIN_API PitchNamesController::createView (FIDString name)
{
if (ConstString (name) == ViewType::kEditor)
{
return new VST3Editor (this, "PitchNamesEditor", "pitchnames.uidesc");
}
return 0;
}
//-----------------------------------------------------------------------------
CView* PitchNamesController::createCustomView (UTF8StringPtr name, const UIAttributes& attributes, IUIDescription* description, VST3Editor* editor)
{
#if MULTI_CHANNEL_SCENARIO
if (ConstString (name) == "PitchNamesDataBrowser")
{
IDataBrowser* dataSource = new PitchNamesDataBrowserSource (this, kChannel0UnitId);
return new CDataBrowser (CRect (0, 0, 100, 100), 0, dataSource, CDataBrowser::kDrawRowLines|CDataBrowser::kDrawColumnLines|CDataBrowser::kDrawHeader|CDataBrowser::kVerticalScrollbar);
}
#else
/* ============ */
/* === 中略 === */
/* ============ */
}
/* ============ */
/* === 中略 === */
/* ============ */
//-----------------------------------------------------------------------------
tresult PLUGIN_API PitchNamesProcessor::initialize (FUnknown* context)
{
tresult result = AudioEffect::initialize (context);
if (result == kResultOk)
{
addAudioOutput (USTRING("Audio Output"), SpeakerArr::kStereo);
#if MULTI_CHANNEL_SCENARIO
addEventInput (USTRING("Event Input"), 2);
#else
addEventInput (USTRING("Event Input"), 1);
#endif
}
return result;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API PitchNamesProcessor::process (ProcessData& data)
{
if (data.numSamples && data.numOutputs)
{
for (int32 i = 0; i < data.outputs[0].numChannels; i++)
memset (data.outputs[0].channelBuffers32[i], 0, data.numSamples * sizeof (float));
data.outputs[0].silenceFlags = 0x3;
}
return kResultOk;
}
}} // namespaces
特に大切な部分は、
tresult PLUGIN_API PitchNamesProcessor::process (ProcessData& data)
{
if (data.numSamples && data.numOutputs)
{
for (int32 i = 0; i < data.outputs[0].numChannels; i++)
memset (data.outputs[0].channelBuffers32[i], 0, data.numSamples * sizeof (float));
data.outputs[0].silenceFlags = 0x3;
}
return kResultOk;
}
ですね。ここで音声生成のアルゴリズムを記述しています。
ProcessData& data が、出力されるデータ列を記述するためのアドレスで、 data.outputs[0] が左信号、 data.outputs[1] が右信号です。サラウンドになるともっと増えます。
実際に出力しているのは、以下の1行です。
memset (data.outputs[0].channelBuffers32[i], 0, data.numSamples * sizeof (float));
memsetは、 *memset(buf, ch, n) とすると、「bufからnバイト分だけchをセットする」という関数です。
つまり、
*buf = data.outputs[0].channelBuffers32[i];
ch = 0;
n = data.numSamples * sizeof (float);
とは、 「出力データの左音声」 のchannelBuffers32
から、 「出力データのサンプル数 × 浮動点小数のサイズ」 ** 分だけ 「0」 をセットする という意味です。
つまり、channelBuffers32
からサンプル数だけ出力データを全て0にするということです。
もしこれが正弦波(sin関数)ならば、サンプル数だけsin(n/x)
の値をセットすることになります。
少し分かって貰えたでしょうか。AudioUnitもほぼ同じ要領です。
AU (Audio Units)
AUとは
AU (AudioUnits) とは、Mac OS Xにおける音声処理を仮想化する技術です。
基本的にはVST/VSTiと変わりませんが、仕組みは異なります。
VSTがダイナミックライブラリ(dll/dylib)なのに対して、AUはComponentという形式を使います。
VSTがクロスプラットフォームなのに対し、AUはMacでしか使えません。その代わり、OSに近い低レベルレイヤーにあるので、非常にクオリティが高いです。
前提
まず大前提として、Macが必要です。おそらくMax OS X10.5以降なら大丈夫だと思います。
ちなみに私はMac OS X 10.9.3/MacbookAir Late 2010を使っています。
AUに対応しているかを確認するには、ターミナルを開いて、
ls /Library/Audio/Plug-Ins/
# Components/ HAL/ MAS/ VST/ VST3/
というように、 Componentsフォルダが存在するかを確認して下さい。ちなみにこの /Library/Audio/Plug-Ins/Components/ にAUプラグインを配置すると認識されます。
蛇足ですが、 /Library/Audio/Plug-Ins/VST/ にVSTプラグインを配置すると、VSTとして認識されます。
SDK・サンプルコードの準備
AUのSDKはMacにプリインストールされています。Macのバージョンによって微妙に違うので、その都度確認して下さい。
AUのサンプルコードは、Apple Developer Libraryからダウンロードしてください。
https://developer.apple.com/library/mac/samplecode/sc2195/Introduction/Intro.html
分かりづらいですが、赤い円のところをクリックするとダウンロードできます。
サンプルコードを見てみる
さっきと順序が逆ですが、ライブラリを理解するためにサンプルコードを見てみましょう。
SinSynth.h
/*
File: SinSynth.h
Abstract: SinSynth.h
Version: 1.0.1
======== 中略 ==========
Copyright (C) 2014 Apple Inc. All Rights Reserved.
*/
#include "AUInstrumentBase.h"
#include "SinSynthVersion.h"
static const UInt32 kNumNotes = 12;
struct TestNote : public SynthNote
{
virtual ~TestNote() {}
virtual bool Attack(const MusicDeviceNoteParams &inParams)
{
/* ======== 中略 ========== */
double sampleRate = SampleRate();
phase = 0.;
amp = 0.;
maxamp = 0.4 * pow(inParams.mVelocity/127., 3.);
up_slope = maxamp / (0.1 * sampleRate);
dn_slope = -maxamp / (0.9 * sampleRate);
fast_dn_slope = -maxamp / (0.005 * sampleRate);
return true;
}
virtual void Kill(UInt32 inFrame); // voice is being stolen.
virtual void Release(UInt32 inFrame);
virtual void FastRelease(UInt32 inFrame);
virtual Float32 Amplitude() { return amp; } // used for finding quietest note for voice stealing.
virtual OSStatus Render(UInt64 inAbsoluteSampleFrame, UInt32 inNumFrames, AudioBufferList** inBufferList, UInt32 inOutBusCount);
double phase, amp, maxamp;
double up_slope, dn_slope, fast_dn_slope;
};
class SinSynth : public AUMonotimbralInstrumentBase
{
public:
SinSynth(AudioUnit inComponentInstance);
virtual ~SinSynth();
virtual OSStatus Initialize();
virtual void Cleanup();
virtual OSStatus Version() { return kSinSynthVersion; }
virtual AUElement* CreateElement( AudioUnitScope scope,
AudioUnitElement element);
virtual OSStatus GetParameterInfo( AudioUnitScope inScope,
AudioUnitParameterID inParameterID,
AudioUnitParameterInfo & outParameterInfo);
MidiControls* GetControls( MusicDeviceGroupID inChannel)
{
SynthGroupElement *group = GetElForGroupID(inChannel);
return (MidiControls *) group->GetMIDIControlHandler();
}
private:
TestNote mTestNotes[kNumNotes];
};
よく見てみると、 AUInstrumentBaseというクラスをベースにしているのがわかります。SinSynthはシンセサイザーだということがわかりますね。
ここでちょっと、AUInstrumentBaseがどういうものなのか見てみましょう。
AUInstrumentBaseは、 /CoreAudio/AudioUnits/AUPublic/AUInstrumentBase/AUInstrumentBase.hに存在します。
/*
File: AUInstrumentBase.h
Abstract: Part of CoreAudio Utility Classes
Version: 1.0.4
=========== 中略 ===========
Copyright (C) 2013 Apple Inc. All Rights Reserved.
*/
#ifndef __AUInstrumentBase__
#define __AUInstrumentBase__
#include <vector>
#include <stdexcept>
#include <AudioUnit/AudioUnit.h>
#include <CoreAudio/CoreAudio.h>
#include <libkern/OSAtomic.h>
#include "MusicDeviceBase.h"
#include "LockFreeFIFO.h"
#include "SynthEvent.h"
#include "SynthNote.h"
#include "SynthElement.h"
/////////////////////////////////
typedef LockFreeFIFOWithFree<SynthEvent> SynthEventQueue;
class AUInstrumentBase : public MusicDeviceBase
{
public:
/* =========== 中略 =========== */
virtual OSStatus Initialize();
/*! @method Parts */
AUScope & Parts() { return mPartScope; }
/*! @method GetPart */
AUElement * GetPart( AudioUnitElement inElement)
{
return mPartScope.SafeGetElement(inElement);
}
virtual AUScope * GetScopeExtended (AudioUnitScope inScope);
virtual AUElement * CreateElement( AudioUnitScope inScope,
AudioUnitElement inElement);
virtual void CreateExtendedElements();
virtual void Cleanup();
/* =========== 中略 =========== */
virtual OSStatus Render( AudioUnitRenderActionFlags & ioActionFlags,
const AudioTimeStamp & inTimeStamp,
UInt32 inNumberFrames);
virtual OSStatus StartNote( MusicDeviceInstrumentID inInstrument,
MusicDeviceGroupID inGroupID,
NoteInstanceID * outNoteInstanceID,
UInt32 inOffsetSampleFrame,
const MusicDeviceNoteParams &inParams);
virtual OSStatus StopNote( MusicDeviceGroupID inGroupID,
NoteInstanceID inNoteInstanceID,
UInt32 inOffsetSampleFrame);
/* =========== 中略 =========== */
void SetNotes(UInt32 inNumNotes, UInt32 inMaxActiveNotes, SynthNote* inNotes, UInt32 inNoteSize);
void PerformEvents( const AudioTimeStamp & inTimeStamp);
OSStatus SendPedalEvent(MusicDeviceGroupID inGroupID, UInt32 inEventType, UInt32 inOffsetSampleFrame);
virtual SynthNote* VoiceStealing(UInt32 inFrame, bool inKillIt);
UInt32 MaxActiveNotes() const { return mMaxActiveNotes; }
UInt32 NumActiveNotes() const { return mNumActiveNotes; }
void IncNumActiveNotes() { ++mNumActiveNotes; }
void DecNumActiveNotes() { --mNumActiveNotes; }
UInt32 CountActiveNotes();
SynthPartElement * GetPartElement (AudioUnitElement inPartElement);
// this call throws if there's no assigned element for the group ID
virtual SynthGroupElement * GetElForGroupID (MusicDeviceGroupID inGroupID);
virtual SynthGroupElement * GetElForNoteID (NoteInstanceID inNoteID);
SInt64 mAbsoluteSampleFrame;
private:
SInt32 mNoteIDCounter;
SynthEventQueue mEventQueue;
UInt32 mNumNotes;
UInt32 mNumActiveNotes;
UInt32 mMaxActiveNotes;
SynthNote* mNotes;
SynthNoteList mFreeNotes;
UInt32 mNoteSize;
AUScope mPartScope;
const UInt32 mInitNumPartEls;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////
class AUMonotimbralInstrumentBase : public AUInstrumentBase
{
public:
AUMonotimbralInstrumentBase(
AudioComponentInstance inInstance,
UInt32 numInputs,
UInt32 numOutputs,
UInt32 numGroups = 16,
UInt32 numParts = 1);
virtual OSStatus RealTimeStartNote( SynthGroupElement *inGroup,
NoteInstanceID inNoteInstanceID,
UInt32 inOffsetSampleFrame,
const MusicDeviceNoteParams &inParams);
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// this is a work in progress! The mono-timbral one is finished though!
class AUMultitimbralInstrumentBase : public AUInstrumentBase
{
public:
AUMultitimbralInstrumentBase(
AudioComponentInstance inInstance,
UInt32 numInputs,
UInt32 numOutputs,
UInt32 numGroups,
UInt32 numParts);
virtual OSStatus GetPropertyInfo( AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement,
UInt32 & outDataSize,
Boolean & outWritable);
virtual OSStatus GetProperty( AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement,
void * outData);
virtual OSStatus SetProperty( AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement,
const void * inData,
UInt32 inDataSize);
private:
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endif
SinSynth.cpp
/*
File: SinSynth.cpp
Abstract: SinSynth.h
Version: 1.0.1
========= 中略 =========
Copyright (C) 2014 Apple Inc. All Rights Reserved.
*/
/* ========= 中略 ========= */
#include "SinSynth.h"
static const UInt32 kMaxActiveNotes = 8;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const double twopi = 2.0 * 3.14159265358979;
inline double pow5(double x) { double x2 = x*x; return x2*x2*x; }
#pragma mark SinSynth Methods
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//COMPONENT_ENTRY(SinSynth)
AUDIOCOMPONENT_ENTRY(AUMusicDeviceFactory, SinSynth)
static const AudioUnitParameterID kGlobalVolumeParam = 0;
static const CFStringRef kGlobalVolumeName = CFSTR("global volume");
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SinSynth::SinSynth
//
// This synth has No inputs, One output
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SinSynth::SinSynth(AudioUnit inComponentInstance)
: AUMonotimbralInstrumentBase(inComponentInstance, 0, 1)
{
CreateElements();
Globals()->UseIndexedParameters(1); // we're only defining one param
Globals()->SetParameter (kGlobalVolumeParam, 1.0);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SinSynth::~SinSynth
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SinSynth::~SinSynth()
{
}
/* ========= 中略 ========= */
OSStatus SinSynth::Initialize()
{
/* ========= 中略 ========= */
AUMonotimbralInstrumentBase::Initialize();
/* ========= 中略 ========= */
return noErr;
}
AUElement* SinSynth::CreateElement( AudioUnitScope scope,
AudioUnitElement element)
{
switch (scope)
{
case kAudioUnitScope_Group :
return new SynthGroupElement(this, element, new MidiControls);
case kAudioUnitScope_Part :
return new SynthPartElement(this, element);
default :
return AUBase::CreateElement(scope, element);
}
}
OSStatus SinSynth::GetParameterInfo( AudioUnitScope inScope,
AudioUnitParameterID inParameterID,
AudioUnitParameterInfo & outParameterInfo)
{
if (inParameterID != kGlobalVolumeParam) return kAudioUnitErr_InvalidParameter;
if (inScope != kAudioUnitScope_Global) return kAudioUnitErr_InvalidScope;
outParameterInfo.flags = SetAudioUnitParameterDisplayType (0, kAudioUnitParameterFlag_DisplaySquareRoot);
outParameterInfo.flags += kAudioUnitParameterFlag_IsWritable;
outParameterInfo.flags += kAudioUnitParameterFlag_IsReadable;
AUBase::FillInParameterName (outParameterInfo, kGlobalVolumeName, false);
outParameterInfo.unit = kAudioUnitParameterUnit_LinearGain;
outParameterInfo.minValue = 0;
outParameterInfo.maxValue = 1.0;
outParameterInfo.defaultValue = 1.0;
return noErr;
}
/* ========= 中略 ========= */
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TestNote::Release(UInt32 inFrame)
{
SynthNote::Release(inFrame);
/* ========= 中略 ========= */
}
void TestNote::FastRelease(UInt32 inFrame) // voice is being stolen.
{
SynthNote::Release(inFrame);
/* ========= 中略 ========= */
}
void TestNote::Kill(UInt32 inFrame) // voice is being stolen.
{
SynthNote::Kill(inFrame);
/* ========= 中略 ========= */
}
OSStatus TestNote::Render(UInt64 inAbsoluteSampleFrame, UInt32 inNumFrames, AudioBufferList** inBufferList, UInt32 inOutBusCount)
{
float *left, *right;
/* ========= 中略 ========= */
float globalVol = GetGlobalParameter(kGlobalVolumeParam);
// TestNote only writes into the first bus regardless of what is handed to us.
const int bus0 = 0;
int numChans = inBufferList[bus0]->mNumberBuffers;
if (numChans > 2) return -1;
left = (float*)inBufferList[bus0]->mBuffers[0].mData;
right = numChans == 2 ? (float*)inBufferList[bus0]->mBuffers[1].mData : 0;
double sampleRate = SampleRate();
double freq = Frequency() * (twopi/sampleRate);
/* ========= 中略 ========= */
switch (GetState())
{
case kNoteState_Attacked :
case kNoteState_Sostenutoed :
case kNoteState_ReleasedButSostenutoed :
case kNoteState_ReleasedButSustained :
{
for (UInt32 frame=0; frame<inNumFrames; ++frame)
{
if (amp < maxamp) amp += up_slope;
float out = pow5(sin(phase)) * amp * globalVol;
phase += freq;
if (phase > twopi) phase -= twopi;
left[frame] += out;
if (right) right[frame] += out;
}
}
break;
case kNoteState_Released :
{
UInt32 endFrame = 0xFFFFFFFF;
for (UInt32 frame=0; frame<inNumFrames; ++frame)
{
if (amp > 0.0) amp += dn_slope;
else if (endFrame == 0xFFFFFFFF) endFrame = frame;
float out = pow5(sin(phase)) * amp * globalVol;
phase += freq;
left[frame] += out;
if (right) right[frame] += out;
}
if (endFrame != 0xFFFFFFFF) {
/* ========= 中略 ========= */
NoteEnded(endFrame);
}
}
break;
case kNoteState_FastReleased :
{
UInt32 endFrame = 0xFFFFFFFF;
for (UInt32 frame=0; frame<inNumFrames; ++frame)
{
if (amp > 0.0) amp += fast_dn_slope;
else if (endFrame == 0xFFFFFFFF) endFrame = frame;
float out = pow5(sin(phase)) * amp * globalVol;
phase += freq;
left[frame] += out;
if (right) right[frame] += out;
}
if (endFrame != 0xFFFFFFFF) {
/* ========= 中略 ========= */
NoteEnded(endFrame);
}
}
break;
default :
break;
}
return noErr;
}
よく見ると、シンセサイザーとして必要な機能が定義されているのがわかりますね。どうやら、 Renderという関数で音を生成するようです。
SetNoteなどはMIDIの信号を送信するのでしょうね。
ここで、さらに #include に注目してください。
#include <vector>
#include <stdexcept>
#include <AudioUnit/AudioUnit.h>
#include <CoreAudio/CoreAudio.h>
#include <libkern/OSAtomic.h>
#include "MusicDeviceBase.h"
#include "LockFreeFIFO.h"
#include "SynthEvent.h"
#include "SynthNote.h"
#include "SynthElement.h"
ここを見ると分かるのですが、AUに必要なクラス群がよく分かります。
- AudioUnit/AudioUnit.h は必須です。これがないと始まりません。
- CoreAudio/CoreAudio.h Macの音声を操る、最も重要なフレームワークです。
つまり、この2つが一番重要だと分かります。
AUのクラス構成について
- Appendix: Audio Unit Class Hierarchy
https://developer.apple.com/library/mac/documentation/musicaudio/Conceptual/AudioUnitProgrammingGuide/AudioUnitClassHierarchy/AudioUnitClassHierarchy.html
に詳しく解説されています。
テキスト化したものが以下です。
|-- AUElementCreator
|<--|
| |-- ComponentBase
|
|--> AUBase -- AUScope -- AUElement
| |
| |----- AUOElement
| | |
| | |--- AUInputElement
| | |
| | |--- AUOutputElement
|-- AUOutputBase |
| |--- SynthElement
| |
| |--- SynthPartElement
| |
| |--- SynthGroupElement
| |--- SynthNoteList ...
| |--- SynthNote ...
|
|-- AUEffectBase -- AUKernelBase ...
| |
| | AUMIDIBase
| | | |
| | | |
| | | --_
| |---|----- AUMIDIEffectBase
| |
| |--_
|------------ MusicDeviceBase
|
|-- [[AUInstrumentBase]]
|
|-- AUMonotimbralInstrumentBase
|
|-- AUMultitimbralIntrumentBase
括弧をつけたところが AUInstrumentBase です。
なんかよくわかりませんがそういうことにしておきましょう。
とにかく、 CoreAudio と AudioUnit は重要だということを覚えておいてください。
詳しくは他のサイトや、Apple Developer Networkを活用して下さい。
まとめ
今回は、VSTとAudioUnitの入門として書きました。いかがでしたでしょうか。
これを参考に、少しでもVSTやAUが増えれば幸いです。
参考になる文献
VST
-
C++で音作り
http://www39.atwiki.jp/vst_prog/ -
VSTプラグインを自作する
http://gyabo.sakura.ne.jp/tips/vst0.html
AudioUnits
-
ダウンロード可能な Audio Unit 関連のサンプルコード11個
http://d.hatena.ne.jp/shu223/20130221/1361394373 -
Getting Started With Audio Unit - My Codex Leicester
http://nagano.monalisa-au.org/archives/category/getting-started-with-audio-unit?order=ASC
音響生成プログラミング
-
C言語で始める音のプログラミング
http://floor13.sakura.ne.jp/book03/book03.html -
モバイルとシンセで音楽作りな話
https://pfjksynth.wordpress.com/2013/03/ -
PDコミュニティサイト(音声生成とフィルタの基本がわかる)
http://puredata.info/ -
ChucK (リアルタイム音響生成プログラミング言語)
http://chuck.cs.princeton.edu/