Kolophon

Ein Script zur Berechnung von Ober­ton­spektren

Kapitelinhalt: [  Überspringen ]

Das folgende Kapitel beschreibt ein Script, mit dem die Verzerrungen eines Sinus­signals an einer gegebenen statischen Kenn­linie untersucht werden können.  Das heißt, Sinus­signale mit wachsendem Pegel werden an einer gegebenen Kenn­linie verzerrt und dann die Ober­ton­verteilung im ver­zerr­ten Signal in Abhängigkeit vom Pegel errechnet und in einem Diagramm dar­gestellt. 

Der Artikel dient dabei nicht dazu, dass Script selbst zu verteilen (im Sinne von Open Source) – dafür ist das Script viel zu sehr „Heim­werker­pro­gramm­ierung“ und auch zu konkret auf eine Anwendung beschränkt – sondern nur, die Ergebnisse dieses Scriptes für den Leser nachvollziehbar zu machen. 

nach oben

Mathematische Hintergründe

Kapitelinhalt: [  Überspringen ]

Das Script hat die Auf­gabe, eine Zahlen­folge, die ein sinus­förmiges Signal einer Perioden­länge und verschiener Amplitude darstellt, an einer eben­falls durch Werte­paare repräsentierten statischen und zeit­in­varianten Kenn­linie zu „ver­zerren„ und die das verzerrte Signal darstellende Zahlen­folge mit Hilfe einer numerisch nachgebildeten Fourier­trans­formation auf seinen Klirr­faktor und die Zusammen­setzung der entstandenen Ober­töne zu unter­suchen.  Dazu im Folgenden zunächst die mathematischen Hinter­gründe. 

Die Fourier-Transformation

Begonnen wird mit der Definition eines periodischen Signals entsprechend der Fourier­trans­formation (siehe [ Goehler ]) – ein periodisches Signal lässt sich als Summe der Sinus- und Cosinus­funktionen seiner Grund­frequenz und der der n-fachen Frequenzen definieren: 

\begin{eqnarray} f(x)& = & \frac{a_0}{2} + \sum_{n=1}^{\infty} {\big [} a_n\cos (n x)+ b_n\sin (n x) {\big ]} \textrm{d} x \tag{1}\end{eqnarray}

Die erstgenannte Gleichung 1 muss nun noch „auf Elektrotechnik umgebaut“ werden (wobei die Spannung U1 aus der einheitenlosen Gleichung 1 eine einheitenbehaftete Gleichung macht – etwas wie U1 = 1 V würde dem Genüge tun): 

\begin{eqnarray} u_A(t) & = & U_{1}\cdot{} \left( \frac{a_0}{2} + \sum_{n=1}^{\infty} {\big [} b_n\cdot{}\sin (n\cdot{}ωt) + a_n\cdot{}\cos (n\cdot{}ωt) {\big ]} \textrm{d} x \right) \tag{2}\end{eqnarray}

Die Koeffizienten für Gleich­anteil, den Grund­ton wie die einzelnen Ober­töne lassen sich (im Zeitbereich) durch die Multiplikation des Signals mit dem entsprechenden Sinus- oder Cosinus­signals und die anschließende Integration über eine Perioden­dauer ermitteln: 

\begin{eqnarray} a_0 & = & \frac{1}{U_1} \cdot{} \frac{2}{ω} \int_{-π}^{π} {\big [} u_E(t) \cdot{} \cos (0\cdot{} ωt) {\big ]} \textrm{d} t \\~\\ & = & \frac{1}{U_1} \cdot{} \frac{2}{ω} \int_{-π}^{π} u_E(t)\textrm{d} t \\~\\ b_0 & = & \frac{1}{U_1} \cdot{} \frac{2}{ω} \int_{-π}^{π} {\big [} u_E(t) \cdot{} \sin (0\cdot{} ωt) {\big ]} \textrm{d} t \\~\\ & = & 0 \\~\\ a_n & = & \frac{1}{U_1} \cdot{} \frac{2}{ω} \int_{-π}^{π} {\big [} u_E(t) \cdot{} \cos (n\cdot{} ωt) {\big ]} \textrm{d} t \\~\\ b_n & = & \frac{1}{U_1} \cdot{} \frac{2}{ω} \int_{-π}^{π} {\big [} u_E(t) \cdot{} \sin (n\cdot{} ωt) {\big ]} \textrm{d} t \tag{3}\end{eqnarray}

nach oben

Die numerische Umsetzung

Obige Gleichung 3 muss jetzt für eine numerische Rechnung um­gestellt werden – aus dem Integral über eine Perioden­dauer des Grund­tones wird jetzt wieder eine Summe für 360 Werte (statt von null bis 2π wird der Ein­fach­heit halber von 0° bis 360° gerechnet), wobei die Werte für 0° und 360° halb gewichtet werden müssen: 

\begin{eqnarray} a_n & = & \frac{1}{U_1} \cdot{} \frac{1}{180} \cdot{} {\Big [} \frac{1}{2}\cdot{} u_A[0] \cdot{} \cos(n\cdot{}0) + \\~\\&& \sum_{i=1}^{360} u_A[i]\cdot{} \cos {\Big (} \frac{2π} {360} \cdot{}n\cdot{}i {\Big )} + \\~\\&& \frac{1}{2}\cdot{} u_A[360] \cdot{} \cos {\Big (} \frac{2π} {360} \cdot{}n\cdot{}360 {\Big )} {\Big ]} \tag{4}\end{eqnarray}

Da davon auszugehen ist, dass sowohl beim Eingangs­signal als auch bei den Sinus­signalen der Wert für 0° gleich dem für 360° ist, können die beiden halben Rand­werte gleichgesetzt und deren Summe in die „große Summe“ genommen werden: 

\begin{eqnarray} u_A[0] & = & u_A[360] \\~\\ \cos(0) & = & \cos {\Big (} \frac{2π} {360} \cdot{}n\cdot{}360 {\Big )} = \cos(n\cdot{}2π) = 1 \\~\\ a_n & = & \frac{1}{U_1} \cdot{} \frac{1}{180} \cdot{} {\Big [} \frac{1}{2}\cdot{} u_A[0] \cdot{} \cos(0) + \\~\\&& \sum_{i=1}^{360} u_A[i]\cdot{} \cos {\Big (} \frac{2π} {360} \cdot{}n\cdot{}i {\Big )} + \\~\\&& \frac{1}{2}\cdot{} u_A[360] \cdot{} \cos {\Big (} \frac{2π} {360} \cdot{}n\cdot{}i {\Big )} {\Big ]} \\~\\ & = & \frac{1}{U_1} \cdot{} \frac{1}{180} \cdot{} \sum_{i=0}^{360} {\Big [} u_A[i]\cdot{} \cos {\Big (} \frac{π} {180} \cdot{}n\cdot{}i {\Big )} {\Big ]} \tag{5}\end{eqnarray}

Das Gleiche noch einmal für die Sinus-Parameter: 

\begin{eqnarray} u_A[0] & = & u_A[360] \\~\\ \sin(0) & = & \sin {\Big (} \frac{2π} {360} \cdot{}n\cdot{}360 {\Big )} = \sin(n\cdot{}2π) = 0 \\~\\ b_n & = & \frac{1}{U_1} \cdot{} \frac{1}{180} \cdot{} {\Big [} \frac{1}{2}\cdot{} u_A[0] \cdot{} \sin(0) + \\~\\&& \sum_{i=1}^{360} u_A[i]\cdot{} \sin {\Big (} \frac{2π} {360} \cdot{}n\cdot{}i {\Big )} + \\~\\&& \frac{1}{2}\cdot{} u_A[360] \cdot{} \sin {\Big (} \frac{2π} {360} \cdot{}n\cdot{}i {\Big )} {\Big ]} \~\\ & = & \frac{1}{U_1} \cdot{} \frac{1}{180} \cdot{} \sum_{i=0}^{360} {\Big [} u_A[i]\cdot{} \sin {\Big (} \frac{π} {180} \cdot{}n\cdot{}i {\Big )} {\Big ]} \tag{6}\end{eqnarray}

Mit diesen Gleichungen gerechnet würde man für den Grund­ton wie für jede Harmonische zwei Koeffizienten erhalten, wobei sich in der Verteilung auf bn und an die jeweilige Phasenlage von Grund­ton und Harmonischen wiederspiegelt.  Für die Ermittlung des Betrages der Signal­anteile muss jeweils die Wurzel der Quadrate­summe beider Koeffizienten ermittelt werden. 

Die (aus­steuerungs­un­ab­hängigen) Koeffizienten für die einzelnen Obertöne wiederum ergeben sich aus dem Verhältnis der Leistung eines Ober­tones zur Leistung des Gesamt­signals; d. h. auch hier muss quadratisch addiert werden. 

\begin{eqnarray} a_{k,n} & = & \frac{\sqrt{a_n^2 + b_n^2 } } {\sqrt{ a_1^2 + b_1^2 + a_2^2 + b_2^2 + a_3^2 + b_3^2 + … + a_m^2 + b_m^2 } } \\~\\ & = & \sqrt{ \frac{a_n^2 + b_n^2 } { \sum_{l=1}^m { \left( a_l^2 + b_l^2 \right) } } } \tag{7}\end{eqnarray}

Jetzt können Gleichung 5 und Gleichung 6 eingesetzt werden.  Dadurch, das an und bn in Zähler und Nenner in gleicher Potenz (Wurzel aus der Quadrate­summe) vorliegen, entfallen sämtliche linearen Faktoren vor den Integralen bzw. Summen – d. h. die Teiler 1 / 180 und 1 /U1 – und Gleichung 7 lässt sich zu etwas monströs er­schein­en­dem, leicht iterierbarem zusammenfassen 

\begin{equation} a_{k,n} = \sqrt{ \left( \frac{1}{180} \cdot{} \frac{1}{U_1} \right)^2 } \cdot{} \sqrt{ \left( 180 \cdot{} U_1 \right)^2 } \cdot{} \\~\\ \sqrt{ \frac{ \left( \sum_{i=0}^{360} {\Big [} u_A[i]\cdot{} \!\cos \!{\Big (} \frac{π\cdot n\cdot i} {180} {\Big )} {\Big ]} \right)^{\!2} + \left( \sum_{i=0}^{360} {\Big [} u_A[i]\cdot{} \!\sin \!{\Big (} \frac{π\cdot n\cdot i } {180} {\Big )} {\Big ]} \right)^{\!2} } { \sum_{l=1}^{m} {\Big \{} \left( \sum_{i=0}^{360} {\Big [} u_A[i]\cdot{} \!\cos \!{\Big (} \frac{π\cdot l\cdot i } {180} {\Big )} {\Big ]} \right)^{\!2} + \left( \sum_{i=0}^{360} {\Big [} u_A[i]\cdot{} \!\sin \!{\Big (} \frac{π\cdot l\cdot i } {180} {\Big )} {\Big ]} \right)^{\!2} {\Big \}} } } \tag{8}\end{equation}

wobei m im Script über die Programm­variable maxOvertones = 15 gesetzt wurde. 

Vergleichbares gilt für den Klirrfaktor: 

\begin{equation} k = \\~\\ \sqrt{ \frac{ \sum_{l=2}^{m} {\Big \{} \left( \sum_{i=0}^{360} {\Big [} u_A[i]\cdot{} \!\cos \!{\Big (} \frac{π\cdot l\cdot i} {180} {\Big )} {\Big ]} \right)^{\!2} + \left( \sum_{i=0}^{360} {\Big [} u_A[i]\cdot{} \!\sin \!{\Big (} \frac{π\cdot l\cdot i } {180} {\Big )} {\Big ]} \right)^{\!2} {\Big \}} } { \sum_{l=1}^{m} {\Big \{} \left( \sum_{i=0}^{360} {\Big [} u_A[i]\cdot{} \!\cos \!{\Big (} \frac{π\cdot l\cdot i } {180} {\Big )} {\Big ]} \right)^{\!2} + \left( \sum_{i=0}^{360} {\Big [} u_A[i]\cdot{} \!\sin \!{\Big (} \frac{π\cdot l\cdot i } {180} {\Big )} {\Big ]} \right)^{\!2} {\Big \}} } } \tag{9}\end{equation}

Diesen Formeln beschreiben das, was am Ende umgesetzt werden muss, im Script wird jedoch wieder zurück in Einzel­schritte zerlegt.  Soll heißen, bei der numerischen Umsetzung obiger Gleichungen wird natürlich rationell gearbeitet – zum einen werden alle Werte für alle Sinus- und Cosinus­folgen sin(π ⋅ n ⋅ i / 360) und cos(π ⋅ n ⋅ i / 360) genau einmal „vorgekocht“, zum anderen werden auch die Koeffizienten an und bn für jedes n einmal unskaliert berechnet und dann für die Berechnung jedes Werts von ak,n und k wieder­verwendet. 

Außerdem entfällt – wie auch in Gleichung 8 und Gleichung 9 bereits sichtbar – die Skalierung der Koeffizienten an und bn über die Division durch U1 wie auch durch den Wert 180, da die einzelnen Koeffizienten ohnehin durcheinander dividiert werden. 

nach oben

Kenn­linien­approximation

Last but not least die Ermittlung der Ausgangs­werte­folge der Verzerrung an der Kennlinie durch lineare Approximation – die Kenn­linie, an der das Eingangs­signal „verzerrt“ wird, muss als Array von Wertepaaren [x[i], y[i]] vorliegen (wobei die x-Werte das Eingangs­signal und die y-Werte für das zugehörige Ausgangs­signal repräsentieren). 

Mit jedem Augen­blicks­wert des Eingangs­signals uE[i] wird nun das Kenn­linien­array durchsucht, bis sich ein x[j] ≤ uE[i] und x[j+1] > uE[i] findet – damit wird der zugehörige Augen­blicks­wert des Ausgangs­signals approximiert: 

\begin{equation} \textrm{Wenn } u_E ≥ x[j] \textrm{ und } u_E[i] ≤ x[j+1] \textrm{ :} \\~\\ u_A = y[j] + \frac{u_E - x[j]} {x[j+1] - x[j]} \cdot{} {\Large (} y[j+1] - y[j] {\Large )} \tag{10}\end{equation}

Obige Gleichung 10 setzt natürlich voraus, dass der unter x[j+1] hinter­legte Wert immer größer oder zumindest gleich dem unter x[j] hinter­legten Wert ist, das heißt, dass die Kenn­linie „von links nacht rechts“ notiert bzw. sortiert ist. 

nach oben

Das Script kennl_k2k3.py

Kapitelinhalt: [  Überspringen ]

Nach der Dar­stellung der mathe­matischen Hinter­gründe ein Über­blick über die Um­setzung in Form des Python-Scripts kennl_k2k3.py

Signaldarstellung

Zunächst eine grund­sätzliche An­merkung zur Darstellung von Signalen – Signale mit der Dauer einer Phasen­länge des Grund­tones wie beispiels­weise das Eingangs­signal werden im Zeitbereich numerisch in einer Werte­liste von 361 Werten (null bis ein­schließ­lich 360 Grad – die in Gleichung 5 und Gleichung 6 an­ge­deutete Ver­ein­fachung wurde im Script nicht mehr um­gesetzt) dargestellt.  Im Falle des Eingangs­signals handelt es sich um eine Liste der Werte einer Sinus­funktion von null bis 360 Grad. 

Diese das Eingangs­signal repräsentierende Liste kann jetzt zunächst skaliert (Parameter gain) und um einen absoluten Betrag verschoben (Parameter offset) werden, um dann als Argument für die Verzerrung durch eine mathematische Funktion (z. B. der Tangens Hyperbolicus für die Eingangsstufe eines OTA) oder für die Verformung an einer Kenn­linie zu dienen. 

Diese Signalliste wird für jede Einstellung des Parameters Gain (des Eingangs­signal­pegels) erstellt und verzerrt / verformt; Ergebnis ist ein Liste von Signallisten – die Ausgangs­signale der Verzerrung / Verformung bei verschiedenen Gain-Stufen. 

Um die Koeffizienten der verschiedenen Obertöne zu ermitteln, wird zunächst einmal ein Liste von jeweils zwei Signallisten bzw. einem Listen­paar erstellt – d. h. für den Grundton und jede Harmonische je eine Signal­liste mit dem Sinus und mit dem Cosinus der entsprechenden Frequenz. 

In der Funktion getOvertoneValue, die eine Fourier­transformation nachbildet, wird nun das „verzerrte Signal“ (bzw. dessen Signal­liste) jedes Pegels elementeweise mit den Sinus- und Cosinus­listen des Grund­tons und jedes Ober­tons multipliziert, die Produkte dieser elementeweisen Multi­plikation getrennt für Sinus und Cosinus aufsummiert, die Summen (für Sinus und Cosinus für jeden Grund- und Oberton für jedes Gain) einzeln quadriert und anschließend der werte für Sinus und Cosinus zu einem Koeffizienten addiert. 

Der in dieser Funktion ermittelte Koeffizient für dieses Gain und diesen Oberton wird durch die Summe aller Koeffizienten für dieses Gain und für alle Obertöne einschließlich des Grundtons dividiert – die Wurzel dieses Quotienten bildet dann den Parameter ak,n – die Wurzel als dem Quotienten der Summe der Koeffizienten aller Obertöne (ohne Grundton) dividiert durch der Summe der Koeffizienten aller Obertöne (einschließlich des Grundtons) wiederum bildet den Klirrfaktor.  (Siehe dazu auch obige Gleichung 8 und Gleichung 9). 

Beim Summieren der Elementeprodukte wird das erste und das letzte Produkt jeweils halbiert – die in Gleichung 5 und Gleichung a6a6a6 an­ge­deuteten Ver­ein­fachung wurden, wie gesagt, im Script nicht mehr um­gesetzt. 

Voreinstellungen

  • minGain = -40: Geringster Eingangs­signal­pegel [dBV]. 

  • maxGain = 20: Höchster Eingangs­signal­pegel [dBV]. 

  • gainStepWdth = 1: Abstand der Gain­werte, für die die Obertöne berechnet werden. 

  • gain = 0: Eingangs­signal­pegel. 

  • nbrs = 360: Zahl der Mess­werte. 

  • offset = 0: Gleichspannungsoffset für die Sinusfunktion. 

  • maxOvertones = 15: Höchster zu berechneter Oberton. 

Globale Variablen

  • sinuscosinus = []: Zwei Signallisten (sin(x), cos(x)) des Grund­tones oder einer Oberwelle. 

  • fourierMtrx = []: Liste von Signallisten-Paare der verschiedenen Obertöne. 

  • out = []: Signalliste der Werte eines Signals nach der Verzerrung. 

  • outs = []: Array der Signallisten der Signale jeden Pegels nach der Verzerrung. 

  • OvertoneValues = []: Liste der Pegel einer Oberwelle bei den verschiedenen Gain­werten. 

  • ListOfOvertones = []: Liste der Ausgangs­werte-Listen aller Obertöne. 

nach oben

Dateiformate

Um das Script für mehrere Kenn­linien einsetzen zu können, musste zumindest für diese Kenn­linien selbst eine Art Datei­format für eine JSON-Datei festgelegt werden – die Kenn­linien­datei: 

Kenn­linien­datei

Die Kenn­linien werden als Sammlung von Kenn­linienpunkten in eine JSON-Datei eingetragen: Neben der eigentlichen, im Array punkte eingetragenen Kenn­linie enthält die Datei weitere Informationen:

  • name:  Name der Kenn­linie, z.B. für Name der Diagramm-Bilddatei
  • title:  Diagrammtitel
  • xtitle und ytitle:  Achsentitle für das Diagramm
  • xmin, ymin, xmax und ymax:  Minimal- und Maximal­werte der Kenn­linie
  • xAP und yAP:  Lage des Arbeitspunktes auf der Kenn­linie
  • gain:  Skalierung für das zu verzerrende Eingangs­signal, im Standardfall gleich eins (wichtig ist das Vorzeichen)
  • punkte:  Liste von Kenn­linien­punkten – d. h. Liste von Werte­paaren mit Ein- und Ausgangs­wert; d. h. Liste von zwei-Elemente-Listen.  Eine entsprechende JSON-kompatible Folge von Werte­paaren kann in EXCEL mit der VERKETTEN-Funktion wie folgt erstellt werden: 
    VERKETTEN(" , [ ";<x-Wert>" , ";<y-Wert>;" ]")
    Also z. B:
    VERKETTEN(" [ [ ";B1;" , ";C1;" ]")

    VERKETTEN(" , [ ";B5;" , ";C5;" ]")
    VERKETTEN(" , [ ";B6;" , ";C6;" ]")
    VERKETTEN(" , [ ";B7;" , ";C7;" ]")

    (Am Ende die erste Klammer wieder schließen.
  • plotlist:  Liste der Graphen für die verschiedenen Harmonischen; Liste hat folgende Parameter: 
    • harmon:  Nummer der Harmonischen
    • annotate:  Label, das an den Graphen geschrieben werden soll
    • color:  Farbe des Graphen
    • linestyle:  Linienart des Graphen
    • linewidth:  Linienbreite des Graphen
    • xy:  Position des Labels, das an den Graphen geschrieben werden soll – muss (leider) im fertigen Diagramm händisch gesetzt werden. 

Literaturhinweise

[ Goehler ]

Goehler, Wilhelm: Formelsammlung höhere Mathematik / zsgst. von Wilhelm Goehler, Bearb. von Barbara Ralle, 14., überarb. Auflage; Verlag Harri Deutsch, Thun und Frankfurt am Main, 1999; S. 50