Code: Alles auswählen
import wave
import array
import logging
import math
log = logging.getLogger("PyDC")
log.setLevel(logging.DEBUG)
log.addHandler(logging.StreamHandler())
WAV_ARRAY_TYPECODE = {
1: "b", # 8-bit wave file
2: "h", # 16-bit wave file
4: "l", # 32-bit wave file TODO: Test it
}
# Maximum volume value in wave files:
MAX_VALUES = {
1: 255, # 8-bit wave file
2: 32768, # 16-bit wave file
4: 2147483647, # 32-bit wave file
}
HUMAN_SAMPLEWIDTH = {
1: "8-bit",
2: "16-bit",
4: "32-bit",
}
def human_duration(t):
return u"%.1f sec" % round(t, 1)
def sinus_values(count, max_value):
"""
>>> values = list(sinus_values(10, 32768))
>>> len(values)
10
>>> values
[0, 21063, 32270, 28378, 11207, -11207, -28378, -32270, -21063, 0]
>>> tl = TextLevelMeter(32768, width=40)
>>> for v in values:
... tl.feed(v)
'| * |'
'| | * |'
'| | *|'
'| | * |'
'| | * |'
'| * | |'
'| * | |'
'|* | |'
'| * | |'
'| * |'
"""
count -= 1
for index in xrange(0, count + 1):
angle = 360.0 / count * index
y = math.sin(math.radians(angle)) * max_value
y = int(round(y))
yield y
def sinus_values_by_hz(framerate, hz, max_value):
"""
Create sinus values with the given framerate and Hz.
Note:
We skip the first zero-crossing, so the values can be used directy in a loop.
>>> values = sinus_values_by_hz(22050, 1200, 255)
>>> len(values) # 22050 / 1200Hz = 18,375
18
>>> values
(87, 164, 221, 251, 251, 221, 164, 87, 0, -87, -164, -221, -251, -251, -221, -164, -87, 0)
>>> tl = TextLevelMeter(255, width=40)
>>> for v in values:
... tl.feed(v)
'| | * |'
'| | * |'
'| | * |'
'| | *|'
'| | *|'
'| | * |'
'| | * |'
'| | * |'
'| * |'
'| * | |'
'| * | |'
'| * | |'
'|* | |'
'|* | |'
'| * | |'
'| * | |'
'| * | |'
'| * |'
>>> values = sinus_values_by_hz(44100, 1200, 255)
>>> len(values) # 44100 / 1200Hz = 36,75
37
"""
count = int(round(float(framerate) / float(hz)))
count += 1
values = tuple(sinus_values(count, max_value))
values = values[1:]
return values
class BaseConfig(object):
FRAMERATE = 22050
SAMPLEWIDTH = 2 # 1 for 8-bit, 2 for 16-bit, 4 for 32-bit samples
VOLUME_RATIO = 90 # "Loundness" in percent of the created wave file
class WaveBase(object):
def __init__(self, destination_filepath, cfg):
self.destination_filepath = destination_filepath
self.cfg = cfg
self.wavefile = wave.open(destination_filepath, "wb")
self.typecode = self.get_typecode(cfg.SAMPLEWIDTH)
self.wavefile.setnchannels(1) # Mono
self.wavefile.setsampwidth(cfg.SAMPLEWIDTH)
self.wavefile.setframerate(cfg.FRAMERATE)
self.set_wave_properties()
wave_max_value = MAX_VALUES[self.cfg.SAMPLEWIDTH]
self.used_max_values = int(round(
float(wave_max_value) / 100 * self.cfg.VOLUME_RATIO
))
log.info("Create %s wave file with %sHz and %s max volumen (%s%%)" % (
HUMAN_SAMPLEWIDTH[self.cfg.SAMPLEWIDTH],
self.cfg.FRAMERATE,
self.used_max_values, self.cfg.VOLUME_RATIO
))
def get_typecode(self, samplewidth):
try:
typecode = WAV_ARRAY_TYPECODE[samplewidth]
except KeyError:
raise NotImplementedError(
"Only %s wave files are supported, yet!" % (
", ".join(["%sBit" % (i * 8) for i in WAV_ARRAY_TYPECODE.keys()])
)
)
return typecode
def pformat_pos(self):
sec = float(self.wave_pos) / self.framerate / self.samplewidth
return "%s (frame no.: %s)" % (human_duration(sec), self.wave_pos)
def _hz2duration(self, hz):
return hz2duration(hz, framerate=self.framerate)
def _duration2hz(self, duration):
return duration2hz(duration, framerate=self.framerate)
@property
def wave_pos(self):
pos = self.wavefile._nframeswritten * self.samplewidth
return pos
def set_wave_properties(self):
self.framerate = self.wavefile.getframerate() # frames / second
self.samplewidth = self.wavefile.getsampwidth() # 1 for 8-bit, 2 for 16-bit, 4 for 32-bit samples
self.max_value = MAX_VALUES[self.samplewidth]
self.nchannels = self.wavefile.getnchannels() # typically 1 for mono, 2 for stereo
print "Framerate: %sHz samplewidth: %i (%sBit, max volume value: %s) channels: %s" % (
self.framerate,
self.samplewidth, self.samplewidth * 8, self.max_value,
self.nchannels,
)
assert self.nchannels == 1, "Only MONO files are supported, yet!"
def get_samples(self, hz):
values = tuple(
sinus_values_by_hz(self.cfg.FRAMERATE, hz, self.used_max_values)
)
real_hz = float(self.cfg.FRAMERATE) / len(values)
#~ log.debug("Real sinus frequency: %.2fHz" % real_hz)
return array.array(self.typecode, values)
def write_sinus(self, hz):
start_pos = self.pformat_pos()
samples = self.get_samples(hz)
print "sinus sample length:", len(samples)
self.wavefile.writeframes(samples)
log.debug("Write %sHz sinus %s - %s" % (
hz, start_pos, self.pformat_pos()
))
def write_silence(self, sec):
start_pos = self.pformat_pos()
count = int(round(sec * self.framerate * self.samplewidth))
silence = [0x00 for _ in xrange(count)]
silence = array.array(self.typecode, silence)
self.wavefile.writeframes(silence)
log.debug("Write %ssec. silence %s - %s" % (
sec, start_pos, self.pformat_pos()
))
def close(self):
self.wavefile.close()
log.info("Wave file '%s' written (%s)" % (
self.destination_filepath, self.pformat_pos()
))
cfg = BaseConfig()
w=WaveBase("test.wav", cfg)
w.write_silence(sec=1)
w.write_sinus(hz=100)
w.write_silence(sec=0.1)
w.write_sinus(hz=200)
w.write_silence(sec=0.1)
w.write_sinus(hz=10)
w.write_silence(sec=1)
w.close()
Beim öffnen der wave Datei sollte man eigentlich drei Sinuskurven in Unterschiedlicher Länge sehen.Framerate: 22050Hz samplewidth: 2 (16Bit, max volume value: 32768) channels: 1
Create 16-bit wave file with 22050Hz and 29491 max volumen (90%)
Write 1sec. silence 0.0 sec (frame no.: 0) - 1.0 sec (frame no.: 44100)
sinus sample length: 221
Write 100Hz sinus 1.0 sec (frame no.: 44100) - 1.0 sec (frame no.: 44320)
Write 0.1sec. silence 1.0 sec (frame no.: 44320) - 1.1 sec (frame no.: 48730)
sinus sample length: 110
Write 200Hz sinus 1.1 sec (frame no.: 48730) - 1.1 sec (frame no.: 48840)
Write 0.1sec. silence 1.1 sec (frame no.: 48840) - 1.2 sec (frame no.: 53250)
sinus sample length: 2205
Write 10Hz sinus 1.2 sec (frame no.: 53250) - 1.3 sec (frame no.: 55454)
Write 1sec. silence 1.3 sec (frame no.: 55454) - 2.3 sec (frame no.: 99554)
Wave file 'test.wav' written (2.3 sec (frame no.: 99554))
Ich sehe allerdings nur zwei. Hinten fehlt immer etwas...
Jemand eine Idee warum?
EDIT: Die geöffnete WAVE Datei ist allerdings auch 2,3Sek lang. Sie hat allerdings 49778 Samples. Wobei 99554/2 = 49777 ist. Also eigentlich richtig...