15

I have a 2 seconds 16bit single channel 8khz wav file and I need to change its volume.

It should be quite straightforward, because changing the volume is the same as changing the amplitude of the signal, and I just need to attenuate it, that is to multiply it for a number between 0 and 1. But it doesn't work: the new sound is lower but VERY full of noise. What am I doing wrong?

Here is my code:

import wave, numpy, struct

# Open
w = wave.open("input.wav","rb")
p = w.getparams()
f = p[3] # number of frames
s = w.readframes(f)
w.close()

# Edit
s = numpy.fromstring(s, numpy.int16) * 5 / 10  # half amplitude
s = struct.pack('h'*len(s), *s)

# Save
w = wave.open("output.wav","wb")
w.setparams(p)
w.writeframes(s)
w.close()

Thank you guys!

Ricard Bou
  • 449
  • 1
  • 3
  • 11
  • 2
    Why are you using `* 5 / 10` instead of `/ 2`? – John Dvorak Nov 11 '12 at 08:27
  • 1
    If I had to guess, I'd say that the `* 5` part is clipping and overflowing. – Nick ODell Nov 11 '12 at 08:28
  • 3
    Are you reading the file in the correct endianness? [WAV files are little-endian.](http://web.archive.org/web/19991115123323/http://www.borg.com/~jglatt/tech/wave.htm) Using the other endian will halve the sample and add a LOT of noise. – John Dvorak Nov 11 '12 at 08:33
  • 2
    Oh my...!! Sorry for posting that... The response was too easy. By doing this: s = numpy.fromstring(s, numpy.int16) * 5 / 10 # half amplitude The signal is saturated, because I multiplied the integers before dividing them. A solution: s = numpy.fromstring(s, numpy.int16) / 10 * 5 # half amplitude Be careful, this does NOT work as the division results zero: s = numpy.fromstring(s, numpy.int16) * (5 / 10) # half amplitude I keep it here in case it helps someone else. – Ricard Bou Nov 11 '12 at 08:33
  • Jan, because they are supposed to be vars: 5 is desired volume and 10 is original volume. – Ricard Bou Nov 11 '12 at 08:34
  • Nick, YES!! You are right. Thank you! – Ricard Bou Nov 11 '12 at 08:34
  • You should post your comment as an answer and accept it when possible (+24h IIRC) – John Dvorak Nov 11 '12 at 08:35
  • Thank you Jan, but stackoverflow lets me wait for another 7 hours before I can post an answer... :) – Ricard Bou Nov 11 '12 at 08:36
  • @JanDvorak Since you found the problem, it would be fair if you posted an answer and the OP accepted it. A full solution would probably include multiplying with `float(desired_volume) / float(orig_volume)`. – user4815162342 Nov 11 '12 at 08:37
  • @user4815162342 Too bad I'm not fluent in python. I didn't even know if python didn't use float types for everything. – John Dvorak Nov 11 '12 at 08:39
  • Another thing: the `*` operator in `struct.pack('h', *s)` line converts the entire numpy array into a tuple of int objects. This is potentially very inefficient—you should use `s.tostring` instead, which will efficiently create a string directly from the array contents. – user4815162342 Nov 11 '12 at 08:40
  • If it's scaled by a float, just using `tostring` will give the wrong result. But instead you could do `s = (s * (desired/original)).astype(numpy.int16).tostring()`. – Eryk Sun Nov 11 '12 at 11:17

4 Answers4

18

I wrote a library to simplify this type of thing

You can do that like so:

from pydub import AudioSegment

song = AudioSegment.from_wav("never_gonna_give_you_up.wav")

# reduce volume by 10 dB
song_10_db_quieter = song - 10

# but let's make him *very* quiet
song = song - 36

# save the output
song.export("quieter.wav", "wav")
SiHa
  • 5,520
  • 12
  • 23
  • 37
Jiaaro
  • 67,024
  • 38
  • 154
  • 182
7

As you can see in the comments of the question, there are several solutions, some more efficient.

The problem was immediately detected by Jan Dvorak ("the * 5 part is clipping and overflowing") and the straightforward solution was:

s = numpy.fromstring(s, numpy.int16) / 10 * 5

In this case, this solution was perfect for me, just good enough.

Thank you all folks!

Ricard Bou
  • 449
  • 1
  • 3
  • 11
3

This can be done with the audioop module in Python's standard library. This way, no dependencies like pydub or numpy are needed.

import wave, audioop

factor = 0.5

with wave.open('input.wav', 'rb') as wav:
    p = wav.getparams()
    with wave.open('output.wav', 'wb') as audio:
        audio.setparams(p)
        frames = wav.readframes(p.nframes)
        audio.writeframesraw( audioop.mul(frames, p.sampwidth, factor))
dnknth
  • 31
  • 1
1

The code which makes sound louder plus filter low and high frequencies

from pydub import AudioSegment

audio_file = "first.mp3"

song = AudioSegment.from_mp3(audio_file)

new = song.low_pass_filter(1000)

new1 = new.high_pass_filter(1000)

# increae volume by 6 dB
song_6_db_quieter = new1 + 6

# save the output
song_6_db_quieter.export("C://Users//User//Desktop//second.mp3", "mp3")