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 die getBeats() und die toString()-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 der toString()-Methode beginnen. Weiters sollten Sie die addNote()-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 als String, 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 auf System.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 inklusive beginIndex und exklusive endIndex, hat also die Länge endIndex - 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 Parametern beginIndex=1 und endIndex=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 Parameter beginIndex und gibt eine neue Melodie bestehend aus den Noten von inklusive beginIndex 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 auf System.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 gibt true zurück. Gibt es jedoch im SongBook unter diesem Namen bereits eine Melodie, so wird die übergebene Melodie nicht hinzugefügt, die Methode gibt false 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 die testing-Methode aufzurufen
  • java MelodySequencer [number] um die testCases-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

?

Überprüft die korrekte Basisimplementierung der Klasse Note.

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

?

Überprüft die korrekte Basisimplementierung der Klasse Melody.

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

?

Überprüft die korrekte Umsetzung der Teilaufgabe 'transponieren'.

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

?

Überprüft die korrekte Umsetzung der Teilaufgabe 'kopieren'.

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

?

Überprüft die korrekte Implementierung des Songbooks.