カーブの種類とカーブを指定してWAVEの音量を調整する方法について。
カーブと波形の例
カーブの種類と音量を調整した波形の例。 各画像は3秒の長さの音声の1秒後からフェードアウトした波形のもの。 各コードは、波形の各サンプルに音量を適用する例(入力はPCM形式、ステレオ16bitのWAVEと仮定)。
short left = reader.ReadInt16(); // 左チャンネル
short right = reader.ReadInt16(); // 右チャンネル
double volume = 0.5; // 音量(0.0〜1.0)
double ratio; // 変化量(0.0〜1.0)
線形カーブ(Bカーブ)
線形カーブ

// f(x) = x
ratio = volume;
left = (short)((double)left * ratio);
right = (short)((double)right * ratio);
指数カーブ(Aカーブ)
指数カーブ

底を10とした指数カーブ。
// f(x) = (n^x - 1.0) / (n - 1.0) ただしn = 10
ratio = (Math.Pow(10.0, volume) - 1.0) / 9.0;
left = (short)((double)left * ratio);
right = (short)((double)right * ratio);
対数カーブ(Cカーブ)
対数カーブ

底を10とした対数カーブ。
// f(x) = log_n(x * (n - 1.0) + 1.0) ただしn = 10
ratio = Math.Log10(volume * 9.0 + 1.0);
left = (short)((double)left * ratio);
right = (short)((double)right * ratio);
余弦カーブ(S字型のカーブ)
余弦カーブ

// f(x) = (1.0 - cos(x * π)) / 2.0
ratio = (1.0 - Math.Cos(volume * Math.PI)) * 0.5;
left = (short)((double)left * ratio);
right = (short)((double)right * ratio);
実装例
カーブを与えてフェードアウトする正弦波を生成・再生する例。
using System;
using System.IO;
using System.Media;
class Volume {
static void Main()
{
using (var stream = new MemoryStream()) {
var writer = new BinaryWriter(stream);
const int sampleFreq = 44100;
var sampleCount = sampleFreq * 3; // 44.1kHz, 3 secs
var dataLength = sampleCount * 2; // mono 16 bit
// WAVEファイルヘッダ
writer.Write(new[] {(byte)'R', (byte)'I', (byte)'F', (byte)'F'});
writer.Write((UInt32)(40 + sampleCount * 2));
writer.Write(new[] {(byte)'W', (byte)'A', (byte)'V', (byte)'E'});
writer.Write(new[] {(byte)'f', (byte)'m', (byte)'t', (byte)' '});
writer.Write((UInt32)0x00000010);
writer.Write((UInt16)0x0001); // WAVE_FORMAT_PCM
writer.Write((UInt16)1); // mono
writer.Write((UInt32)sampleFreq);
writer.Write((UInt32)(sampleFreq * 2));
writer.Write((UInt16)2);
writer.Write((UInt16)16); // bits per seconds
writer.Write(new[] {(byte)'d', (byte)'a', (byte)'t', (byte)'a'});
writer.Write((UInt32)dataLength);
// WAVEデータ
for (var i = 0; i < sampleCount; i++) {
double t = (double)i / sampleFreq;
// 再生後1秒でフェードアウトを開始し、3秒で完全に無音となるようにボリュームを変える
double volume;
if (1.0 <= t)
volume = (3.0 - t) / 2.0;
else
volume = 1.0;
// ボリュームにカーブを与える
const int curve = 3;
switch (curve) {
case 0:
default:
// 線形カーブ f(x) = x
volume = volume;
break;
case 1:
// 指数カーブ f(x) = (n^x - 1.0) / (n - 1.0) ただしn = 10
volume = (Math.Pow(10.0, volume) - 1.0) / 9.0;
break;
case 2:
// 対数カーブ f(x) = log_n(x * (n - 1.0) + 1.0) ただしn = 10
volume = Math.Log10(volume * 9.0 + 1.0);
break;
case 3:
// 余弦カーブ f(x) = (1.0 - cos(x * π)) / 2.0
volume = (1.0 - Math.Cos(volume * Math.PI)) * 0.5;
break;
}
// 正弦波を作成
const double waveFreq = 440.0; // Hz
double fx = Math.Sin((2.0 * Math.PI) * t * waveFreq);
// 正弦波にボリュームを与えた上で書き込む
short sample = (short)((double)short.MaxValue * fx * volume);
writer.Write(sample);
}
// 作成した音声をSoundPlayerで再生する
stream.Position = 0L;
using (var player = new SoundPlayer(stream)) {
player.PlaySync();
}
#if false
stream.Position = 0L;
using (var s = File.OpenWrite("test.wav")) {
stream.CopyTo(s);
}
#endif
}
}
}