カーブの種類とカーブを指定して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
    }
  }
}