Skip to content

Commit 2e86df6

Browse files
authored
Merge pull request #5 from KAIST-MACLab/features/CLI
Features/cli
2 parents 3f2366a + dcd34cd commit 2e86df6

4 files changed

Lines changed: 262 additions & 4 deletions

File tree

pyproject.toml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
[tool.poetry]
22
name = "pytsmod"
33
version = "0.2.0"
4-
description = ""
4+
description = "An open-source Python library for audio time-scale modification."
55
authors = ["Sangeon Yong <koragon2@kaist.ac.kr>"]
66

7+
license = "GPL-3.0"
8+
readme = "README.md"
9+
710
[tool.poetry.dependencies]
811
python = "^3.6"
912
numpy = "^1.16.0"
@@ -15,8 +18,8 @@ librosa = "^0.8"
1518
pytest = "^5.2"
1619
flake8 = "^3.8.3"
1720

18-
# [tool.poetry.scripts]
19-
# tsmod = 'pytsmod.console:run'
21+
[tool.poetry.scripts]
22+
tsmod = 'pytsmod.console:run'
2023

2124
[build-system]
2225
requires = ["poetry>=0.12"]

pytsmod/console.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import sys
2+
sys.path.append('./')
3+
4+
from pytsmod import ola, wsola
5+
from pytsmod import phase_vocoder as pv
6+
from pytsmod import phase_vocoder_int as pv_int
7+
import argparse
8+
import soundfile as sf
9+
10+
11+
def run():
12+
parser = argparse.ArgumentParser(description='Processing time-scale modification for given audio file.')
13+
parser.add_argument('algorithm', nargs='?', choices=['ola', 'wsola', 'pv', 'pv_int', 'hp'])
14+
# parser.add_argument('--help', action='store_true')
15+
16+
args, sub_args = parser.parse_known_args()
17+
18+
if args.algorithm == 'ola':
19+
parser = argparse.ArgumentParser()
20+
parser.add_argument('input_file', type=str)
21+
parser.add_argument('output_file', type=str)
22+
parser.add_argument('alpha', type=float)
23+
parser.add_argument('--win_type', '-wt', default='hann', type=str)
24+
parser.add_argument('--win_size', '-ws', default=1024, type=int)
25+
parser.add_argument('--syn_hop_size', '-sh', default=512, type=int)
26+
27+
params = parser.parse_args(sub_args)
28+
29+
x, sr = sf.read(params.input_file)
30+
31+
y = ola(x, params.alpha, win_type=params.win_type,
32+
win_size=params.win_size, syn_hop_size=params.syn_hop_size)
33+
elif args.algorithm == 'wsola':
34+
parser = argparse.ArgumentParser()
35+
parser.add_argument('input_file', type=str)
36+
parser.add_argument('output_file', type=str)
37+
parser.add_argument('alpha', type=float)
38+
parser.add_argument('--win_type', '-wt', default='hann', type=str)
39+
parser.add_argument('--win_size', '-ws', default=1024, type=int)
40+
parser.add_argument('--syn_hop_size', '-sh', default=512, type=int)
41+
parser.add_argument('--tolerance', '-t', default=512, type=int)
42+
43+
params = parser.parse_args(sub_args)
44+
45+
x, sr = sf.read(params.input_file)
46+
47+
y = wsola(x, params.alpha, win_type=params.win_type,
48+
win_size=params.win_size, syn_hop_size=params.syn_hop_size,
49+
tolerance=params.tolerance)
50+
elif args.algorithm == 'pv':
51+
parser = argparse.ArgumentParser()
52+
parser.add_argument('input_file', type=str)
53+
parser.add_argument('output_file', type=str)
54+
parser.add_argument('alpha', type=float)
55+
parser.add_argument('--win_type', '-wt', default='sin', type=str)
56+
parser.add_argument('--win_size', '-ws', default=2048, type=int)
57+
parser.add_argument('--syn_hop_size', '-sh', default=512, type=int)
58+
parser.add_argument('--zero_pad', '-z', default=0, type=int)
59+
parser.add_argument('--restore_energy', '-e', action='store_true')
60+
parser.add_argument('--fft_shift', '-fs', action='store_true')
61+
parser.add_argument('--phase_lock', '-pl', action='store_true')
62+
63+
params = parser.parse_args(sub_args)
64+
65+
x, sr = sf.read(params.input_file)
66+
67+
y = pv(x, params.alpha, win_type=params.win_type,
68+
win_size=params.win_size, syn_hop_size=params.syn_hop_size,
69+
zero_pad=params.zero_pad, restore_energy=params.restore_energy,
70+
fft_shift=params.fft_shift, phase_lock=params.phase_lock)
71+
elif args.algorithm == 'pv_int':
72+
parser = argparse.ArgumentParser()
73+
parser.add_argument('input_file', type=str)
74+
parser.add_argument('output_file', type=str)
75+
parser.add_argument('alpha', type=int)
76+
parser.add_argument('--win_type', '-wt', default='hann', type=str)
77+
parser.add_argument('--win_size', '-ws', default=2048, type=int)
78+
parser.add_argument('--syn_hop_size', '-sh', default=512, type=int)
79+
parser.add_argument('--zero_pad', '-z', default=None, type=int)
80+
parser.add_argument('--restore_energy', '-e', action='store_true')
81+
parser.add_argument('--fft_shift', '-fs', action='store_true')
82+
83+
params = parser.parse_args(sub_args)
84+
print(params.zero_pad)
85+
86+
x, sr = sf.read(params.input_file)
87+
88+
y = pv_int(x, params.alpha, win_type=params.win_type,
89+
win_size=params.win_size, syn_hop_size=params.syn_hop_size,
90+
zero_pad=params.zero_pad,
91+
restore_energy=params.restore_energy,
92+
fft_shift=params.fft_shift)
93+
# elif args.algorithm == 'hp':
94+
# pass
95+
96+
sf.write(params.output_file, y, sr)
97+
98+
99+
if __name__ == '__main__':
100+
run()

pytsmod/pvtsm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def phase_vocoder_int(x, s, win_type='hann', win_size=2048, syn_hop_size=512,
172172

173173
y[c, :] = y_chan
174174

175-
return y
175+
return y.squeeze()
176176

177177

178178
def _find_peaks(spec):

tests/test_console.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import pytest
2+
from pytsmod import ola, wsola
3+
from pytsmod import phase_vocoder as pv
4+
from pytsmod import phase_vocoder_int as pv_int
5+
import soundfile as sf
6+
import numpy as np
7+
import os
8+
from subprocess import call
9+
10+
11+
@pytest.mark.parametrize('algorithm', ['ola', 'wsola', 'pv', 'pv_int'])
12+
def test_console_default_params(algorithm):
13+
test_file = 'tests/data/castanetsviolin.wav'
14+
alpha = 2
15+
x, sr = sf.read(test_file)
16+
y = globals()[algorithm](x, alpha)
17+
18+
cmd = ['python', 'pytsmod/console.py', algorithm,
19+
test_file, 'temp_cli.wav', str(alpha)]
20+
if algorithm == 'pv_int':
21+
cmd.append('-fs')
22+
call(cmd)
23+
24+
sf.write('temp.wav', y, sr)
25+
y_, _ = sf.read('temp.wav')
26+
27+
y_cli, _ = sf.read('temp_cli.wav')
28+
29+
os.remove('temp.wav')
30+
os.remove('temp_cli.wav')
31+
32+
assert np.allclose(y_, y_cli)
33+
34+
35+
@pytest.mark.parametrize('alpha', [1.25])
36+
@pytest.mark.parametrize('win_type', ['sin'])
37+
@pytest.mark.parametrize('win_size', [512])
38+
@pytest.mark.parametrize('syn_hop_size', [256])
39+
def test_console_ola(alpha, win_type, win_size, syn_hop_size):
40+
test_file = 'tests/data/castanetsviolin.wav'
41+
x, sr = sf.read(test_file)
42+
y = ola(x, alpha, win_type=win_type, win_size=win_size,
43+
syn_hop_size=syn_hop_size)
44+
45+
cmd = ['python', 'pytsmod/console.py', 'ola',
46+
test_file, 'temp_cli.wav', str(alpha),
47+
'-wt', win_type, '-ws', str(win_size),
48+
'-sh', str(syn_hop_size)]
49+
call(cmd)
50+
51+
sf.write('temp.wav', y, sr)
52+
y_, _ = sf.read('temp.wav')
53+
54+
y_cli, _ = sf.read('temp_cli.wav')
55+
56+
os.remove('temp.wav')
57+
os.remove('temp_cli.wav')
58+
59+
assert np.allclose(y_, y_cli)
60+
61+
62+
@pytest.mark.parametrize('alpha', [1.25])
63+
@pytest.mark.parametrize('win_type', ['sin'])
64+
@pytest.mark.parametrize('win_size', [512])
65+
@pytest.mark.parametrize('syn_hop_size', [256])
66+
@pytest.mark.parametrize('tolerance', [256])
67+
def test_console_wsola(alpha, win_type, win_size, syn_hop_size, tolerance):
68+
test_file = 'tests/data/castanetsviolin.wav'
69+
x, sr = sf.read(test_file)
70+
y = wsola(x, alpha, win_type=win_type, win_size=win_size,
71+
syn_hop_size=syn_hop_size, tolerance=tolerance)
72+
73+
cmd = ['python', 'pytsmod/console.py', 'wsola',
74+
test_file, 'temp_cli.wav', str(alpha),
75+
'-wt', win_type, '-ws', str(win_size),
76+
'-sh', str(syn_hop_size), '-t', str(tolerance)]
77+
call(cmd)
78+
79+
sf.write('temp.wav', y, sr)
80+
y_, _ = sf.read('temp.wav')
81+
82+
y_cli, _ = sf.read('temp_cli.wav')
83+
84+
os.remove('temp.wav')
85+
os.remove('temp_cli.wav')
86+
87+
assert np.allclose(y_, y_cli)
88+
89+
90+
@pytest.mark.parametrize('alpha', [1.25])
91+
@pytest.mark.parametrize('win_type', ['hann'])
92+
@pytest.mark.parametrize('win_size', [1024])
93+
@pytest.mark.parametrize('syn_hop_size', [256])
94+
@pytest.mark.parametrize('zero_pad', [256])
95+
@pytest.mark.parametrize('restore_energy', [True])
96+
@pytest.mark.parametrize('fft_shift', [True])
97+
@pytest.mark.parametrize('phase_lock', [True])
98+
def test_console_pv(alpha, win_type, win_size, syn_hop_size, zero_pad,
99+
restore_energy, fft_shift, phase_lock):
100+
test_file = 'tests/data/castanetsviolin.wav'
101+
x, sr = sf.read(test_file)
102+
y = pv(x, alpha, win_type=win_type, win_size=win_size,
103+
syn_hop_size=syn_hop_size, zero_pad=zero_pad,
104+
restore_energy=restore_energy, fft_shift=fft_shift,
105+
phase_lock=phase_lock)
106+
107+
cmd = ['python', 'pytsmod/console.py', 'pv',
108+
test_file, 'temp_cli.wav', str(alpha),
109+
'-wt', win_type, '-ws', str(win_size),
110+
'-sh', str(syn_hop_size), '-z', str(zero_pad),
111+
'-e', '-fs', '-pl']
112+
call(cmd)
113+
114+
sf.write('temp.wav', y, sr)
115+
y_, _ = sf.read('temp.wav')
116+
117+
y_cli, _ = sf.read('temp_cli.wav')
118+
119+
os.remove('temp.wav')
120+
os.remove('temp_cli.wav')
121+
122+
assert np.allclose(y_, y_cli)
123+
124+
125+
@pytest.mark.parametrize('alpha', [2])
126+
@pytest.mark.parametrize('win_type', ['sin'])
127+
@pytest.mark.parametrize('win_size', [1024])
128+
@pytest.mark.parametrize('syn_hop_size', [256])
129+
@pytest.mark.parametrize('zero_pad', [256])
130+
@pytest.mark.parametrize('restore_energy', [True])
131+
@pytest.mark.parametrize('fft_shift', [False])
132+
def test_console_pv_int(alpha, win_type, win_size, syn_hop_size, zero_pad,
133+
restore_energy, fft_shift):
134+
test_file = 'tests/data/castanetsviolin.wav'
135+
x, sr = sf.read(test_file)
136+
y = pv(x, alpha, win_type=win_type, win_size=win_size,
137+
syn_hop_size=syn_hop_size, zero_pad=zero_pad,
138+
restore_energy=restore_energy, fft_shift=fft_shift)
139+
140+
cmd = ['python', 'pytsmod/console.py', 'pv',
141+
test_file, 'temp_cli.wav', str(alpha),
142+
'-wt', win_type, '-ws', str(win_size),
143+
'-sh', str(syn_hop_size), '-z', str(zero_pad),
144+
'-e']
145+
call(cmd)
146+
147+
sf.write('temp.wav', y, sr)
148+
y_, _ = sf.read('temp.wav')
149+
150+
y_cli, _ = sf.read('temp_cli.wav')
151+
152+
os.remove('temp.wav')
153+
os.remove('temp_cli.wav')
154+
155+
assert np.allclose(y_, y_cli)

0 commit comments

Comments
 (0)