Skip to content
On this page

Versuch 6: Synthesizer

In diesem Versuch werden wir einen Audiosynthesizer erstellen.

Screenshot

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

  1. Erstellen Sie eine Klasse Model.java.
  2. Erstellen Sie eine Attribut der Klasse AudioPlayerEngine und instanzieren Sie dieses im Konstruktor (audioPlayer).
  3. Vergessen Sie nicht, den Konstruktoraufruf in einen try ... catch Block zu wrappen.
  4. Instanzieren Sie Model in App.java gleich nach dem Aufruf der main Methode.

Audio Testen

Um zu sehen, ob Audio ausgegeben werden kann, erstellen Sie eine Methode testAudio um einen Sinus bei 500 Hz auszugeben.

  1. Hierzu erstellen Sie ein double Array der länge 4410 (100ms).
  2. Generieren Sie nun die 4410 Werte via Sinus im Array mit der Frequenz 500Hz. Die Werte im Array sollen zwischen -1 und 1 sein.
  3. Rufen Sie audioPlayer.play() auf und übergeben Sie das double Array mit dem Sinus.
  4. Rufen Sie Ihre testAudio Methode im Konstruktor von Model auf.
  5. Ihr Sinus läuft nun bis die App beendet wird! Es wäre schöner, würde der Sinus nach einer Sekunde wieder stoppen.
  6. Nach dem Aufruf von play können Sie mit Thread.sleep eine Sekunde lang warten und via audioPlayer.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.

Beispiel Sinus

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 Sie synthesize 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 in Model und generieren Sie ein double Array analog wie in der ersten Testmethode testAudio. Verwenden Sie allerdings Sinusform um das Array zu füllen anstelle eines direkten Aufrufs von Math.sin(). Verwenden Sie frequency als Frequenz für die Waveform. Beachten Sie, dass anhand von frequency auch die länge des Arrays ändert.
  • Rufen Sie testWaveform im Konstruktor anstelle von testAudio 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

uml

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

button

Ü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.