93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Basic command-line tool used to transform .wav files.
|
|
|
|
@author: Szymon Szczyrbak
|
|
"""
|
|
|
|
import wave
|
|
import numpy as np
|
|
import struct
|
|
from argparse import ArgumentParser
|
|
|
|
parser = ArgumentParser(description="Preform transformation on wav files.")
|
|
parser.add_argument('--input', '-i', dest='input_path', help="Input wav file path.")
|
|
parser.add_argument('--output', '-o', dest='output_path', help="Output wav file path.")
|
|
parser.add_argument('-p', dest='pitch_shift_value', type=int, help="Pitch shift value.")
|
|
parser.add_argument('-a', dest='amplitude_multiplier_value', type=float, help="Amplitude multiplier value.")
|
|
|
|
# https://stackoverflow.com/questions/43963982/python-change-pitch-of-wav-file
|
|
def shift_pitch(input_path, output_path, pitch_shift_value):
|
|
# Read input file.
|
|
wr = wave.open(input_path, 'r')
|
|
# Set the parameters for the output file.
|
|
par = list(wr.getparams())
|
|
par[3] = 0 # The number of samples will be set by writeframes.
|
|
par = tuple(par)
|
|
ww = wave.open(output_path, 'w')
|
|
ww.setparams(par)
|
|
|
|
fr = 20 # TODO: Not sure what does it do... Higher number reduces reverb?
|
|
sz = wr.getframerate()//fr # Number of samples processed in one loop iteration.
|
|
|
|
c = int(wr.getnframes()/sz) # number of samples /
|
|
shift = pitch_shift_value//fr
|
|
for num in range(c):
|
|
# Read chunk and split into left and right channels
|
|
da = np.fromstring(wr.readframes(sz), dtype=np.int16)
|
|
left, right = da[0::2], da[1::2]
|
|
# Extract the frequencies using FFT.
|
|
lf, rf = np.fft.rfft(left), np.fft.rfft(right)
|
|
# Increase the pitch by rolling arrays.
|
|
lf, rf = np.roll(lf, shift), np.roll(rf, shift)
|
|
# Highest frequencies rolled at the start of the array. Zero 'em.
|
|
if pitch_shift_value > 0:
|
|
lf[0:shift], rf[0:shift] = 0, 0
|
|
else:
|
|
lf[shift-1:-1], rf[shift-1:-1] = 0, 0 # TODO: Not sure if it's alright for negative shift.
|
|
# Inverse FFT.
|
|
nl, nr = np.fft.irfft(lf), np.fft.irfft(rf)
|
|
# Combine left and right channel.
|
|
ns = np.column_stack((nl, nr)).ravel().astype(np.int16)
|
|
# Write to output file.
|
|
ww.writeframes(ns.tostring())
|
|
wr.close()
|
|
ww.close()
|
|
|
|
|
|
# https://stackoverflow.com/questions/13329617/change-the-volume-of-a-wav-file-in-python
|
|
def multiply_amplitude(input_path, output_path, amplitude_multiplier_value):
|
|
# Read input file.
|
|
wr = wave.open(input_path, 'r')
|
|
samples_num = wr.getparams()[3]
|
|
# Set the parameters for the output file.
|
|
par = list(wr.getparams())
|
|
par[3] = 0 # The number of samples will be set by writeframes.
|
|
par = tuple(par)
|
|
ww = wave.open(output_path, 'w')
|
|
ww.setparams(par)
|
|
|
|
da = np.fromstring(wr.readframes(samples_num), np.int16) * amplitude_multiplier_value # multiply amplitude
|
|
da = da.astype(np.int16)
|
|
ns = struct.pack('h'*len(da), *da)
|
|
ww.writeframes(ns)
|
|
|
|
wr.close()
|
|
ww.close()
|
|
|
|
|
|
def main():
|
|
args = parser.parse_args()
|
|
input_path = args.input_path
|
|
output_path = args.output_path
|
|
pitch_shift_value = args.pitch_shift_value
|
|
amplitude_multiplier_value = args.amplitude_multiplier_value
|
|
if isinstance(pitch_shift_value, int):
|
|
shift_pitch(input_path, output_path, pitch_shift_value)
|
|
elif isinstance(amplitude_multiplier_value, float):
|
|
# TODO: Weird noises for amplitude x5.
|
|
multiply_amplitude(input_path, output_path, amplitude_multiplier_value)
|
|
# TODO: More transformations. Should only one transformation be preformed in one run?
|
|
|
|
if __name__ == "__main__":
|
|
main() |