efinf:blc2016:bitsundbytes:wavdatei

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).

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

String manipulation

Die Daten zuerst in einem String geschrieben. Damit das effizient ist, wird ein String der korrekten Länge angelegt:

  filesize = ????
  wav = 0.chr*filesize  # String mit ASCII 0 gefüllt.

Der String kann dann direkt veränder werden, z.B. mit

  wav[8..11]="WAVE"  # Ersetze die Bytes 8 bis 11 mit den 4 Bytes der ASCII-Codes von "WAVE"

Little-Endian Kodierung

Da wir viele Werte Little-Endian kodieren werden müssen, wird eine Funktion definiert:

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)

Konstanten des Programms

Wir definieren zuerst die Eckdaten unserer Wav-Datei:

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
#

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).

  sq32 = Math.sin(30/180.0*Math::PI)

Ausgabe in eine Datei

Die Datei wird in dem Verzeichnis erzeugt (bzw. überschrieben), wo das Programm gestartet wird.

File.open("output.wav","w"){|o|
  o.write(wav)
} 

Code anzeigen

Code anzeigen

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)
}
  • 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.

Anzeigen

Anzeigen

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
}
  • efinf/blc2016/bitsundbytes/wavdatei.txt
  • Last modified: 2016/09/20 15:11
  • by Ivo Blöchliger