課題 (07:サウンド)

作業について

今回の課題は以下のパッケージに作成してください。

パッケージの名前
j1.lesson07

作成するクラスの名前は問題ごとに指示があります。下記を参照してください

課題の提出方法については下記を参照してください。

また、別のコンピューター上に移動する際には、下記を参考にプログラムを持ち帰ってください。

新しい内容

音を合成する

この課題では、2つのWAVEファイルの音データを合成して、新しい音データを作成します。音データの合成には、複雑な処理は必要ありません。それぞれの音データを加えることで、合成することができます。

音声を合成するために、音データを加算すると結果が-1.0から+1.0の範囲を超えることがあります。例えば、ひとつの音データが0.7で、もうひとつの音データが0.8の場合、加算すると1.5となり1を超えてしまいます。1.0を超えた値はサンプルの範囲外であるため、加算結果は1を超えない値に変換する必要があります。この場合、1を超えた値だけを変換するのではなくすべての加算結果を変換しなければ音の波形が歪んだものとなってしまいます。

2つの音声を取り扱う

2つの音声を取り扱うには、「Clip.load()」や「Clip.create()」の結果をそれぞれ別の変数に保存します。

Clip first = Clip.load();
Clip second = Clip.load();
Clip third = Clip.create(44100);
...

標本化周波数を取得する

2つの音を合成するためには、それぞれの標本化周波数を確認しないと正しく合成できません。

標本化周波数を取得するには、次のように「clip.getSamplingRate()」というメソッドを利用します。

package j1.lesson07;

import javax.swing.JOptionPane;

import gpjava.Clip;

public class SoundInfo {

    public static void main(String[] args) {
        new SoundInfo().start();
    }

    void start() {
        Clip clip = Clip.load();
        JOptionPane.showMessageDialog(null,
                "標本化周波数: " + clip.getSamplingRate());
        JOptionPane.showMessageDialog(null,
                "量子化ビット数: " + clip.getQuantizationBitRate());
        JOptionPane.showMessageDialog(null,
                "サンプル数: " + clip.getSampleCount());
    }
}

その他、例のように「clip.getQuantizationBitRate()」で量子化精度(ビット数)、「clip.getSampleCount()」でサンプルの個数がそれぞれ返されます。

音声を丸ごと配列で操作する

今回の授業では、配列と反復を習ったので、今後は音声の内容全体を配列で取り扱っていきます。

音声から配列を取り出して、別の配列を音声に登録するサンプルは以下の通りです。

package j1.lesson07;

import gpjava.Clip;

public class SoundArray {
    public static void main(String[] args) {
        new SoundArray().start();
    }

    void start() {
        Clip clip = Clip.load();
        double[] samples = clip.getSampleData();
        double[] half = new double[samples.length];
        for (int i = 0; i < samples.length; i++) {
            half[i] = samples[i] * 0.6;
        }
        Clip result = Clip.create(clip.getSamplingRate());
        result.setSampleData(half);
        result.save();
    }
}

音声全体のサンプル配列を取り出すには、次の「clip.getSampleData()」のように書きます。

Clip clip = Clip.load();
double[] samples = clip.getSampleData();

この例では、同じ長さの別の配列を作成し、サンプル配列の中身をそれぞれ0.6倍した値を設定しています。

double[] half = new double[samples.length];
for (int i = 0; i < samples.length; i++) {
    half[i] = samples[i] * 0.6;
}

最後に、作成した配列を音声に「clip.setSampleData()」で書き戻しています。これは、前回の2つ引数がある「clip.setSampleData([配列], [繰り返し回数])」の繰り返し回数が1になっているだけです。

Clip result = Clip.create(clip.getSamplingRate());
result.setSampleData(half);
result.save();

上記のように、新しく音声を作成する際にほかの音声と同じ標本化周波数を利用する場合、「Clip.create([ほかの音声].getSamplingRate())」というように書くとよいでしょう。

問題

1. 同じ大きさで音声を合成

作成するクラスの名前
ComposeHalf
利用するWAVEファイル
first.wav
kinkon.wav

ダウンロードした2つの音声を合成してください。なお、これらの標本化周波数はいずれも同じ値に設定してあります。

音声を合成する際には、それぞれのサンプルを0.45倍してから和を計算してください。

また、作成した音声を再生する前に、必ず波形を確認してください。

ヒント

2つの音声は再生時間が異なります。次の点に注意しましょう。

  • 音声を合成するには、長いほうの音声のサンプル数と同じ長さの配列を作る
  • 長さの違う音声を1つのfor文で処理しようとすると大変

2. 音の大きさをそろえてから合成

作成するクラスの名前
ComposeNormalize
利用するWAVEファイル
first.wav
kinkon.wav

先ほどの音の合成では、2つの音声のもともとの音の大きさについて考慮していませんでした。次は、2つの音声の音の大きさをそろえてから合成します。

なお、この問題のヒントは一番最後にあります

2.a 一番大きなサンプル値を探す

音の中から絶対値が一番大きなサンプルを探して、その値を絶対値のまま表示してください。

具体的には、次のように利用できるメソッド「findMax」を作成してください。

void start() {
    Clip clip1 = Clip.load();
    double max1 = findMax(clip1);
    JOptionPane.showMessageDialog(null, "最大の絶対値は: " + max1);
}

double findMax(Clip clip) {
   ...
}

2.b 一番大きなサンプルの絶対値を0.95にする

続けてプログラムを書き換えます。(絶対値を確認し終えたら、メッセージダイアログを表示する部分などは不要です。)

先ほど計算した一番大きな絶対値を利用して、音声全体のサンプル絶対値の最大を0.95にしてください。

具体的には、次のように利用できるメソッド「maximize」を作成してください。

void start() {
    Clip clip1 = Clip.load();
    double[] maximized1 = maximize(clip1);
    Clip result = Clip.create(clip1.getSamplingRate());
    result.setSampleData(maximized1);
    result.save();
}

double[] maximize(Clip clip) {
   ...
}

今回ダウンロードしたkinkon.wavは音の大きさを小さめに、first.wavは大きめに設定してあります。ここまでのプログラムを動かすと、それぞれが同じような音の大きさになれば成功です。

なお、作成した音声を再生する前に、必ず波形を確認してください。

2.c 音の大きさをそろえた状態で合成する

続けてプログラムを書き換えます。

2つの音の大きさをそろえた状態で、2つの音声を合成してください。

音声を合成する際には、それぞれのサンプルを0.5倍してから和を計算してください。

なお、作成した音声を再生する前に、必ず波形を確認してください。

ヒント (2.a)

まず、サンプルが1つしかない場合に、一番大きなサンプルの絶対値は、1つしかないサンプルと同じ値です。

サンプルが2つある場合には、次のように計算します。

  • 最初の1つの絶対値よりも、今回の絶対値の方が大きければ、今回の絶対値が最大
  • そうでなければ、最初の1つの絶対値が最大

サンプルが3つある場合には、次のように計算します。

  • ここまでの最大の絶対値よりも、今回の絶対値の方が大きければ、今回の絶対値が最大
  • そうでなければ、ここまでの最大の絶対値が最大

これは、Javaで書くと次のようになるでしょう。

double lastMax = (ここまでの最大の絶対値);
double current = (今回の絶対値);

if (lastMax < current) {
    lastMax = current;
}

JOptionPane.showMessageDialog(null, "ここまでの最大は" + lastMax);

もう少しヒントを出すと、これを繰り返しでうまく使えれば、全体の最大値も計算できます。

double lastMax = (最初の絶対値);
for (...) {
    double current = (今回の絶対値);
    if (lastMax < current) {
        lastMax = current;
    }
}
JOptionPane.showMessageDialog(null, "全体の最大は" + lastMax);

ヒント (2.b)

ここまでにすでに絶対値の最大が分かっているので、絶対値の最大が0.95になるような計算式を作成して、すべてのサンプルに適用してやればうまくいきます。

なお、絶対値の最大が1.0になるようにするには、次のような計算式を使います。

サンプルの値 / 絶対値の最大

たとえば、サンプルの値が0.3で、絶対値の最大が0.5の場合、上記の式は「0.3 / 0.5 = 0.6」となります。

ヒント (2.c)

1問目と基本的に同じです。

また、作成した音声を再生する前に、必ず波形を確認してください。