JUCEでADX2LEを使ってAUプラグインを作る の続きです。
シングルスレッドで動かすとは?
通常、サウンド処理は常時それなりの負荷がかかるため、別スレッドで動かすことが多いです。
古いシステムや特殊な環境ではスレッドが複数動作しなかったり、実際には一つしか動いていないとかいろいろあるようです。
ADX2にはシングルスレッドでも動作するモードがあり、かなり特殊な条件のもと使うもののようです。
ここで紹介するのは、良い例なのかわかりませんが、
オフラインレンダリングのようなことを無理やりできないかというチャレンジです。
リアルタイム系のシステムを無理やり、少し騙しつつ動かすような動作なので、何か問題が起こるかもしれません。
オーディオの出力
ADX2で、オフラインレンダリングができるような機能はあるのだろうか?
ゲーム内では各種オーディオ要求に返す仕組みで実装されている様子ですが、AUとかプラグインJUCEから呼ばれるようなものは用意されていない。
そこで、ちょっと工夫が必要そうです。
失敗した方法
まず、オーディオのバッファをJUCEのprocessBlockとは無関係に生成されたバッファを同期させて読ませるようなことをしてみたのですが、
要求量に対して枯渇しないようなバッファーを用意するために、遅延が起きたり、最悪間に合わない(オフラインレンダリング)などおこるため却下。
成功した方法
次のシングルスレッドモードというのを使う。
シングルスレッドモードでADX2を動かす
オフラインレンダリングは、レンダリングの量が変化する。実時間よりも短い時間で JUCEのprocessBlockに必要なバッファ要求が送られてくる。
このタイミングで、ADX2のサーバー処理を呼び出してしまえば、ADX2の処理を倍速以上で動かすといったことが可能になる。
CriAtomExConfig atomex_config;
/* ライブラリ設定をデフォルト値で初期化 */
criAtomEx_SetDefaultConfig(&atomex_config);
/* マルチスレッドモードを指定 */
atomex_config.thread_model = CRIATOMEX_THREAD_MODEL_SINGLE;
/* 初期化 */
criAtomEx_Initialize(&atomex_config);
ADX2は初期化時にスレッドモードがいくつか選べるようになっています。
ここで、シングルスレッドモードという、一つのスレッドからしか呼ばない前提にしておくと、オーディオサーバー処理を自前で呼び出すモードに変更できます。
ここで、
オーディオレンダリングで足りない場合に、サーバー処理を叩くことで、擬似的にオフラインレンダリング的なことが可能になります。
具体的には、processBlockのコールバック内で、生成されたサンプル数が足りない時にのみ、呼び出す。
こうすることで、(少し強引ですが)必要なサンプルが得られ、レンダリングした結果の音をバウンスしたり、DAWのエフェクトをかけたりといったことが可能になります。
バッファの調停
少し注意しないといけないのは、ADX2ではサーバーを呼び出すと256サンプル単位でバッファを生成している様子で、これが足りるまで呼び出し、バッファに貯めておく。
その貯めたバッファの内容をprocessBlockにコピーしていくが、必要量が256で割れない場合など、次回持ち越し分などをうまく処理する必要がある。
そこで、バッファの消費量という値をうまくつかって、足りない時だけ呼び出すようにprocessBlockを変更してみた。
のですが、ごめんなさい。うまく動作できたものがまだできていないので、保留中。
(誰かうまくできたら教えてください)
って放置してたので、ちょっとミスがあるかもだけどこんな感じ
SoundManager.cppのacf,acbのパスはフルパスで書いてあるので、適宜自分の環境のパスに置き換えてください。
バスのフィルターコールバック
BusのMasterOutputの出力を取り出して、
バッファにためて
processBlockで書き込み
Busの方は0を出力するようにして音を鳴らさないようにしている。
/*
==============================================================================
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);
static const int maxBufferSize = 512*4;
static float cri_fillter_buffer[maxBufferSize][2];
static int readPos;
static int writePos;
static int currentSize;
static std::mutex mutex;
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);
}
static void user_filter_callback_func(void *obj, CriAtomPcmFormat format,
CriSint32 num_channels, CriSint32 num_samples, void *data[])
{
//std::lock_guard<std::mutex> lock(SoundManager::mutex);
CriSint32 i,j;
if(format == CRIATOM_PCM_FORMAT_SINT16)
{
for(i = 0;i < num_channels;i++)
{
CriSint16 *pcm = (CriSint16*)data[i];
for(j = 0; j < num_samples; j++)
{
pcm[j] >>= 1;
}
}
} else if(format == CRIATOM_PCM_FORMAT_FLOAT32)
{
int tmpStartPos = SoundManager::writePos;
for(int channel = 0;channel < num_channels;channel++)
{
if(channel < 2)
{
SoundManager::writePos = tmpStartPos;
CriFloat32 *pcm = (CriFloat32*)data[channel];
for(CriSint32 sample = 0; sample < num_samples; sample++)
{
SoundManager::cri_fillter_buffer[SoundManager::writePos][channel] = pcm[sample];
//(float)(sample/num_channels)/num_samples;//pcm[sample];
pcm[sample] *= 0.0f; // コピー後に無音に変更
SoundManager::writePos++;
SoundManager::currentSize++;
if(SoundManager::writePos == SoundManager::maxBufferSize) SoundManager::writePos = 0; }
}
}
}
}
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
};
/*
==============================================================================
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コーデック用) */
float SoundManager::cri_fillter_buffer[maxBufferSize][2];
int SoundManager::readPos;
int SoundManager::writePos;
int SoundManager::currentSize;
std::mutex SoundManager::mutex;
SoundManager::SoundManager()
{
SoundManager::readPos = maxBufferSize/2;
SoundManager::writePos = 0;
SoundManager::currentSize = 0;
for(int channel = 0;channel < 2;channel++)
{
for(int i = 0;i<maxBufferSize;i++)
{
SoundManager::cri_fillter_buffer[i][channel] = 0;
}
}
/* エラーコールバック関数の登録 */
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.thread_model = CRIATOMEX_THREAD_MODEL_SINGLE; // シングルスレッドモードにする
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);
// フィルターコールバック
criAtomExAsr_SetBusFilterCallbackByName("MasterOut",NULL,user_filter_callback_func,NULL);
}
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);
}
/*
==============================================================================
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 Gj2h201903AudioProcessor : public AudioProcessor
{
public:
//==============================================================================
Gj2h201903AudioProcessor();
~Gj2h201903AudioProcessor();
//==============================================================================
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
#ifndef JucePlugin_PreferredChannelConfigurations
bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
#endif
void processBlock (AudioBuffer<float>&, 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 (Gj2h201903AudioProcessor)
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"
//==============================================================================
Gj2h201903AudioProcessor::Gj2h201903AudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
: AudioProcessor (BusesProperties()
#if ! JucePlugin_IsMidiEffect
#if ! JucePlugin_IsSynth
.withInput ("Input", AudioChannelSet::stereo(), true)
#endif
.withOutput ("Output", AudioChannelSet::stereo(), true)
#endif
)
#endif
{
}
Gj2h201903AudioProcessor::~Gj2h201903AudioProcessor()
{
}
//==============================================================================
const String Gj2h201903AudioProcessor::getName() const
{
return JucePlugin_Name;
}
bool Gj2h201903AudioProcessor::acceptsMidi() const
{
#if JucePlugin_WantsMidiInput
return true;
#else
return false;
#endif
}
bool Gj2h201903AudioProcessor::producesMidi() const
{
#if JucePlugin_ProducesMidiOutput
return true;
#else
return false;
#endif
}
bool Gj2h201903AudioProcessor::isMidiEffect() const
{
#if JucePlugin_IsMidiEffect
return true;
#else
return false;
#endif
}
double Gj2h201903AudioProcessor::getTailLengthSeconds() const
{
return 0.0;
}
int Gj2h201903AudioProcessor::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 Gj2h201903AudioProcessor::getCurrentProgram()
{
return 0;
}
void Gj2h201903AudioProcessor::setCurrentProgram (int index)
{
}
const String Gj2h201903AudioProcessor::getProgramName (int index)
{
return {};
}
void Gj2h201903AudioProcessor::changeProgramName (int index, const String& newName)
{
}
//==============================================================================
void Gj2h201903AudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
// Use this method as the place to do any pre-playback
// initialisation that you need..
}
void Gj2h201903AudioProcessor::releaseResources()
{
// When playback stops, you can use this as an opportunity to free up any
// spare memory, etc.
}
#ifndef JucePlugin_PreferredChannelConfigurations
bool Gj2h201903AudioProcessor::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 Gj2h201903AudioProcessor::processBlock (AudioBuffer<float>& 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());
// ADX2のサーバーをすこし動かす
if(SoundManager::currentSize < 512)
{
criAtomEx_ExecuteMain();
}
//std::lock_guard<std::mutex> lock(SoundManager::mutex);
int tmpStartPos = SoundManager::readPos;
// This is the place where you'd normally do the guts of your plugin's
// audio processing...
for (int channel = 0; channel < totalNumOutputChannels; ++channel)
{
float* channelData = buffer.getWritePointer (channel);
// ..do something to the data...
// ADX2LEのオーディオフィルターコールバックからbufferを得る
// bufferをコピーしておく
int numSmaples = buffer.getNumSamples();
if(channel <2){
SoundManager::readPos = tmpStartPos;
for (int sample = 0; sample < numSmaples; ++sample)
{
// コピー
channelData[sample] = SoundManager::cri_fillter_buffer[SoundManager::readPos][channel];
SoundManager::readPos++;
SoundManager::currentSize--;
if(SoundManager::readPos == SoundManager::maxBufferSize) SoundManager::readPos = 0;
// ADX2のサーバーをすこし動かす
if(SoundManager::currentSize < 512)
{
criAtomEx_ExecuteMain();
}
}
}
}
}
//==============================================================================
bool Gj2h201903AudioProcessor::hasEditor() const
{
return true; // (change this to false if you choose to not supply an editor)
}
AudioProcessorEditor* Gj2h201903AudioProcessor::createEditor()
{
return new Gj2h201903AudioProcessorEditor (*this);
}
//==============================================================================
void Gj2h201903AudioProcessor::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 Gj2h201903AudioProcessor::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 Gj2h201903AudioProcessor();
}
上のコードで気が付いている問題
- サンプリングレートを合わせていない、音程が変。
- acf,acbのパスが直指定フルパス、外部から指定できるようにするか最初の方でdefineするかとか何かしら対応したほうが良さそう。
- mutexの使い方かバッファ管理がいまいちなのか、長時間動かすと落ちる