プログラムで音声フォーマットのWAVファイルが扱えるようになれば、それを読みこんで、エフェクト処理して、それを書き出す、自作のデジタルエフェクターではそれをリアルタイムで行うだけの違いでしかありません。PCでプログラムのひな形を作るのがサウンドエフェクトプログラミングの第一歩となります。
WAVライブラリ
C言語ではじめる音のプログラミング
http://floor13.sakura.ne.jp/book03/book03.html
サウンドプログラミング入門
http://floor13.sakura.ne.jp/book06/book06.html
良本なのでぜひ両方ともお手元に置いてあげてください。
サンプル音源やソースコードも上記サイトで全公開されています。そのソースコードの中にWAVファイルを読み書きするCプログラムがあるのですが、Cではちょっとアレなので、それをC++で書き直してみました。
wave.hpp(C++)
https://github.com/DIYFXWorld/Other
オリジナルのソースコードではデータ圧縮もサポートしていますが、面倒なので非圧縮データのみの扱いとしました。
WAVライブラリ
http://floor13.sakura.ne.jp/book03/book03.html
サウンドプログラミング入門
http://floor13.sakura.ne.jp/book06/book06.html
サンプル音源やソースコードも上記サイトで全公開されています。そのソースコードの中にWAVファイルを読み書きするCプログラムがあるのですが、Cではちょっとアレなので、それをC++で書き直してみました。
wave.hpp(C++)
https://github.com/DIYFXWorld/Other
オリジナルのソースコードではデータ圧縮もサポートしていますが、面倒なので非圧縮データのみの扱いとしました。
wavファイルの情報を表示する
wav_info.cpp
ステレオデータとビット深度
ステレオ(2ch)の音声データを作るにはwaveクラスのコンストラクタの第3引数に2を渡します。(デフォルトは1=モノラルです)。データへのアクセスはL[], R[]で行います。
WAVファイルのビット深度はsave関数の第3引数に8を指定します。16ビットで書き出すには第3引数を省略するか16を指定します。
wave<float>とか書くのが面倒であれば、次のエイリアスが使えます。
wave_d ---- wave<double>
wave_f ---- wave<float>
wave_i ---- wave<int>
wave_s ---- wave<short>
wave_c ---- wave<char>
wave_uc ---- wave<unsigned char>
最後に簡単なサウンドエフェクトのトレモロ効果を作ってみます。WAVデータを読み込んでトレモロ効果を加えてから、WAVファイルへ書き出すプログラムです。
最後にMinGWコンソールでC/C++プログラムをコンパイルする時のスクリプトを紹介します。ふつうC/C++プログラムをコンパイルするには
$gcc helllo.c
$g++ hello.cpp
といった感じでやります、これだと出力ファイル名がa.exeという名前になってしまいます。これでは使いづらいので、buildという拡張子無しのファイルを作って以下の内容をコピーします。
build
#!/bin/bash
path=$1
#echo "path: $path"
basename=${path##*/}
#echo "basename: $basename"
filename=${basename%.*}
#echo "filename: $filename"
extension=${basename##*.}
#echo "extension: $extension"
g++ $1 $2 $3 $4 $5 $6 $7 $8 $9 -o $filename
MinGWコンソールで
$build helllo.c
または
$build helllo.cpp
これで出力ファイル名とソースファイル名が一致します。21世紀にもなってこんなこと、いちいちユーザーに書かせるな!って感じですが、Linuxの人達はこうゆうこのが萌えらしいです。文化の違いなんでしょう。
よく使うオプションやソースコードはbuildスクリプトに書き込んでいけば良いので、小規模構成のプログラムならわざわざmakefileを書くまでもなく、これだけで間に合います。
手始めにwave.hppを使ってWAVファイルの情報を表示するプログラムを作ってみます。wave.hppを同じフォルダにコピーしておきます。プログラムファイル名はwav_info.cppとします。
wav_info.cpp
#include "wave.hpp"
main( int argc, char *argv[] )
{
if( argc < 2 )
{
printf( "usage : wav_info file_name.wav" );
return 0;
}
wav_info wi = get_wav_info( argv[ 1 ] );
puts( "" );
printf( "Sampling rate : %d Hz\r\n", wi.fs );
printf( "Sample bits : %d bit\r\n", wi.bits );
printf( "Channel : " );
if( wi.channel == 1 ) puts( "1 (Mono)" );
if( wi.channel == 2 ) puts( "2 (Stereo)" );
printf( "Time length : %f s\r\n", (double)wi.length / wi.fs );
}
double -1.0~0.99999... ( -1.0 <= x < 1.0 )
float -1.0~0.99999... ( -1.0 <= x < 1.0 )
int -32768~32767
short -32768~32767
char -128~127
unsigned char 0~255
main( int argc, char *argv[] )
{
if( argc < 2 )
{
printf( "usage : wav_info file_name.wav" );
return 0;
}
wav_info wi = get_wav_info( argv[ 1 ] );
puts( "" );
printf( "Sampling rate : %d Hz\r\n", wi.fs );
printf( "Sample bits : %d bit\r\n", wi.bits );
printf( "Channel : " );
if( wi.channel == 1 ) puts( "1 (Mono)" );
if( wi.channel == 2 ) puts( "2 (Stereo)" );
printf( "Time length : %f s\r\n", (double)wi.length / wi.fs );
}
サイン波データを作ってみる
1000Hzの正弦波データを作ってWAVファイルに書き込んでみます。データ型はfloat型を使いました。
wave_sine.cpp
#include "wave.hpp"
#include <cmath>
main()
{
int fs = 44100; // サンプリングレート
int freq = 1000; // 周波数
float length = 5.f; // 音声データの長さ(秒)
float gain = 0.2f; // ゲイン
wave<float> src( fs, fs * length );
float t = ( 2.0*M_PI ) / fs * freq;
for( auto i( 0 ); i < src.length; ++i )
{
src[ i ] = sin( t * i ) * gain;
}
save( "wave_sine.wav", src );
}
#include <cmath>
main()
{
int fs = 44100; // サンプリングレート
int freq = 1000; // 周波数
float length = 5.f; // 音声データの長さ(秒)
float gain = 0.2f; // ゲイン
wave<float> src( fs, fs * length );
float t = ( 2.0*M_PI ) / fs * freq;
for( auto i( 0 ); i < src.length; ++i )
{
src[ i ] = sin( t * i ) * gain;
}
save( "wave_sine.wav", src );
}
waveクラスが音声データを扱うクラスです。テンプレートでデータ型を指定します。使用できるデータ型と数値範囲は以下の通りです。数値範囲を超えると数値が反転してノイズになります。実数を使う時は1.0にならないように気を付けます。
float -1.0~0.99999... ( -1.0 <= x < 1.0 )
int -32768~32767
short -32768~32767
char -128~127
unsigned char 0~255
wave::fs ---- サンプリングレート
wave::length ---- 音声データの個数
wave::channel ---- 1=モノラル, 2=ステレオ
データフォーマットを決める
・STM32などの32ビットマイコンで扱うのなら符号付き16ビットデータ(short)が恐らくもっとも高速に扱えます。
・最初に紹介した参考書籍のC言語ではじめる音のプログラミング、サウンドプログラミング入門では( -1.0 <= x < 1.0 )に正規化されたdouble(64ビット)で音声データを扱っています。
・ネットで多く見られるVST用のソースコードは( -1.0 <= x < 1.0 )に正規化されたfloat(32ビット)でデータを扱っています。
データ型は各自で好きなのを使えば良いです。実数を使えば精度は高くなりますが、FPUの無いマイコンで動作させることは難しくなります。
WAVフォーマットの内部表現(非圧縮)は符号付き16ビット、または符号なし8ビットデータどちらかしかありませんので、ファイルに落とす際にデータ変換されて書き込まれます。WAVファイルから読み込むときも、任意のデータ型と数値範囲に変換されます。
WAVフォーマットの内部表現(非圧縮)は符号付き16ビット、または符号なし8ビットデータどちらかしかありませんので、ファイルに落とす際にデータ変換されて書き込まれます。WAVファイルから読み込むときも、任意のデータ型と数値範囲に変換されます。
ステレオデータとビット深度
ステレオ(2ch)の音声データを作るにはwaveクラスのコンストラクタの第3引数に2を渡します。(デフォルトは1=モノラルです)。データへのアクセスはL[], R[]で行います。
wave_sine_st.cpp
#include "wave.hpp"
#include <cmath>
main()
{
int fs = 44100; // サンプリングレート
int freq = 1000; // サイン波の周波数
int length = 5; // 音声データの長さ(秒)
float gain = 0.2; // ゲイン
wave_f src( fs, fs*length, 2 ); // 第3引数に2を指定するとステレオ
double s = (2.0*M_PI) / fs * freq;
for( auto i( 0 ); i < src.length; ++i )
{
float v = sin( s * i ) * gain;
src.L[ i ] = v; // L channel
src.R[ i ] = v; // R channel
}
save( "wave_sine_st_8bit.wav", src, 8 ); // 8ビットフォーマット
// save( "wave_sine_st_16bit.wav", src ); // 16ビットフォーマット(デフォルト)
}
#include <cmath>
main()
{
int fs = 44100; // サンプリングレート
int freq = 1000; // サイン波の周波数
int length = 5; // 音声データの長さ(秒)
float gain = 0.2; // ゲイン
wave_f src( fs, fs*length, 2 ); // 第3引数に2を指定するとステレオ
double s = (2.0*M_PI) / fs * freq;
for( auto i( 0 ); i < src.length; ++i )
{
float v = sin( s * i ) * gain;
src.L[ i ] = v; // L channel
src.R[ i ] = v; // R channel
}
save( "wave_sine_st_8bit.wav", src, 8 ); // 8ビットフォーマット
// save( "wave_sine_st_16bit.wav", src ); // 16ビットフォーマット(デフォルト)
}
WAVファイルのビット深度はsave関数の第3引数に8を指定します。16ビットで書き出すには第3引数を省略するか16を指定します。
wave<float>とか書くのが面倒であれば、次のエイリアスが使えます。
wave_d ---- wave<double>
wave_f ---- wave<float>
wave_i ---- wave<int>
wave_s ---- wave<short>
wave_c ---- wave<char>
wave_uc ---- wave<unsigned char>
トレモロエフェクト
最後に簡単なサウンドエフェクトのトレモロ効果を作ってみます。WAVデータを読み込んでトレモロ効果を加えてから、WAVファイルへ書き出すプログラムです。
tremolo.cpp
#include "wave.hpp"
#include <cmath>
main()
{
wave_f src;
load( "_aruhinokuregatanokotodearu.wav", src ); // ファイルから読み込み
wave_f dst( src.fs, src.length ); // 出力データの入れ物
float freq = 1.f; // 周波数
float t = (2.f*M_PI) / src.fs * freq;
for( auto i( 0 ); i < src.length; ++i )
{
dst[ i ] = src[ i ] * sin( t * i ); // トレモロ効果
}
save( "toremolo.wav", dst ); // ファイルへ書き出し
}
#include <cmath>
main()
{
wave_f src;
load( "_aruhinokuregatanokotodearu.wav", src ); // ファイルから読み込み
wave_f dst( src.fs, src.length ); // 出力データの入れ物
float freq = 1.f; // 周波数
float t = (2.f*M_PI) / src.fs * freq;
for( auto i( 0 ); i < src.length; ++i )
{
dst[ i ] = src[ i ] * sin( t * i ); // トレモロ効果
}
save( "toremolo.wav", dst ); // ファイルへ書き出し
}
C/C++ビルド用スクリプト
最後にMinGWコンソールでC/C++プログラムをコンパイルする時のスクリプトを紹介します。ふつうC/C++プログラムをコンパイルするには
または
$g++ hello.cpp
といった感じでやります、これだと出力ファイル名がa.exeという名前になってしまいます。これでは使いづらいので、buildという拡張子無しのファイルを作って以下の内容をコピーします。
build
path=$1
#echo "path: $path"
basename=${path##*/}
#echo "basename: $basename"
filename=${basename%.*}
#echo "filename: $filename"
extension=${basename##*.}
#echo "extension: $extension"
g++ $1 $2 $3 $4 $5 $6 $7 $8 $9 -o $filename
または
$build helllo.cpp
これで出力ファイル名とソースファイル名が一致します。21世紀にもなってこんなこと、いちいちユーザーに書かせるな!って感じですが、Linuxの人達はこうゆうこのが萌えらしいです。文化の違いなんでしょう。
よく使うオプションやソースコードはbuildスクリプトに書き込んでいけば良いので、小規模構成のプログラムならわざわざmakefileを書くまでもなく、これだけで間に合います。
コメント
コメントを投稿