Appearance
Versuch 6: Synthesizer
In diesem Versuch werden wir einen Audiosynthesizer erstellen.
Mit einem Klick auf eine Waveform, wird diese via Audio ausgegeben (periodisch). Die Frequenz lässt sich mit dem Slider einstellen.
Importieren
Clonen und importieren Sie das Respository git@gitlab.fhnw.ch:oop/oop2-versuche/v04-synthesizer.git
.
AudioPlayerEngine
Die AudioPlayerEngine verfügt über eine Methode void play(double[] samples)
und void stop()
um samples im Loop zu spielen und wieder abzubrechen. Samples werden mit der Samplefrequenz 44100Hz ausgegeben. Ein double Array mit 44100 Werten würde also einen Buffer von 1s füllen.
Model Implementieren
- Erstellen Sie eine Klasse
Model.java
. - Erstellen Sie eine Attribut der Klasse
AudioPlayerEngine
und instanzieren Sie dieses im Konstruktor (audioPlayer
). - Vergessen Sie nicht, den Konstruktoraufruf in einen
try ... catch
Block zu wrappen. - Instanzieren Sie Model in
App.java
gleich nach dem Aufruf dermain
Methode.
Audio Testen
Um zu sehen, ob Audio ausgegeben werden kann, erstellen Sie eine Methode testAudio
um einen Sinus bei 500 Hz auszugeben.
- Hierzu erstellen Sie ein double Array der länge 4410 (100ms).
- Generieren Sie nun die 4410 Werte via Sinus im Array mit der Frequenz 500Hz. Die Werte im Array sollen zwischen -1 und 1 sein.
- Rufen Sie
audioPlayer.play()
auf und übergeben Sie das double Array mit dem Sinus. - Rufen Sie Ihre
testAudio
Methode im Konstruktor von Model auf. - Ihr Sinus läuft nun bis die App beendet wird! Es wäre schöner, würde der Sinus nach einer Sekunde wieder stoppen.
- Nach dem Aufruf von
play
können Sie mitThread.sleep
eine Sekunde lang warten und viaaudioPlayer.stop()
den Ton wieder abbrechen.
Waveforms Interface
Anstelle eines Sinus möchten wir auch andere Waveforms erstellen. Am einfachsten wäre es, diese via "Funktion" im Sinne einer mathematischen Funktion zu erstellen.
Beispiele hierfür: Sinus, Rechteck, Dreieck, Sägezahn, Rampe ...
Hierfür erstellen wir ein Interface, welches ähnlich wie y(x) = ...
einen double
entgegennimmt und einen double
zurückgibt.
Hierbei soll der Wertebereich des Parameters x
von 0 bis 1 gehen und die Funktion Werte zwischen -1 und +1 zurückgeben. So lassen sich relativ einfach verschiedene Waveforms implementieren.
Das Interface benennen wir Waveform
und die Methode benennen wir double synthesize(double x)
.
- Erstellen Sie das Interface in einem neuen Java Package
waveforms
. - Erstellen Sie eine Klasse
Sinusform
und implementieren Siesynthesize
gemäss Skizze. - Instanzieren Sie
Sinusform
in Model. - Erstellen Sie ein Attribut
frequency
in Model und setzen Sie dieses entsprechend. - Erstellen Sie eine neue Methode
testWaveform
inModel
und generieren Sie ein double Array analog wie in der ersten TestmethodetestAudio
. Verwenden Sie allerdingsSinusform
um das Array zu füllen anstelle eines direkten Aufrufs vonMath.sin()
. Verwenden Siefrequency
als Frequenz für die Waveform. Beachten Sie, dass anhand vonfrequency
auch die länge des Arrays ändert. - Rufen Sie
testWaveform
im Konstruktor anstelle vontestAudio
auf.
Mehr Waveforms
Erstellen Sie nun Waveforms für Rechteck, Dreieck, Sägezahn und Rampe, instanzieren und testen Sie diese.
Erstellen Sie ein Array vom Typen Waveform
und initialisieren Sie dieses mit je einer Instanz jeder Waveform.
Initialisieren von Arrays
In Java können Arrayelemente auch direkt "inline" definiert werden.
java
int[] values = new int[] { 1, 2 , 3} // values ist ein Array mit drei Werten
View und Controller
Controller erstellen und instanzieren
Erstellen Sie eine Klasse Controller
und übergeben Sie im Konstruktor das Objekt model
. Instanzieren Sie Controller
in App
direkt nach Model
.
Erstellen Sie in View ein Attribut controller
vom Typen Controller
. Erstellen Sie einen Konstruktor für View, in welchem Sie ein controller
Objekt mitgeben und weisen Sie this.controller = controller
zu. Passen Sie nun den Konstruktoraufruf von View
in App
an und übergeben Sie das controller
Objekt.
Estellen und Implementieren Sie die Methoden gemäss UML. buttonPressed
soll das Abspielen einer Waveform starten und buttonReleased
soll das Spielen wieder abbrechen. Übergeben Sie jeweils den index der Waveform, so dass Model weiss, welche Waveform aus dem Array abgespielt werden soll.
View erstellen
Erstellen Sie nun eine View, welche pro Waveform über einen Button verfügt. Registrieren Sie auf dem Button einen MouseListener
und verwenden für diesen Sie eine Instanz von MouseAdapter
. Überschreiben Sie die Methoden mousePressed
und mouseReleased
und rufen Sie die entsprechenden Methoden des Controllers auf.
Ordnen Sie die Buttons in einem Grid an und fügen Sie einen Slider für die Frequenz hinzu.
Erstellen Sie die update
Methode in View. Rufen Sie diese in Controller auf.
Fügen Sie einen JSlider
für die Frequenz hinzu.
Testen Sie, ob beim Knopfdruck der Ton entsprechend der Waveform bei gwünschter Frequenz ertönt.
Bonus: Waveform Buttons
Übergeben Sie init
von View neu auch model
. Instanzieren Sie folgend für jede waveform
in model
je einen WaveformButton
und übergeben Sie jeweils das waveform
entsprechende Objekt dem Konstruktor von WaveformButton
.
Erstellen Sie die Klasse WaveformButton
, welche von JButton
erbt. Erstellen Sie Waveformpanel
welches von JPanel
erbt. WaveformButton
verwendet GridBagLayout
und soll im Konstruktor genau ein WaveformPanel
hinzufügen.
Überschreiben Sie bei WaveformPanel
die Methode paintComponents
und zeichnen Sie so die jeweilige Waveform
.