Hinweis: Dieses Projekt benötigt Version 3 von MakeCode, welche hier verfügbar ist: https://makecode.calliope.cc/v3 .
Um unseren Calliope in einen "Musik-Papagei" zu verwandeln, lassen wir sein Mikrofon (ähnlich wie bei "Alexa" und co.) permanent mitlaufen. Indem wir die aufgenommenen Daten analysieren, können wir im Display anzeigen, welcher Ton dem Calliope gerade "vorgespielt" wird. Wir werden auf diese Art außerdem ganze Melodien abspeichern und den Calliope dazu bringen, sie danach abzuspielen!
Hoffentlich ist Her Majesty "amused" :-) Und so funktionierts: Wenn unsere Ohren ein Musikinstrument hören oder dass jemand singt oder sonst irgendein Geräusch, nehmen sie tatsächlich winzige Änderungen des Luftdrucks wahr. Man kann das als so genannte "Waveform" zeichnen, mit einer horizontalen Zeitachse und einer vertikalen Achse, welche die Kompression und Ausdehnung der Luft darstellt:
Im Mikrofon des Calliope ist eine kleine Membran, die durch solche akustischen Wellen in Bewegung versetzt wird. Diese Bewegung wird in elektrische Spannung umgewandelt und so messbar gemacht. Das ist im menschlichen Ohr praktisch genau so: da heißt diese Membran "Trommelfell" und seine Bewegung wird in Nervenimpulse umgewandelt, die schließlich von unserem Gehirn auswertet werden.
Beliebige Klänge zu analysieren ist ziemlich kompliziert. Es ist viel einfacher, die Höhe eines so genannten "reinen Tons" zu berechnen, dessen Waveform aus einer einfachen Sinuskurve besteht. Und so sieht sie aus:
Die Lautstärke des entsprechenden Tons kann man an der Amplitude ablesen (d. h. dem Unterschied zwischen dem höchsten und dem niedrigsten Wert, für uns nicht interessant). Die Tonhöhe hängt direkt von seiner Frequenz ab (der Anzahl von Wiederholungen pro Sekunde, angegeben in "Hertz"/"Hz", genau das was wir hier brauchen). Es gibt natürlich Tabellen, in denen steht, welcher Ton welche Frequenz hat, wobei das "A4" (440 Hz) ziemlich berühmt ist, weil das die Frequenz einer Stimmgabel ist:
Aber wie können wir die Frequenz aus einer Aufnahme berechnen? Der Mikrofon-Pin des Calliope liefert Werte zwischen 0 und 1023, wobei ein konstanter Wert von ungefähr 512 für "Stille" steht. Wir messen alle 250 µs (0,00025 s) diesen Wert, was uns eine etwas gröbere Version der ursprünglichen Sinuskurve liefert:
Weil wir die Zeitdifferenz zwischen jedem dieser Messpunkte ("Samples") kennen, müssen wir nur zählen, wie viele von ihnen wir seit dem Anfang einer Wiederholung der Sinuskurve aufgenommen haben. Als Referenz nehmen wir dafür jeweils den ersten Wert, der über der Mitte (512) liegt. Im abgebildeten Beispiel passiert das bei den Samples Nr. 2 (585) und Nr. 10 (594). Also haben wir eine Dauer von 8 Samples, die Periode dauerte also 8 * 250 µs = 2000 µs (oder 0,002 s).
Wir wollten eigentlich aber nicht die Dauer wissen, sondern den Kehrwert, die Frequenz. Mit anderen Worten: "wie oft passt diese Periode in eine Sekunde". In unserem Beispiel wäre das 1 / 0,002 = 500 Mal (denn 500 * 0,002 s = 1 s). Das heißt, wir haben gerade einen Ton von 500 Hz gemessen, der in der Tabelle irgendwo zwischen B4 und C5 liegen würde.
Bis jetzt ist unser Ergebnis ziemlich ungenau, weil wir uns nur die Anzahl von Samples in einer Periode angesehen haben. Der nächst höhere Ton, den wir damit messen könnten hätte eine Periodendauer von 7 * 250 µs (d. h. eine Frequenz von 571 Hz), der nächst tiefere 9 * 250 µs (also 444 Hz). Um auch Werte dazwischen zu bekommen, betrachten wir einfach einen etwas längeren Zeitabschnitt (im Programm sind es 400 * 250 µs = 0,1 s). Dann berechnen wir die durchschnittliche Dauer aller Perioden während dieser Zeit und nehmen wieder den Kehrwert, was dann wesentlich näher an der "wahren" Frequenz des Tons liegt.
Die durchschnittliche Periodendauer in "Anzahl Samples" wird multipliziert mit der "Dauer" eines einzelnen Samples (250/1000000 s), was die durchschnittliche Periodendauer in Sekunden liefert. Diese Berechnung kann etwas vereinfacht werden, was eine kürzere Gleichung und weniger Raum für Rundungsfehler ergibt:
Unser Programm nimmt nun einfach das Ergebnis, schaut in einer Liste von Frequenzen nach, welcher Ton am nächsten liegt (wie in der Tabelle oben) und zeigt den Namen dieses Tons auf dem Display an. Wenn es ein Halbton ist, wechselt die Anzeige z. B. zwischen "F" und "#".
Ich habe nicht vergessen, dass ich auch versprochen hatte, eine Melodie zu speichern und dann abzuspielen. Das ist glücklicherweise wesentlich einfacher :-) Mit den Blöcken des MakeCode-Editors können wir feststellen, ob z. B. gerade Knopf "A" gedrückt wurde. Wann immer das passiert, fangen wir an, jeden Ton, den wir messen, in einer Liste zu speichern (genauer gesagt zwei Listen, eine für die Frequenzen und eine für die Tondauern). Nochmal "A" gedrückt beendet diesen Vorgang. Ein Druck auf "B" führt dazu, dass der Calliope diese beiden Listen der Reihe nach durchgeht und jeden Ton in der entsprechenden Dauer abspielt.
So können wir ihm beibringen, uns jede Melodie nachzuspielen! Noch ein Hinweis: Auch wenn es möglich wäre, einfach so alles aufzunehmen und abzuspielen, was das Mikrofon misst (ohne zu berechnen, welcher Ton das ist, im PC wäre das eine ".WAV"-Datei), würde der Speicher des Calliope in sehr kurzer Zeit voll sein. Außerdem klingt es scheußlich... Mit unserem Programm sind wir zwar auf Sinustöne beschränkt, brauchen aber nur extrem wenig Speicher und es klingt bei der Wiedergabe zumindest "etwas" netter :-)
Achtung: Das Programm kann keine komplexen Klänge wie von einer Gitarrensaite, einen Klavierton, Gesang etc. verarbeiten. Mit dem folgenden Java-Programm lassen sich aber (wie in den Videos von oben) Sinustöne mit einer Art "virtuellem Klavier" auf dem Computer spielen:
https://www.uni-due.de/~sw1084/VirtualPiano.jar
(benötigt die Java Laufzeitumgebung: https://www.java.com/)
oder man kann fürs Ausprobieren einzelner Töne diese Webseite verwenden:
https://www.szynalski.com/tone-generator/
Wenn der erkannte Ton etwas zu tief ist, kann man am "waitMicros"-Befehl "schrauben", dann muss man den Wert, der da von 250 abgezogen wird, etwas verringern. Wenn der Ton zu hoch ist, muss der Wert etwas vergrößert werden. Ansonsten: Einfach in Makecode in die JavaScript-Ansicht wechseln und den angehängten Code reinkopieren :-)
Viel Spaß!
Thorsten Kimmeskamp
PS: Ursprüngliche Idee (ohne die Papgei-Funktion) entdeckt auf: https://learn.adafruit.com/circuit-playground-o-phonor/musical-note-basics
Comments