JUCEのAudioIODeviceについての覚え書き

オーディオアプリケーションフレームワークであるJUCEでは、様々なプラットフォームネイティブなオーディオAPI（オーディオドライバ）をサポートしています。これらの実装は`juce_audio_devices`モジュールに置かれています。
[公式リファレンス: juce_audio_devices](https://docs.juce.com/master/group__juce__audio__devices.html)

サポートするオーディオAPI(一例)

+ Windows: DirectSound, WASAPI, ASIO
+ macOS: CoreAudio
+ Linux: ALSA, JACK

さて、現状でも十分なくらいのオーディオAPIを公式にサポートしているJUCEですが、何らかの事情で公式にサポートされていないオーディオAPIで動作するアプリケーションを実装したいとなった場合はどうしましょう？そのような状況にも対応できるように、JUCEでは開発者が独自にオーディオデバイスを追加できる仕組みが用意されています。


# JUCEにおけるオーディオ処理の仕組み
まずは前提の知識として、JUCEのオーディオ処理の仕組みについて確認しておきましょう。
JUCEのオーディオに関する機能は複数のクラスに分散されており、それらが協調して動作することで、オーディオAPIとのコミュニケーションから信号処理までを分散して各クラスに分割して実装することができます。
JUCEにおけるオーディオデバイスに関連するクラス群の一覧についてはらぴすさんの記事で詳細が書かれているので、そちらを参照するとよいでしょう。[JUCEでのオーディオデバイスの扱い方](https://qiita.com/lapis_tw/items/fff09ada48e0221fe506)

JUCEでは、オーディオ処理を管理する`AudioDeviceManager`というクラスが提供されます。このクラスはオーディオデバイスを抽象化したクラス`AudioIODevice`とオーディオ処理（信号処理・オーディオバッファ操作）を抽象化したクラス`AudioIODeviceCallback`の橋渡し役も担っています。

次の図は、`AudioDeviceManager`クラス、`AudioIODevice`クラス、`AudioIODeviceCallback`クラスがどのようなやり取りを行っているかを図にしたものです。`AudioDeviceManager`が`AudioIODevice`のデバイス作成やオーディオスレッドの開始を実行し、`AudioIODevice`内で走っているオーディオスレッドからのコールバックに基づいて`AudioDeviceManager`登録された`AudioIODeviceCallback`を継承したオブジェクトに対してコールバック関数が実行されるという仕組みになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93566/92cc8b7e-bf2f-aea7-919e-b6855545b40e.png)


# オーディオデバイスを定義するクラス

オーディオデバイスに関連するクラス群のうち、オーディオAPIの抽象化に関係するクラスは主に次のクラスになります。

+ `AudioIODevice`: プラットフォームネイティブなオーディオAPIの実装を定義する。[公式リファレンス](https://docs.juce.com/master/classAudioIODevice.html)
+ `AudioIODeviceType`: `AudioIODevice`の”型情報”みたいなもの。インスタンス生成やメタ情報を定義する。[公式リファレンス](https://docs.juce.com/master/classAudioIODeviceType.html)


独自のオーディオデバイスをアプリケーションに追加したい場合、以下の作業を行うことで、JUCEのフレームワークに独自のオーディオデバイスを組み込んで使用することができます。

1. `AudioIODevice`と`AudioIODeviceType`をそれぞれ継承したクラスを実装する。
2. プログラム実行時、独自に追加した`AudioIODeviceType`の子クラスを`AudioDeviceManager`に登録する。
3. `AudioDeviceManager`を介して独自に追加したオーディオデバイスを生成する

次の図は、`AudioIODevice`クラス、`AudioIODeviceType`クラス、`AudioDeviceManager`クラスがどのようなやり取りを行っているかを図にしたものです。`AudioIODeviceType`を継承した`MyAudioIODeviceType`クラスを`AudioDeviceManager`に登録することで、`AudioDeviceManager`から`MyAudioIODeviceType`に対して`MyAudioIODevice`のインスタンス生成とオーディオスレッドの開始を実行することができます。`MyAudioIODevice`が生成されたら引き続きオーディオスレッドの開始を行うことで、`MyAudioIODevice`内のオーディオスレッドからのコールバックに基づいて`AudioDeviceManager`に対してコールバック関数が実行されるという仕組みになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93566/0cdf89e8-b8c6-291a-ae57-40eb40e75c76.png)



# Sine波を入力する`SineWave Audio Device`の実装例

ここでは実例として、`SineWave Audio Device`を例に、JUCEに独自のオーディオデバイスを追加するまでのソースコードを載せておきます。
この`SineWave Audio Device`は、毎フレーム常に入力バッファへSine波を入力するといった処理を行いますが、`AudioSourcePlayer`クラスや`AudioProcessorPlayer`クラスの`audioDeviceIOCallback関数`から戻される出力バッファの中身を受け取った後はプラットフォームネイティブなオーディオAPIに渡すことはしていません。そのため、**このオーディオデバイスを選択した場合、スピーカーに音声は流れません。**

■ SineWaveAudioIODevice.h

```
#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

// AudioIODeviceクラスを継承する
// オーディオスレッドとしてTimerクラスを継承する
class SineWaveAudioIODevice : public AudioIODevice, private Timer
{
public:
	SineWaveAudioIODevice(const String& deviceName, const String& typeName, const String& outputDeviceID, const String& inputDeviceID);
	~SineWaveAudioIODevice() override;

	// AudioIODeviceクラスで宣言された関数（インターフェース）をオーバーライドする
	virtual StringArray getOutputChannelNames() override { return { "Out1", "Out2", "Out3", "Out4" }; };
	virtual StringArray getInputChannelNames() override { return{ "In1", "In2", "In3", "In4" }; };
	virtual Array<double> getAvailableSampleRates() override;
	virtual Array<int> getAvailableBufferSizes() override;
	virtual int getDefaultBufferSize() override;
	virtual String open(const BigInteger& inputChannels, const BigInteger& outputChannels, double sampleRate, int bufferSizeSamples) override;
	virtual void close() override;
	virtual bool isOpen() override;
	virtual void start(AudioIODeviceCallback* callback) override;
	virtual void stop() override;
	virtual bool isPlaying() override;
	virtual String getLastError() override;
	virtual int getCurrentBufferSizeSamples() override { return bufferSize; };
	virtual double getCurrentSampleRate() override { return sampleRate; };
	virtual int getCurrentBitDepth() override { return bitDepth; };
	virtual BigInteger getActiveOutputChannels() const override { return channelsOut; };
	virtual BigInteger getActiveInputChannels() const override { return channelsIn; };
	virtual int getOutputLatencyInSamples() override { return 0; };
	virtual int getInputLatencyInSamples() override { return 0; };

private:
	// Timerクラスのメンバ関数（インターフェース）をオーバーライドする
	virtual void timerCallback() override;

	int bufferSize{ 512 };
	double sampleRate{ 44100 };
	int bitDepth{ 32 };
	BigInteger channelsIn, channelsOut;
	int numChannelsIn, numChannelsOut;
	CriticalSection audioDeviceLock;
	bool closed{ false };

	// AudioDeviceMangerで保持しているコールバック先のオブジェクト
	AudioIODeviceCallback* deviceManagerCallbackHandler{ nullptr };
};

// AudioIODeviceTypeクラスを継承する
class SineWaveAudioIODeviceType : public AudioIODeviceType
{
public:
	SineWaveAudioIODeviceType()
		: AudioIODeviceType("SineWave Audio Device")
	{};

	~SineWaveAudioIODeviceType() override
	{};

	// AudioIODeviceTypeクラスのメンバ関数（インターフェース）をオーバーライドする
	virtual void scanForDevices() override {}
	virtual StringArray getDeviceNames(bool wantInputNames = false) const override;
	virtual int getDefaultDeviceIndex(bool forInput) const override;
	virtual int getIndexOfDevice(AudioIODevice* device, bool asInput) const override;
	virtual bool hasSeparateInputsAndOutputs() const override { return true; };
	virtual AudioIODevice* createDevice(const String& outputDeviceName, const String& inputDeviceName) override;

private:
	StringArray inputDeviceNames { "SineWave Input1", "SineWave Input2" };
	StringArray outputDeviceNames{ "SineWave Output1", "SineWave Output2" };
};
```

■ SineWaveAudioIODevice.cpp

```
#include "SineWaveAudioIODevice.h"

SineWaveAudioIODevice::SineWaveAudioIODevice(const String & deviceName, const String & typeName, const String & outputDeviceID, const String & inputDeviceID)
	: AudioIODevice(deviceName, typeName)
{
}

SineWaveAudioIODevice::~SineWaveAudioIODevice()
{
}

Array<double> SineWaveAudioIODevice::getAvailableSampleRates()
{
	Array<double> ret;
	ret.add(44100);
	ret.add(48000);
	ret.add(88200);
	ret.add(96000);
	return ret;
}

Array<int> SineWaveAudioIODevice::getAvailableBufferSizes()
{
	Array<int> ret;
	ret.add(256);
	ret.add(512);
	ret.add(1024);
	return ret;
}

int SineWaveAudioIODevice::getDefaultBufferSize()
{
	return bufferSize;
}

String SineWaveAudioIODevice::open(const BigInteger & inputChannels, const BigInteger & outputChannels, double sampleRate_, int bufferSizeSamples)
{
	ScopedLock lock(audioDeviceLock);
	sampleRate = sampleRate_;
	channelsIn = inputChannels;
	channelsOut = outputChannels;
	bufferSize = bufferSizeSamples;

	numChannelsIn = channelsIn.countNumberOfSetBits();
	numChannelsOut = channelsOut.countNumberOfSetBits();

	closed = false;

	return String();
}

void SineWaveAudioIODevice::close()
{
	stop();
	closed = true;
}

bool SineWaveAudioIODevice::isOpen()
{
	return ! closed;
}

void SineWaveAudioIODevice::start(AudioIODeviceCallback * callback)
{
	// AudioDeviceManagerのインスタンスに自身のオーディオデバイスを渡すとともに、オーディオスレッドが開始したことを通知する
	deviceManagerCallbackHandler = callback;
	deviceManagerCallbackHandler->audioDeviceAboutToStart(this);

	startTimerHz(30);
}

void SineWaveAudioIODevice::stop()
{
	// AudioDeviceManagerのインスタンスにオーディオスレッドが停止したことを通知する
	if (deviceManagerCallbackHandler != nullptr)
	{
		ScopedLock lock(audioDeviceLock);
		stopTimer();
		deviceManagerCallbackHandler->audioDeviceStopped();
		deviceManagerCallbackHandler = nullptr;
	}
}

bool SineWaveAudioIODevice::isPlaying()
{
	return deviceManagerCallbackHandler != nullptr;
}

String SineWaveAudioIODevice::getLastError()
{
	return String();
}

// このオーディオデバイスのオーディオスレッドループ
void SineWaveAudioIODevice::timerCallback()
{
	AudioBuffer<float> inputBuffer(numChannelsIn, bufferSize);
	AudioBuffer<float> outputBuffer(numChannelsOut, bufferSize);

	// audioDeviceIOCallback()に渡すSine波のサンプルデータを生成する
	for (int ch_index = 0; ch_index < inputBuffer.getNumChannels(); ++ch_index)
	{
		auto ch_data = inputBuffer.getWritePointer(ch_index);
		for (int sample = 0; sample < inputBuffer.getNumSamples(); ++sample)
		{
			ch_data[sample] = sinf(juce::MathConstants<float>::twoPi * sample / inputBuffer.getNumSamples()) * 0.5f;
		}
	}

	// AudioDeviceManagerを介してオーディオバッファの更新を実行する
	deviceManagerCallbackHandler->audioDeviceIOCallback(
		inputBuffer.getArrayOfReadPointers(), inputBuffer.getNumChannels(),
		outputBuffer.getArrayOfWritePointers(), outputBuffer.getNumChannels(),
		bufferSize
		);
}

//==============================================================================
StringArray SineWaveAudioIODeviceType::getDeviceNames(bool wantInputNames) const
{
	if(wantInputNames)
		return inputDeviceNames;
	else
		return outputDeviceNames;
}

int SineWaveAudioIODeviceType::getDefaultDeviceIndex(bool forInput) const
{
	return 0;
}

int SineWaveAudioIODeviceType::getIndexOfDevice(AudioIODevice * device, bool asInput) const
{
	if (asInput) {
		switch (device->getActiveInputChannels().getHighestBit())
		{
		case 0:
		case 1:
			return 0;
			break;
		case 2:
		case 3:
			return 1;
			break;
		default:
			return -1;
			break;
		}
	} else {
		switch (device->getActiveOutputChannels().getHighestBit())
		{
		case 0:
		case 1:
			return 0;
			break;
		case 2:
		case 3:
			return 1;
			break;
		default:
			return -1;
			break;
		}
	}
}

// SineWaveAudioIODeviceのインスタンスを生成するメンバ関数
AudioIODevice * SineWaveAudioIODeviceType::createDevice(const String & outputDeviceName, const String & inputDeviceName)
{
	std::unique_ptr<SineWaveAudioIODevice> device;
	device.reset(new SineWaveAudioIODevice(outputDeviceName, getTypeName(), outputDeviceName, inputDeviceName));
	return device.release();
}
```


■ MainComponent.cpp

```
#include "MainComponent.h"
#include "SineWaveAudioIODevice.h"

MainComponent::MainComponent()
{
	// AudioDeviceManagerに独自のオーディオデバイスを追加登録する
	deviceManager.addAudioDeviceType(new SineWaveAudioIODeviceType());
	setSize(800, 600);
}
```

### サンプル実装例を組み込んだアプリケーションの動作例

前述の通り`SineWave Audio Device`の出力バッファに値を渡してもスピーカーから音が聞こえてくることはないため、実際の信号を耳で確認することはできません。ここでは、オーディオビジュアライザを利用して視覚的に信号を確認することにしました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93566/1e44a489-c8c4-bc21-5b58-407a8e4d2d8a.png)