Discussion:
Software-Konfigurierbarer Rauschgenerator auf 8-Bitter
Add Reply
Johannes Bauer
2019-10-06 07:43:02 UTC
Antworten
Permalink
Hallo Gruppe,

ich habe mir einen Rauschgenerator gebaut mit einem AVR ATtiny26. Mega
simples Design: PRNG erzeugt 8-Bit Wort, wirft es auf PORTA, R2R,
Verstärker, Lautsprecher.

Es geht nicht um irgendwie physikalisch korrektes rosa/braunes Rauschen,
sondern darum, dass das -- klingt blöd, weiß ich -- "sanft" klingt und
eben in Software "abschwächbar" ist. Konkret verändere ich also die
Verteilung Energie/Frequenz in Software.

Das ist unheimlich simpel: Ich habe einen 8-Bit Akkumulator, der jeweils
auf PORTA ausgegeben wird. In jeder Iteration wird der durch

accu += (prng() / divisor)

Voller Code: https://github.com/johndoe31415/tiny26noise/blob/master/main.c

Verändert. Je größer "divisor", desto "sanfter" das Rauschen.
Prinzipiell funktioniert das auch prima, mit einer Einschränkung: In der
Praxis erhalte ich mit größeren Divisor-Werten auch immer ein lästiges
knacksen.

Da das immer an denselben Stellen auftritt in demselben Pattern (ist ja
ein 100% deterministischer PRNG) nehme ich an, dass das an der Division
liegt, die auf dem AVR in Software emuliert wird und daher nicht
Laufzeitkonstant ist.

Jetzt habe ich überlegt, wie ich das besser machen kann. Klar, kann nur
Zweierpotenzen als Teiler zulassen, aber dann ist der Unterschied
zwischen zwei Levels recht stark. Fällt jemandem eine gute Idee ein, wie
man das ohne Hardwaredivision, nur mit Integerarithmetik und in
konstanter Laufzeit hinkriegt? Mir fehlt gerade eine gute Idee dafür.

Viele Grüße,
Johannes
--
"Performance ist nicht das Problem, es läuft ja nachher beides auf der
selben Hardware." -- Hans-Peter Diettrich in d.s.e.
Christian Zietz
2019-10-06 08:33:25 UTC
Antworten
Permalink
Post by Johannes Bauer
Jetzt habe ich überlegt, wie ich das besser machen kann. Klar, kann nur
Zweierpotenzen als Teiler zulassen, aber dann ist der Unterschied
zwischen zwei Levels recht stark. Fällt jemandem eine gute Idee ein, wie
man das ohne Hardwaredivision, nur mit Integerarithmetik und in
konstanter Laufzeit hinkriegt? Mir fehlt gerade eine gute Idee dafür.
Hat der µC denn einen HW-Multiplizierer (8 x 8 = 16)? Dann könntest Du
recht einfach berechnen: prng() * x / 256 ( = prng() / (256/x)) und
hättest immerhin 255 Stufen zur Auswahl.

Grüße
Christian
--
Christian Zietz - CHZ-Soft - czietz (at) gmx.net
WWW: http://www.chzsoft.de/
PGP/GnuPG-Key-ID: 0x52CB97F66DA025CA / 0x6DA025CA
Johannes Bauer
2019-10-06 08:42:06 UTC
Antworten
Permalink
Post by Christian Zietz
Hat der µC denn einen HW-Multiplizierer (8 x 8 = 16)? Dann könntest Du
recht einfach berechnen: prng() * x / 256 ( = prng() / (256/x)) und
hättest immerhin 255 Stufen zur Auswahl.
Oh, die Idee ist gut! Im vorliegenden Fall geht das leider nicht, habe
auch keinen HW-Multiplizierer, aber kommt definitiv in die Trickkiste.

Habe ein bisschen damit herumexperimentiert eine Bitmaske zu einem
gegebenen Maximalwert vorauszuberechnen (das ist dann nur eine
Subtraktion im Worst-case), aber irgnedwo ist da noch ein Wurm drin.
Siehe https://github.com/johndoe31415/tiny26noise/blob/master/noisegen.c

Vielen Dank & viele Grüße,
Johannes
--
"Performance ist nicht das Problem, es läuft ja nachher beides auf der
selben Hardware." -- Hans-Peter Diettrich in d.s.e.
Christian Zietz
2019-10-06 09:20:49 UTC
Antworten
Permalink
Post by Johannes Bauer
Habe ein bisschen damit herumexperimentiert eine Bitmaske zu einem
gegebenen Maximalwert vorauszuberechnen (das ist dann nur eine
Subtraktion im Worst-case), aber irgnedwo ist da noch ein Wurm drin.
Siehe https://github.com/johndoe31415/tiny26noise/blob/master/noisegen.c
Ist denn die (in SW durchgeführte) Division grundsätzlich zu langsam
oder stört nur deren eingangswertabhängige Laufzeit? Falls letzteres,
kannst Du ja die Worst-Case-Samplerate bestimmen, d.h. die erreichbare
Samplerate bei den ungünstigsten Eingabewerten. Und dann diese
Samplerate erzwingen, indem Du die Samples nicht gleich am Ende der
Berechnung sondern z.B. timer-gesteuert ausgibst.

Grüße
Christian

PS: Hier mangels HW-Multiplikator nicht hilfreich, aber dennoch viel zu
wenig bekannt: Man kann auch die Division durch viele Ganzzahlen durch
Multiplikation und Shifts hinbekommen. Code sagt mehr also Worte:
<https://godbolt.org/z/1oMbHS>. gcc vermeidet hier die (auch auf einem
x86 teure) Division durch 3.
--
Christian Zietz - CHZ-Soft - czietz (at) gmx.net
WWW: http://www.chzsoft.de/
PGP/GnuPG-Key-ID: 0x52CB97F66DA025CA / 0x6DA025CA
Roland Krause
2019-10-06 08:46:43 UTC
Antworten
Permalink
Post by Johannes Bauer
Hallo Gruppe,
ich habe mir einen Rauschgenerator gebaut mit einem AVR ATtiny26. Mega
simples Design: PRNG erzeugt 8-Bit Wort, wirft es auf PORTA, R2R,
Verstärker, Lautsprecher.
Es geht nicht um irgendwie physikalisch korrektes rosa/braunes Rauschen,
sondern darum, dass das -- klingt blöd, weiß ich -- "sanft" klingt und
eben in Software "abschwächbar" ist. Konkret verändere ich also die
Verteilung Energie/Frequenz in Software.
Das ist unheimlich simpel: Ich habe einen 8-Bit Akkumulator, der jeweils
auf PORTA ausgegeben wird. In jeder Iteration wird der durch
accu += (prng() / divisor)
Voller Code: https://github.com/johndoe31415/tiny26noise/blob/master/main.c
Verändert. Je größer "divisor", desto "sanfter" das Rauschen.
Prinzipiell funktioniert das auch prima, mit einer Einschränkung: In der
Praxis erhalte ich mit größeren Divisor-Werten auch immer ein lästiges
knacksen.
Da das immer an denselben Stellen auftritt in demselben Pattern (ist ja
ein 100% deterministischer PRNG) nehme ich an, dass das an der Division
liegt, die auf dem AVR in Software emuliert wird und daher nicht
Laufzeitkonstant ist.
Jetzt habe ich überlegt, wie ich das besser machen kann. Klar, kann nur
Zweierpotenzen als Teiler zulassen, aber dann ist der Unterschied
zwischen zwei Levels recht stark. Fällt jemandem eine gute Idee ein, wie
man das ohne Hardwaredivision, nur mit Integerarithmetik und in
konstanter Laufzeit hinkriegt? Mir fehlt gerade eine gute Idee dafür.
Viele Grüße,
Johannes
Moin,
mir ist nicht ganz klar, wie Dein Wert für "value" kleiner 0 werden
soll. prng() liefert ja einen uint, also Werte von 0-255.
Also Deine Zuweisungen value = 0 sollte den Knackser auslösen. value >
255 solltest Du nie erreichen.

Meiner Meinung nach müsste das so aussehen:

while (true) {
uint8_t value = prng() / divisor;
if (enabled) {
PORTA = value;
} else {
PORTA = 0;
}
}


Gruß
--
Roland - ***@freenet.de
Roland Krause
2019-10-06 09:34:08 UTC
Antworten
Permalink
Post by Roland Krause
Post by Johannes Bauer
Hallo Gruppe,
ich habe mir einen Rauschgenerator gebaut mit einem AVR ATtiny26. Mega
simples Design: PRNG erzeugt 8-Bit Wort, wirft es auf PORTA, R2R,
Verstärker, Lautsprecher.
Es geht nicht um irgendwie physikalisch korrektes rosa/braunes Rauschen,
sondern darum, dass das -- klingt blöd, weiß ich -- "sanft" klingt und
eben in Software "abschwächbar" ist. Konkret verändere ich also die
Verteilung Energie/Frequenz in Software.
Das ist unheimlich simpel: Ich habe einen 8-Bit Akkumulator, der jeweils
auf PORTA ausgegeben wird. In jeder Iteration wird der durch
accu += (prng() / divisor)
https://github.com/johndoe31415/tiny26noise/blob/master/main.c
Verändert. Je größer "divisor", desto "sanfter" das Rauschen.
Prinzipiell funktioniert das auch prima, mit einer Einschränkung: In der
Praxis erhalte ich mit größeren Divisor-Werten auch immer ein lästiges
knacksen.
Da das immer an denselben Stellen auftritt in demselben Pattern (ist ja
ein 100% deterministischer PRNG) nehme ich an, dass das an der Division
liegt, die auf dem AVR in Software emuliert wird und daher nicht
Laufzeitkonstant ist.
Jetzt habe ich überlegt, wie ich das besser machen kann. Klar, kann nur
Zweierpotenzen als Teiler zulassen, aber dann ist der Unterschied
zwischen zwei Levels recht stark. Fällt jemandem eine gute Idee ein, wie
man das ohne Hardwaredivision, nur mit Integerarithmetik und in
konstanter Laufzeit hinkriegt? Mir fehlt gerade eine gute Idee dafür.
Viele Grüße,
Johannes
Moin,
mir ist nicht ganz klar, wie Dein Wert für "value" kleiner 0 werden
soll. prng() liefert ja einen uint, also Werte von 0-255.
Also Deine Zuweisungen value = 0 sollte den Knackser auslösen. value >
255 solltest Du nie erreichen.
    while (true) {
        uint8_t value = prng() / divisor;
        if (enabled) {
            PORTA = value;
        } else {
            PORTA = 0;
        }
    }
Gruß
.. und prng() würde ich durch rand() ersetzen, so heißt die Funktion
zumindest beim XC8 (PIC, Microchip). Dann sollte das klappen.
--
Roland - ***@freenet.de
Volker Bartheld
2019-10-07 10:18:51 UTC
Antworten
Permalink
Post by Johannes Bauer
ich habe mir einen Rauschgenerator gebaut mit einem AVR ATtiny26. Mega
simples Design: PRNG erzeugt 8-Bit Wort, wirft es auf PORTA, R2R,
Verstärker, Lautsprecher.
accu += (prng() / divisor)
In der Praxis erhalte ich mit größeren Divisor-Werten auch immer ein lästiges
knacksen.
Reicht Dir eine "logarithmische" Lautstärkeskala mit 8 Stufen? Dann
könntest Du einfach nach rechts shiften statt zu teilen, hast dann aber...
Post by Johannes Bauer
Klar, kann nur Zweierpotenzen als Teiler zulassen, aber dann ist der
Unterschied zwischen zwei Levels recht stark.
Wenn Dein ATtiny noch ein bisserl PLatz im EEPROM hat, kannst Du dort auch
einfach eine Rauschtabelle ablegen. die 128 Bytes reichen evtl. dafür.
Teilen mußt Du dann aber natürlich immer noch.
Post by Johannes Bauer
Fällt jemandem eine gute Idee ein, wie
man das ohne Hardwaredivision, nur mit Integerarithmetik und in
konstanter Laufzeit hinkriegt?
Externe Beschaltung mit Rauschdiode am ADC scheidet kategorisch aus?

Ciao,
Volker
Marcel Mueller
2019-10-07 21:02:23 UTC
Antworten
Permalink
Post by Johannes Bauer
Es geht nicht um irgendwie physikalisch korrektes rosa/braunes Rauschen,
sondern darum, dass das -- klingt blöd, weiß ich -- "sanft" klingt und
eben in Software "abschwächbar" ist. Konkret verändere ich also die
Verteilung Energie/Frequenz in Software.
Das ist unheimlich simpel: Ich habe einen 8-Bit Akkumulator, der jeweils
auf PORTA ausgegeben wird. In jeder Iteration wird der durch
accu += (prng() / divisor)
Der Divisor hier ist eigentlich Grütze. Er reduziert nur die Zahl der
Zufallsbits, die von prng kommen. Sehr deutlich wird das bei
Zweierpotenzen. Diese entsprechen einem Shift-Operator, der einfach die
unteren Bits entsorgt.
Post by Johannes Bauer
Voller Code: https://github.com/johndoe31415/tiny26noise/blob/master/main.c
Verändert. Je größer "divisor", desto "sanfter" das Rauschen.
Prinzipiell funktioniert das auch prima, mit einer Einschränkung: In der
Praxis erhalte ich mit größeren Divisor-Werten auch immer ein lästiges
knacksen.
Überläufe?
Post by Johannes Bauer
Da das immer an denselben Stellen auftritt in demselben Pattern (ist ja
ein 100% deterministischer PRNG) nehme ich an, dass das an der Division
liegt, die auf dem AVR in Software emuliert wird und daher nicht
Laufzeitkonstant ist.
Das würde mich wundern, wenn das der wesentliche Punkt ist, zumal der
Divisor ja auch noch konstant ist.

Berechne doch mal die Folge auf dem PC vor und schau dir das Ergebnis
mit Audacity an.
Post by Johannes Bauer
Jetzt habe ich überlegt, wie ich das besser machen kann. Klar, kann nur
Zweierpotenzen als Teiler zulassen, aber dann ist der Unterschied
zwischen zwei Levels recht stark. Fällt jemandem eine gute Idee ein, wie
man das ohne Hardwaredivision, nur mit Integerarithmetik und in
konstanter Laufzeit hinkriegt? Mir fehlt gerade eine gute Idee dafür.
Ich würde komplett anders vorgehen.

prng() liefert die Pseudo-Zufallsbits, also Weißes Rauschen. Dieses
würde ich durch ein IIR-Filter jagen und damit passend maskieren. Im
einfachsten Fall ist das ein Integrator (Addition), dann gibt es erst
mal Rosa Rauschen. Du musst nur aufpassen, dass Du keine Überläufe
bekommst, sonst ist das Spektrum komplett im Eimer.

Das ist deiner Methode bis dahin gar nicht so unähnlich (die Addition),
bis auf die Sache mit den Überläufen eben.

Jetzt kann man natürlich den Erwartungswert von prng() bei jeder
Addition abziehen und somit eine Eskalation des Akkumulators
abschwächen, verhindern wird es sie aber nicht, denn Rosa Rauschen hat
nun einmal bei der Frequenz 0 eine Singularität (unendliche Energie).
Technisch wäre es zudem eine Null-Operation, da es nur das oberste Bit
von prng toggeln würde, das ohnehin zufällig ist. Also bringt nichts.

Das bringt mich zum zweiten Punkt: in jedem Fall braucht der Akkumulator
mehr Bits (also faktisch 16), von denen nur die obersten an den ADC
gehen. Jetzt hat man Platz für weitere Kalkulationen.

Jetzt kommt der eigentliche Clou: ein geeignetes Filter würde die nach
obigem Muster kompensierten Akkumulatorwerte zwar ebenfalls
tiefpassfiltern, aber es hat eben auch eine Resonanzfrequenz unterhalb
der nicht mehr verstärkt oder besser noch Hochpass filtert. Damit ist
man den lästigen DC-Anteil los und ebenso sind unnötige Rumpelgeräusche
weg, die letztlich DAC-Headroom kosten, von denen der 8-Bitter ohnehin
viel zu wenig hat.

Man braucht also am besten einen Bandpass mit einer sehr schlechten
Güte, vorzugsweise aperiodisch, also Q=0,7. Die Resonanzfrequenz des
Filters ist grob die gewünschte untere Grenzfrequenz des Rosa Rauschens.

Technisch ist ein IIR Filter ein bisschen +,-,*. Division braucht man
dafür gar nicht. Man nennt das auch Biquad, weil es sich mit einer
biquadratischen Gleichung modellieren lässt.

y[n] = a0 x[n] + a1 x[n-1] + a2 x[n-2] + b1 y[n-1] + b2 y[n-2]

y sind die Ausgabewerte, x die Eingabewerte aus prng. [n-1] meint den
letzten Ein- bzw. Ausgabewert, [n-2] den vorletzten.

Die Koeffizienten a0 .. b2 bekommt man z.B. hier her:
https://www.earlevel.com/main/2013/10/13/biquad-calculator-v2/
Dort "Bandpass" wählen und Güte 0,7 (default).
Die Koeffizienten hängen nur von dem Verhältnis untere Grenzfrequenz zu
Samplingrate bzw. Schleifenfrequenz ab.

Bei allen hier relevanten Filter ist a1 = 0 - der Term entfällt also und
man braucht nur noch den vorletzten Eingabewert - Und a2 = -a0, zudem
ist b1 immer negativ. Damit reduziert sich die Formel zu

y[n] = a0 x[n] - a0 x[n-2] - (-b1) y[n-1] + b2 y[n-2]

Da x[n] ohnehin zufällig ist, ist es auch x[n] - x[n-2]. Man kann sich
also die Nummer mit x[n-2] komplett schenken.

y[n] = a0 x[n] - (-b1) y[n-1] + b2 y[n-2]

Die drei y-Akkumulatoren müssen wie schon erwähnt 16-bittig ausgeführt
werden. Für x genügen 8 Bit, mehr kommt von prng ohnehin nicht.

Die Koeffizienten liegen aufgrund des großen Verhältnisses von Unterer
zu Samplingrate alle nahe bei ganzen Zahlen. Daher bietet es sich an,
die Koeffizienten ebenfalls nur 8-Bittig auszuführen. Gespeichert wird
dann nur die Abweichung zur nächsten ganzen Zahl. Damit reduzieren sich
die Multiplikationen für die auf 8 Bit mal 16 Bit.

Beispiel: b1 = -1,98; per Definition sind die oberen 8 Bit des
y-Akkumulator die Vorkommastellen, die später an den DAC gehen, und die
unteren 8 Bit die Nachkommastellen.
b1 * y[n-1] = b1' * y[n-1] - (y[n-1] << 1) mit
b1' = b1 + 2 = 0,02 entspricht 0x05
Das ergibt natürlich eine sehr grobe Quantisierung (also die 0x05).
Anders formuliert, auch 16 Bit reichen nicht. Das Filter wäre instabil.
Man braucht mehr.
Man kann sich aber leicht mehr Nachkommastellen herauskitzeln, indem man
mit Shift-Operatoren arbeitet:
b1 * y[n-1] = ((b1' * y[n-1]) >> 5) - (y[n-1] << 1) mit
b1' = 32 * 2 - b1 = 0,64 entspricht 0xA4
Analog geht man für die anderen Terme vor.

Bleibt noch die Sache mit der Pegelregelung. Dazu nimmt man sich den
a0-Koeffizienten vor. Also:
a0 * x[n] = (a0' * x[n]) >> S
Man beachte die Shift-Richtung. Per Definition sind x[n]
Vorkommastellen, also die oberen 8 Bit. Diese werden multipliziert mit
den Nachkommastellen von a0. Heißt, die oberen 8 Bit des 16-bittigen
Multiplikationsergebnisses sind am Ende Vorkommastellen, die unteren
Nachkomma, exakt passend zu dem y-Akkumulator.
Allerdings wird man schnell feststellen, dass der y-Akkumulator auf
diese Weise überläuft. Daher muss das Signal noch zusätzlich
abgeschwächt werden. Das macht man mit S. Zudem muss man sich auch hier
für a0 zusätzliche Bits erarbeiten, analog zu b1 oben. Daher wird S noch
größer. Vielleicht wählt man S = 8, was nichts anderes bedeutet, als die
oberen 8 Bit des Multiplikationsergebnisses werden nur zu den
Nachkommastellen des y-Akkumulators addiert. Damit muss man nur noch bei
Übertrag an die oberen 8 Bit von y ran.

Die eigentliche Pegelanpassung mach man jetzt über S (in 6dB-Schritten)
und dem a0'-Koeffizienten für's feine. Dieser hat keinen Einfluss auf
den Frequenzgang.
Am Ende muss man ausprobieren, wie weit hoch man sich traut. Es ist
dabei essentiell, dass der y-Akkumulator niemals überläuft.

Alternativ könnte es auch klappen, wenn S = 8 und a0' = 1.0 wählt, also
gar keine Multiplikation mit a0. Damit bekommt man die weiter reduzierte
Formel:

y[n] = (x[n] >> 8)
+ ((b1' * y[n-1]) >> 5) - (y[n-1] << 1)
- ((b2' * y[n-2]) >> 5) + y[n-2]

wobei >> 8 jetzt nur der Addition zu den unteren 8 Bit von y entspricht.
Man braucht jetzt genau 2 8*16 Bit Multiplikationen pro Ausgabewert und
keine Division.


Alle bisher genannten Kalkulationen beziehen sich auf Signed-Integer
Werte. Technisch hat das bis hier hin keine Auswirkung, aber bei der
Übergabe an den DAC muss man das beachten. hier genügt es nicht, die
oberen 8 Bit des y-Akkumulators zu nehmen, man muss zudem das oberste
Bit toggeln (entspricht +128).

Kleiner Hinweis noch: Die Anzahl der signifikanten Bits bei der
Kalkulation der b-Koeffizienten sind eklatant wichtig für Genauigkeit
und Stabilität des Filters. Die beiden b-Terme liefern stets ähnlich
große Zahlen, die sich gegenseitig weitgehend Auslöschen. Stimmt das
Verhältnis der beiden durch Quantisierungsfehler nicht, so wächst y
schnell mal exponentiell über alle Grenzen.

Während man peinlich darauf achten muss dass y[] iterativ nicht
überläuft, sind temporäre Überläufe während der Kalkulation eines y[n],
beispielsweise durch y[n-1] << 1 hingegen unkritisch. Die kompensieren
sich automatisch.


Marcel
Rafael Deliano
2019-10-08 18:46:42 UTC
Antworten
Permalink
Post by Johannes Bauer
Es geht nicht um irgendwie physikalisch korrektes rosa/braunes Rauschen,
http://www.embeddedFORTH.de/temp/Kolm.pdf

-20dB/Dekade sind analog als Filter billig zu haben,
das ist ein 1pol Tiefpaß.

MfG JRD
Marte Schwarz
2019-10-09 07:07:31 UTC
Antworten
Permalink
Hi Rafael,
Post by Rafael Deliano
Post by Johannes Bauer
Es geht nicht um irgendwie physikalisch korrektes rosa/braunes Rauschen,
http://www.embeddedFORTH.de/temp/Kolm.pdf
-20dB/Dekade sind analog als Filter billig zu haben,
das ist ein 1pol Tiefpaß.
Nicht wirklich, erst dann, wenn man ca 1/2 Dekade jenseits der
Eckfrequenz des Filters ist. Über den gesamten Frequenzbereich sind wir
beim Integrator erster Ordnung. Wenn man nun die Steilheit auf die
Potenz 5/3 reduzieren wollte, könnte man auf die Idee kommen, dass man
ein Mischer bräuchte, der dem -20 dB eben noch einmal das Orginalsignal
zumischte, wäre da nicht der Phasengang des Integrators. Also müsste man
mit einem Allpassfilter den Phasengang des ungefilterten Signals dem des
Integrators anpassen, bevor man summiert...
Könnte zielführend werden, wenn das mit den idealen Integratoren in der
Praxis nicht regelmäßig an Offsets scheitern würde.

Marte
Rafael Deliano
2019-10-09 15:46:39 UTC
Antworten
Permalink
Post by Marte Schwarz
Nicht wirklich, erst dann, wenn man ca 1/2 Dekade jenseits der
Eckfrequenz des Filters ist.
Auch die -3dB Schaltungen funktionieren nur über begrenzten
Frequenzbereich, typisch 2 Dekaden. Für die üblichen Anwendungen
ist nicht mehr erforderlich.

MfG JRD

Loading...