* Alf P. Steinbach:
Just as a contribution, ...

The original code I posted was only written for Python 3.1.1 (because the code was for my writings which assumes 3.x). In the simple_sound module this caused a deprecation warning with 2.x. And the example program didn't work with 2.x.

I've now installed CPython 2.6.4 and fixed the code so that it works nicely also with that version of Python.


<code file="simple_sound.py">
"Generate simple mono (single-channel) [.wav], [.aiff] or [.aifc] files."

# Works with Python 2.6.4 and Python 3.1.1, but has not been extensively tested.
# Author: Alf P. Steinbach.
#
# Changes from original 3.1.1 version:
# * A deprecation warning suppressed by explicit cast to int.
# * The default sound library is now not imported until it's actually used.
# * Added exception handling (for the code's original purpose I couldn't).
#
# Notes:
# (1) It might be possible to optimize this by using array of 16-bit integers, 
then
#     checking 'sys.byteorder' and doing a 'data.byteswap()' call as 
appropriate.
# (2) Data is kept in memory until 'close' due to a bug in the 'wave' module. That bug
#     has now been fixed. But it may be present in any Python installation.

import collections
import array
import math

default_sample_rate     = 44100             # Usual CD quality.

def sample_sawtooth( freq, t ):
    linear = freq*t % 1.0
    return 2*linear - 1.0

def sample_square( freq, t ):
    linear = freq*t % 1.0
    if linear < 0.5:
        return -1.0
    else:
        return 1.0

def sample_triangle( freq, t ):
    linear = freq*t % 1.0
    if linear < 0.5:
        return 4.0*linear - 1.0
    else:
        return 3.0 - 4.0*linear

def sample_sine( freq, t ):
    return math.sin( 2*math.pi*freq*t )

DataFormat              = collections.namedtuple( "DataFormat",
    "open_func, append_int16_func"
    )

def _append_as_big_endian_int16_to( bytes_array, i ):
    if i < 0:
        i = i + 65536
    assert( 0 <= i < 65536 )
    bytes_array.append( i // 256 )
    bytes_array.append( i % 256 )

def _append_as_little_endian_int16_to( bytes_array, i ):
    if i < 0:
        i = i + 65536
    assert( 0 <= i < 65536 )
    bytes_array.append( i % 256 )
    bytes_array.append( i // 256 )

def aiff_format():
    import aifc
    return DataFormat( aifc.open, _append_as_big_endian_int16_to )

def wav_format():
    import wave
    return DataFormat( wave.open, _append_as_little_endian_int16_to )

class Writer:
    "Writes normalized samples to a specified file or file-like object"
    def __init__(
        self,
        filename,
        sample_rate     = default_sample_rate,
        data_format     = None
        ):

        if data_format is None:
            data_format = aiff_format()
        self._sample_rate = sample_rate
        self._append_int16_func = data_format.append_int16_func
        self._writer = data_format.open_func( filename, "w" )
        self._writer.setnchannels( 1 )
        self._writer.setsampwidth( 2 )          # 2 bytes = 16 bits
        self._writer.setframerate( sample_rate )
        self._samples = []

    def sample_rate( self ):
        return self._sample_rate

    def write( self, normalized_sample ):
        assert( -1 <= normalized_sample <= +1 )
        self._samples.append( normalized_sample )

    def close( self ):
        try:
            data = array.array( "B" )               # B -> unsigned bytes.
            append_int16_to = self._append_int16_func
            for sample in self._samples:
                level = int( round( 32767*sample ) )
                append_int16_to( data, level )
            self._writer.setnframes( len( self._samples ) )
            self._writer.writeframes( data )
        finally:
            self._writer.close()

def example( filename = "ringtone.aiff" ):
    global default_sample_rate

    sample_rate = default_sample_rate
    total_time  = 2
    n_samples   = sample_rate*total_time

    writer = Writer( filename )
    for i in range( n_samples ):
        t = 1.0*i/sample_rate
        samples = (
            sample_sine( 440, t ),
            sample_sine( (5.0/4.0)*440, t ),
            )
        sample = sum( samples )/len( samples )
        writer.write( sample )
    writer.close()

if __name__ == "__main__":
    example()
</code>


Cheers, & enjoy!,

- Alf
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to