7
7

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.

PortAudioを使ってD言語で波形を鳴らす

Posted at

D言語 Advent Calendar 8日目の記事です。

D言語でサウンドを扱った記事ってあまり見かけないような…と思って書きました。

#はじめに
まずD言語の標準ライブラリに音を鳴らす機能はありません。
今回は外部のライブラリにPortAudioを使用します。

#PortAudioとは何やら
オープンソースのシンプルなマルチプラットフォーム音声入出力ライブラリです。
シンプルな機能しかないのでゲームなどで沢山の音を鳴らしたいときは、
XAudio2とかOpenALのような多機能なオーディオライブラリを使った方が
いいでしょう。

PortAudio自体はC言語で記述されているため、ポーティングすれば様々な言語で
使用することができます。

#準備
##本家PortAudioをビルドする。

PortAudio 公式サイト
Download から Stable Releases のアーカイブ ( tgz ) をダウンロードしてきます。

###Windows環境の場合 (Visual Studio)
アーカイブを展開すると build/msvc/ に portaudio.sln があると思います。
これをVisual Studioで開いてビルドするんですが、大体の環境でビルドエラーが発生すると思います。

1>  asiolist.cpp
1>c1xx : fatal error C1083: ソース ファイルを開けません。'..\..\src\hostapi\asio\ASIOSDK\host\pc\asiolist.cpp': No such file or directory
1>  asiodrivers.cpp
1>c1xx : fatal error C1083: ソース ファイルを開けません。'..\..\src\hostapi\asio\ASIOSDK\host\asiodrivers.cpp': No such file or directory
1>  asio.cpp
1>c1xx : fatal error C1083: ソース ファイルを開けません。'..\..\src\hostapi\asio\ASIOSDK\common\asio.cpp': No such file or directory

ASIO 関連でエラーが出ているようです。

PortAudio は WMME, WASAPI, DirectSound, ASIO など様々な Audio API に
対応してますが、ASIO を使用するには Steinberg SDK が必要です。

Steinberg SDK は 手に入れるのにデベロッパ登録とかしないといけないらしく、
今回は ASIO 関連の機能をOFFにすることで対処したいと思います。

  1. プロジェクトツリーのhostapiのASIOを丸ごと削除する。
  2. defファイルからASIO関連の下記関数を削除する。
PaAsio_GetAvailableBufferSizes      @50
PaAsio_ShowControlPanel             @51
PaAsio_GetInputChannelName          @53
PaAsio_GetOutputChannelName         @54

これでDLLのビルドが上手く行くと思います。

次にdmdでリンクできるインポートライブラリ( lib )を作成します。
Visual Studio が出力する lib は残念ながら dmd で使用することができません。
アーカイブに含まれている portaudio.def を改造してこんな感じにします。

portaudio_x86.def
LIBRARY 'portaudio_x86.dll'
EXPORTS
	_Pa_GetVersion								= Pa_GetVersion
	_Pa_GetVersionText                       	= Pa_GetVersionText
	_Pa_GetErrorText                         	= Pa_GetErrorText
	_Pa_Initialize                           	= Pa_Initialize
	_Pa_Terminate                            	= Pa_Terminate
	_Pa_GetHostApiCount                      	= Pa_GetHostApiCount
	_Pa_GetDefaultHostApi                    	= Pa_GetDefaultHostApi
	_Pa_GetHostApiInfo                       	= Pa_GetHostApiInfo
	_Pa_HostApiTypeIdToHostApiIndex          	= Pa_HostApiTypeIdToHostApiIndex
	_Pa_HostApiDeviceIndexToDeviceIndex      	= Pa_HostApiDeviceIndexToDeviceIndex
	_Pa_GetLastHostErrorInfo                 	= Pa_GetLastHostErrorInfo
	_Pa_GetDeviceCount                       	= Pa_GetDeviceCount
	_Pa_GetDefaultInputDevice                	= Pa_GetDefaultInputDevice
	_Pa_GetDefaultOutputDevice               	= Pa_GetDefaultOutputDevice
	_Pa_GetDeviceInfo                        	= Pa_GetDeviceInfo
	_Pa_IsFormatSupported                    	= Pa_IsFormatSupported
	_Pa_OpenStream                           	= Pa_OpenStream
	_Pa_OpenDefaultStream                    	= Pa_OpenDefaultStream
	_Pa_CloseStream                          	= Pa_CloseStream
	_Pa_SetStreamFinishedCallback            	= Pa_SetStreamFinishedCallback
	_Pa_StartStream                          	= Pa_StartStream
	_Pa_StopStream                           	= Pa_StopStream
	_Pa_AbortStream                          	= Pa_AbortStream
	_Pa_IsStreamStopped                      	= Pa_IsStreamStopped
	_Pa_IsStreamActive                       	= Pa_IsStreamActive
	_Pa_GetStreamInfo                        	= Pa_GetStreamInfo
	_Pa_GetStreamTime                        	= Pa_GetStreamTime
	_Pa_GetStreamCpuLoad                     	= Pa_GetStreamCpuLoad
	_Pa_ReadStream                           	= Pa_ReadStream
	_Pa_WriteStream                          	= Pa_WriteStream
	_Pa_GetStreamReadAvailable               	= Pa_GetStreamReadAvailable
	_Pa_GetStreamWriteAvailable              	= Pa_GetStreamWriteAvailable
	_Pa_GetSampleSize                        	= Pa_GetSampleSize
	_Pa_Sleep                                	= Pa_Sleep
	_PaUtil_InitializeX86PlainConverters     	= PaUtil_InitializeX86PlainConverters
	_PaUtil_SetDebugPrintFunction            	= PaUtil_SetDebugPrintFunction
	_PaWasapi_GetDeviceDefaultFormat         	= PaWasapi_GetDeviceDefaultFormat
	_PaWasapi_GetDeviceRole                  	= PaWasapi_GetDeviceRole
	_PaWasapi_ThreadPriorityBoost            	= PaWasapi_ThreadPriorityBoost
	_PaWasapi_ThreadPriorityRevert           	= PaWasapi_ThreadPriorityRevert
	_PaWasapi_GetFramesPerHostBuffer         	= PaWasapi_GetFramesPerHostBuffer
	_PaWasapi_GetJackDescription             	= PaWasapi_GetJackDescription
	_PaWasapi_GetJackCount                   	= PaWasapi_GetJackCount
console
implib portaudio_x86.lib portaudio_x86.def

###Mac環境の場合
アーカイブを展開。コンソールに以下コマンドを打ち込んでインストール完了です。簡単!

console
./configure
make install

##PoitAudioのD言語バインディングを手に入れる
なんとウォルター先生がポーティングしてくれていました。
ありがたく使わせてもらいましょう。

PortAudio D bindings

deimos 以下の portaudio.di をダウンロードします。

##サイン波を鳴らしてみる
とりあえず動作確認として、シンプルなサイン波形を鳴らしてみましょう。

testaudio.d
import core.thread;
import std.stdio, std.conv, std.math;
import deimos.portaudio;

const int sampleRate   = 44100;
const float frequency  = 440;	// 波形の周波数[Hz]
const float phaseShift = 2 * PI * frequency / sampleRate;

struct AudioState
{
	float phase = 0;
}

extern(C)
int audioCallback(const(void)* inputBuffer, 
	void* outputBuffer, size_t framesPerBuffer,
	const(PaStreamCallbackTimeInfo)* timeInfo,
	PaStreamCallbackFlags statusFlags, void *userData)
{
	if (Thread.getThis() is null) {
		thread_attachThis();
	}

	auto state = cast(AudioState*)userData;
	auto pout = cast(float*)outputBuffer;

	foreach(i; 0 .. framesPerBuffer) {
		float s = sin(state.phase);
		*pout++ = s;
		*pout++ = s;

		state.phase += phaseShift;
	}
	return 0;
}

void main()
{
	PaError err;
	try {
		err = Pa_Initialize();
		if (err != paNoError) {
			throw new Exception(to!string(Pa_GetErrorText(err)));
		}

		AudioState state;

		PaStream* stream;
		err = Pa_OpenDefaultStream(&stream,
			0, 2, paFloat32, sampleRate,
			paFramesPerBufferUnspecified,
			&audioCallback, &state);
		if (err != paNoError) {
			throw new Exception(to!string(Pa_GetErrorText(err)));
		}

		Pa_StartStream(stream);

		Pa_Sleep(3_000);

		Pa_StopStream(stream);
		Pa_CloseStream(stream);
		Pa_Terminate();
	
	} catch (Exception e) {
		stderr.writefln("error %s", e);
	}
}

###Windows環境でビルド
testaudio.d と同じディレクトリに
portaudio.diが入っているディレクトリの deimos と
portaudio_x86.lib を置いておきます。

console
dmd testaudio.d portaudio_x86.lib

###Mac環境でビルド
testaudio.d と同じディレクトリに
portaudio.diが入っているディレクトリの deimos を置いておきます。

console
dmd testaudio.d -L-L/usr/local/lib -L-lportaudio

###実行!
440Hzのサイン波形が3秒くらい鳴ったでしょうか…?
frequency を変えると音の高さが変わると思います。

*補足
PortAudio は音データが必要になったタイミングで audioCallback を呼んできます。
Cレイヤーで作成されたスレッドから呼ばれているため、こんな風にDレイヤーの
スレッドマネージャにatatchしておくと良いらしいです。

if (Thread.getThis() is null) {
	thread_attachThis();
}

#まとめ
いかがでしたか?
ちょっとチュートリアルみたいな記事になってしまいました。

サイン波形を鳴らすだけだと詰まらないので、次はMMLで曲を再生してみたいと思います。
(モノはだいたい出来ているのであとは記事を書くだけ)
D言語AC枠にも空きがあるので近いうちに続編を書ければ…と思います。

それでは。

7
7
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
7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?