Portamento berechnen



  • Hallo!

    Ich arbeite schon seit mehreren Monaten an einem Musikprogramm für meinen ESP32. Dieses Programm nutzt den eingebauten DAC, um Musik mit subtraktiver Synthese und Ringmodulation zu erzeugen. Da die Musikdaten nur Noten enthalten, muss ich diese in Frequenzen umrechnen. Das mache ich bisher durch ein Array, das die Frequenzen der tiefsten Oktave enthält, welche dann bitverschoben werden, um die höheren Oktaven zu berechnen. Ich fand, dass das ein guter Kompromiss zwischen einer Tabelle mit allen verfügbaren Frequenzen und einer aufwändigen Berechnung ist.
    Momentan werden die Frequenzen (oder um genauer zu sein: Der Wert, der hinzugefügt wird, um eine bestimmte Anzahl an Überläufen pro Sekunde zu erhalten) beim Einlesen in einer Variable gespeichert. Daher könnte ich theoretisch auch die aufwändige Berechnung ohne Tabelle nutzen.
    Mein Problem ist aber, dass ich Portamento (= einen Ton in einen anderen übergehen lassen) gerne für meine Musik nutzen würde. Das bedeutet, dass ich 50000 * Anzahl der Stimmen (15) Mal pro Sekunde die neue Frequenz berechnen muss. An sich kein riesiges Problem, mit der kleinen Tabelle geht das ja relativ schnell. Aber diese Tabelle kann nur ganze Töne ausspucken, keine Töne zwischen Tönen. Und ich kann auch nicht einfach die Frequenz des Tons darunter und darüber nehmen und mit linearer Interpolation vermischen, denn das würde falsch klingen. Ich könnte auch eine Tabelle mit Multiplikatoren für den tieferen Ton speichern, aber ich bin gegen Tabellen, wenn etwas theoretisch unendlich genau sein soll. Daher müsste ich die Berechnung 2 ^ (note / 12) nutzen. Allerdings verbraucht eine einzige Hoch-Rechnung 20 µs, was natürlich überhaupt nicht geht, das ist das Zeitbudget für alle Stimmen zusammen.
    Daher meine Frage: Gibt es eine andere Möglichkeit, die Töne zu berechnen, ohne maximal 0,5 µs zu verbrauchen?
    Hoffentlich bin ich hier überhaupt im richtigen Forum, die Frage hat ja nur so semi-viel mit C++ zu tun.

    Gruß
    NoobTracker


  • Mod

    Da das eine Frage nach Optimierung einer Berechnung ist. habe ich das Gefühl, dass ich (und sicher auch andere) dir helfen können. Aber du hast deine Frage so formuliert, dass ich keine Ahnung habe, was du überhaupt möchtest. Kannst du deine Frage mit mehr Mathematik und weniger Musik stellen?



  • Okay, ich versuch's.
    Mein aktuelles Programm zur Berechnung der Frequenz (* 10 für eine Nachkommastelle):

    uint32_t IRAM_ATTR getFreq(uint8_t note) {
      uint16_t freq[] = {654, 693, 734, 777, 824, 873, 925, 980, 1038, 1100, 1165, 1234};
      return freq[note % 12] << (note / 12);
    }
    

    Was genau ich machen möchte, ist, z.B. die Note 12.5 zu berechnen. Dafür könnte ich pow(2, (12.5 / 12)) * x rechnen, aber das ist zeitaufwändig. Ich könnte auch eine größere Tabelle nutzen mit z.B. 10tel-Tönen, aber dann könnte es ziemlich komisch kantig klingen, wenn ich einen Ton in den Halbton darüber übergehen lassen will und das über 2 Sekunden strecke. Generell bin ich gegen eine Tabelle.

    Beantwortet das deine Frage(n)?


  • Mod

    @NoobTracker sagte in Portamento berechnen:

    Beantwortet das deine Frage(n)?

    Ja. Wenn jetzt nicht gerade Freitag Abend wäre, bekämst du auch bestimmt eine Antwort 🙂

    Eine Frage hätte ich aber noch: Wieso 50000x pro Sekunde? Wenn das Frequenzen um die 1000 Herz sind, dann würdest du damit ja nach einem Bruchteil einer Schwingung schon die Frequenz wechseln. Kann dein Tongenerator überhaupt so schnell angesteuert werden?



  • Hallo,

    im C++ Standard gibt es noch die spezialisierte Funktion exp2.

    Aber versuche mal die Funktion get_scale aus den Antworten von Fast way to get a close power-of-2 number (floating-point).



  • @NoobTracker
    Hast du schon einen digitalen Filter ausprobiert?

    Je nach Größe des Filters sind die Verzögerungen relativ gering.

    Das Problem ist aber die Kompliziertheit. Mann muss schon wissen was Abtasttheorem, Fourier-, Laplacetransformation, FIR, IIR, lineare Gleichungssysteme, Butterworth Filter, Bandpass, Tiefpass,... sind.

    Im folgenden habe ich dir mal ein Github Projekt gelinkt.

    DSPFilters



  • @SeppJ 50000 Mal pro Sekunde kommen daher, dass das Programm mit 50 kHz arbeitet. Ich habe gleichzeitig bis zu 30 Stimmen (immer zwei im Doppelpack), da kann ich nicht so einfach den Timer verlangsamen. Außerdem ist das schon sinnreich, wenn ich nur 1000 Mal pro Sekunde die Frequenz berechnen würde, dann würde die Frequenz sich erst nach der durchlaufenen Wellenform ändern, das wäre ungenau und dieses Musikprogramm ist darauf ausgelegt, extrem genaue Ringmodulation und sowas verarbeiten zu können.



  • @Quiche-Lorraine Ich weiß nicht, was ein Filter bringen sollte. Ich habe schon einen, solange man auf die Resonanz verzichtet, ist ein Filter ja nur sowas wie filterValue = (newValue + (filterValue * filter)) / (filter + 1). Wenn man dann mit filterValue weiterrechnet, hat man einen Tiefpassfilter, wenn man mit newValue - filterValue rechnet, dann hat man einen Hochpassfilter und durch Kombination einen Bandpassfilter, soweit ich weiß.



  • @Th69 Puh, irgendwie verstehe ich das nicht, weil mein Englisch nicht das Beste ist und ich auch die Programme nicht verstehe.



  • Sorry, das war ein falscher Link von mir.

    Probiere mal fastapprox, s.a. Erklärung in Fast Approximate Logarithm, Exponential, Power, and Inverse Root (ist aber auch leider alles in englisch).
    Die Funktion heißt fastpow2 in fastexp.h.

    Ich habe auch noch Fast pow() With Adjustable Accuracy gefunden, aber das verwendet eine Lookup-Tabelle.

    Diese setzen beide auf Bitmanipulationen der Floating-Point-Repräsentation IEEE 754.
    Geht natürlich ein bißchen auf Kosten der Genauigkeit (accurency), aber das solltest du mal bei dir testen, ob das für deine Werte (und Wertebereich) ein Rolle spielt.



  • Schade, daß keine Antwort mehr kommt...



  • @Th69 Doch, doch, ich hatte nur den Link für's Forum verloren und nicht mehr danach gesucht.
    Ich habe mal ein wenig rumprobiert und konnte feststellen, dass ich meine Töne mithilfe eines 256-Wertearrays multiplizieren kann, sodass sie in den nächsten Ton übergehen. Mein Fehler war, dass ich dachte, dass ich meine Tabelle der untersten Oktave erweitern müsste. Darum dachte ich, dass man eine wirklich große Liste bräuchte, ein zwölftel reicht aber aus.