In den letzten Tagen hatte ich an meinem Hobby-Javascript-Funktionsplotter „Zykloidulus“ weiter programmiert, um Liniengrafiken mittels Fourier-Transformation in einzelne Drehbewegungen zu zerlegen. Diese Drehbewegungen sollten dann aufeinander aufaddiert wieder die ursprüngliche Liniengrafik nachzeichnen. Seit heute klappt das:
Ich startete mit einer neuen HTML-Seite, bestehend aus dem Canvas als Zeichenfläche und ein paar Buttons, welche Funktionen aufrufen. Im folgenden Video zeichne ich eine Hand mit der Maus und drücke den fourier-Knopf. Das Ergebnis wird als Epizykloide (schwarz) und sich drehende, aneinandergereihte Vektorlinien (rot) dargestellt. Mit dem subdiv-Knopf füge ich zusätzliche Punkte in der Liniengrafik ein. Daraufhin nähert sich die daraus generierte Epizykle besser an. Das sind die Daten, welche mittels Fourier-Transformation ermittelt wurden:
Spektrum_Hand = [ { freq: -35, amp: 0.0003686, phase: -1.5236009 }, { freq: -34, amp: 0.0012953, phase: -1.1669831 }, { freq: -33, amp: 0.0023193, phase: -1.3474052 }, { freq: -32, amp: 0.0082433, phase: -1.4717065 }, { freq: -31, amp: 0.0295951, phase: 0.9387542 }, { freq: -30, amp: 0.0118085, phase: -0.1819100 }, { freq: -29, amp: 0.0084302, phase: -0.9733754 }, { freq: -28, amp: 0.0107256, phase: -1.6145778 }, { freq: -27, amp: 0.0055092, phase: 0.5508575 }, { freq: -26, amp: 0.0282102, phase: -2.0025910 }, { freq: -25, amp: 0.0140332, phase: -2.1524231 }, { freq: -24, amp: 0.0096098, phase: -1.3614207 }, { freq: -23, amp: 0.0052851, phase: -0.0287800 }, { freq: -22, amp: 0.0102071, phase: -0.8459905 }, { freq: -21, amp: 0.0229444, phase: -0.0303723 }, { freq: -20, amp: 0.0336553, phase: -0.6223523 }, { freq: -19, amp: 0.0193167, phase: 0.1048640 }, { freq: -18, amp: 0.0222801, phase: -1.1807482 }, { freq: -17, amp: 0.0201180, phase: -0.4136481 }, { freq: -16, amp: 0.0151423, phase: -2.1911763 }, { freq: -15, amp: 0.0389249, phase: 1.4865131 }, { freq: -14, amp: 0.0276480, phase: 1.0410499 }, { freq: -13, amp: 0.0580259, phase: 1.2996377 }, { freq: -12, amp: 0.0588539, phase: 1.2406528 }, { freq: -11, amp: 0.0620339, phase: 0.8851068 }, { freq: -10, amp: 0.0204838, phase: -0.1967784 }, { freq: -9, amp: 0.0461501, phase: 1.0957855 }, { freq: -8, amp: 0.0940981, phase: -2.2153818 }, { freq: -7, amp: 0.2368071, phase: -2.2072689 }, { freq: -6, amp: 0.2375195, phase: -2.4316229 }, { freq: -5, amp: 0.5711301, phase: 2.0492726 }, { freq: -4, amp: 0.4427810, phase: -2.1482357 }, { freq: -3, amp: 0.5485490, phase: -2.3804487 }, { freq: -2, amp: 0.8690513, phase: -2.2145402 }, { freq: -1, amp: 1.6186980, phase: -1.9505409 }, { freq: 0, amp: 2.1221538, phase: 1.6137504 }, { freq: 1, amp: 0.1933930, phase: -1.5236009 }, { freq: 2, amp: 0.1692300, phase: -1.1669831 }, { freq: 3, amp: 0.1338133, phase: -1.3474052 }, { freq: 4, amp: 0.2651344, phase: -1.4717065 }, { freq: 5, amp: 0.6021577, phase: 0.9387542 }, { freq: 6, amp: 0.1644724, phase: -0.1819100 }, { freq: 7, amp: 0.0848002, phase: -0.9733754 }, { freq: 8, amp: 0.0809638, phase: -1.6145778 }, { freq: 9, amp: 0.0321103, phase: 0.5508575 }, { freq: 10, amp: 0.1297364, phase: -2.0025910 }, { freq: 11, amp: 0.0517852, phase: -2.1524231 }, { freq: 12, amp: 0.0288296, phase: -1.3614207 }, { freq: 13, amp: 0.0130220, phase: -0.0287800 }, { freq: 14, amp: 0.0208185, phase: -0.8459905 }, { freq: 15, amp: 0.0389687, phase: -0.0303723 }, { freq: 16, amp: 0.0477998, phase: -0.6223523 }, { freq: 17, amp: 0.0230053, phase: 0.1048640 }, { freq: 18, amp: 0.0222801, phase: -1.1807482 }, { freq: 19, amp: 0.0168923, phase: -0.4136481 }, { freq: 20, amp: 0.0106615, phase: -2.1911763 }, { freq: 21, amp: 0.0229186, phase: 1.4865131 }, { freq: 22, amp: 0.0135555, phase: 1.0410499 }, { freq: 23, amp: 0.0235503, phase: 1.2996377 }, { freq: 24, amp: 0.0196179, phase: 1.2406528 }, { freq: 25, amp: 0.0168105, phase: 0.8851068 }, { freq: 26, amp: 0.0044540, phase: -0.1967784 }, { freq: 27, amp: 0.0079181, phase: 1.0957855 }, { freq: 28, amp: 0.0124655, phase: -2.2153818 }, { freq: 29, amp: 0.0235417, phase: -2.2072689 }, { freq: 30, amp: 0.0170531, phase: -2.4316229 }, { freq: 31, amp: 0.0280702, phase: 2.0492726 }, { freq: 32, amp: 0.0137665, phase: -2.1482357 }, { freq: 33, amp: 0.0095076, phase: -2.3804487 }, { freq: 34, amp: 0.0066519, phase: -2.2145402 }, { freq: 35, amp: 0.0030856, phase: -1.9505409 } ]
71 Punkte in der Liniengrafik ergeben auch 71 Einträge in diesem Spektrum. Jeder Eintrag beschreibt einen sich drehenden Vektor mit freq als Drehgeschwindigkeit, amp als Länge und phase als Startpunkt auf dem gedachten Kreis. Die Zeit t läuft von 0 bis 2 π, also genau einmal ’rum. Beispielsweise beschreibt der erste Eintrag einen Vektor, der sich 35fach rechtsherum dreht, 0,0003686 Einheiten lang ist, und bei ‑1.5236009 Radiant, also bei ‑87,3 Grad, mit der Drehung startet.
Wie funktioniert die Fourier-Transformation?
Ich versuche es erst einmal mit eigenen Worten: Gegeben ist eine Menge von anzahl Punkten P[n], also z. B. die Punkte der Liniengrafik »Hand«. Wenn anzahl die Anzahl der Punkte ist, läuft die Frequenz freq in einer Schleife von -anzahl : 2 bis +anzahl : 2 mit Schrittweite 1 durch. Und jetzt kommt die Magie: Man verdreht die Punkte P[n] freq-mal um den Koordinatenursprung.
„Meine“ Youtube-Lehrer haben das schön grafisch dargestellt, besser könnte ich es nicht darstellen (siehe unten).
Verdrehen heißt, der Drehwinkel ist abhängig von der Entfernung des jeweiligen Punktes vom Koordinatenursprung. Der entfernteste Punkt dreht sich z. B. bei freq = 1 genau eine volle Drehung während einer der nur halb so weit vom Koordinatenursprung weg ist sich auch nur eine halbe Drehung macht (erstes Video). Der Drehwinkel ist also freq * 2 * Pi * ( Abstand / Maximal-Abstand ).
Das Coding Train Video (2.) zeigt auf unterhaltsame Weise, wie man anfängt und am Ende mit einer Menge sich drehenden Kreisen seine Lokomotive zeichnet. Die letzten beiden Videos sind mehr unterhaltsamer Natur, zeigen ganz anschaulich, wieso man mit diesem mathematischen Verfahren spielen sollte.
Es ist ein ganz spannendes Thema, denn Fourier-Transformation und das Arbeiten mit Frequenzspektren ermöglicht zum Beispiel auch die Ausgabe als Audiodatei bei gleichzeitigen Herausfiltern von zu hohen oder störenden Frequenzen, was auf einem Oszilloskop die Ecken abrunden und damit das Knacksen minimiert.
Oszilloskopmusik-Pionier Jerobeam Fenderson hat mit seiner Musik Polygone, Animationen und kleine Filme auf einem Oszilloskop erscheinen lassen, wenn es im sogenannten X‑Y-Modus eingestellt ist. Dabei wobei die x‑Koordinate einer Zeichnung als linker und die y‑Koordinate als rechter Stereokanal ausgegeben. Ein sehr inspirierender Ansatz, der zum experimentieren einlädt. Er verwendete zwar dabei nicht die Fourier-Transformation, um die Formen zu glätten aber es ist ein interessanter, synergetischer Ansatz, Musik zu kreieren. Jede Form hat eine eigene Klangfarbe, die Größe ist die Lautstärke und die Tonhöhe ändert die auf dem Oszilloskop gezeichnete Form nicht, nur die Zeichengeschwindigkeit.
Ich empfand es als Herausforderung, einen WAV-Export für mein Zeichenprogramm zu programmieren. Ich wollte wissen, wie das klingt, was ich zeichne. Das Ergebnis stundenlangen Tüftelns war der wav-Button und die entsprechend damit aufgerufene Funktion. Statt x- und y‑Koordinaten am Bildschirm werden die Werte in Daten-Puffer für den linken und rechten Audiokanal ausgegeben. Pro Kanal werden die Maximal- und Minimalwerte ermittelt, daraus ein Skalierungsfaktor errechnet, um alle Werte auf ‑32767 … 32768 zu begrenzen. Dann wird die WAV-Datei daraus erzeugt. Hier ist die Ansicht auf einem Oszilloskop (simuliert mit Oscilloscope! und mit dem Smartphone abgefilmt).
Es ist übrigens gar nicht so einfach, die Maximal- und Minimalwerte von aufeinander-addierten Sinus- und Cosinuswellen zu ermitteln, denn das Ergebnis ist davon abhängig, wieviele Punkte man dabei auf dem Weg der Linie misst. Es kann immer ein höherer Wert zwischen zwei »Sample-Punkten« liegen. Ich habe mir damit geholfen, dass ich den Maximalwert mit 0,9 multipliziert habe, damit die für die WAV errechneten Werte beim verwendeten Datentyp »16-Bit-unsigned-integer« keinen Überlauf erzeugen. Das ergibt hässliche Knackser / Übersteuerung. Zu hören ist nun ein schönes Brummen.
Wie sieht die Formel aus?
Wie am Anfang erwähnt, ging es mir zunächst darum, die mathematische Formel zusammen mit der entstehenden Zeichnung künstlerisch auif einem Bild darzustellen. Allerdings ist die noch sehr lang und unübersichtlich. Für die Hand mit ihren 70 Punkten würde das so aussehen:
Auch wenn ich bereits die Komponente mit der Frequenz 0 weggelassen habe — die verschiebt ja nur die gesamte Zeichnung auf der Ebene umher — sind es noch zwei sehr lange Formeln. Das Ganze braucht also noch ein wenig Kosmetik, damit es verständlich wird und vielleicht eines Tages mal auf ein T‑Shirt passt. Der erste Schritt ist das Rechnen mit komplexe Zahlen statt Vektoren aus X und Y.
Die Koordinate eines Punktes lässt sich statt als Vektor aus zwei Koordinaten (linkes Bild) auch als komplexe Zahl beschreiben (rechts). Vorteil: Die Summenformel aus Kreisbewegungen enthält beide Komponenten: Rechts-Links und Oben-Unten. Letztere werden mit i multipliziert. Merke: Multiplizieren mit i dreht um 90 Grad nach links um den Koordinatenursprung. z( t ) ist die Funktion, welche die Hand in ein Koordinatensystem auf die komplexe Zahlenebene zeichnen würde, wenn t von 0 bis 2 * Pi läuft:
Challenge für die Beispiel-Hand, alles in nur eine Formel zu schreiben, im Prinzip erfüllt. Doch da geht noch was.
Der nächste Schritt ist das Ersetzen von a*(cos(x)+i*sin(x)) durch eine Funktion a*cis(x). So kommen die Dopplungen raus, da man ja den Winkel in cos() und sin() schreiben muss. So kommen die Terme in der Klammer nur einmal vor. Das erspart etwa die Hälfte.
Das ist nun die Formel für eine Funktion, welche diese Beispiel-Hand in ein Koordinatensystem zeichnet. Sieht doch schonmal ganz gut aus, oder?