0
votes

I am trying to play a sound from a sawtooth wave. I created the waveform in Python and was able to save it as a WAV file, but when I try to play it it says the file is unplayable because the file type is unsupported, the file extension is incorrect, or the file is corrupt. I used this individual's tutorial (https://thehackerdiary.wordpress.com/2017/06/09/it-is-ridiculously-easy-to-generate-any-audio-signal-using-python/) and they worked around it by encoding the raw waveform from 16 bit to 8 bit in Audacity. How can this be done using only Python?

import soundfile

data, samplerate = soundfile.read('sawtooth_100_hz.wav')
soundfile.write('sawtooth_100_hz_8bit.wav', data, samplerate, subtype='PCM_S8')

^^ I tried this and got the following error: ValueError: Invalid combination of format, subtype and endian

4

4 Answers

1
votes

I think the people who wrote this tutorial went for the long way. There is an easier way to convert a NumPy array to a wav file which is used below to generate the same wav file as the one generated in the tutorial:

import numpy as np
from scipy.io import wavfile

sampling_rate = 44100
freq = 440
samples = 44100

x = np.arange(samples)
y = 100*np.sin(2 * np.pi * freq * x / sampling_rate)

wavfile.write("test.wav", sampling_rate, y)

And you can use wavfile.read() method to read this file with no problem

1
votes

Surprisingly, the underlying libsndfile library doesn't support WAV files with signed 8 bit samples (only unsigned), see http://www.mega-nerd.com/libsndfile/#Features.

You can also check this with the soundfile module:

>>> import soundfile as sf
>>> sf.available_subtypes('wav')
{'PCM_16': 'Signed 16 bit PCM', 'PCM_24': 'Signed 24 bit PCM', 'PCM_32': 'Signed 32 bit PCM', 'PCM_U8': 'Unsigned 8 bit PCM', 'FLOAT': '32 bit float', 'DOUBLE': '64 bit float', 'ULAW': 'U-Law', 'ALAW': 'A-Law', 'IMA_ADPCM': 'IMA ADPCM', 'MS_ADPCM': 'Microsoft ADPCM', 'GSM610': 'GSM 6.10', 'G721_32': '32kbs G721 ADPCM'}

You could try using AIFF or FLAC instead?

Or you could create a RAW file (i.e. a headerless file containing no information about its own data format), which is incidentally what they did in the tutorial you were mentioning (note that they are using these options: -t raw -e signed -b 8).

For a bit more information about creating and playing signals, see:

0
votes

It sounds like you just want to generate the samples and playback from within Python?

If so, it looks like library "sounddevice" will let you write samples directly to your audio device:

https://python-sounddevice.readthedocs.io/en/0.3.15/usage.html#playback

I'm not in a python environment right now, so haven't tested, but mixing it with your sample code would just be:

import sounddevice as sd
import numpy as np

sampling_rate = 44100
freq = 440
samples = 44100

x = np.arange(samples)
y = 100*np.sin(2 * np.pi * freq * x / sampling_rate)

sd.play(y, sampling_rate)

Sounddevice's author is on SO, see his reply to a similar question: https://stackoverflow.com/a/34179010/1339735

You might have some scaling to do - not sure if it accepts values from -1 to 1 like most float playback or +/- 100 like in your example.

0
votes

All of the above answers were helpful, but ultimately I found a solution to my problem from this thread: How to generate audio from a numpy array?

This was my code:

import numpy as np
from scipy.io.wavfile import write
from scipy import signal as sg

#data = np.random.uniform(-1,1,44100) # 44100 random samples between -1 and 1
sampling_rate = 44100                    ## Sampling Rate
freq = 150                               ## Frequency (in Hz)
duration = 3   # in seconds, may be float

t = np.linspace(0, duration, sampling_rate*duration) # Creating time vector
data = sg.sawtooth(2 * np.pi * freq * t, 0)          # Sawtooth signal

'''
Scaling data to 16 bit. Divide each number by max number in array to get
fraction and multiply data by 32767 because that is the max value a 16 bit
integer can take
'''
scaled = np.int16(data/np.max(np.abs(data)) * 32767) 
write('test.wav', 44100, scaled) # Write to file. Can be overridden