OneBitSound

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Dieser Beitrag hat zum Ziel darzulegen, wie man einen einfachen Synthesizer baut, welcher auf dem Prinzip der Ein-Bit-Schwingungen basiert.


Klangsynthese mit Sampletiefe von einem Bit

Um zu verstehen, was mit One Bit Sound (deutsch: Ein-Bit-Klang) gemeint ist muss man zuerst verstehen, was mit One Bit Sound nicht gemeint ist.

Nicht gemeint sind:

  • Rechteck-Schwingungen

Genaugenommen sind Rechteckschwingungen eine Unterart von Ein-Bit-Schwingungen. Bei den letzteren wechseln sich Einsen und Nullen dem Algorithmus entsprechend, bei den ersteren folgen zusammenhängende Gruppen von Einsen und Nullen mit gleicher Anzahl aufeinander.

  • Mit PWM erzeugte Schwingungen

Bei diesen Schwingungen folgt immer eine zusammenhängende Gruppe von Einsen einer zusammenhängenden Gruppe von Nullen. Bei den Ein-Bit-Schwingungen, wie schon bereits festgestellt, ist die Abfolge algorithmusbedingt. Außerdem ist die PWM-Frequenz höher als die maximal hörbare, dagegen haben die Ein-Bit-Schwingungen eine hörbare Frequenz.

Ein-Bit-Schwingungen sind also rechteckige Schwingungen, deren Wert sich durch den Algorithmus beliebig ändert.

Ästhetik des Ein-Bit-Klangs

Man stellt sich sicherlich die Frage nach der Zweckmäßigkeit der Ein-Bit-Synthese, wo doch die Rechenleistung des Controllers, welcher im Synthesizer One-Bit-Groovebox (http://web.media.mit.edu/~nvawter/projects/1bit/) benutzt wurde auch für den Synthesizer AVRSYN (http://www.jarek-synth.strona.pl/) mit der Sampletiefe vom 16 Bit ausreicht. Die negative Antwort hinsichtlich der Zweckmäßigkeit über "weniger" Möglichkeiten dieser Syntheseart ist hinreichend bekannt und wurde schon in den Achtzigern über die Monotonie der "Computermusik" geäußert. Dagegen sind die positiven Antworten interessanter:

  • mehr Möglichkeiten als man erwartet

Insbesondere die Stücke von Tristan Perich (Link s.u.) für Controller mit Orchester demonstrieren tatsächliche Möglichkeiten dieser Syntheseart.

  • wenig Rechenleistung erforderlich

Wie dieser Artikel im Software-Teil zeigt, ist unter Verzicht auf Effekte, MIDI- und Sequenzer-Teil ist es möglich, auch die kleinsten Mikrocontroller zu nutzen.

  • beabsichtigte Homecomputermusik-Nachahmung

Die Klangerzeugung des Atari 2600 synthesiert Ein-Bit-Klänge, daher macht man mit der Nachbildung des Klangbausteins des Atari automatisch Ein-Bit-Musik.

Verwendung von Ein-Bit-Schwingungen

Die bekannteste Anwendung von Ein-Bit-Schwingungen ist der Soundchip des Computers Atari 2600. Die Weiterentwicklung des Konzepts fand in der One-Bit-Groovebox von Noah Vawter (http://web.media.mit.edu/~nvawter/projects/1bit/index.html) statt. Im Bild ist der Nachbau der Groovebox auf einer Lochraster-Platine zu sehen.

OneBitGrooveBoxWithRose.jpg

Die One-Bit-Groovebox ist ein auf Live-Performance ausgelegter Synthesizer. Er besitzt keinen Computeranschluss, weil dies den Musiker an den Computer fesseln würde, dafür aber einen eingebauten Sequencer. Die Erstimplementierung durch Noah Vawter läuft auf dem Mikrocontroller AVR Mega 32 mit der (maximalen) Taktfrequenz von 16 MHz. Das Gerät besitzt eine analoge Eingabe (Potentiometer, experimentierfreudige Musiker können diesen z. B. durch einen Fotowiderstand ersetzen) und zehn digitale Eingaben (Knopfschalter). Letztere sind in folgende Gruppen aufgeteilt: Record-Knöpfe (analoger Wert wird im Sequencer gespeichert), Jam-Knöpfe (analoger Wert wird vom Synthesizer unmittelbar übernommen), ein Knopf zur Auswahl des Audioeffekts und ein Knopf zum Einklopfen vom Tempo.

Eine weitere Neuheit gegenüber dem Soundchip von Atari 2600 besteht in der Anwendung von digitalen Audioeffekten auf den durch den Synthesizer erzeugten Klang. Die Palette reicht vom Ring-Modulator bis zum Arpeggiator. Die Effekte verbrauchen aber nach der Aussage des Autors den größten Teil der Rechenleistung.

Tristan Perich (http://www.tristanperich.com/) komponiert mit dem Ein-Bit-Klang komplette Musikstücke. Ausserdem benutzte er einen AVR Mega 8-Mikrocontroller als Musikmedium (analog zu einer CD) für seine Ein-Bit-Musikstücke.

Die Gruppe "Loud Objects", deren Mitglied Tristan Perich ist, kombiniert den Ein-Bit-Klang mit dem Live-Zusammenlöten von Controllern, welche den Klang erzeugen. Auf deren Website (http://www.loudobjects.com/) ist es möglich, einen Kleinstsynthesizer namens Noise Toy entweder bereits zusammengebaut oder als Kit zu bestellen. Dazu gibt es auf der gleichen Seite verschiedene Beispiele für klangerzeugende Programme.

Die in diesem Abschnitt vorgestellte Musiker und Entwickler gehen den Weg der meisten Möglichkeiten, der möglichst vielfältigen Klänge. Das führt zu einem mächtigen und komplexen Musikinstrument, dessen Fähigkeiten für einen Anfänger nur langsam erschließbar sind. Das Ziel dieses Beitrags ist aber der Bau eines kleinen, leicht verständlichen Synthesizers. Das Noise Toy von Loud Objects geht in diese Richtung, und kann auch als Hardwareplattform benutzt werden. Die im Beitrag präsentierte Algorithmen der Klangsynthese sind weitgehend hardwareunabhängig, dennoch sollten bei der Hardwareauswahl einige Aspekte berücksichtigt werden.

Hardware des Synthesizers

Die Ansprüche an die Hardwareplattform sind minimal. Selbstverständlich muss die Rechenleistung für die Berechnungen ausreichen. Dasselbe gilt für die Speicherausstattung. Es müssen auch Pins für die Ein- und Ausgabe bereit stehen.

Werden nur perkussive Klänge (d.h. Schlagzeug) gewünscht, ist die Stabilisierung der Taktfrequenz durch ein Quarz nicht erforderlich.

Ansonsten besteht lediglich die Anforderung an den Entwickler, sich mit der Hardwareplattform auseinandergesetzt zu haben.

Es ist von Vorteil, eine Möglichkeit zum Debugging zu haben. Ob diese sich im JTAG-Interface, UART-Ausgabe oder gar in einer einzigen LED erschöpft bleibt dem Entwickler überlassen.

Bei der Wahl der Klangausgabe ist das Endergebnis zu berücksichtigen. Sind tiefe Frequenzen im Klang erwünscht, könnte zum Beispiel ein Piezo-Summer nicht ausreichen. Ein zusätzlicher Punkt, welchen es zu beachten gilt, ist die Leistung des resultierenden Klangs. Bei der Ausgabe auf Kopfhörer oder Verstärker hat sich folgender Schaltkreis bewährt:

OneBitSoundOutput.png

Bei Bedarf kann der Widerstand geändert oder durch einen Wechselwiderstand ersetzt werden. Der Klinkenstecker ist genauso anpassbar.

Software des Synthesizers

Der Umstieg von der gewohnten 16-Bit oder 8-Bit- auf die Ein-Bit-Klangerzeugung beinhaltet einige Tücken, welche im folgenden Abschnitt ausgeräumt werden.

Theorie der Ein-Bit-Klangerzeugung

Das Ergebnis der üblichen digitalen Klangerzeugung sind Amplitudenwerte aus einem Zahlenbereich, oft [math]\displaystyle{ -2^{15} \le n \le 2^{15}-1 }[/math] bei der Amplitude von 16 Bit oder [math]\displaystyle{ 0 \le n \le 2^{8}-1 }[/math] bei der Amplitude von 8 Bit. Diese Werte werden an den D/A-Wandler weitergereicht.

Bei der Ein-Bit-Klangerzeugung gibt es keine D/A-Wandlung, da die Ausgabewerte bereits als analog betrachtet werden. Daher kann man im Vergleich zu den 16 Bit breiten Werten sagen, dass 0 bei der Ein-Bit-Klangerzeugung für [math]\displaystyle{ -2^{15} \le n \le -1 }[/math] steht, so wie 1 für [math]\displaystyle{ 0 \le n \le 2^{15}-1 }[/math].

Man betrachte was mit einer Sinusschwingung geschieht, wenn diese mit einer Ein-Bit-Schwingung approximiert wird:

SineToOneBit.png

Die Sinusschwingung wird hier also zu einer Rechteckschwingung mit Tastverhältnis 50:50. Dasselbe Ergebnis bekommt man allerdings auch bei der Approximation der Rechteckschwingung (trivial) und der Dreieckschwingung mit derselben Frequenz (im Bild). Man kann also sagen, dass die Ein-Bit-Schwingung die Nulldurchgänge der Amplitudenwerte nachbildet.


Vom Klang zu Algorithmus

Wie bereits erwähnt ist das Ziel des Artikels der Bau eines einfachen Synthesizers. Man könnte versuchen einen Klang mit vielen einstellbaren Parametern zu erzeugen, aber dies würde die Übersichtlichkeit des Projekts zunichte machen. Es bietet sich an einen vorhandenen Klang nachzubilden. Um das Ganze möglichst einfach zu gestalten und möglicherweise auch auf eine stabilisierte Taktfrequenzquelle zu verzichten bietet sich die Nachbildung des Klangs eines Schlaginstruments, im vorliegenden Fall einer Bass Drum, an.

Zuerst muss man die Frequenzverteilung bestimmen. Dafür nutzen wir die Aufnahme einer gebeatboxten (d.h. mit menschlicher Stimme erzeugten) Bass Drum (Aufnahme). Mit dem Programm Sonic Visualiser (http://www.sonicvisualiser.org/) erzeugen wir ein Sonogramm der Aufnahme. Dieses sieht folgendermaßen aus:

BassDrumSpectrum.png

Es ist erkennbar, dass die dominierende Frequenz (die rote Linie im Bild) in den ersten 200 Millisekunden von [math]\displaystyle{ \approx }[/math]200 Hertz auf [math]\displaystyle{ \approx }[/math]100 Hertz stetig heruntergeht. In den weiteren 200 Millisekunden verbleibt die dominierende Frequenz bei 100 Hertz.

BassDrumSpectrumMeasured.png

Kleine Einstreuungen bei [math]\displaystyle{ \approx }[/math]43 Hertz haben nichts zu bedeuten -- nur ein leises Röcheln in den Stimmbändern.

Somit ist auch der Algorithmus klar:

  1. 200 Millisekunden langes Abgleiten von 200 Hertz auf 100 Hertz
  2. 200 Millisekunden lange Erzeugung von Schwingungen mit der Frequenz von 100 Hertz

Implementierung

Bei der Implementierung wurde versucht, die Details wie die Syntax der Interruptspezifizierung möglichst frei zu halten, denn dies erleichtert die Portierung. Im Übrigen wurde es schon früher im Artikel auf eine gute Kenntnis der Werkzeuge, mit denen man arbeitet, hingewiesen. Die Implementierung wird nicht als zusammenhängendes Quelltext vorgestellt, sondern als einzelne Schritte mitsamt derer Erklärungen.

Da die Spezifikationen des Hardwaresystems bisher sehr frei waren, geht die Software von den folgenden Annahmen aus:

  • es gibt eine Timer-Interruptroutine, welche vom Timer mit der Frequenz von 16000 Hertz aufgerufen wird
  • es gibt eine Port-Interruptroutine, welche durch die Änderung der Spannung am Port (d.h. Knopfdruck) aufgerufen wird
  • es gibt einen Port-Pin für die Ausgabe, auf welchen bitweise zugegriffen werden kann

Anmerkung: Es empfiehlt sich dennoch die Abtastfrequenz zu kontrollieren. Bei der Erstimplementierung der Ein-Bit-Klangerzeugung für eine Bass Drum wurde festgestellt, dass die Abtastfrequenz nur 880 Hertz betrug, statt der vermuteten 12000. Hardwarebedingt war eine Korrektur nicht möglich. Der Algorithmus funktionierte mit geringfügigen Anpassungen trotzdem.

Zuerst definiert man häufig benötigte Konstanten. Mann macht es besser durch die Präprozessor-Konstanten als über const-Variablen, denn im letzteren Fall könnte der Compiler auf die Idee kommen, den Wert der Konstanten zur Laufzeit zu berechnen.

Um Datentypen mit der vordefinierten Bitbreite zu nutzen, binden wir die Datei stdint.h ein:

#include <stdint.h>

Als erste Konstante benötigt man die Abtastfrequenz Fs. Mit dieser Frequenz wird eine Interruptroutine (mehr dazu weiter unten) von einem Timer aufgerufen.

#define Fs 16000

Dann benötigen wir beide Randfrequenzen (in Hertz) des Klangs (siehe dazu voriges Abschnitt):

#define F1 200
#define F2 100

Es folgen die Definitionen der Phase 1 und Phase 2 des Algorithmus' (jeweils 200 Millisekunden, berechnet als ein Fünftel einer Sekunde und somit auch von 16000 Hertz):

#define PH1DUR (Fs / 5)
#define PH2DUR (Fs / 5)

Wir ändern die Klangfrequenz lieber über die Periodendauer der Frequenz, damit der Controller nicht bei jeder Änderung deren eine Division ausführen muss. Dazu brauchen wir die Periodendauer der Randfrequenzen:

#define F1PERIOD (Fs / F1)
#define F2PERIOD (Fs / F2)

Weiterhin benötigen wir die Dauer einer Frequenz beim Herunterlaufen von 200 zu 100 Hertz.

#define GLPERIOD (PH1DUR / (F1 - F2))

Und schließlich brauchen wir noch die Bezeichnung für den Port-Pin, auf dem die Klangausgabe stattfindet. Compiler- und controllerabhängig sind die Pinbezeichnungen entweder deren Stellen im Port-Wert oder Bitmasken. Man nehme hier an, dass die Bitstelle angegeben ist.

#define SOUND_OUT (BIT6)

Dann definieren wir eine globale Statusvariable tbd. Diese bezeichnet den Zeitpunkt der Klanggenerierung. Damit die Sounderzeugung nicht bereits beim Einschalten des Systems losläuft, setzen wir den Anfangswert der Variable so, dass dieser die [math]\displaystyle{ 200 + 200 }[/math] Millisekunden des Klangs übersteigt.

uint16_t t_bd = PH1DUR + PH2DUR;

Dazu definieren man den Zwischenspeicher für das resultierende Bit. Der Datentyp uint8_t wird verwendet, weil die bit-Datentypen nicht von allen Compilern sowie Controllerfamilien unterstützt wird.

uint8_t bd_out = 0;

Man benötigt noch eine Variable, in der die Periodendauer gespeichert wird. Man benötigt auch eine Laufvariable, welche zum Zählen der Zeiteinheiten pro Periodendauer gebraucht wird.

uint8_t p_dur;
uint8_t p_i;

Zur Berechnung des Zeitpunktes für die Vergrößerung der Periodendauer brauchen wir eine zusätzliche Variable.

uint8_t p_inc_i;

Wir kommen nun zu den Funktionen. Die erste Funktion ist die Interruptroutine, welche die Klangerzeugung startet. Dazu setzt diese die Zeitvariable tbd auf 0. Aufgerufen wird die Routine beim Port-Interrupt, d.h. bei einem Knopfdruck.

void HitDrum(void) __interrupt
{
  t_bd = 0;
  bd_out = 0;
  p_dur = F1PERIOD;
  p_i = F1PERIOD;
  p_inc_i = GLPERIOD;
}

Die zweite Interruptroutine erzeugt den Klang. Dazu wird sie durch den entsprechend konfigurierten Timer aufgerufen.

void PlaySound(void) __interrupt
{
  if(t_bd >= (PH1DUR + PH2DUR))  /* Klangerzeugung abbrechen? */
  {
    return;
  }

  if(t_bd < PH1DUR)  /* Phase 1 der Klangerzeugung. */
  {
    /* Vergrößerung der Periodendauer.
       Dadurch Gleiten der Ausgabefrequenz nach unten. */
    if(p_inc_i > 0)
    {
      p_inc_i--;
    }
    else
    {
      if(p_dur < F2PERIOD)  /* Frequenzbegrenzung bis zur F2. */
      {
        p_dur++;
      }
      p_inc_i = GLPERIOD;
    }
  }
  else  /* Phase 2 der Klangerzeugung. */
  {
    /* Nichts tun, denn die Ausgabefrequenz verbleibt bei 100 Hz. */
  }

  /* Erzeugung der Ein-Bit-Schwingung. */
  if(p_i > 0)
  {
    p_i--;
  }
  else
  {
    bd_out = (bd_out ^ 0x1) & 0x1;
    p_i = p_dur;
  }

  /* Ausgabe der Ein-Bit-Schwingung.
     P1OUT ist der Register, mit dem die Werte
     auf dem Port P1 ausgegeben werden. */
  if(bd_out)
  {
    P1OUT |= (1 << SOUND);
  }
  else
  {
    P1OUT &= ~(1 << SOUND);
  }
  
  t_bd++;  /* Eine Zeiteinheit weiter gehen. */
}

Selbstverständlich sind die Initialisierungsroutinen für Interrupts sowie Konfiguration der Peripherieeinheiten ausgelassen worden. Aber deren Compiler- und Controllerabhängigkeit wurde bereits mehrfach erwähnt.

Verbesserung des Klangs

Der resultierende Klang ist schon recht trommelähnlich, allerdings fehlt ihm die "Schärfe", die "Kantigkeit". Um dieses Problem zu beheben, denke man zuerst an das Erste, was beim Schlagen einer Trommel passiert: Der Stick, mit dem man schlägt, trifft die Membran der Trommel. Das Geräusch des Aufschlags lässt sich gut durch ein kurzes weißes Rauschen imitieren. Um weißes Rauschen zu erzeugen brauchen wir ein (Pseudo-)Zufallsgenerator. Eine effiziente Implementierung finden wir im Quelltext der bereits erwähnten One-Bit-Groovebox; diese basiert auf dem Prinzip des Linear Feedback Shift Register (LFSR).

Zuerst fügen wir der Implementierung die Dauer des Rauschens (20 Millisekunden = ein Fünfzigstel Sekunde) hinzu:

#define NOISEDUR (Fs / 50)

Dann die Variablen für den Zufallsgenerator:

uint16_t noiseReg = 1,
         noiseReg2 = 1;

Danach die Funktion des Zufallsgenerators selbst:

uint16_t updNoise(void)
{
  uint16_t b1,b2,b3,b4;

  // 16-bit PRNG
  b1 = ((noiseReg&0x8000)>>15);
  b2 = ((noiseReg&0x4000)>>14);
  b3 = ((noiseReg&0x1000)>>12);
  b4 = ((noiseReg&0x0008)>> 4);
  b1 = (b1 ^ b2 ^ b3 ^ b4)&0x1;
  noiseReg <<= 1;
  noiseReg |= b1;

  // 15-bit PRNG
  b1 = ((noiseReg2&0x4000)>>14);
  b2 = ((noiseReg2&0x2000)>>13);
  b1 = (b1 ^ b2)&0x1;
  noiseReg2 <<= 1;
  noiseReg2 |= b1;

  return(noiseReg);
}

Und schließlich den Funktionsaufruf, und zwar direkt vor die Klangausgabe:

  if(t_bd < NOISEDUR)
  {
    bd_out = (bd_out ^ (uint8_t) updNoise()) & 0x1;
  }

Der Klang ist jetzt noch ein wenig authentischer.

Fazit

Die vorgestellte Art der Klangsynthese ist leicht zu implementieren und lädt zu Experimenten ein. Wie wird der Klang der Bass Drum sich anhören, wenn man ein Effekt darauf anwendet? Und wenn man die Effektparameter ändert? Die Möglichkeiten sind nicht unbegrenzt, aber sicherlich mehr, als dem Leser am Anfang dieses Artikels vorschwebte.

Für weitere Experimente werden die Webseiten aus dem nächsten Abschnitt sehr hilfreich sein.

One Bit Musiker/Entwickler

Video zum Artikel

Video (Englisch)

Siehe auch