Suche passende Datenstruktur für 2 Einträge

Moin moin,

nachdem ich endlich rausgefunden hab wie man Töne wiedergeben kann etc. hab ich das nächste Problem. Damit ich nicht total schrottigen Quellcode schreibe brauche ich nun irgendeine Datenstruktur für mein Programm. Ziel ist es einen Ton abzuspeichern (z.B. A) und die dazugehörige Frequenz (zB. 69).

Sollte also ungefähr so aussehen zum schluss:

Code:
0: [code] [64]
1: [D] [65]
2: [E] [66]
3: [F] [67]
4: [G] [68]
5: [A] [69]

usw...

Ziel ist es nämlich am Ende eine Funktion zu besitzen:

Java:
playTone(char note);

die mir anhand des parameters den richtigen Ton spielt (Die FETT gedruckten Parameter wären die Ergebnisse aus der Datenstruktur):

Java:
playTone('C');

würde also den Ton C spielen:

Java:
msg.SetMessage(ShortMessage.NOTE_ON, 0, [B]64[/B], 64);


Hoffe einer kann mir helfen hab echt keine Idee...Hab mir mal Enum angesehen aber glaube das ist nicht das richtige für dieses Problem^^
 
Zuletzt bearbeitet von einem Moderator:
Vielleicht [JAPI]Map[/JAPI] ([JAPI]HashMap[/JAPI].
Ich weiß nicht, in wiefern das hilft,
oder ob ich zu nahe-liegend denke.

AS3
 
Möglich wäre da eine Map. z.B.
Java:
Map<Character, Integer> keys = new HashMap<Character, Integer>();
keys.put('C', 64);
keys.put('D', 65);
keys.put('E', 66);
keys.put('F', 67);
An die Frequenz kommt man dann mit:
Java:
int playTone = keys.get('E');
 
gerne übersehen bei so einfachen Keys ist ein Array,
muss nicht komplett befüllt sein, Ränder egal, einfach Größe 500 oder so,

'C' kann als Zahlenwert direkter Array-Index sein, 67 in diesem Fall,
Array jedenfalls schneller, spart hier gar das Autoboxing,
 
Ich würe es ja als Konstanten oder besser als Enumeration lösen, da die Datenstruktur sich ja nicht ändert.
Java:
public enum Note {
  NOTE_A(64),
  NOTE_B(65);

  private frequenz;

  private getFrequenz() {
    return frequenz
  }

  private Note(int frequenz) {
    this.frequenz = frequenz
  }
}
 
Bei einer so überschaubaren Menge von Einträgen und der angenommenen Unveränderlichkeit, würde ich mich auch TheWhiteShadow's Vorschlag anschließen. Das macht Deinen Quellcode auch viel sprechender. Und der Aufrufer von playTone kann die Methode dann nicht mehr "falsch" aufrufen, was er bei einem char als Parameter könnte (bspw. indem er '!' als Parameter überbibt).

Nur drei kleine Anmerkungen habe ich zu TheWhiteShadow's Code:
- Die Instanzvariable in Zeile 5 muss den Datentyp int haben.
- Sie sollte final sein
- Die Methode getFrequenz muss natürlich public.

Wenn's mehr Einträge werden sollen, dann doch eher Map. Unveränderlichkeit kann man hier mit Collections.unmodifiableMap herstellen.
 
Zuletzt bearbeitet von einem Moderator:
Würde auch eher enums verwenden. Pro Oktave 12 Elemente + 1 * "C" bleiben selbst bei vielen Oktaven noch recht überschaubar.
BTW.: Die Periode sollte final sein. Was wäre falsch daran, sie dann auch noch public zu machen und auf den Getter zu verzichten?
 
[OT]
Spacerat hat gesagt.:
BTW.: Die Periode sollte final sein. Was wäre falsch daran, sie dann auch noch public zu machen und auf den Getter zu verzichten?
Hat für mich etwas mit Kapselung zu tun. Direkter Zugriff auf die Variable heißt, es muss diese Variable für immer und alle Zeit geben oder man nimmt in Kauf, dass Clientcode bei Änderungen nicht mehr kompiliert. Bei Aufruf eines Getters ist es für den Clientcode transparent, woher dieser den int nimmt. In diesem Fall aber vielleicht auch over-engineert, da stimme ich Dir zu.[/OT]
 
Zuletzt bearbeitet von einem Moderator:
[OT]
Hat für mich etwas mit Kapselung zu tun. Direkter Zugriff auf die Variable heißt, es muss diese Variable für immer und alle Zeit geben oder man nimmt in Kauf, dass Clientcode bei Änderungen nicht mehr kompiliert. Bei Aufruf eines Getters ist es für den Clientcode transparent, woher dieser den int nimmt.
Ich schreib' das mal OT...
Genau dabei erschliesst sich mir nicht; Den Methodennamen des Getters könnte man ebenso ändern. Okay, man hätte da noch die @Deprecated-Möglichkeit, aber wäre das der einzige Grund? ???:L[/OT]
 
Im allgemeinen wird die API dadurch zumindest "robuster" gegen Änderungen. Wenn die Frequenz z.B. in Zukunft in kHz gespeichert werden soll, kann man immernoch
return (int)(freq*1000);
schreiben (etwas gestelztes Beispiel, aber von der Idee her...)
 
[OT]
Spacerat hat gesagt.:
aber wäre das der einzige Grund?
Kurze Antwort: Meiner Meinung nach ja. Aber das ist ein sehr wichtiger Grund. Stell Dir mal die nicht ganz unrealistische Aufgabe vor, von Enum-Konstanten mit public Instanzvariablen auf eine Map-basierte Lösung umzuprogrammieren. Wir haben uns aufgrund schlechten API-Designs alle daran gewöhnt, dass Änderungen an einer Stelle immer Änderungen an anderen Stellen nach sich ziehen. Bei einer sauber designeten API müsste das aber nicht sein. Man könnte das an diesem Beispiel hier sehr anschaulich zeigen. Auf der anderen Seite wäre es gerade für dieses Beispiel -wie schon gesagt- aber möglicherweise doch over-engineered.
[/OT]
 
Es geht um MIDI, oder? Und wenn ich das richtig verstanden habe, geht es grob um eine Transformation der Bauart [c](Name, Oktave) → Notennummer[/c], richtig?

Machen wir erst einmal die Gegenrichtung. MIDI note #0 ist C-2, also (C, -2), MIDI note #127 ist G8, also (G, 8). Die Notennamen wiederholen sich mit jeder zwölften Note, also:

Code:
Name(Notennummer) = Notennummer mod 12
Oktave(Notennummer) = Notennummer div 12 - 2
wobei die Namen wie folgt nummeriert sind:
Code:
C  →  0
C# →  1
D  →  2
D# →  3
E  →  4
F  →  5
F# →  6
G  →  7
G# →  8
A  →  9
A# → 10
B  → 11
(Enharmonische Verwechslungen etc. spielen in der Abbildung keine Geige. 😉 Und zumindest in Deutschland würde man wahrscheinlich statt B den Namen H verwenden, das nur am Rande.)

Damit kommen wir dann auch ganz einfach in die von dir gewünschte Richtung, also [c](Name, Oktave) → Notennummer[/c]:
Code:
Notennummer(Name, Oktave) = (Oktave + 2) * 12 + Name
Nehmen wir mal an, du verwendest für die Namen chars, z.B. 'c' für C und 'G' für G#. Dann würde ich die Umrechnung von Name zu Nummer (siehe gerade aufgeführte Tabelle) einfach eine switch-Anweisung verwenden. Für die Gegenrichtung könnte man auch eine switch-Anweisung verwenden, oder aber ein simples Array. Mit Strings wie "A#" würde dies ännlich ablaufen.

EDIT: Soll also heißen: Man braucht weder eine gigantische [c]Map<String, Integer>[/c] noch Enums noch sonst irgendetwas, sondern entweder nur ein paar simple statische Methoden oder eine Klasse, die aber eigentlich nur ein int kapselt, nämlich die Notennummer, und mit der Umgebung kann man dann über entsprechenden Strings oder chars oder wasweißich kommunizieren.

Ich hoffe, das hilft. Wenn etwas unklar ist, einfach nachfragen. 😉

Ark
 
Zuletzt bearbeitet:
😳 Da ist was dran. Bei Midi wird die Periodendauer ja gar nicht benötigt. Und dann kann man anstatt "getPeriod()" auch "ordinal()" des enums verwenden. Dann spart man sich im Prinzip sogar noch die Implementierung einer Klasse, die man mit "new" instanzieren müsste.
 
Zuletzt bearbeitet von einem Moderator:
Hatte gerade Langeweile … EDIT: … und noch geeignete fromString()-Methoden hinzugefügt.
Java:
public final class MIDINotes{

	private MIDINotes(){
		// private
	}

	public static int note(final char name, final int octave){
		final int n;
		switch(name){
			case 'c':
				n = 0;
				break;
			case 'C':
				n = 1;
				break;
			case 'd':
				n = 2;
				break;
			case 'D':
				n = 3;
				break;
			case 'e':
				n = 4;
				break;
			case 'f':
				n = 5;
				break;
			case 'F':
				n = 6;
				break;
			case 'g':
				n = 7;
				break;
			case 'G':
				n = 8;
				break;
			case 'a':
				n = 9;
				break;
			case 'A':
				n = 10;
				break;
			case 'b':
				n = 11;
				break;
			default:
				throw new Error();
		}
		return (octave + 2) * 12 + n;
	}

	public static char name(final int note){
		switch(note % 12){
			case 0:
				return 'c';
			case 1:
				return 'C';
			case 2:
				return 'd';
			case 3:
				return 'D';
			case 4:
				return 'e';
			case 5:
				return 'f';
			case 6:
				return 'F';
			case 7:
				return 'g';
			case 8:
				return 'G';
			case 9:
				return 'a';
			case 10:
				return 'A';
			case 11:
				return 'b';
			default:
				throw new Error();
		}
	}

	public static int octave(final int note){
		return note / 12 - 2;
	}

	public static String toString(final int note, final boolean useSharp){
		final int octave = octave(note);
		final char name = name(note);

		StringBuilder res = new StringBuilder(4);

		if(useSharp){
			if(name >= 'A' && name <= 'Z'){
				res.append(name).append('#');
			}
			else{
				res.append((char)(name + ('A' - 'a')));
			}
		}
		else{
			res.append(name);
		}
		res.append(octave);

		return res.toString();
	}

	public static String toString(final int note){
		return toString(note, true);
	}

	public static int fromString(final String s, final boolean sharpUsed){
		char name = s.charAt(0);
		final int octave;

		if(sharpUsed){
			if(s.charAt(1) == '#'){
				octave = Integer.parseInt(s.substring(2));
			}
			else{
				name -= 'A' - 'a';
				octave = Integer.parseInt(s.substring(1));
			}
		}
		else{
			octave = Integer.parseInt(s.substring(1));
		}

		return note(name, octave);
	}

	public static int fromString(final String s){
		return fromString(s, true);
	}

	public static void main(final String[] args){
		final boolean useSharp = true;
		for(int i = 0; i < 128; i++){
			System.out.print(toString(i, useSharp) + "\t");
			System.out.println(fromString(toString(i, useSharp), useSharp));
		}
	}
}
(Kommentare habe ich mal weggelassen.)

So, und jetzt die Frage für den Handwerker-versus-Künstler-Thread: Macht das so ein Handwerker oder ein Künstler? 😉

Ark
 
Zuletzt bearbeitet:
Hi,

danke erstmal für die ganzen Antworten. Werd mir das erstmal jetzt alles in Ruhe nochmal ansehen^^ Hatte am Wochenende leider keine Zeit.

Werd dann nochmal schreiben sobald eine Lösung steht^^ (Hoffe ich zumindets ansonsten wenn irgendwelche Fehler auftreten 😀 )
 
@ Ark

Zu dem Quelltext. Danke erstmal das du dir überhaupt die Mühe gemacht hast :toll:
Aber bekomme nur eine richtige Ausgabe wenn ich 2 kleine Sachen ändere. Ich muss zum einen den Konstruktor auf public stellen und dann in der Main die Klasse MidiNotes instanziieren, damit ich dann toString und fromString ansprechen kann. Aber dann läufts auch^^ Liegt wahrscheinlich daran das ich die Main in einer seperaten Klasse habe.

Werde noch ein bisschen rumm probieren aber glaube das ist so ziemlich die beste Lösung 🙂
 
Ich muss zum einen den Konstruktor auf public stellen und dann in der Main die Klasse MidiNotes instanziieren, damit ich dann toString und fromString ansprechen kann.
???:L
Du weißt aber schon, dass meine main()-Methode nur zum Test da ist, und dass der Konstruktor absichtlich private ist, weil die Klasse nur eine Art Hilfsklasse (ähnlich Math) ist? Wozu brauchst du da bitte Instanzen? Und die toString()-Methode, die du meinst, ist möglicherweise nicht die, die ich geschrieben habe? Achte mal auf die Signatur, und außerdem sind alle Methoden static!

(Nebenbei ist mir noch aufgefallen, dass man statt [c]new Error()[/c] besser [c]new RuntimeException()[/c] verwenden sollte, und zwar in beiden Fällen.)

Ark
 
Also bei mir funktionierts anders nicht. Ich rufe die KLasse MidiNotes noch ind er Klasse Intervall auf und um dort dann die Methode playNote() aufrufen zu können muss ich es so machen:

Java:
MidiNotes mn = new MidiNotes();

mn.playTone();

Alles andere wirft bei mir einen Fehler ???:L
 
was ist denn 'alles andere' und welche Fehler?
99.99999999999999% aller denkbaren Kombinationen von Quelltext, etwa 'glkhlksjlkvj' werfen natürlich Fehler...

statische Methoden ruft man mit Klassenname.methodenName(Parameter) auf
 
Also bei mir funktionierts anders nicht. Ich rufe die KLasse MidiNotes noch ind er Klasse Intervall auf und um dort dann die Methode playNote() aufrufen zu können muss ich es so machen:

Java:
MidiNotes mn = new MidiNotes();

mn.playTone();

Alles andere wirft bei mir einen Fehler ???:L
Okay, du meinst vielleicht eine andere Klasse MidiNotes (und eben nicht MIDINotes) oder du hast meine Klasse umbenannt. Jedenfalls durchblicke ich das nicht mehr so ganz.

Ark
 

Neue Themen


Zurück
Oben