6

非常に基本的なサウンド合成を使用して、ゲームのオーディオとエフェクトをオンザフライで作成しています。基本的に、周波数と振幅と持続時間を指定してサウンドを再生できるメソッドがいくつかあります。

短いフレーズとメロディーについては、簡単に書き直したり新しいメロディーをコードに追加したりできるように、基本的な記譜法を考案したいと思います (最終的にはファイルから読み取ることができるかもしれませんが、それはおそらくやり過ぎです)。

ただし、これを実装する方法がわかりません。

私は、MIDI # フィールドと周波数フィールドを持つ 88 の基本的なピアノの音符すべてを含む列挙型 EqualTemperamentTuning を作成することから始めました。これは少なくとも、周波数ではなく音名を扱うことができることを意味します。

public enum EqualTemperamentTuning {
    A_0         (1, 27.5),
    A_SHARP_0   (2, 29.1352),
        ...
    C_8         (88, 4186.01);

    private int num;
    private double frequency;

    EqualTemperamentTuning(int num, double frequency){

        this.num = num;
        this.frequency = frequency;
    }

    public double getFrequency(){
        return frequency;
    }

    public double getNum(){
        return num;
    }
}

次に、さらにオブジェクトを作成し始めました。最初は、EqualTemperamentTuning、振幅、および長さを保持する Note です。

public class Note {

    /** Note frequency */
    private double frequency;
    /** Note Amplitude */
    private double amplitude;
    /** Note length */
    private int length;     

    public Note(EqualTemperamentTuning tuning, double amplitude, int length){

        this.frequency = tuning.getFrequency();
        this.amplitude = amplitude;
        this.length = length;   
    }

    public double getFrequency(){
        return frequency;
    }

    public double getAmplitude(){
        return amplitude;
    }

    public int getLength(){
        return length;  
    }
}

最後に、演奏したいメロディーを定義するために、NotePhrase クラスを作成しました。

public class NotePhrase {

    /** The arrayList of notes*/
    private Note[] notes;

    public NotePhrase(Note[] notes){

        this.notes = notes;
    }

    public double getFrequency(int counter){

        // for each note in the array
        for (int i = 0; i< notes.length; i++){

            // for each unit of length per note
            for (int j=0; j<notes[i].getLength(); j++){

                counter--;

                // return the frequency at this point
                if (counter <= 0) return notes[i].getFrequency();

            }
        }
        return -1;  
    }
}

次に、オーディオ生成クラスに、ウェーブ ジェネレーターからサンプルを生成するループ (カウンター付き) があります。再生する新しいサンプルが必要になるたびに、上記の NotePhrase.getFrequency(int counter) メソッドに従ってウェーブの周波数を設定します。これは (実際にはまだテストしていません!) 周波数と振幅 (追加予定) に従って NotePhrase メロディーを再生するだけです。

問題は、あまりエレガントに見えないことです。具体的には、メロディーを読みやすい方法で「書く」のが非常に難しいことです。たくさんの新しい Note オブジェクトをコーディングしてから、それらの配列を使用して NotePhrase オブジェクトを作成する必要があります...これらのメロディーの束をハードコーディングして、後でそれらを簡単に切り替える方法は明らかではありません。

本当に私は、メロディーの列挙型などを作成したいと思います。ここでは、異なるメロディーごとに人間が判読できる構成を簡単にハードコーディングできます。次に、それらを使用したいときに、列挙型をオーディオプレーヤーに渡すだけです.. .

私が持っている最高のものは次のとおりです。

private static enum Melody {
    NOKIA_RINGTONE ( new Note(EqualTemperamentTuning.E_5, 0.5, 1), new Note(EqualTemperamentTuning.D_5, 0.5, 1))
    ;

    private Note[] notes = new Note[2];

    Melody (Note note1, Note note2){
        this.notes[0] = note1;
        this.notes[1] = note2;
    }
}

これを実行時に NotePhrase オブジェクトにロードします。異なる量の音符を持つメロディー用に新しいコンストラクターを作成する必要があるため、これはあまり良くありません。逆に、音符の配列で列挙型を作成すると、別の場所で 2 つの部分に分けてメロディーを「書いている」だけで、さらに混乱するように見えます...

だから私はこれを適切に構造化する方法について行き詰まっていますか? つまり、どのクラスを作成し、どの情報を保持する必要があるか...これを「正しく」取得したいのは、将来、表記法を拡張してエフェクト (エコーなど) を含めたいと思うかもしれません。適切なクラス、構造、および関係 (名前でさえも) によって、私のプログラムが非常に理解しやすくなったり、理解しにくくなったりする可能性があるという経験はほとんどありません。

エッセイで申し訳ありませんが、これは非常によく表現された (エヘム) 質問ではないかもしれませんが、Java と OOP の初心者として、ヒントは大歓迎です!

編集* *

回答と提案をありがとう、とても役に立ちました。このケースで与えられた答えを考えてみると、一般的なオーディオの実装を再考するきっかけになりましたが、これは現在かなり不安定です。ただし、誰を正しいとマークする必要があるかはわかりません。実際には、すべての推奨事項を取り入れて、そこから試してみるつもりです.

4

4 に答える 4

4

Melody は真の定数を表すのではなく、Note データのコレクションであるため、Melody に列挙型を使用しないでください。配列も使用せず、ArrayList<Note>より柔軟な配列を使用することをお勧めします。

Melody では、Note を Measure と Beat に関連付け、Melody クラスに addNote(Note, int measure, int beatFraction) を指定します。これは、Remove Note と同じです。

12 のコア ノートを列挙型として作成し、それらに加えてオクターブ int を使用して Note オブジェクトを作成することを検討してください。

... このエクササイズにはさまざまな遊び方があります。「鍵」は実験を続けることです。

于 2012-05-23T16:16:07.657 に答える
3

おそらく、 Fluent インターフェイス パターンをBuilder パターンと組み合わせて使用​​できます。次に、次のように記述できます。

new NotePhraseBuilder()
     .beat(120)
     .measure(fourQuarters)
     .E_5()
     .quarter()
     .D_5()
     .half()
     ...;

NotePhraseBuilder には、、、のようなメソッドが含まれていますが、、のようなメソッドは含まれbeat()measure()いませE_5()ん。quarter()half()

class NotePhraseBuilder {
    ...
    public NoteBuilder E_5() {
        return new NoteBuilder(this, E_5);
    }
    ...
}

class NoteBuilder {
    private final NotePhraseBuilder parent;
    private final EqualTemperamentTuning tuning;

    public NoteBuilder(NotePhraseBuilder parent_, EqualTemperamentTuning tuning_) {
        this.parent = parent_;
        this.tuning = tuning_;
    }

    public NotePhraseBuilder quarter() {
        parent.add(new Note(tuning_, parent.getAmplitude(), parent.getTempo() / 4));
        return parent;
    }

    ...
}

Hovercraft Full Of Eelsのヒントをこのパターンに組み込むことができます。

注: メソッドNotePhraseBuilder.E_5()やその他のメソッドは、その名前に等しいNoteBuilder-constructor を呼び出します。EqualTemperamentTuning88 個のメソッドを記述するのは無駄に思えますが、これをもっと美しく行うことはできますか?

于 2012-05-23T16:50:42.010 に答える
3

心に浮かぶのは、音符を書き留めるために使用する記譜法にできるだけ近い単純な内部DSLを実装することです。Javaでは、次のようになります

MelodyTrack  track1 = MelodyTrack().create().setTiming(TimeSignature.C)
.eighth(Notes.E5)
.bar().half(Notes.B5).quarter(Notes.A5).quarter(Notes.B5).bar().half(Notes.C6)

Martin Fowler によって書かれた DSL に関する良い本があります。

これは、どのように見えるかの単なる例です。私は、ある形式や別の形式に固執しません。基本的な考え方は、DSL は、プログラマーとドメインの専門家 (ミュージシャン) の両方によって読み取り可能でなければならないということです。また、シーケンサーでデータを表現する方法は、メロディーを書き留める方法に影響を与えるべきではありません。

PS 何か深刻なことをしている場合は、外部 DSL 別名 midi ファイルまたはそのようなものと、ある種のシーケンス ライブラリを使用して、アプリケーションの別のリソース ファイルに音楽を保存するようにしてください。

于 2012-05-23T16:50:01.610 に答える
3

音符であれ、キーやテンポの変化であれ、楽譜は一連のイベントと考えることができます。

イベントのタイムラインを考えて、そこから構築する方が実り多いかもしれません。MIDI プロトコルからさらにアイデアが得られるかもしれません。

于 2012-05-23T21:08:47.853 に答える