#はじめに
JUCEはマルチプラットフォームなオーディオアプリを簡単に作れる環境ですごい。
プラグインもアプリケーションも同時に作れる。
プラグイン作成初心者ですが、JUCEのサンプルを参考にしつつ試して見ました。
#作った(作ろうと目指した)もの
JCUEを使って
ADX2LEのACFやACBといった、ゲームで使う目的のサウンドデータをサンプラーのように再生する
アプリケーションとAUプラグインを作ってみようと思いました。
#ADX2LEとは?
- ゲームエンジンやゲーム向けのサウンド再生モジュール。
- シンセ、ミキサー、エフェクタ、同時発音時の排他処理、ランダマイズなどゲーム向け機能が充実したサウンドライブラリ。
- 独自の圧縮コーデックを持っている。
- 大量の音声データやデータ読み込み中でも音途切れしないストリーミング再生などが簡単に行える。
- 無料。
リンク->[ADX2LE>https://game.criware.jp/products/adx2-le/]
#対象者
- ADX2LEをAUプラグインやC++で扱ってみたい人。
- MacでC++でADX2LEをつかってみたい人。
- ADX2LEのような外部スタティックライブラリをJUCEで使う場合の参考にしたい人。
- XcodeやJUCEは少し触ったことがある人。
ソースも少し載せていますが、参考になるか微妙なところ申しわけありません。
要望があれば公開できるような形にできればとも考えていますが、
何かしらのヒントになれば幸い。(かなり、わかる人向けのマニアックな内容です・・・)
#ADX2LEをMacで使う
ADX2LEのMac版は、Cocos2dxの中にMACOSX版のADX2LEがあります。
.aのstaticなライブラリと、includeのヘッダーを利用することで、Macで音を鳴らすことができます。
さらにacf acbなどいろいろ必要。
C++で書く必要。
そこで、これGitHubで公開している MMLでDoremi
を移植してみようと思います。
JUCEのProjucerを起動して
とりあえずAudioPlug-in形式ので作成。XCode用で。
とりあえずソースを入れる
githubからSoundManager
https://github.com/tatmos/ADX2LEMML20140119/blob/master/Doremi/SoundManager.cpp
パスで問題になりそうなので、全てSourceの下に展開しています。
Publicにacf acbがあります。
XCodeでビルド
とりあえず StandardPluginで
##エラーを解決していく
#include <cri_le_xpt.h>
だとうまくいかなかったので
#include "cri_le_xpt.h"
#include "cri_le_atom_ex.h"
#include "cri_le_atom_macosx.h"
#include "cri_le_atom_ex_monitor.h"
にそれぞれ変更。
##SoundManagerをMac版に書き換える
もともと、MMLを再生するものだったのですが、ソースは残したまま、MIDIキーで音程が変化するように再生できるものに初期化まわりなど変更
/*
==============================================================================
SoundManager.h
Created: 17 Nov 2017 7:32:50pm
Author: Takashi Tanaka
==============================================================================
*/
#pragma once
#include "cri_adx2le.h"
#include <iostream>
#include <string>
#include <iterator>
#include <vector>
#include <cctype>
using namespace std;
class SoundManager
{
public:
SoundManager();
~SoundManager();
void Play(string &mml);
void PlayNote(int noteNo,int velocity);
void StopNote(int noteNo);
private:
/* エラーコールバック関数 */
static void user_error_callback_func(const CriChar8 *errid, CriUint32 p1, CriUint32 p2, CriUint32 *parray)
{
const CriChar8 *errmsg;
/* エラー文字列の表示 */
errmsg = criErr_ConvertIdToMessage(errid, p1, p2);
//print("%s\n", errmsg);
cout << errmsg << "\n";
return;
}
static void *user_alloc_func(void *obj, CriUint32 size)
{
void *ptr;
ptr = malloc(size);
return ptr;
}
static void user_free_func(void *obj, void *ptr)
{
free(ptr);
}
private:
CriAtomExPlayerHn player; /* 再生プレーヤ */
CriAtomExVoicePoolHn voice_pool; /* ボイスプール */
CriAtomExAcbHn acb_hn; /* ACBハンドル(音声データ) */
CriAtomDbasId dbas_id; /* D-BASの作成*/
CriAtomExPlaybackId playback_id; /* VoiceキューのプレイバックID(再生開始時に保持する) */
int noteNo = 0;
int octNo = 4;
int lenNo = 4;
int volNo = 15;
int tempo = 120;
int lastPlaybackIdListforNote[128]; // 128キーのplaybackID
};
CPP側では、初期化まわりをCocos2dxのを参考に変更。
/*
==============================================================================
SoundManager.cpp
Created: 17 Nov 2017 7:32:50pm
Author: Takashi Tanaka
==============================================================================
*/
#include "unistd.h"
#include "SoundManager.h"
/* ACF/ACBのマクロ定義ヘッダ */
#include "Public/CueSheet_0.h"
#include "Public/Doremi_acf.h"
/* サンプルで使用するファイル名 */
const CriChar8* acf_file_name = "Public/Doremi.acf";
#define ACB_FILE "Public/CueSheet_0.acb"
#define AWB_FILE "Public/CueSheet_0.awb"
/* 最大ボイス数を増やすための関連パラメータ */
#define MAX_VOICE (24)
#define MAX_VIRTUAL_VOICE (MAX_VOICE + 8) /* バーチャルボイスは多め */
#define MAX_CRIFS_LOADER (MAX_VOICE + 8) /* 読み込み数も多めに */
/* 最大サンプリングレート(ピッチ変更含む) */
#define MAX_SAMPLING_RATE (48000*4)
/* HCA-MXコーデックのサンプリングレート */
const CriSint32 sampling_rate_hcamx = 32000;
/* ボイスプールへの設定 */
const CriSint32 max_voice = 8; /* デフォルト設定でも8 */
const CriSint32 max_sampling_rate = 48000*2; /* ピッチ変更も加味 */
CriAtomExVoicePoolHn g_standard_voice_pool; /* ボイスプールハンドル(ADX/HCAコーデック用) */
CriAtomExVoicePoolHn g_hcamx_voice_pool; /* ボイスプールハンドル(HCA-MXコーデック用) */
SoundManager::SoundManager()
{
/* エラーコールバック関数の登録 */
criErr_SetCallback(user_error_callback_func);
/* メモリアロケータの登録 */
criAtomEx_SetUserAllocator(user_alloc_func, user_free_func, NULL);
/* ライブラリの初期化(最大ボイス数変更) */
// CriAtomExConfig lib_config;
CriFsConfig fs_config;
criFs_SetDefaultConfig(&fs_config);
fs_config.num_loaders = 100+16; /* ざっくり多め。ストリーミング最大数+ACB等の読み込みが使用 */
CriAtomExConfig_MACOSX atom_macosx_config;
criAtomEx_SetDefaultConfig_MACOSX(&atom_macosx_config);
atom_macosx_config.atom_ex.fs_config = &fs_config;
atom_macosx_config.hca_mx.output_sampling_rate = MAX_SAMPLING_RATE;
atom_macosx_config.atom_ex.max_virtual_voices = 108;
criAtomEx_Initialize_MACOSX(&atom_macosx_config, NULL, 0);
/* ボイスプール作成 */
CriAtomExStandardVoicePoolConfig vp_config;
/* ボイスプールの設定。まずはデフォルト設定にして、その上で必要な値へ書き換えていく */
criAtomExVoicePool_SetDefaultConfigForStandardVoicePool(&vp_config);
vp_config.num_voices = max_voice;
//vp_config.player_config.streaming_flag = CRI_TRUE;
vp_config.player_config.max_sampling_rate = max_sampling_rate;
/* 上で作った設定オブジェクトを渡して、ボイスプールを作成 */
g_standard_voice_pool = criAtomExVoicePool_AllocateStandardVoicePool(&vp_config, NULL, 0);
/* HCA-MX再生用のボイスプール作成 */
CriAtomExHcaMxVoicePoolConfig hcamx_config;
criAtomExVoicePool_SetDefaultConfigForHcaMxVoicePool(&hcamx_config);
hcamx_config.num_voices = max_voice;
/* 一応ストリーミング再生可能で初期化しておく。メモリ再生のみなら不要。 */
hcamx_config.player_config.streaming_flag = CRI_TRUE;
hcamx_config.player_config.max_sampling_rate = sampling_rate_hcamx;
g_hcamx_voice_pool = criAtomExVoicePool_AllocateHcaMxVoicePool(&hcamx_config, NULL, 0);
/* D-Basの作成(最大ストリーム数はここで決まります) */
dbas_id = criAtomDbas_Create(NULL, NULL, 0);
/* ACFファイルの読み込みと登録 */
//criAtomEx_RegisterAcfFile(NULL, ACF_FILE, NULL, 0);
CriChar8 acf_path[256];
sprintf(acf_path, "%s", acf_file_name);
criAtomEx_RegisterAcfFile(NULL, "/Users/takashitanaka/Documents/JUCE/ADX2LESynth/Source/Public/Doremi.acf", NULL, 0);
/* DSP設定のアタッチ */
criAtomEx_AttachDspBusSetting("DspBusSetting_0", NULL, 0);
/* ボイスプールの作成(最大ボイス数変更/最大ピッチ変更/ストリーム再生対応) */
CriAtomExStandardVoicePoolConfig vpool_config;
criAtomExVoicePool_SetDefaultConfigForStandardVoicePool(&vpool_config);
vpool_config.num_voices = MAX_VOICE;
vpool_config.player_config.max_sampling_rate = MAX_SAMPLING_RATE;
vpool_config.player_config.streaming_flag = CRI_TRUE;
voice_pool = criAtomExVoicePool_AllocateStandardVoicePool(&vpool_config, NULL, 0);
/* ACBファイルを読み込み、ACBハンドルを作成 */
acb_hn = criAtomExAcb_LoadAcbFile(NULL, "/Users/takashitanaka/Documents/JUCE/ADX2LESynth/Source/Public/CueSheet_0.acb", NULL,
"/Users/takashitanaka/Documents/JUCE/ADX2LESynth/Source/Public/CueSheet_0.awb", NULL, 0);
/* Player作成にも設定は必要 */
CriAtomExPlayerConfig pf_config;
criAtomExPlayer_SetDefaultConfig(&pf_config);
pf_config.max_path_strings = 1;
pf_config.max_path = 256;
player = criAtomExPlayer_Create(&pf_config, NULL, 0);
criAtomExPlayer_SetCueId(player, acb_hn, 4);
}
SoundManager::~SoundManager()
{
/* DSPのデタッチ */
criAtomEx_DetachDspBusSetting();
/* プレーヤハンドルの破棄 */
criAtomExPlayer_Destroy(player);
/* ボイスプールの破棄 */
criAtomExVoicePool_Free(voice_pool);
/* ACBハンドルの破棄 */
criAtomExAcb_Release(acb_hn);
/* ACFの登録解除 */
criAtomEx_UnregisterAcf();
/* D-BASの破棄 */
criAtomDbas_Destroy(dbas_id);
/* ライブラリの終了 */
criAtomEx_Finalize_MACOSX();
}
enum MMLType{
none,
note,
len,
rest,
and_,
tempo,
period,
octave,
neiro,
volume,
};
class Note
{
public:
int lenNo = 0;
int number = 0;
MMLType mmlType = MMLType::none;
int noteNo = 0;
int octNo = 4;
int volNo = 15;
};
bool IsInteger(const string &str)
{
if (str.find_first_not_of("0123456789") != string::npos) {
return false;
}
return true;
}
void SoundManager::Play(string &mml)
{
string::iterator it = mml.begin();
vector<Note> noteList;
Note* newNote = NULL;
MMLType mmlType = MMLType::none;
while (it != mml.end())
{
string numberStr = "";
int number = 0;
string tmp;
std::copy(it, it + 1, std::inserter(tmp, tmp.begin()));
bool noteFlag = false;
if (IsInteger(tmp))
{
numberStr = "";
while (it != mml.end())
{
numberStr += tmp;
it++;
if (it == mml.end())break;
tmp = "";
std::copy(it, it + 1, std::inserter(tmp, tmp.begin()));
if (IsInteger(tmp) == false){ it--; break; }
}
number = stoi(numberStr);
switch (mmlType)
{
case MMLType::note:
case MMLType::rest:
if (newNote != NULL){
newNote->lenNo = number;
}break;
case MMLType::len:lenNo = number; break;
case MMLType::octave:octNo = number; break;
case MMLType::volume:
if (newNote != NULL){
newNote->volNo = number;
volNo = number;
}break;
case MMLType::tempo:
if (newNote != NULL){
newNote->number = number;
}break;
default:
if (newNote != NULL){
newNote->number = number;
}break;
}
}
else if (mblen(&((tmp.c_str())[0]), MB_CUR_MAX) != 1)
{
//cout << "!!" << tmp << endl;
if (it == mml.end())break;
it++;
}
else {
if (tmp == "c"){ noteNo = 0; noteFlag = true; mmlType = MMLType::note; }
else if (tmp == "d"){ noteNo = 2; noteFlag = true; mmlType = MMLType::note; }
else if (tmp == "e"){ noteNo = 4; noteFlag = true; mmlType = MMLType::note; }
else if (tmp == "f"){ noteNo = 5; noteFlag = true; mmlType = MMLType::note; }
else if (tmp == "g"){ noteNo = 7; noteFlag = true; mmlType = MMLType::note; }
else if (tmp == "a"){ noteNo = 9; noteFlag = true; mmlType = MMLType::note; }
else if (tmp == "b"){ noteNo = 11; noteFlag = true; mmlType = MMLType::note; }
else if (tmp == "r"){ noteNo = -255; noteFlag = true; mmlType = MMLType::rest; }
else if (tmp == "&" || tmp == "^"){ noteNo = -254; noteFlag = true; mmlType = MMLType::and_; }
else if (tmp == "@"){ noteNo = -253; noteFlag = true; mmlType = MMLType::neiro; }
else if (tmp == "o"){ noteNo = -252; noteFlag = true; mmlType = MMLType::octave; }
else if (tmp == "l"){ noteNo = -251; noteFlag = true; mmlType = MMLType::len; }
else if (tmp == "t"){ noteNo = -250; noteFlag = true; mmlType = MMLType::tempo; }
else if (tmp == "."){ noteNo = -249; noteFlag = true; mmlType = MMLType::period; }
else if (tmp == "+")noteNo++;
else if (tmp == "-")noteNo--;
else if (tmp == ">")octNo++;
else if (tmp == "<")octNo--;
else if (tmp == "v"){ noteNo = -248; noteFlag = true; mmlType = MMLType::volume; }
else { mmlType = MMLType::none; }
if (noteFlag){
newNote = new Note();
noteList.push_back(*newNote);
newNote = &(noteList[noteList.size() - 1]);
newNote->octNo = octNo;
newNote->lenNo = lenNo;
newNote->volNo = volNo;
newNote->mmlType = mmlType;
}
}
if (newNote != NULL){
newNote->noteNo = noteNo;
}
if (it == mml.end())break;
it++;
}
{
bool andFlag = false;
float deltaLength = 0;
vector<Note>::iterator it = noteList.begin();
while (it != noteList.end())
{
switch ((*it).mmlType)
{
case MMLType::rest:criAtomExPlayer_Stop(player); break;
case MMLType::note:
{
if (andFlag == false){
criAtomExPlayer_Stop(player);
}
float pitch = (float)((((*it).octNo - 4) * 12) + (*it).noteNo) * 100 + 300;
cout << pitch << " ";
criAtomExPlayer_SetPitch(player, pitch);
if (andFlag == false){
playback_id = criAtomExPlayer_Start(player);
}
else {
criAtomExPlayer_UpdateAll(player);
andFlag = false;
}
deltaLength = (float)((*it).lenNo);
} break;
case MMLType::neiro:criAtomExPlayer_SetCueId(player, acb_hn, (*it).number); deltaLength = 0; break;
case MMLType::tempo:tempo = (*it).number; deltaLength = 0; break;
case MMLType::octave:deltaLength = 0; break;
case MMLType::period: deltaLength = (float)deltaLength * 2; break;
case MMLType::and_:deltaLength = 0; andFlag = true; break;
case MMLType::volume:{
criAtomExPlayer_SetVolume(player, ((float)(*it).volNo / 15));
deltaLength = 0;
} break;
default:deltaLength = 0; break;
}
if (deltaLength != 0 && tempo != 0){
float sleepTime = (500 * ((float)120 / tempo)) * ((float)4 / deltaLength);
cout << sleepTime << endl;
sleep((float)sleepTime/1000.0);
}
else {
sleep(0);
}
++it;
}
cout << "end" << endl;
criAtomExPlayer_Stop(player);
}
}
void SoundManager::PlayNote(int noteNo,int velocity)
{
if(noteNo >= 72)
{
criAtomExPlayer_SetCueId(player, acb_hn, noteNo-72);
return;
}
if(lastPlaybackIdListforNote[noteNo] != 0)
{
// すでに鳴っていたら止める
StopNote(noteNo);
}
{
float pitch = (noteNo-60) * 100 + 300;
criAtomExPlayer_SetPitch(player, pitch);
criAtomExPlayer_SetVolume(player, (float)velocity/127.0);
lastPlaybackIdListforNote[noteNo] = criAtomExPlayer_Start(player);
}
}
void SoundManager::StopNote(int noteNo)
{
if(lastPlaybackIdListforNote[noteNo] != 0)
{
criAtomExPlayback_Stop(lastPlaybackIdListforNote[noteNo]);
lastPlaybackIdListforNote[noteNo] = 0;
}
//criAtomExPlayer_Stop(player);
}
ACFやACBのパスも とりあえずフルパス指定で。
音を止めるとかのためにplayback系のAPIを使ってたり。
pianoの音がなるように
ヘッダーインクルードして
publicなところにsmを用意
#include "SoundManager.h"
...
public:
SoundManager* sm;
コンストラクタの
setSize (400, 300);の下あたりに
processor.sm = new SoundManager();
processor.sm->PlayNote(62,127);
て初期化と再生を追加。
Extra Linker flagsに設定
-lcri_ware_macosx_le
##外部ライブラリの検索パスを設定
とりあえずフルパスでlibcri_ware_macosx_le.aの場所を指定。
これで、XCode上でstaticライブラリ(ADX2LE)を参照してビルドできる。
スタティックライブラリのリンクとかうまくいかない場合
もし
スタティックライブラリのリンクとかうまくいかない場合
XCode上でlibcri_ware_macosx_le.aを追加してみたところ
Library Search PathをXCode上で指定してみたところ
Header Search PathをXCode上で指定してみたところ
Build Phases からlibcri_ware_macosx_leを設定してみたところ
で、cleanしてビルド
すると大抵動きます。
JUCEで設定変えて作り直すと消えちゃうので、JUCE側で設定しておくのがおすすめですが、とりあえず動かすなら上の方法でも可。
ライブラリパスはフルパスで書いてあるので、適宜自分の環境のパスに置き換えてください。
##MIDIの設定、ベンダーの設定
SynthでMidiInputを受け付けるように。
あと、yourcompanyのところを適当に変更する。
#Logicで音がなる。
LogicのMIDIで音がなるけれど、これはプラグインが一つのゲームのような状態で鳴っていて、
Logicのエフェクトをかけることができない。(同様にバウンスもできない)
さぁ どうしようか・・・
##SoundManagerの初期化
/*
==============================================================================
This file was auto-generated!
It contains the basic framework code for a JUCE plugin editor.
==============================================================================
*/
#include "PluginProcessor.h"
#include "PluginEditor.h"
//==============================================================================
Adx2lesynthAudioProcessorEditor::Adx2lesynthAudioProcessorEditor (Adx2lesynthAudioProcessor& p)
: AudioProcessorEditor (&p), processor (p)
{
// Make sure that before the constructor has finished, you've set the
// editor's size to whatever you need it to be.
setSize (400, 300);
processor.sm = new SoundManager();
//string str = u8"l4cderl8gec4d2";
//processor.sm->Play(str);
processor.sm->PlayNote(62,127);
}
Adx2lesynthAudioProcessorEditor::~Adx2lesynthAudioProcessorEditor()
{
}
//==============================================================================
void Adx2lesynthAudioProcessorEditor::paint (Graphics& g)
{
// (Our component is opaque, so we must completely fill the background with a solid colour)
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
g.setColour (Colours::white);
g.setFont (15.0f);
g.drawFittedText ("Hello ADX2LE World!", getLocalBounds(), Justification::centred, 1);
}
void Adx2lesynthAudioProcessorEditor::resized()
{
// This is generally where you'll want to lay out the positions of any
// subcomponents in your editor..
}
###SoundManagerの宣言
/*
==============================================================================
This file was auto-generated!
It contains the basic framework code for a JUCE plugin processor.
==============================================================================
*/
#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
#include "SoundManager.h"
class NoteObject
{
public:
int noteNo = 0;
int velocity = 0;
};
//==============================================================================
/**
*/
class Adx2lesynthAudioProcessor : public AudioProcessor
{
public:
//==============================================================================
Adx2lesynthAudioProcessor();
~Adx2lesynthAudioProcessor();
//==============================================================================
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
#ifndef JucePlugin_PreferredChannelConfigurations
bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
#endif
void processBlock (AudioSampleBuffer&, MidiBuffer&) override;
//==============================================================================
AudioProcessorEditor* createEditor() override;
bool hasEditor() const override;
//==============================================================================
const String getName() const override;
bool acceptsMidi() const override;
bool producesMidi() const override;
bool isMidiEffect () const override;
double getTailLengthSeconds() const override;
//==============================================================================
int getNumPrograms() override;
int getCurrentProgram() override;
void setCurrentProgram (int index) override;
const String getProgramName (int index) override;
void changeProgramName (int index, const String& newName) override;
//==============================================================================
void getStateInformation (MemoryBlock& destData) override;
void setStateInformation (const void* data, int sizeInBytes) override;
SoundManager* sm;
private:
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Adx2lesynthAudioProcessor)
NoteObject noteObjectList[16];
};
/*
==============================================================================
This file was auto-generated!
It contains the basic framework code for a JUCE plugin processor.
==============================================================================
*/
#include "PluginProcessor.h"
#include "PluginEditor.h"
//==============================================================================
Adx2lesynthAudioProcessor::Adx2lesynthAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
: AudioProcessor (BusesProperties()
#if ! JucePlugin_IsMidiEffect
#if ! JucePlugin_IsSynth
.withInput ("Input", AudioChannelSet::stereo(), true)
#endif
.withOutput ("Output", AudioChannelSet::stereo(), true)
#endif
)
#endif
{
}
Adx2lesynthAudioProcessor::~Adx2lesynthAudioProcessor()
{
}
//==============================================================================
const String Adx2lesynthAudioProcessor::getName() const
{
return JucePlugin_Name;
}
bool Adx2lesynthAudioProcessor::acceptsMidi() const
{
#if JucePlugin_WantsMidiInput
return true;
#else
return false;
#endif
}
bool Adx2lesynthAudioProcessor::producesMidi() const
{
#if JucePlugin_ProducesMidiOutput
return true;
#else
return false;
#endif
}
bool Adx2lesynthAudioProcessor::isMidiEffect() const
{
#if JucePlugin_IsMidiEffect
return true;
#else
return false;
#endif
}
double Adx2lesynthAudioProcessor::getTailLengthSeconds() const
{
return 0.0;
}
int Adx2lesynthAudioProcessor::getNumPrograms()
{
return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs,
// so this should be at least 1, even if you're not really implementing programs.
}
int Adx2lesynthAudioProcessor::getCurrentProgram()
{
return 0;
}
void Adx2lesynthAudioProcessor::setCurrentProgram (int index)
{
}
const String Adx2lesynthAudioProcessor::getProgramName (int index)
{
return {};
}
void Adx2lesynthAudioProcessor::changeProgramName (int index, const String& newName)
{
}
//==============================================================================
void Adx2lesynthAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
// Use this method as the place to do any pre-playback
// initialisation that you need..
}
void Adx2lesynthAudioProcessor::releaseResources()
{
// When playback stops, you can use this as an opportunity to free up any
// spare memory, etc.
}
#ifndef JucePlugin_PreferredChannelConfigurations
bool Adx2lesynthAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
#if JucePlugin_IsMidiEffect
ignoreUnused (layouts);
return true;
#else
// This is the place where you check if the layout is supported.
// In this template code we only support mono or stereo.
if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono()
&& layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
return false;
// This checks if the input layout matches the output layout
#if ! JucePlugin_IsSynth
if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
return false;
#endif
return true;
#endif
}
#endif
void Adx2lesynthAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
ScopedNoDenormals noDenormals;
const int totalNumInputChannels = getTotalNumInputChannels();
const int totalNumOutputChannels = getTotalNumOutputChannels();
MidiBuffer::Iterator MidiItr(midiMessages);
MidiMessage MidiMsg;int smpPos;
while(MidiItr.getNextEvent(MidiMsg,smpPos))
{
if(MidiMsg.isNoteOn())
{
for(int currentTimberNo = 0;currentTimberNo < 16;currentTimberNo ++)
{
if(noteObjectList[currentTimberNo].velocity == 0)
{
noteObjectList[currentTimberNo].noteNo = MidiMsg.getNoteNumber();
noteObjectList[currentTimberNo].velocity = 127;
sm->PlayNote(noteObjectList[currentTimberNo].noteNo,
noteObjectList[currentTimberNo].velocity);
break;
}
}
}
if(MidiMsg.isNoteOff())
{
for(int currentTimberNo = 0;currentTimberNo < 16;currentTimberNo ++)
{
if(noteObjectList[currentTimberNo].velocity != 0 &&
noteObjectList[currentTimberNo].noteNo == MidiMsg.getNoteNumber()
)
{
noteObjectList[currentTimberNo].velocity = 0;
sm->StopNote(noteObjectList[currentTimberNo].noteNo);
}
}
}
}
// In case we have more outputs than inputs, this code clears any output
// channels that didn't contain input data, (because these aren't
// guaranteed to be empty - they may contain garbage).
// This is here to avoid people getting screaming feedback
// when they first compile a plugin, but obviously you don't need to keep
// this code if your algorithm always overwrites all the output channels.
for (int i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
buffer.clear (i, 0, buffer.getNumSamples());
// This is the place where you'd normally do the guts of your plugin's
// audio processing...
for (int channel = 0; channel < totalNumInputChannels; ++channel)
{
float* channelData = buffer.getWritePointer (channel);
// ..do something to the data...
// ADX2LEのオーディオフィルターコールバックからbufferを得る
// bufferをコピーしておく
}
}
//==============================================================================
bool Adx2lesynthAudioProcessor::hasEditor() const
{
return true; // (change this to false if you choose to not supply an editor)
}
AudioProcessorEditor* Adx2lesynthAudioProcessor::createEditor()
{
return new Adx2lesynthAudioProcessorEditor (*this);
}
//==============================================================================
void Adx2lesynthAudioProcessor::getStateInformation (MemoryBlock& destData)
{
// You should use this method to store your parameters in the memory block.
// You could do that either as raw data, or use the XML or ValueTree classes
// as intermediaries to make it easy to save and load complex data.
}
void Adx2lesynthAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
// You should use this method to restore your parameters from this memory block,
// whose contents will have been created by the getStateInformation() call.
}
//==============================================================================
// This creates new instances of the plugin..
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
return new Adx2lesynthAudioProcessor();
}
ADX2LEを再生エンジンとして使う
メリット
・サンプラー的に使う時に、マルチボイス再生が可能なので、プログラム側はとてもシンプルに書ける。
・発音リミット処理とか、発音プライオリティ処理がデータ側に持っている。(変わりに 別途るCRI Atom Craftで設定が必要)
・内臓エフェクトでコンプやリミッターなどが使える。
・ゲーム向けなので、ランダムな音の再生が楽。(ランダムの制御は・・・Randomシードとか指定すればある程度固定化もできるかな)
・ゲーム向きなので処理が軽い。
デメリット
- 複数同時使用などは想定外なので、同じアプリでプラグインとして動作させることが難しい。初期化とか工夫が必要そう。(すでに起動済みならとか動作を変更するなど)。
- ACFが異なる場合に動作が変わってしまう。
- もともとゲーム内でACFという共通データが1つしかロードが許されない構造になっている。(同じ ACFで複数ACBはOK)
おわりに
試しに無茶なプラグインが作れる環境として、JUCEは便利かもしれない。
もっとUIとか何か目標をたてて作っていくとよいかもしれない。
ToDo
ネタ候補
・キューの一覧を表示して、そこから再生するキューを選ぶUIを作る。
・ACF,ACBの差し替えUIを作る。
・キューのInstモードとDrumモードを切り替える。(InstモードはNoteで音程が変わる。DrumモードはNoteでキューが変わる。)
・ModurationやMIDIでAISACやブロックインデクス、セレクタラベルのコントロール。
・ボリュームコントロール。
・PitchBendでピッチ変更
・Editボタンで、CRI Atom Craftの起動。