2012-05-23 10 views
6

Estoy creando audio y efectos en mi juego sobre la marcha con síntesis de sonido muy básica. Básicamente, tengo algunos métodos que pueden reproducir un sonido dada una frecuencia & amplitud & duración.¿Cómo crear una clase de notación musical simple pero bien estructurada (puntuación musical) en java?

Para frases cortas y melodías, me gustaría encontrar una notación básica para que pueda reescribir fácilmente o agregar nuevas melodías en el código (al final tal vez podría leer de los archivos, pero eso probablemente sea exagerado))

No estoy seguro de cómo implementar esto sin embargo.

Comencé creando un ecualizador de temperatura igual de enum que contiene las 88 notas básicas de piano con un campo MIDI # y un campo de frecuencia. Esto al menos significa que puedo tratar nombres de notas y no frecuencias.

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

Entonces empecé a crear más objetos, primero una nota que contiene un EqualTemperamentTuning, una amplitud y una longitud:

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

último para definir la melodía Quiero jugar, he creado una clase 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; 
    } 
} 

Luego, en mi clase de generación de audio I tienen un bucle (con el contador) que genera muestras de un generador de ondas. Cada vez que necesito una nueva muestra para reproducir, establece la frecuencia de la onda de acuerdo con el método NotePhrase.getFrequency (int counter) anterior. Esto debería (¡aún no lo he probado!) Solo toque la melodía de NotePhrase de acuerdo con la frecuencia y amplitud (para ser agregado).

El problema es que no parece muy elegante y más específicamente es muy difícil "escribir" una melodía de forma legible. Tengo que codificar un montón de nuevos objetos Note y luego construir un objeto NotePhrase con una matriz de ellos ... No es obvio para mí cómo codificaría un montón de estas melodías y luego cambiaría fácilmente entre ellas más tarde.

Realmente me gustaría crear una enumeración de Melodías o algo así, donde puedo codificar fácilmente una configuración humana legible para cada melodía diferente, y luego cuando quiero usarlas, simplemente paso el tipo de enumeración al reproductor de audio ...

El mejor que tengo es:

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

Qué me permitiría cargar en un objeto NotePhrase en tiempo de ejecución. Esto no es muy bueno porque tengo que crear un nuevo constructor para melodías con diferentes cantidades de notas. Si lo hago al revés y construyo la enumeración con una serie de notas, entonces simplemente estoy "escribiendo" la melodía en otro lugar y en dos partes, lo que parece aún más confuso ...

Así que estoy atascado en cuanto a cómo estructurar esto correctamente? es decir, qué clases crear y qué información deben contener ... Quiero obtener este "correcto" porque, me gustaría expandir la notación en el futuro para incluir efectos (eco, etc.) y ya he encontrado con mi muy poca experiencia, que las clases, estructura y relaciones correctas (incluso nombres) pueden hacer que mis programas sean muy fáciles o difíciles de entender.

Disculpas por el ensayo, esta podría no ser una pregunta muy bien formulada (ejem), pero como un principiante OOP java &, cualquier sugerencia sería muy bienvenida.

EDITAR * *

Gracias por las respuestas & sugerencias, muy útil. Pensar en las respuestas dadas en este caso me llevó a replantear mi implementación general de audio, que ahora es bastante inestable. Sin embargo, no estoy seguro de a quién debería marcar como correcto, ya que realmente voy a tomar todas las recomendaciones a bordo y tratar de ir desde allí.

+2

"Esto no es muy bueno porque tengo que crear un nuevo constructor para melodías con diferentes cantidades de notas". Puede usar varargs aquí – Zavior

+0

Thx, varargs es exactamente lo que necesito allí. – kiman

+0

Puede ser útil crear clases de voz y medición, por lo que su clase Melody no intenta hacer más de una cosa. –

Respuesta

4

No utilice una enumeración para una Melodía ya que la Melodía no representa una constante verdadera, sino más bien una colección de datos de la Nota. Yo recomendaría no usar matrices tampoco, sino un ArrayList<Note> que es más flexible.

En Melody, yo anudaría la Nota a una Medida y un Latido, y le daría a la clase Melodía un addNote (Nota, int medida, int beatFraction), lo mismo para eliminar Nota.

Considere la posibilidad de hacer sus 12 notas principales como enumeraciones, y luego usarlas más una octava int para crear un objeto Note.

... hay muchas maneras de jugar con este ejercicio. La "clave" es seguir experimentando.

+0

Gracias por los consejos. Creo que hice Melody una enumeración, porque estaba pensando que una Melodía era como una partitura. Fue arreglado y solo pudo ser creado dentro de sí mismo. Voy a jugar con el código con tus sugerencias en mente. – kiman

+0

@kirman: de nada, pero no confunda código con datos. Considero que Melody es una colección de datos. Los enumerados se usan mejor para bloques de construcción atómicos o para constantes de estado. –

3

Lo que se tiene en cuenta es implementar un simple DSL interno que sea lo más parecido posible a la notación que utiliza para escribir las notas musicales. en Java que podría parecerse a

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

hay un good book on DSLs, written by Martin Fowler

Esto es sólo un ejemplo de cómo podría ser similar, no insisto en una forma u otra - la idea básica es que su DSL debe ser legible tanto por los programadores como por los expertos de dominio (músicos). Y la forma en que se representan los datos en su secuenciador no debe afectar la forma en que escribe las melodías, por ejemplo, para hacerlo más difícil.

P.S. Si está haciendo algo serio, intente utilizar una DSL externa a.k.a. un archivo midi o algo así y una biblioteca de secuencias de algún tipo, y almacene música en los archivos de recursos separados de su aplicación.

3

Quizás pueda usar el Fluent interface pattern en combinación con el Builder pattern. A continuación, podría escribir:

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

NotePhraseBuilder contiene métodos como beat(), measure(), E_5(), pero no hay métodos como 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; 
    } 

    ... 
} 

se pueden incorporar los consejos de Hovercraft Full Of Eels dentro de este patrón.

Nota: Los métodos NotePhraseBuilder.E_5() y otros como él, llaman la -constructor NoteBuilder con un EqualTemperamentTuning igual a su nombre. Escribir 88 métodos parece un desperdicio, ¿se puede hacer esto más hermoso?

+0

Similar al enfoque de [Boris Treukhov] (http://stackoverflow.com/users/241986/boris-treukhov). –

+0

ambas respuestas se publicaron prácticamente al mismo tiempo :-) –

+0

Y ambas mejores respuestas que la mía. 1+ a ambos. –

3

La notación musical se puede considerar como una serie de eventos, ya sean notas o cambios de tecla y tempo.

Puede ser más útil para usted pensar en una línea de tiempo con eventos y construir desde allí.El protocolo MIDI puede darte más ideas.

+0

Sí, una línea de tiempo tiene sentido. Creo que aún no me he dado cuenta de lo que realmente necesitaré de esta implementación de audio que estoy realizando, ¡lo que hace que sea difícil diseñarla! MIDI parece complicado, pero creo que es mejor que lo comprenda, ya que es muy relevante para lo que estoy haciendo ... – kiman

+1

MIDI es bastante simple de corazón: para una melodía, tiene nota y nota de los eventos que tienen una valor de tono y una posición en la línea de tiempo. Tocar la melodía es un proceso de enviar los eventos a una fuente de sonido en el momento correcto. Es posible que también desee ver los rastreadores de mods. – blank

Cuestiones relacionadas