2012-05-23 7 views
6

Tworzę audio i efekty w mojej grze w locie z bardzo podstawową syntezą dźwięku. Zasadniczo, mam kilka metod, które mogą odtwarzać dźwięk z częstotliwością & czas trwania amplitudy.Jak utworzyć prostą, ale dobrze zorganizowaną klasę zapisu nutowego (punkt muzyczny) w języku Java?

W przypadku krótkich fraz i melodii, chciałbym wymyślić podstawową notację, aby móc z łatwością przepisać lub dodać nowe melodie do kodu (w końcu może mógłbym odczytać z plików, ale to chyba przesada).

Nie jestem pewien, jak to zaimplementować.

Zacząłem od stworzenia enum EqualTemperamentTuning, który zawiera wszystkie 88 podstawowych nut fortepianu z polem MIDI # i polem częstotliwości. To przynajmniej oznacza, że ​​mogę zajmować się nazwami nut, a nie częstotliwościami.

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; 
    } 
} 

Potem zaczął tworzyć więcej przedmiotów, najpierw notatkę, która posiada EqualTemperamentTuning, amplitudę i długość:

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; 
    } 
} 

Wreszcie zdefiniowania melodii chcę grać, stworzyłem klasę 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; 
    } 
} 

Wtedy w mojej klasie dźwięku generującego mam pętlę (przy kasie), który generuje próbki z generatora fal. Za każdym razem, gdy potrzebuję nowej próbki do odegrania, ustawia częstotliwość fali zgodnie z powyższą metodą NotePhrase.getFrequency (int counter). To powinno (jeszcze nie przetestowałem!) Po prostu odtwarzaj melodię NotePhraz zgodnie z częstotliwością i amplitudą (do dodania).

Problem polega na tym, że nie wydaje się bardzo elegancki, a dokładniej bardzo trudno jest "napisać" melodię w jakikolwiek czytelny sposób. Muszę zakodować całą masę nowych obiektów Note, a następnie skonstruować obiekt NotePhrase z ich szeregiem ... Nie jest dla mnie oczywiste, jak mocno zakodowałem kilka melodii, a następnie łatwo przełączałem się między nimi później.

Naprawdę chciałbym stworzyć wyliczenie Melodii lub coś w tym rodzaju, gdzie mogę z łatwością zakodować ludzką czytelną konfigurację dla każdej melodii, a kiedy chcę z nich skorzystać, wystarczy podać typ wyliczeniowy do odtwarzacz audio ...

najlepszym mam to:

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; 
    } 
} 

który ja następnie załadować do obiektu NotePhrase przy starcie. Nie jest to zbyt dobre, ponieważ muszę stworzyć nowy konstruktor melodii z różną ilością nut. Jeśli zrobię to na odwrót i skonstruuję enum z szeregiem nut, to po prostu "piszę" melodię gdzie indziej i na dwie części, co wydaje się jeszcze bardziej zagmatwane ...

Więc utknąłem jak właściwie to ustrukturyzować? tj. jakie klasy tworzyć i jakie informacje powinny posiadać ... Chcę uzyskać to "prawo", ponieważ, chciałbym rozszerzyć notację w przyszłości, aby uwzględnić efekty (echo itp.) i już znalazłem z moim bardzo niewielkie doświadczenie, że odpowiednie klasy, struktura i relacje (nawet nazwy) mogą sprawić, że moje programy będą bardzo łatwe lub trudne do zrozumienia.

Przepraszam za esej, to może nie być bardzo dobrze sformułowane (ahem) pytanie, ale jako początkujący użytkownik języka Java & początkujący, wszelkie wskazówki byłyby bardzo mile widziane!

EDIT * *

Dzięki za odpowiedzi & sugestie, bardzo pomocne. Myślenie o odpowiedziach podanych w tym przypadku skłoniło mnie do ponownego przemyślenia mojej ogólnej implementacji audio, która jest teraz dość chwiejna. Nie jestem pewien, kogo mam oznaczyć jako poprawny, ponieważ zamierzam wziąć wszystkie rekomendacje na pokładzie i spróbować od tego miejsca.

+2

"Nie jest to zbyt dobre, ponieważ muszę stworzyć nowy konstruktor melodii z różną ilością nut." Możesz użyć varargs tutaj – Zavior

+0

Thx, varargs jest dokładnie to, czego potrzebuję tam. – kiman

+0

Może to pomóc w utworzeniu klas Voice and Measure, więc twoja klasa Melody nie próbuje zrobić więcej niż jedną rzecz. –

Odpowiedz

4

Nie należy używać wyliczenia dla melodii, ponieważ Melodia nie reprezentuje stałej rzeczywistej, ale raczej zbiór danych typu Note. Zalecam, aby nie używać tablic, ale raczej jako bardziej elastyczną.

W Melodii, zawiązałbym Notatkę do miary i rytmu i dałbym klasie Melody addNote (Note, int measure, int beatFraction), to samo dla Remove Note.

Rozważ wykonanie 12 podstawowych nut jako wyliczeń, a następnie użyj ich oraz oktawę int do utworzenia obiektu Note.

... z tym ćwiczeniem można korzystać na wiele sposobów. "Kluczem" jest ciągłe eksperymentowanie.

+0

Dzięki za porady. Wydaje mi się, że stworzyłem melodię, ponieważ myślałem, że melodia jest jak kawałek nuty. Zostało to naprawione i mogło zostać stworzone tylko w sobie. Będę musiał majstrować przy kodzie z myślą o twoich sugestiach. – kiman

+0

@kirman: Nie ma za co, ale nie myl kodu z danymi. Uważam Melody za zbiór danych. Enumy są lepiej stosowane do bloków atomowych lub do stałych stanu. –

3

To, co przychodzi na myśl, to wprowadzenie prostego wewnętrznego numeru wewnętrznego DSL, który będzie jak najbardziej zbliżony do zapisu używanego do zapisywania nut. w Javie może to wyglądać

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

jest good book on DSLs, written by Martin Fowler

To jest właśnie przykład jak to może wyglądać, nie domagać się takiej czy innej formie - Podstawowym założeniem jest to, że DSL musi być czytelny zarówno przez programistów, jak i ekspertów dziedzinowych (muzyków). Sposób, w jaki dane są reprezentowane w twoim sekwenserze, nie powinien wpływać na sposób zapisywania melodii, tj. Utrudniać.

P.S. Jeśli robisz coś poważnego, powinieneś spróbować użyć zewnętrznego DSL a.k.a. pliku midi lub czegoś podobnego oraz biblioteki sekwencyjnej i przechowywać muzykę w osobnych plikach zasobów twojej aplikacji.

3

Być może można użyć Fluent interface pattern w połączeniu z Builder pattern. Można wtedy napisać:

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

NotePhraseBuilder zawiera metody takie jak beat(), measure(), E_5(), ale nie ma metody, takie jak 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; 
    } 

    ... 
} 

można włączyć wskazówki od Hovercraft Full Of Eels ramach tego wzorca.

Uwaga: Metody NotePhraseBuilder.E_5() i inni podobni do niego, zadzwoń NoteBuilder -constructor z EqualTemperamentTuning równej ich nazwy. Pisanie 88 metod wydaje się być marnotrawstwem, czy można to zrobić piękniej?

+0

Podejście podobne do podejścia [Boris Treukhov] (http://stackoverflow.com/users/241986/boris-treukhov). –

+0

obie odpowiedzi zostały wysłane wirtualnie w tym samym czasie :-) –

+0

I obie lepsze odpowiedzi niż moje. 1+ dla obu. –

3

Notację muzyczną można traktować jako serię zdarzeń, czy to notatki, czy zmiany klucza i tempa.

To może być bardziej owocne myślenie o osi czasu z wydarzeniami i budowanie z tego miejsca.Protokół MIDI może dać ci więcej pomysłów.

+0

Tak, oś czasu ma sens. Myślę, że nie do końca rozumiem, czego tak naprawdę potrzebuję od tej implementacji audio, która sprawia, że ​​projektowanie jest trudne! MIDI wygląda na skomplikowaną, ale myślę, że prawdopodobnie lepiej zrozumiem to, ponieważ jest to bardzo istotne dla tego, co robię ... – kiman

+1

MIDI jest dość proste w sercu - w przypadku melodii, na której zapisujesz i notujesz zdarzenia, które mają wartość tonu i pozycja na osi czasu. Odtwarzanie melodii to proces wysyłania zdarzeń do źródła dźwięku we właściwym czasie. Możesz również zajrzeć do modułów śledzących mod. – blank

Powiązane problemy