efinf:blc2016:bitsundbytes:wavdatei

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
efinf:blc2016:bitsundbytes:wavdatei [2016/09/11 08:44] – [Ruby Details] Ivo Blöchligerefinf: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, aufgenommen z.B. vom menschlichen Ohr.
 +
 +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://de.wikipedia.org/wiki/RIFF_WAVE#Beispiel_eines_allgemein_lesbaren_WAVE-PCM-Formats
 +
 +==== 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  # String mit ASCII 0 gefüllt.
 +</code>
 +Der String kann dann direkt veränder werden, z.B. mit
 +<code ruby>
 +  wav[8..11]="WAVE"  # Ersetze die Bytes 8 bis 11 mit den 4 Bytes der ASCII-Codes von "WAVE"
 +</code>
 +=== 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)  # Wird kein zweiter Parameter angegeben, ist dieser automatisch 2
 +  res = 0.chr*bytes       # String vorbereiten
 +  bytes.times{|i|         # Bytes durchgehen
 +    res[i] = (wert & 0xff).chr    # Wert in 8-Bit-ASCII umwandeln (nicht zwingend ein Symbol!)
 +    wert >>=8             # Kurzform für:  wert = wert >> 8
 +  }
 +  return res              # Resultat der Funktion
 +end
 +#Zum Testen:
 +puts to_le(1819242339, 4)
 +</code>
 +
 +=== Konstanten des Programms ===
 +Wir definieren zuerst die Eckdaten unserer Wav-Datei:
 +<code ruby>
 +duration = 2
 +
 +format = 0x0001      # Siehe Dokumentation
 +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
 +#
 +</code>
 +
 +=== Sinus ===
 +Um eine Schwingung darzustellen, kann die Sinus-Funktion verwendet werden (andere periodische Funktionen sind aber auch möglich, z.B. Sägezahn, Dreieck oder Rechteck).
 +<code ruby>
 +  sq32 = Math.sin(30/180.0*Math::PI)
 +</code>
 +
 +=== Ausgabe in eine Datei ===
 +Die Datei wird in dem Verzeichnis erzeugt (bzw. überschrieben), wo das Programm gestartet wird.
 +<code ruby>
 +File.open("output.wav","w"){|o|
 +  o.write(wav)
 +
 +</code>
 +
 +
 +==== 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,4) die Folge 0x20,0x10,0x00,0x00 von 4 Bytes erzeugt.
 +
 +def to_le(wert, bytes=2)  # Wird kein zweiter Parameter angegeben, ist dieser automatisch 2
 +  res = 0.chr*bytes       # String vorbereiten
 +  bytes.times{|i|         # Bytes durchgehen
 +    res[i] = (wert & 0xff).chr    # Wert in 8-Bit-ASCII umwandeln (nicht zwingend ein Symbol!)
 +    wert >>=8             # Kurzform für:  wert = wert >> 8
 +  }
 +  return res              # Resultat der Funktion
 +end
 +
 +duration = 2
 + 
 +format = 0x0001      # Siehe Dokumentation
 +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] = "RIFF"                                                                                                                                                                                                
 +wav[4..7] = to_le(filesize-8, 4)                                                                                                                                                                                  
 +wav[8..11]="WAVE"                                                                                                                                                                                                 
 +                                                                                                                                                                                                                  
 +wav[12..15] = "fmt "                                                                                                                                                                                              
 +wav[16..19] = to_le(16,4)                                                                                                                                                                                         
 +wav[20..21] = to_le(format,2)                                                                                                                                                                                     
 +wav[22..23] = to_le(channels,2)                                                                                                                                                                                   
 +wav[24..27] = to_le(rate,4)                                                                                                                                                                                       
 +wav[28..31] = to_le(bytes_per_second,4)                                                                                                                                                                           
 +wav[32..33] = to_le(blockalign,2)                                                                                                                                                                                 
 +wav[34..35] = to_le(bitssample,2)                                                                                                                                                                                 
 +                                                                                                                                                                                                                  
 +wav[36..39] = "data"                                                                                                                                                                                              
 +wav[40..43] = to_le(filesize-44,4)                                                                                                                                                                                
 +                                                                                                                                                                                                                  
 +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::PI)*30000
 +   # Wert in die "WAV-Datei" schreiben
 +   wav[(s*2+offset)..(s*2+offset+1)] = to_le(v.to_i,2)
 +end
 +
 +# Wav-Datei effektiv abspeichern
 +File.open("output.wav","w"){|o|
 +  o.write(wav)
 +}
 +</code>
 +</hidden>
 +==== 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 
 +  "2cdef4gg2aaaa8g2aaaa8g2ffff4ee2dddd8c"
 +
 +Oder dieses hier: 
 +  "2ebeef4ea2ebeef4eba2ebeefe+d-h#gfed8c2a#ggf4a+d2-agfe4a+c2c-hah#d#faa+c-ha#ge#gh+e-2ebeef4ea2ebeef4eba2ebeefe+d-h#gfed8c2a#ggf4a+d2-agfe4a+c2c-hahe#gh+edc-h8a"
 +
 +Schreiben Sie ein Programm, das die Melodie zum String erzeugt.
 +
 +<hidden Anzeigen>
 +<code ruby synthi.rb>
 +def get_frequency(ton, pm, oktave)
 +    tabelle = {'a'=>0,
 +               'h'=>2,
 +               'c'=>-9,
 +               'd'=>-7,
 +               'e'=>-5,
 +               'f'=>-4,
 +               "g"=>-2}
 +    
 +    return 0.0 if ton=='p'
 +    
 +    halbton = tabelle[ton]+pm+12*oktave
 +    lambda = 2.0**(1.0/12.0)
 +    return 440.0*(lambda**halbton);
 +end
 +
 +# Schwingungsfunktion zum Zeitpunkt t mit Frequenz freq
 +# Werte zwischen -1 und 1
 +def get_function(freq, t) 
 +   return Math.sin(freq*2*t*Math::PI)*(0.5**(t/0.1)); 
 +end
 +
 +# Liefert ein Array mit Ganzzahlen zurück
 +# (zwischen min -32000 und max 32000)
 +def get_samples(freq, dauer, rate=44100)
 +    anzahl = (dauer*rate).to_i
 +    return Array.new(anzahl) {|i|
 +       (get_function(freq, i.to_f/rate)*20000).to_i                
 +    }
 +end
 +
 +# to_le wandelt einen Zahlwert (wert) in ein Folge von einer gegbenen (bytes) Bytes um.
 +# Z.B. wird mit to_le(0x1020,4) die Folge 0x20,0x10,0x00,0x00 von 4 Bytes erzeugt.
 +
 +def to_le(wert, bytes=2)  # Wird kein zweiter Parameter angegeben, ist dieser automatisch 2
 +  res = 0.chr*bytes       # String vorbereiten
 +  bytes.times{|i|         # Bytes durchgehen
 +    res[i] = (wert & 0xff).chr    # Wert in 8-Bit-ASCII umwandeln (nicht zwingend ein Symbol!)
 +    wert >>=8             # Kurzform für:  wert = wert >> 8
 +  }
 +  return res              # Resultat der Funktion
 +end
 +
 +# lied: String
 +# Ausgabe: Array mit Ganzzahlen
 +def play(lied, dauer=0.2, rate=44100)
 +   achtel = 2
 +   pm = 0
 +   oktave = 0
 +   samples = []
 +   lied.each_char{|c|
 +        case c
 +        when 'b'
 +            pm=-1
 +        when '#'
 +            pm=1
 +        when '+'
 +            oktave+=1
 +        when '-'
 +            oktave-=1
 +        when '1'..'9'
 +            achtel = c.to_i
 +        else
 +            freq = get_frequency(c, pm, oktave);
 +            samples+=get_samples(freq, achtel*dauer, rate);
 +        end
 +   }
 +   return samples
 +end
 +
 + 
 +format = 0x0001      # Siehe Dokumentation
 +channels = 1         # Mono
 +rate = 44100         # 44100Hz Samplingrate
 +bytes_per_second = rate*2
 +blockalign = 2
 +bitssample = 16
 +
 +lied = "2cdef4gg2aaaa8g2aaaa8g2ffff4ee2dddd8c"
 +samples = play(lied,0.1)
 +
 +# Uwandeln, zusammenfügen als String
 +samples = samples.map{|s| to_le(s,2)}.join("")
 +
 + 
 +filesize = 44 + samples.size
 + 
 +wav = 0.chr*44                                                                                                                                                                                              
 +                                                                                                                                                                                                                  
 +wav[0..3] = "RIFF"                                                                                                                                                                                                
 +wav[4..7] = to_le(filesize-8, 4)                                                                                                                                                                                  
 +wav[8..11]="WAVE"                                                                                                                                                                                                 
 +                                                                                                                                                                                                                  
 +wav[12..15] = "fmt "                                                                                                                                                                                              
 +wav[16..19] = to_le(16,4)                                                                                                                                                                                         
 +wav[20..21] = to_le(format,2)                                                                                                                                                                                     
 +wav[22..23] = to_le(channels,2)                                                                                                                                                                                   
 +wav[24..27] = to_le(rate,4)                                                                                                                                                                                       
 +wav[28..31] = to_le(bytes_per_second,4)                                                                                                                                                                           
 +wav[32..33] = to_le(blockalign,2)                                                                                                                                                                                 
 +wav[34..35] = to_le(bitssample,2)                                                                                                                                                                                 
 +                                                                                                                                                                                                                  
 +wav[36..39] = "data"                                                                                                                                                                                              
 +wav[40..43] = to_le(filesize-44,4)                                                                                                                                                                                
 +                                                                                                                                                                                                                  
 +
 +
 +# Wav-Datei effektiv abspeichern
 +File.open("output.wav","w"){|o|
 +  o.write(wav) # Header
 +  o.write(samples)  # PCM-Daten
 +}
 +</code>
 +</hidden>