Writing WAV file using Python, Numpy array and WAVE module
Asked Answered
P

3

9

I am trying to implement the Karplus-Strong algorithm.

All is looking fine when I play (through Jupyter Notebook using Audio(y, rate=Fs)) the collected numpy array (representing guitar accord).

Unfortunately, writing the numpy array: y, into wav file using WAVE module is incorrect (using the next python code):

noise_output = wave.open('k-s.wav', 'w')
noise_output.setparams((1, 4, Fs, 0, 'NONE', 'not compressed'))

for i in range(0, len(y)):
     value = y[i]
     packed_value = struct.pack('f', value)
     noise_output.writeframes(packed_value)

noise_output.close()

Each element of y is

<type 'numpy.float64'>

How should I amend the writing loop in order write the WAV file correctly?

Some more information about the issue. Before writing to WAV, the first elements of the y array are:

 [ 0.33659756  0.33659756 -0.43915295 -0.87036152  1.40708988  0.32123558
-0.6889402   1.9739982  -1.29587159 -0.12299964  2.18381762  0.82228042
 0.24593503 -1.28067426 -0.67568838 -0.01843234 -1.830472    1.2729578
-0.56575346  0.55410736]

After writing the elements to the WAV file, close the WAV file and read it again, I got this for the first 20 elements of the collected array:

[ 1051481732  1051481732 -1092560728 -1084305405  1068768133  1050966269
 -1087349149  1073523705 -1079648481 -1107564740  1074512811  1062371576
  1048303204 -1079775966 -1087571478 -1130954901 -1075163928  1067642952
 -1089415880  1057872379]
Punster answered 24/11, 2016 at 9:14 Comment(6)
You need to convert your float data to integer and pack it in Little Endian.Serilda
How to convert it to integer? By some scaling or? The float numbers are pretty close to 1, so I will need some scaling maybe? @PM2RingPunster
Yes, you need to scale them. I suggest using signed 16 bit integers, so scale your floats so they'll fit in -2**15 < x < 2**15, and use '<h' as the format string for struct.pack.Serilda
@PM2Ring It worked, thanks (even more clearly using signed 32 bit)! I am still puzzled though, why the previous solution failed?Punster
As far as I know (and from what I can see in the docs) the Python wave module only supports integer data formats for WAV files. So even though your packed float data is the right size, it sounds terrible because it gets interpreted as 32 bit integer data. BTW, you should always specify the correct endian type in a struct format string.Serilda
@PM2Ring Yes, I will have that in mind using struct. Thanks again!Punster
C
9
import scipy.io.wavfile
scipy.io.wavfile.write("karplus.wav", Fs, y)

Tada! AFAIK works with float64 and float32, and probably others. For stereo, shape must be (nb_samples, 2). See scipy.io.wavfile.write.

Capers answered 24/11, 2016 at 9:48 Comment(2)
Yes, I tried with scipy and all is working fine. But I am still puzzled why is this happening when I am using WAVE?Punster
@Punster hi sir can u help with how to pass edited wav to another function without saving it ? #63467845Lambertson
A
10

Here are code samples to write a (stereo) wave file using the wave standard library. I included two examples: one using numpy, and one that doesn't require any dependencies.

Using a numpy array

Note that if your data is in a numpy array, no need for the struct library.

import wave
import numpy as np

samplerate = 44100

# A note on the left channel for 1 second.
t = np.linspace(0, 1, samplerate)
left_channel = 0.5 * np.sin(2 * np.pi * 440.0 * t)

# Noise on the right channel.
right_channel = np.random.random(size=samplerate)

# Put the channels together with shape (2, 44100).
audio = np.array([left_channel, right_channel]).T

# Convert to (little-endian) 16 bit integers.
audio = (audio * (2 ** 15 - 1)).astype("<h")

with wave.open("sound1.wav", "w") as f:
    # 2 Channels.
    f.setnchannels(2)
    # 2 bytes per sample.
    f.setsampwidth(2)
    f.setframerate(samplerate)
    f.writeframes(audio.tobytes())

Using a list

This is (almost) the same code but without using numpy. No external dependencies are required.

import math
import random
import struct
import wave

samplerate = 44100

left_channel = [
    0.5 * math.sin(2 * math.pi * 440.0 * i / samplerate) for i in range(samplerate)
]
right_channel = [random.random() for _ in range(samplerate)]

with wave.open("sound2.wav", "w") as f:
    f.setnchannels(2)
    f.setsampwidth(2)
    f.setframerate(samplerate)
    for samples in zip(left_channel, right_channel):
        for sample in samples:
            sample = int(sample * (2 ** 15 - 1))
            f.writeframes(struct.pack("<h", sample))
Albumen answered 15/10, 2020 at 16:35 Comment(1)
would you mind helping me on a similar question; #72783779Scalable
C
9
import scipy.io.wavfile
scipy.io.wavfile.write("karplus.wav", Fs, y)

Tada! AFAIK works with float64 and float32, and probably others. For stereo, shape must be (nb_samples, 2). See scipy.io.wavfile.write.

Capers answered 24/11, 2016 at 9:48 Comment(2)
Yes, I tried with scipy and all is working fine. But I am still puzzled why is this happening when I am using WAVE?Punster
@Punster hi sir can u help with how to pass edited wav to another function without saving it ? #63467845Lambertson
C
3

Read and write wave file to and from a file:

from  scipy.io import wavfile 

sampling_rate, data = wavfile.read(wpath)
wavfile.write('abc1.wav', sampling_rate, data)
Catatonia answered 1/2, 2022 at 22:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.