erzeugte Wave Datei ist abgeschnitten...

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

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()
Ausgabe ist:
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))
Beim öffnen der wave Datei sollte man eigentlich drei Sinuskurven in Unterschiedlicher Länge sehen.
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...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ah! hab array.array durch struct.pack ersetzt und nun ist alles richtig.

Code: Alles auswählen

import wave
import logging
import math
import struct

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 write_sinus(self, hz):
        start_pos = self.pformat_pos()

        values = sinus_values_by_hz(
            self.cfg.FRAMERATE, hz, self.used_max_values
        )
        self.write_values(values)

        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)]
        self.write_values(silence)
        log.debug("Write %ssec. silence %s - %s" % (
            sec, start_pos, self.pformat_pos()
        ))

    def write_values(self, values):
        samples = "".join([struct.pack(self.typecode, value) for value in values])
        self.wavefile.writeframes(samples)

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

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ach und schneller und schöner geht es damit:

Code: Alles auswählen

    def write_values(self, values):
        values = tuple(values) # for iterator/generator objects
        value_length = len(values)
        pack_format = "%i%s" % (value_length, self.typecode)
        samples = struct.pack(pack_format, *values)
        self.wavefile.writeframes(samples)
Frage: Wie kann man das einfacher machen:

Code: Alles auswählen

silence = [0x00 for _ in xrange(count)]
???

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@writeframes: Alternativ hätte man das `array` vielleicht in eine Zeichen-/Bytekette umwandeln können bevor man es schreibt.

``silence = [0] * length``?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:``silence = [0] * length``?
Gute Idee, danke! ...und übernommen: https://github.com/jedie/PyDragon32/com ... b83#L2L575

Ich dachte das ergebnis von array.array und struct.pack wäre quasi das gleiche...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten