6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JUCEAdvent Calendar 2017

Day 14

JUCEでADX2LEを使ってAUプラグインを作る

Last updated at Posted at 2017-12-13

#はじめに

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用で。

とりあえずソースを入れる

ADX2LEからヘッダー類
image.png

githubからSoundManager
https://github.com/tatmos/ADX2LEMML20140119/blob/master/Doremi/SoundManager.cpp

パスで問題になりそうなので、全てSourceの下に展開しています。
Publicにacf acbがあります。

image.png

image.png

XCodeでビルド

とりあえず StandardPluginで

image.png

##エラーを解決していく

#include <cri_le_xpt.h>

だとうまくいかなかったので

image.png

image.png
いくつかある

#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
/*
  ==============================================================================

    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
/*
  ==============================================================================

    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を使ってたり。

image.png

pianoの音がなるように

ヘッダーインクルードして
publicなところにsmを用意

PluginProcesser.h
#include "SoundManager.h"

...

public:
    SoundManager* sm;

コンストラクタの
setSize (400, 300);の下あたりに

PluginEditor.cpp

    processor.sm = new SoundManager();    
    processor.sm->PlayNote(62,127);

て初期化と再生を追加。

##ライブラリの登録
image.png

Extra Linker flagsに設定

-lcri_ware_macosx_le

##外部ライブラリの検索パスを設定

image.png

とりあえずフルパスでlibcri_ware_macosx_le.aの場所を指定。

これで、XCode上でstaticライブラリ(ADX2LE)を参照してビルドできる。

スタティックライブラリのリンクとかうまくいかない場合

もし
スタティックライブラリのリンクとかうまくいかない場合

image.png

image.png

XCode上でlibcri_ware_macosx_le.aを追加してみたところ

image.png

Library Search PathをXCode上で指定してみたところ

image.png

Header Search PathをXCode上で指定してみたところ

image.png

Build Phases からlibcri_ware_macosx_leを設定してみたところ

で、cleanしてビルド

すると大抵動きます。

JUCEで設定変えて作り直すと消えちゃうので、JUCE側で設定しておくのがおすすめですが、とりあえず動かすなら上の方法でも可。

ライブラリパスはフルパスで書いてあるので、適宜自分の環境のパスに置き換えてください。

##MIDIの設定、ベンダーの設定

image.png

SynthでMidiInputを受け付けるように。
あと、yourcompanyのところを適当に変更する。

#Logicで音がなる。

LogicのMIDIで音がなるけれど、これはプラグインが一つのゲームのような状態で鳴っていて、
Logicのエフェクトをかけることができない。(同様にバウンスもできない)

さぁ どうしようか・・・

##SoundManagerの初期化

PluginEditor.cpp
/*
  ==============================================================================

    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の宣言

PluginProcesspr.h
/*
  ==============================================================================

    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];
};

PluginProcessor.cpp
/*
  ==============================================================================

    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の起動。

続き →
JUCEでADX2LEをシングルスレッドで動かしたい

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?