A simple-to-use sound file writer

S

Steve Holden

Alf said:
* Steve Holden:
Though for what it's worth I wasn't impressed by the results of running
the posted program, since it yielded an AIFF file of mostly zeroes that
produced no audible sound.

$ od -bc sinewave.aiff
0000000 106 117 122 115 000 002 261 076 101 111 106 106 103 117 115 115
F O R M \0 002 261 > A I F F C O M M
0000020 000 000 000 022 000 001 000 001 130 210 000 020 100 016 254 104
\0 \0 \0 022 \0 001 \0 001 X 210 \0 020 @ 016 254 D
0000040 000 000 000 000 000 000 123 123 116 104 000 002 261 030 000 000
\0 \0 \0 \0 \0 \0 S S N D \0 002 261 030 \0 \0
0000060 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
\0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0530500 000 000 000 000 000 000
\0 \0 \0 \0 \0 \0
0530506

Any idea what I did wrong?

That sounds like something I did wrong, not like something you did wrong:

It sounds like a ... BUG! ... in my simple_writer code. :)

Or, that's perhaps not funny, but it occurred to me that it might, to
some at least, appear to be sort of incongruous in the context of the
earlier thread. Heh.

Checking first 20 sample values generated:

<code>
if True:
f = 440
sample_rate = 44100
total_time = 2
n_samples = sample_rate*total_time

writer = simple_sound.Writer( "sinewave.aiff" )
for i in range( n_samples ):
t = 1*i/sample_rate
sample = sample_squares( f, t )
if i < 20: print( sample ) # Check 'em
writer.write( sample )
writer.close()
</code>

<output>
-0.0314107590781
-0.0314107590781
-0.0941083133185
-0.15643446504
-0.218143241397
-0.278991106039
-0.338737920245
-0.397147890635
-0.45399049974
-0.50904141575
-0.562083377852
-0.612907053653
-0.661311865324
-0.707106781187
-0.75011106963
-0.790155012376
-0.827080574275
-0.860742027004
-0.891006524188
-0.917754625684
</output>

Checking generated file:

<dump>
$ od -bc sinewave.aiff | head
0000000 106 117 122 115 000 001 130 266 101 111 106 106 103 117 115 115
F O R M \0 001 X 266 A I F F C O M M
0000020 000 000 000 022 000 001 000 000 254 104 000 020 100 016 254 104
\0 \0 \0 022 \0 001 \0 \0 254 D \0 020 @ 016 254 D
0000040 000 000 000 000 000 000 123 123 116 104 000 001 130 220 000 000
\0 \0 \0 \0 \0 \0 S S N D \0 001 X 220 \0 \0
0000060 000 000 000 000 000 000 373 373 373 373 363 364 353 372 344 024
\0 \0 \0 \0 \0 \0 373 373 373 373 363 364 353 372 344 024
0000100 334 112 324 245 315 053 305 344 276 330 270 016 261 215 253 133
334 J 324 245 315 + 305 344 276 330 270 016 261 215 253 [
</dump>


Hm, I'm inclined to think that you used Python 2.x instead of my 3.1.1!

I no longer have Python 2.x installed, I think, so no time to test that
now.

But would that be the case?

If so, perhaps changing "t = 1*i/sample_rate" to "t =
(1.0*i)/sample_rate" will help?


Cheers,

- Alf

That was indeed the case. So here you have an interesting example of a
piece of code that is pathological in Python2. All you have to change is
to add

from __future__ import __division__

and bingo! It's a multi-language program. But try seeing what 2to3 says
about your Python3 code :)

I will forgive you the omission of the ".0" because I too would assume
that it would be slower. Of course another technique is to scale the
already floating-point raw values before dividing by the range of the
analog output - then an integer division should work basically fine?

Under Python 3 I see

0000000 106 117 122 115 000 002 261 076 101 111 106 106 103 117 115 115
F O R M \0 002 261 > A I F F C O M M
0000020 000 000 000 022 000 001 000 001 130 210 000 020 100 016 254 104
\0 \0 \0 022 \0 001 \0 001 X 210 \0 020 @ 016 254 D
0000040 000 000 000 000 000 000 123 123 116 104 000 002 261 030 000 000
\0 \0 \0 \0 \0 \0 S S N D \0 002 261 030 \0 \0
0000060 000 000 000 000 000 000 373 373 373 373 363 364 353 372 344 024
\0 \0 \0 \0 \0 \0 373 373 373 373 363 364 353 372 344 024
0000100 334 112 324 245 315 053 305 344 276 330 270 016 261 215 253 133
334 J 324 245 315 + 305 344 276 330 270 016 261 215 253 [
0000120 245 176 237 375 232 335 226 043 221 324 215 364 212 210 207 222
245 ~ 237 375 232 335 226 # 221 324 215 364 212 210 207 222
0000140 205 026 203 026 201 224 200 222 200 021 200 021 200 222 201 224
205 026 203 026 201 224 200 222 200 021 200 021 200 222 201 224
0000160 203 026 205 026 207 222 212 210 215 364 221 324 226 043 232 335
203 026 205 026 207 222 212 210 215 364 221 324 226 # 232 335
0000200 237 375 245 176 253 133 261 215 270 016 276 330 305 344 315 053
237 375 245 ~ 253 [ 261 215 270 016 276 330 305 344 315 +
0000220 324 245 334 112 344 024 353 372 363 364 373 373 004 005 014 014
324 245 334 J 344 024 353 372 363 364 373 373 004 005 \f \f


and so on, but I still get silence from the Quicktime player.

regards
Steve
 
S

Steve Holden

Alf said:
* Steve Holden:
Though for what it's worth I wasn't impressed by the results of running
the posted program, since it yielded an AIFF file of mostly zeroes that
produced no audible sound.

$ od -bc sinewave.aiff
0000000 106 117 122 115 000 002 261 076 101 111 106 106 103 117 115 115
F O R M \0 002 261 > A I F F C O M M
0000020 000 000 000 022 000 001 000 001 130 210 000 020 100 016 254 104
\0 \0 \0 022 \0 001 \0 001 X 210 \0 020 @ 016 254 D
0000040 000 000 000 000 000 000 123 123 116 104 000 002 261 030 000 000
\0 \0 \0 \0 \0 \0 S S N D \0 002 261 030 \0 \0
0000060 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
\0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0530500 000 000 000 000 000 000
\0 \0 \0 \0 \0 \0
0530506

Any idea what I did wrong?

That sounds like something I did wrong, not like something you did wrong:

It sounds like a ... BUG! ... in my simple_writer code. :)

Or, that's perhaps not funny, but it occurred to me that it might, to
some at least, appear to be sort of incongruous in the context of the
earlier thread. Heh.

Checking first 20 sample values generated:

<code>
if True:
f = 440
sample_rate = 44100
total_time = 2
n_samples = sample_rate*total_time

writer = simple_sound.Writer( "sinewave.aiff" )
for i in range( n_samples ):
t = 1*i/sample_rate
sample = sample_squares( f, t )
if i < 20: print( sample ) # Check 'em
writer.write( sample )
writer.close()
</code>

<output>
-0.0314107590781
-0.0314107590781
-0.0941083133185
-0.15643446504
-0.218143241397
-0.278991106039
-0.338737920245
-0.397147890635
-0.45399049974
-0.50904141575
-0.562083377852
-0.612907053653
-0.661311865324
-0.707106781187
-0.75011106963
-0.790155012376
-0.827080574275
-0.860742027004
-0.891006524188
-0.917754625684
</output>

Checking generated file:

<dump>
$ od -bc sinewave.aiff | head
0000000 106 117 122 115 000 001 130 266 101 111 106 106 103 117 115 115
F O R M \0 001 X 266 A I F F C O M M
0000020 000 000 000 022 000 001 000 000 254 104 000 020 100 016 254 104
\0 \0 \0 022 \0 001 \0 \0 254 D \0 020 @ 016 254 D
0000040 000 000 000 000 000 000 123 123 116 104 000 001 130 220 000 000
\0 \0 \0 \0 \0 \0 S S N D \0 001 X 220 \0 \0
0000060 000 000 000 000 000 000 373 373 373 373 363 364 353 372 344 024
\0 \0 \0 \0 \0 \0 373 373 373 373 363 364 353 372 344 024
0000100 334 112 324 245 315 053 305 344 276 330 270 016 261 215 253 133
334 J 324 245 315 + 305 344 276 330 270 016 261 215 253 [
</dump>


Hm, I'm inclined to think that you used Python 2.x instead of my 3.1.1!

I no longer have Python 2.x installed, I think, so no time to test that
now.

But would that be the case?

If so, perhaps changing "t = 1*i/sample_rate" to "t =
(1.0*i)/sample_rate" will help?


Cheers,

- Alf

That was indeed the case. So here you have an interesting example of a
piece of code that is pathological in Python2. All you have to change is
to add

from __future__ import __division__

and bingo! It's a multi-language program. But try seeing what 2to3 says
about your Python3 code :)

I will forgive you the omission of the ".0" because I too would assume
that it would be slower. Of course another technique is to scale the
already floating-point raw values before dividing by the range of the
analog output - then an integer division should work basically fine?

Under Python 3 I see

0000000 106 117 122 115 000 002 261 076 101 111 106 106 103 117 115 115
F O R M \0 002 261 > A I F F C O M M
0000020 000 000 000 022 000 001 000 001 130 210 000 020 100 016 254 104
\0 \0 \0 022 \0 001 \0 001 X 210 \0 020 @ 016 254 D
0000040 000 000 000 000 000 000 123 123 116 104 000 002 261 030 000 000
\0 \0 \0 \0 \0 \0 S S N D \0 002 261 030 \0 \0
0000060 000 000 000 000 000 000 373 373 373 373 363 364 353 372 344 024
\0 \0 \0 \0 \0 \0 373 373 373 373 363 364 353 372 344 024
0000100 334 112 324 245 315 053 305 344 276 330 270 016 261 215 253 133
334 J 324 245 315 + 305 344 276 330 270 016 261 215 253 [
0000120 245 176 237 375 232 335 226 043 221 324 215 364 212 210 207 222
245 ~ 237 375 232 335 226 # 221 324 215 364 212 210 207 222
0000140 205 026 203 026 201 224 200 222 200 021 200 021 200 222 201 224
205 026 203 026 201 224 200 222 200 021 200 021 200 222 201 224
0000160 203 026 205 026 207 222 212 210 215 364 221 324 226 043 232 335
203 026 205 026 207 222 212 210 215 364 221 324 226 # 232 335
0000200 237 375 245 176 253 133 261 215 270 016 276 330 305 344 315 053
237 375 245 ~ 253 [ 261 215 270 016 276 330 305 344 315 +
0000220 324 245 334 112 344 024 353 372 363 364 373 373 004 005 014 014
324 245 334 J 344 024 353 372 363 364 373 373 004 005 \f \f


and so on, but I still get silence from the Quicktime player.

regards
Steve
 
A

Alf P. Steinbach

* Steve Holden:
Alf said:
* Steve Holden:
[snip]
So here you have an interesting example of a
piece of code that is pathological in Python2. All you have to change is
to add

from __future__ import __division__

and bingo! It's a multi-language program. But try seeing what 2to3 says
about your Python3 code :)

I will forgive you the omission of the ".0" because I too would assume
that it would be slower.

I did not make any such assumption, no. The *1 just lingered from some testing.


[snip]
and so on, but I still get silence from the Quicktime player.

I don't know, sorry.

It might be that [simple_sound] also needs the "from __future__" treatment.

But anyway, with 2.x compatible code you can now see that the sample values
produced are correct (just print them, or graph them).


Cheers & hth.,

- Alf
 
A

Alf P. Steinbach

* Alf P. Steinbach:
* Steve Holden:
Alf said:
* Steve Holden:
Alf P. Steinbach wrote:
* Grant Edwards:

I will, however, observe that your definition of a square wave is
what I
would have to call a "'square' wave" (and would prefer to call a
"pulse
train"), as I envisage a square wave as a waveform having a 50% duty
cycle, as in

___ ___
| | | |
| | | |
| | | |
+---+---+---+---+ and so on ad infinitum, (though I might allow you
| | | | to adjust the position
| | | | of y=0 if you want)
|___| |___|
That is a square wave.

as opposed to your

_
| |
| |
______| |______ ______
| |
| |
|_|
That isn't.

Arguing to the contrary is just being Humpty Dumpty...
Neither I nor Steve has called that latter wave a square wave.

Steve, quoted above, has written that I defined a square wave that
way.
I have not. So Steve's statement is a misrepresentation (I
described it
as a sum of two square waves, which it is), whatever the reason for
that
misrepresentation.
[snip]
So here you have an interesting example of a
piece of code that is pathological in Python2. All you have to change is
to add

from __future__ import __division__

and bingo! It's a multi-language program. But try seeing what 2to3 says
about your Python3 code :)

I will forgive you the omission of the ".0" because I too would assume
that it would be slower.

I did not make any such assumption, no. The *1 just lingered from some
testing.


[snip]
and so on, but I still get silence from the Quicktime player.

I don't know, sorry.

It might be that [simple_sound] also needs the "from __future__" treatment.

But anyway, with 2.x compatible code you can now see that the sample
values produced are correct (just print them, or graph them).

I have now installed CPython 2.6.4 and the code presented upthread (the one with
divisions fixed for 2.x compatibility) worked nicely with CPython 2.6.4, even
though with a deprecation warning.

It produced the exact same sound file (bytes) as with CPython 3.1.1.

And the QuickTime 7.3 player played it with no problem, a base A pure sinewave,
composed utterly of 50% duty cycle square waves, resounding in my headphones --
it also works with WinAmp 5.54, Windows Mediaplayer 10.00 and VLC 0.9.9, i.e.
I'm unable to reproduce the problem.

So I suspect that you may lack a suitable codec, or it might be a PEBKAC problem?


Cheers & hth.,

- Alf

PS: I had expected some jaggies and such since I only divided the cycle into 100
intervals, but graphing the data in Excel it showed a perfect sine. :)
 
A

Alf P. Steinbach

* 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
 
T

TimmyGee

Alf P. Steinbach wrote:

[...]> Perhaps you'd also admit to being wrong, and retract your innuoendo etc.?

Disregarding any matters of right or wrong (for this post, at least), I
herebe retract anything I have said about you that you consider
innuendo. Feel free to remind me what that was.

regards
 Steve
--
Steve Holden           +1 571 484 6266   +1 800 494 3119
PyCon is coming! Atlanta, Feb 2010  http://us.pycon.org/
Holden Web LLC                http://www.holdenweb.com/
UPCOMING EVENTS:        http://holdenweb.eventbrite.com/

Now now children...
 
A

Albert van der Horst

No, it doesn't. The infinite set of sine waves that make a square wave
leave out the sine waves of frequency 2f, 4f, 6f, 8f, ... (2*n*f) ... .
Once you've left them out, you can never get them back. So sawtooth waves,
for example, can't generally be built out of sets of square waves.

Bullshit. My Boehm (B\"ohm) electronic organ does exactly that.
They even have a chip for it. In the 70's it was a great hype,
a sawtooth organ. Well not exactly a hype, the sound, especially
the low registers, is dramatically better.

If you're interested in frequencies above audible (organ builders
aren't), you need an infinity of squares to build a perfect
sawtooth. But then you need an inifinity of sines to build a
perfect square wave.


Groetjes Albert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,773
Messages
2,569,594
Members
45,117
Latest member
Matilda564
Top