Differences
This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
| efinf:blc2016:bitsundbytes:wavdatei [2016/09/11 08:52] – [Ruby Details] Ivo Blöchliger | efinf:blc2016:bitsundbytes:wavdatei [2016/09/20 13:11] (current) – [Weitere Aufgaben] Ivo Blöchliger | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | {{backlinks> | ||
| + | ===== Dienstag 13. September 2016: Erstellen einer WAV-Datei ===== | ||
| + | ==== Digital Audio: Prinzipien ==== | ||
| + | Schall: Schwankungen des Luftdrucks, erzeugt z.B. durch die Bewegung einer Lautsprechermembran, | ||
| + | |||
| + | Der Luftdruck (bzw. Auslenkung einer Membran) wird viele Male pro Sekunde gemessen (z.B. CD: 44100Hz). Diese Messpunkte (typischerweise vorzeichenbehaftete 16-Bit Ganzzahlen) werden gespeichert. Diese Messwerte werden **Samples** genannt. Die Umwandlung wird auch **Sampling** genannt. | ||
| + | |||
| + | Beim Abspielen, wird entsprechend dieser Werte eine Spannung erzeugt (und die Kurve zwischen Punkten geglättet). | ||
| + | |||
| + | ==== WAV-Format ==== | ||
| + | Das WAV-Format kann ziemlich komplex sein. Wir werden uns auf ein ganz einfaches Format beschränken: | ||
| + | * Mono (1 Kanal) | ||
| + | * 44100Hz Samplingrate | ||
| + | * unkomprimiert | ||
| + | * 16-Bit signed ints, little-endian | ||
| + | * Erzeugen von 2 Sekunden 440Hz-Ton (Kammer-a) | ||
| + | |||
| + | Wir gehen von folgender Beschreibung des WAV-Formats aus: https:// | ||
| + | |||
| + | ==== Ruby Details ==== | ||
| + | === String manipulation === | ||
| + | Die Daten zuerst in einem String geschrieben. Damit das effizient ist, wird ein String der korrekten Länge angelegt: | ||
| + | <code ruby> | ||
| + | filesize = ???? | ||
| + | wav = 0.chr*filesize | ||
| + | </ | ||
| + | Der String kann dann direkt veränder werden, z.B. mit | ||
| + | <code ruby> | ||
| + | wav[8..11]=" | ||
| + | </ | ||
| + | === Little-Endian Kodierung === | ||
| + | Da wir viele Werte Little-Endian kodieren werden müssen, wird eine Funktion definiert: | ||
| + | <code ruby> | ||
| + | def to_le(wert, bytes=2) | ||
| + | res = 0.chr*bytes | ||
| + | bytes.times{|i| | ||
| + | res[i] = (wert & 0xff).chr | ||
| + | wert >> | ||
| + | } | ||
| + | return res # Resultat der Funktion | ||
| + | end | ||
| + | #Zum Testen: | ||
| + | puts to_le(1819242339, | ||
| + | </ | ||
| + | |||
| + | === Konstanten des Programms === | ||
| + | Wir definieren zuerst die Eckdaten unserer Wav-Datei: | ||
| + | <code ruby> | ||
| + | duration = 2 | ||
| + | |||
| + | format = 0x0001 | ||
| + | channels = 1 # Mono | ||
| + | rate = 44100 # 44100Hz Samplingrate | ||
| + | bytes_per_second = rate*2 | ||
| + | blockalign = 2 | ||
| + | bitssample = 16 | ||
| + | |||
| + | filesize = ????? | ||
| + | |||
| + | wav = 0.chr*filesize | ||
| + | |||
| + | # | ||
| + | # Hier die Header-Daten schreiben | ||
| + | # Und dann Audio-Daten erzeugen | ||
| + | # | ||
| + | </ | ||
| + | |||
| + | === Sinus === | ||
| + | Um eine Schwingung darzustellen, | ||
| + | <code ruby> | ||
| + | sq32 = Math.sin(30/ | ||
| + | </ | ||
| + | |||
| + | === Ausgabe in eine Datei === | ||
| + | Die Datei wird in dem Verzeichnis erzeugt (bzw. überschrieben), | ||
| + | <code ruby> | ||
| + | File.open(" | ||
| + | o.write(wav) | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== Lösungsvorschlag ==== | ||
| + | <hidden Code anzeigen> | ||
| + | <code ruby wavgenerator.rb> | ||
| + | # to_le wandelt einen Zahlwert (wert) in ein Folge von einer gegbenen (bytes) Bytes um. | ||
| + | # Z.B. wird mit to_le(0x1020, | ||
| + | |||
| + | def to_le(wert, bytes=2) | ||
| + | res = 0.chr*bytes | ||
| + | bytes.times{|i| | ||
| + | res[i] = (wert & 0xff).chr | ||
| + | wert >> | ||
| + | } | ||
| + | return res # Resultat der Funktion | ||
| + | end | ||
| + | |||
| + | duration = 2 | ||
| + | |||
| + | format = 0x0001 | ||
| + | channels = 1 # Mono | ||
| + | rate = 44100 # 44100Hz Samplingrate | ||
| + | bytes_per_second = rate*2 | ||
| + | blockalign = 2 | ||
| + | bitssample = 16 | ||
| + | |||
| + | filesize = 44 + duration*bytes_per_second | ||
| + | |||
| + | wav = 0.chr*filesize | ||
| + | | ||
| + | wav[0..3] = " | ||
| + | wav[4..7] = to_le(filesize-8, | ||
| + | wav[8..11]=" | ||
| + | | ||
| + | wav[12..15] = "fmt " | ||
| + | wav[16..19] = to_le(16, | ||
| + | wav[20..21] = to_le(format, | ||
| + | wav[22..23] = to_le(channels, | ||
| + | wav[24..27] = to_le(rate, | ||
| + | wav[28..31] = to_le(bytes_per_second, | ||
| + | wav[32..33] = to_le(blockalign, | ||
| + | wav[34..35] = to_le(bitssample, | ||
| + | | ||
| + | wav[36..39] = " | ||
| + | wav[40..43] = to_le(filesize-44, | ||
| + | | ||
| + | offset = 44 | ||
| + | |||
| + | # Samples durchzählen | ||
| + | # s ist die Sample Nummer | ||
| + | for s in 0...(duration*rate) | ||
| + | # Entsprechende Zeit in Sekunden | ||
| + | t = s.to_f/rate | ||
| + | # Wert zum Zeitpunkt t | ||
| + | v = Math.sin(t*440*2*Math:: | ||
| + | # Wert in die " | ||
| + | | ||
| + | end | ||
| + | |||
| + | # Wav-Datei effektiv abspeichern | ||
| + | File.open(" | ||
| + | o.write(wav) | ||
| + | } | ||
| + | </ | ||
| + | </ | ||
| + | ==== Weitere Aufgaben ==== | ||
| + | * Experimentieren Sie mit anderen Funktionen. Fügen Sie z.B. Tremolo oder Vibrato dazu. Erzeugen Sie Rauschen. | ||
| + | * Erzeugen Sie zwei oder mehr Töne gleichzeitig (Funktionen einfach addieren, Aufgepasst auf Überläufe!). | ||
| + | |||
| + | === Melodien codieren === | ||
| + | * cdefgah stehen für die jeweiligen Töne. # bzw. b macht den folgenen Ton einen halben höher bzw. tiefer. | ||
| + | * + - wechselt eine Octave höher oder tiefer | ||
| + | * p steht für Pause | ||
| + | * 12345678 steht für die Länge in Achteln der folgenden Töne | ||
| + | |||
| + | Z.B. Alle meine Entchen ist dann | ||
| + | " | ||
| + | |||
| + | Oder dieses hier: | ||
| + | " | ||
| + | |||
| + | Schreiben Sie ein Programm, das die Melodie zum String erzeugt. | ||
| + | |||
| + | <hidden Anzeigen> | ||
| + | <code ruby synthi.rb> | ||
| + | def get_frequency(ton, | ||
| + | tabelle = {' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | " | ||
| + | | ||
| + | return 0.0 if ton==' | ||
| + | | ||
| + | halbton = tabelle[ton]+pm+12*oktave | ||
| + | lambda = 2.0**(1.0/ | ||
| + | return 440.0*(lambda**halbton); | ||
| + | end | ||
| + | |||
| + | # Schwingungsfunktion zum Zeitpunkt t mit Frequenz freq | ||
| + | # Werte zwischen -1 und 1 | ||
| + | def get_function(freq, | ||
| + | | ||
| + | end | ||
| + | |||
| + | # Liefert ein Array mit Ganzzahlen zurück | ||
| + | # (zwischen min -32000 und max 32000) | ||
| + | def get_samples(freq, | ||
| + | anzahl = (dauer*rate).to_i | ||
| + | return Array.new(anzahl) {|i| | ||
| + | | ||
| + | } | ||
| + | end | ||
| + | |||
| + | # to_le wandelt einen Zahlwert (wert) in ein Folge von einer gegbenen (bytes) Bytes um. | ||
| + | # Z.B. wird mit to_le(0x1020, | ||
| + | |||
| + | def to_le(wert, bytes=2) | ||
| + | res = 0.chr*bytes | ||
| + | bytes.times{|i| | ||
| + | res[i] = (wert & 0xff).chr | ||
| + | wert >> | ||
| + | } | ||
| + | return res # Resultat der Funktion | ||
| + | end | ||
| + | |||
| + | # lied: String | ||
| + | # Ausgabe: Array mit Ganzzahlen | ||
| + | def play(lied, dauer=0.2, rate=44100) | ||
| + | | ||
| + | pm = 0 | ||
| + | | ||
| + | | ||
| + | | ||
| + | case c | ||
| + | when ' | ||
| + | pm=-1 | ||
| + | when '#' | ||
| + | pm=1 | ||
| + | when ' | ||
| + | oktave+=1 | ||
| + | when ' | ||
| + | oktave-=1 | ||
| + | when ' | ||
| + | achtel = c.to_i | ||
| + | else | ||
| + | freq = get_frequency(c, | ||
| + | samples+=get_samples(freq, | ||
| + | end | ||
| + | } | ||
| + | | ||
| + | end | ||
| + | |||
| + | |||
| + | format = 0x0001 | ||
| + | channels = 1 # Mono | ||
| + | rate = 44100 # 44100Hz Samplingrate | ||
| + | bytes_per_second = rate*2 | ||
| + | blockalign = 2 | ||
| + | bitssample = 16 | ||
| + | |||
| + | lied = " | ||
| + | samples = play(lied, | ||
| + | |||
| + | # Uwandeln, zusammenfügen als String | ||
| + | samples = samples.map{|s| to_le(s, | ||
| + | |||
| + | |||
| + | filesize = 44 + samples.size | ||
| + | |||
| + | wav = 0.chr*44 | ||
| + | | ||
| + | wav[0..3] = " | ||
| + | wav[4..7] = to_le(filesize-8, | ||
| + | wav[8..11]=" | ||
| + | | ||
| + | wav[12..15] = "fmt " | ||
| + | wav[16..19] = to_le(16, | ||
| + | wav[20..21] = to_le(format, | ||
| + | wav[22..23] = to_le(channels, | ||
| + | wav[24..27] = to_le(rate, | ||
| + | wav[28..31] = to_le(bytes_per_second, | ||
| + | wav[32..33] = to_le(blockalign, | ||
| + | wav[34..35] = to_le(bitssample, | ||
| + | | ||
| + | wav[36..39] = " | ||
| + | wav[40..43] = to_le(filesize-44, | ||
| + | | ||
| + | |||
| + | |||
| + | # Wav-Datei effektiv abspeichern | ||
| + | File.open(" | ||
| + | o.write(wav) # Header | ||
| + | o.write(samples) | ||
| + | } | ||
| + | </ | ||
| + | </ | ||