Implementieren Sie alle Aufgaben in den dafür vorgesehenen Dateien im Ordner "impl".
- Bitte lesen Sie die Angabe komplett durch, bevor Sie mit der Umsetzung beginnen.
- Halten Sie sich genau an die Angabe und geben Sie nur die geforderten Daten aus.
- Haben Sie Schwierigkeiten bei der Umsetzung einer der Teilaufgaben, versuchen Sie andere Teile zuerst umzusetzen und wenden Sie sich zuletzt nochmal der ungelösten Teilaufgabe zu. Sie erhalten auch Punkte für nicht vollständig implementierte Teilaufgaben
Der MelodySequencer
erzeugt Melodien, die aus mehreren Noten bestehen. Melodien können transponiert werden oder auch ein Ausschnitt der Melodie kopiert werden. Mehrere Melodien können in einem Songbook gesammelt werden.
Aufgabenstellung
Im Rahmen dieser Aufgabe entwicklen Sie einen einfachen MelodySequencer
. Kleinster Bestandteil sind Noten (repräsentiert durch die Klasse Note
). Jede Note besitzt eine bestimmte Länge und eine bestimmte Tonhöhe. Insgesamt gibt es sieben verschiedene Tonhöhen, die nach den sogenannten Solimisationssilben benannt sind: do - re - mi - fa - sol - la - si. Mehrere Noten zusammengesetzt ergeben eine Melodie (Klasse Melody
).
Noten und gesamte Melodien können transponiert werden: Transponieren bezeichnet den Prozess, in dem Noten in ihrer Tonhöhe um einen angegebenen Wert erhöht oder verringert werden. Außerdem kann aus einer Melodie ein Teil von aufeinandefolgenden Noten kopiert werden, welcher infolge selbst zu einer neuen Melodie wird. Um mehrere Melodien zu speichern, entwicklen Sie ein Notenbuch (Klasse SongBook
), das mehrere durch Titel gekennzeichnete Melodien enthält.
Nicht-funktionale Anforderungen
Nutzen Sie wenn möglich immer bestehenden Code und vermeiden Sie so doppelte Codestücke. Sie können bei Bedarf auch zusätzliche Methoden einführen. Dies sollten Sie jedoch nur dann machen, wenn es notwendig ist oder im Rahmen von Codewiederverwendung Sinn macht. Achten Sie bei Ihren Klassen auf korrekte Datenkapselung, also insbesondere auf die richtigen (Sichtbarkeits-)Modifikatoren bei Methoden und Datenfeldern.
Vorgehensweise
Implementierungsreihenfolge
Folgende Vorgehensweise ist bei der Umsetzung der Aufgabe empfehlenswert:
- Beginnen Sie mit der Implementierung der Klasse
Note
. Hierbei sind besonders der Konstruktor mit zwei Parametern sowie diegetBeats()
und dietoString()
-Methode relevant. Nutzen Sie den vorgefertigten Testfall 1 um diese grundlegende Funktionalität zu testen. - Beginnen Sie im nächsten Schritt die Klasse
Melody
zu implementieren. Hierbei sollten Sie wiederum mit dem Konstruktor und dertoString()
-Methode beginnen. Weiters sollten Sie dieaddNote()
-Methode implementieren. Der Testfall 2 überprüft genau diese Funktionalität.
Haben Sie die Schritte 1 und 2 umgesetzt, können Sie nun die weiteren Teilaufgaben umsetzen. Kompilieren Sie früh und oft und versuchen Sie Fehler sofort zu beheben. Testen Sie nach jedem Kompilieren Ihre Implementierung. Sie finden zu jeder Teilaufgabe einen entsprechenden Testfall. Fügen Sie eigenen Testcode in der testing
-Methode (Klasse MelodySequencer
) ein um so weitere Fälle (die vielleicht nicht von den bestehenden Testfällen abgedeckt werden) zu überprüfen. Ihre kommentierten Testfälle in dieser Methode sind Teil der Beurteilung.
Testen
Es ist im Rahmen des Tests nicht erforderlich selbst Eingaben vom User zu verarbeiten. Erzeugen Sie statt dessen zu Testzwecken in der Methode testing()
Objekte der von Ihnen implementierten Klassen und rufen Sie die verschiedenen Methoden auf. Zur Unterstützung finden Sie bereits fertige Testfälle in der Klasse MelodySequencer
Die von Ihnen zu lösenden Aufgaben umfassen die Implementierung der drei unten spezifizierten Klassen, dem Umsetzen und Kommentieren eines eigenen Testfalls und der Beantwortung der Theoriefragen.
Klassen und Methoden
MelodySequencer
-
Diese Klasse ist ausführbar und beinhaltet daher die
main
-Methode.public static void testing()
-
Testen Sie in dieser Methode die Implementierung Ihres Programmes durch Objekt-Instanzierungen und Methodenaufrufe. Erstellen Sie zuletzt einen sinnvollen Testfall und beschreiben Sie in einem Kommentar, wieso Sie diesen Testfall gewählt haben und was Sie damit überprüfen. Geben Sie Ausgaben (Rückgaben von Methoden, etc) hier auf
System.out
aus.
Die weiteren Methoden in der Klasse
MelodySequencer
sollen nicht verändert werden. Diese Methoden dienen dem Ausführen der vorgefertigten Testfälle und werden nicht beurteilt. Nutzen Sie diese Testfälle um Fehler in Ihrem Programm zu entdecken und auch als Anregungen für eigenen Testfälle. Weitere Informationen zum Testen finden Sie unter dem Punkt Vorgehensweise.
Die folgenden Klassen und Methoden sind vollständig zu implementieren. Falls nicht anders angegeben, können Sie davon ausgehen, dass die den Methoden bei einem Aufruf übergebenen Werte gültig sind (daher beispielsweise keine IndexOutOfBoundsException
verursachen). Sie müssen die Gültigkeit daher nicht überprüfen.
Note
-
Diese Klasse repräsentiert eine Note. Eine Note ist der kleinste Bestandteil einer Melodie und ist charakterisiert durch Höhe und Länge. Die Notenlänge wird in Schlägen angegeben. Es gibt insgesamt 7 Noten. In aufsteigender Reihenfolge: do - re - mi - fa - sol - la - si. Die Notennamen haben numerische Entsprechungen.
do re mi fa sol la si 0
1
2
3
4
5
6
Speichern Sie die Notennamen in einer geeigneten Datenstruktur und stellen Sie sicher, dass Sie über den numerischen Index auf den Notennamen zugreifen können. Die Namen sollen nicht veränderlich sein und sind außerdem für alle Instanzen der Klasse
Note
gleich: Wählen Sie geeignete Modifier, um diese zwei Forderungen zu erfüllen.public Note(int noteIndex, int beats)
-
erzeugt eine neue Instanz von
Note
.beats
gibt die Länge der Note an. Die Höhe der Note wird als numerischer Wert angegeben (noteIndex
). Speichern Sie den Notennamen nicht alsString
, sondern behalten Sie ihn als numerischen Wert.
public Note(Note note)
-
ist ein Kopierkonstruktor und gibt eine Kopie der übergebene Instanz von
Note
zurück.
public int getBeats()
- gibt die Notenlänge in Schlägen zurück.
public void transpose(int steps)
-
transponiert die Note um den angegebenen Wert nach oben oder nach unten. Ist die gespeicherte Note beispielsweise do, so lauten diese nach Aufruf der Methode mit
steps=3
fa. Dabei liegt ein zyklischer Abschluss vor: Die sieben Noten lauten do - re - mi - fa - sol - la - si. Auf die letzte Note si folgt wieder do.
public String toString()
-
gibt den Namen der Note gefolgt von einem Leerzeichen und deren Länge in Schlägen zurück.
do 1
Melody
-
Diese Klasse repräsentiert eine Melodie, die aus mehreren Noten besteht. Die Noten werden in geeigneter Form gespeichert. Verschiedene Methoden ermöglichen das Hinzufügen von Noten, das Kopieren von Notenfolgen und das Transponieren der Melodie. Diese Klasse liest weder direkt von
System.in
ein, noch gibt sie direkt aufSystem.out
aus.public Melody(int bpm)
-
erzeugt eine neue Instanz von
Melody
und speichert das übergebene Tempo (bpm
, Schläge pro Minute).
public void addNote(Note note)
- fügt am Ende der Melodie die übergebene Note hinzu.
public Melody copy(int beginIndex, int endIndex)
-
kopiert eine Folge von Noten und gibt diese als neue Melodie (
Melody
) zurück. Die neue Melodie hat dabei das gleiche Tempo, die kopierten Noten sollen echte Kopien der ursprünglichen Noten sein. Die neue Melodie beinhaltet die Noten zwischen inklusivebeginIndex
und exklusiveendIndex
, hat also die LängeendIndex - beginIndex
. Besteht die gespeicherte Melodie beispielsweise aus den Tönen do - re - mi - fa - sol, so lautet die neue Melodie bei Aufruf der Methode mit den ParameternbeginIndex=1
undendIndex=4
re - mi - fa und hat die Länge 3.
public Melody copy(int beginIndex)
-
verhält sich wie
public Melody copy(int beginIndex, int endIndex)
, erwartet jedoch nur den ParameterbeginIndex
und gibt eine neue Melodie bestehend aus den Noten von inklusivebeginIndex
bis zum Ende der Melodie zurück.
public void setBPM(int bpm)
-
setzt das Tempo der Melodie auf den übergebenen Wert (
bpm
, Schläge pro Minute).
public void transpose(int steps)
-
transponiert alle Noten der Melodie um den gleichen Wert nach oben oder nach unten. Besteht die Melodie beispielsweise aus den Noten do - mi - re, so lauten diese nach Aufruf der Methode mit
steps=3
fa - la - sol. Dabei liegt ein zyklischer Abschluss vor: Die sieben Noten lauten do - re - mi - fa - sol - la - si. Auf die letzte Note si folgt wieder do.
public String toString()
-
gibt die Töne der Melodie in der Form Notenname gefolgt von einem Leerzeichen und der Notenlänge (in Schläge pro Minute) zurück. Zwischen zwei Noten steht jeweils ein Leerzeichen, alle Noten stehen in der gleichen Zeile. In einer neuen Zeile wird die Dauer der Melodie in Sekunden ausgegeben. Ist die gespeicherte Melodie beispielsweise do 1 re 1 mi 2 fa 4, ist die Gesamtdauer 8 Schläge. Ist das Tempo der Melodie 80 Schläge pro Minute, so beträgt die Länge 6 Sekunden.
do 1 re 1 mi 2 fa 4 6.0 seconds
SongBook
-
Diese Klasse repräsentiert ein Notenbuch. Ein Notenbuch enthält mehrere Melodien und speichert diese in entsprechender Form. Sie stellt Methoden zur Verfügung um Melodien hinzuzufügen oder um auf gespeicherte Melodien zuzugreifen. Diese Klasse liest weder direkt von
System.in
ein, noch gibt sie direkt aufSystem.out
aus.public SongBook()
-
erzeugt eine neue Instanz von
SongBook
.
public boolean addMelody(String title, Melody melody)
-
speichert im
SongBook
unter dem angegebenen Titel eine Melodie (Melody
) und gibttrue
zurück. Gibt es jedoch imSongBook
unter diesem Namen bereits eine Melodie, so wird die übergebene Melodie nicht hinzugefügt, die Methode gibtfalse
zurück.
public Melody getMelody(String title)
-
gibt die unter dem angegeben Titel gespeicherte Melodie zurück. Existiert keine Melodie unter diesem Titel, so wird
null
zurückgegeben.
Testfragen
- Zum Speichern der Noten in Melody benötigen Sie eine Datenstruktur. Aus welchen Gründen haben Sie sich für diese und für keine andere entschieden?
- Beschreiben Sie die Funktion eines Kopierkonstruktors und erklären Sie kurz den Einsatz im Kontext des Beispiels.
- weitere Theoriefragen
Testfälle
Um die einzelnen Testfälle aufzurufen, nutzen Sie
-
java MelodySequencer
um dietesting
-Methode aufzurufen -
java MelodySequencer [number]
um dietestCases
-Methode und somit den entsprechenden Testfall aufzurufen
Sie finden die geforderte Ausgaben auch in der Beschreibung der Testfälle in der Klasse MelodySequencer
.
spec.$1
JAVA
Note n1 = new Note(0, 2); System.out.println(n1.getBeats()); System.out.println(n1);
OUTPUT
2 do 2
?
spec.$2
JAVA
Melody m1 = new Melody(120); m1.addNote(new Note(0, 2)); m1.addNote(new Note(3, 4)); m1.addNote(new Note(6, 6)); System.out.println(m1); m1.setBPM(160); System.out.println(m1);
OUTPUT
do 2 fa 4 si 6 6.0 seconds do 2 fa 4 si 6 4.5 seconds
?
spec.$3
JAVA
Note n1 = new Note(1, 4); n1.transpose(2); System.out.println(n1); n1.transpose(-5); System.out.println(n1); Melody m1 = new Melody(120); m1.addNote(new Note(0, 2)); m1.addNote(new Note(3, 4)); m1.addNote(new Note(6, 6)); m1.transpose(2); System.out.println(m1);
OUTPUT
fa 4 la 4 mi 2 la 4 re 6 6.0 seconds
?
spec.$4
JAVA
Note n1 = new Note(3, 4); Note n2 = new Note(n1); System.out.println(n2); Melody m1 = new Melody(60); m1.addNote(new Note(0, 2)); m1.addNote(new Note(3, 4)); m1.addNote(new Note(1, 4)); m1.addNote(new Note(2, 1)); m1.addNote(new Note(5, 1)); Melody m2 = m1.copy(2); System.out.println(m2); m1.transpose(1); System.out.println(m2);
OUTPUT
fa 4 re 4 mi 1 la 1 6.0 seconds re 4 mi 1 la 1 6.0 seconds
?
spec.$5
JAVA
Melody m1 = new Melody(60); m1.addNote(new Note(0, 2)); m1.addNote(new Note(3, 4)); SongBook sb1 = new SongBook(); System.out.println(sb1.addMelody("Testtitel", m1)); System.out.println(sb1.addMelody("Another Song", new Melody(1))); Melody m2 = sb1.getMelody("Testtitel"); System.out.println(m1 == m2); System.out.println(sb1.addMelody("Testtitel", new Melody(2))); Melody m3 = sb1.getMelody("Testtitel"); System.out.println(m1 == m3);
OUTPUT
true true true false true
?