今まではずっと同じ周波数の音でしたが今度は高さを変えたいと思います。今回は3秒間のサイン波のwavファイルを作ります。最初は440Hzで2秒後に1オクターブ上の880Hzになりそのまま1秒その周波数のままにします。滑らかに周波数を変化させるためサンプルを取得するときに指定する周波数を計算するクラスをまず作ります。
クラスの機能を決める
3秒間にとるサンプル数はサンプリングレートを48,000とすると144,000です。一番最初は440Hzですが2秒後の96,000番目のサンプルでは880Hzにします。あとは最後までそのままとします。
周波数の変化の目印となるポイントの情報を保持するようにします。
- 0番目で440
- 96000番目で880
JavaではTreeMapを使うことにします。鍵をサンプルの順序、値を周波数をします。鍵の順序を保持する連想配列なので鍵がないときはその前後の値を見て計算するようにします。
TreeMap<Integer, Double> map = new TreeMap(); map.put(0, 440.0); map.put(96000, 880.0);
この連想配列から周波数を取得するようにします。index番目のサンプルの周波数を計算するメソッドを作ります。
public static double getFreq(TreeMap<Integer, Double> map, int index){ if(map.containsKey(index)){ return map.get(index); }
まずindexを鍵としたエントリがあればそのまま値を返します。indexが0または96000の時はこのifに入ります。
これ以外の場合はindexの前と後にあるエントリを取得します。floorが自分より前のエントリ、ceilingが後のエントリです。
Entry<Integer, Double> a = map.floorEntry(index); Entry<Integer, Double> b = map.ceilingEntry(index); if(a == null && b == null){ throw new IllegalStateException("map should contain at least one entry"); }
両方ないということはありえないですし一つもエントリがないと周波数は決められないので例外を投げます。
前か後ろのどっちかがない場合にはある方の値を返すようにします。96001番目以降は後ろがないのでずっと96000番目で設定された値880が返されるはずです。
if(a == null){ return b.getValue(); }else if(b == null){ return a.getValue(); }
最後に両方ある場合です。この場合はindexがこの間のどのくらいにいるかを按分して返します。
double xa = a.getKey(); double xb = b.getKey(); double ya = a.getValue(); double yb = b.getValue(); return ya + (yb - ya) * (index - xa) / (xb - xa); }
計算できているか確認する
144000個の値を確認するのは大変なので2000個おきに一つ確認するようにしましょう。
for(int i = 0;i < 144000;i += 2000){ System.out.println(i + ":" + getFreq(map, i)); }
以下実行結果です。予想通りに変化していると思います。
0:440.0 2000:449.1666666666667 4000:458.3333333333333 6000:467.5 8000:476.6666666666667 10000:485.8333333333333 12000:495.0 14000:504.1666666666667 16000:513.3333333333334 18000:522.5 20000:531.6666666666666 22000:540.8333333333334 24000:550.0 26000:559.1666666666666 28000:568.3333333333334 30000:577.5 32000:586.6666666666666 34000:595.8333333333334 36000:605.0 38000:614.1666666666666 40000:623.3333333333334 42000:632.5 44000:641.6666666666666 46000:650.8333333333334 48000:660.0 50000:669.1666666666666 52000:678.3333333333334 54000:687.5 56000:696.6666666666667 58000:705.8333333333333 60000:715.0 62000:724.1666666666667 64000:733.3333333333333 66000:742.5 68000:751.6666666666667 70000:760.8333333333333 72000:770.0 74000:779.1666666666667 76000:788.3333333333333 78000:797.5 80000:806.6666666666667 82000:815.8333333333333 84000:825.0 86000:834.1666666666667 88000:843.3333333333333 90000:852.5 92000:861.6666666666667 94000:870.8333333333333 96000:880.0 98000:880.0 100000:880.0 102000:880.0 104000:880.0 106000:880.0 108000:880.0 110000:880.0 112000:880.0 114000:880.0 116000:880.0 118000:880.0 120000:880.0 122000:880.0 124000:880.0 126000:880.0 128000:880.0 130000:880.0 132000:880.0 134000:880.0 136000:880.0 138000:880.0 140000:880.0 142000:880.0
コード
最後にコード全体を載せておきます。
package mocha.sound; import java.util.Map.Entry; import java.util.TreeMap; public class FrequencyChangeTest { public static void main(String[] args) { TreeMap<Integer, Double> map = new TreeMap(); map.put(0, 440.0); map.put(96000, 880.0); for (int i = 0; i < 144000; i += 2000) { System.out.println(i + ":" + getFreq(map, i)); } } public static double getFreq(TreeMap<Integer, Double> map, int index) { if (map.containsKey(index)) { return map.get(index); } Entry<Integer, Double> a = map.floorEntry(index); Entry<Integer, Double> b = map.ceilingEntry(index); if (a == null && b == null) { throw new IllegalStateException("map should contain at least one entry"); } if (a == null) { return b.getValue(); } else if (b == null) { return a.getValue(); } double xa = a.getKey(); double xb = b.getKey(); double ya = a.getValue(); double yb = b.getValue(); return ya + (yb - ya) * (index - xa) / (xb - xa); } }