-
Notifications
You must be signed in to change notification settings - Fork 90
/
swinger.py
116 lines (95 loc) · 3.46 KB
/
swinger.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/usr/bin/env python
# encoding: utf=8
"""
swinger.py
(name suggested by Jason Sundram)
Make your music swing (or un-swing).
Created by Tristan Jehan.
"""
from optparse import OptionParser
import os, sys
import dirac
from echonest.audio import LocalAudioFile, AudioData
from echonest.action import render, Playback, display_actions
def do_work(track, options):
verbose = bool(options.verbose)
# swing factor
swing = float(options.swing)
if swing < -0.9: swing = -0.9
if swing > +0.9: swing = +0.9
if swing == 0:
return Playback(track, 0, track.analysis.duration)
beats = track.analysis.beats
offset = int(beats[0].start * track.sampleRate)
# compute rates
rates = []
for beat in beats[:-1]:
# put swing
if 0 < swing:
rate1 = 1+swing
dur = beat.duration/2.0
stretch = dur * rate1
rate2 = (beat.duration-stretch)/dur
# remove swing
else:
rate1 = 1 / (1+abs(swing))
dur = (beat.duration/2.0) / rate1
stretch = dur * rate1
rate2 = (beat.duration-stretch)/(beat.duration-dur)
# build list of rates
start1 = int(beat.start * track.sampleRate)
start2 = int((beat.start+dur) * track.sampleRate)
rates.append((start1-offset, rate1))
rates.append((start2-offset, rate2))
if verbose:
args = (beats.index(beat), dur, beat.duration-dur, stretch, beat.duration-stretch)
print "Beat %d — split [%.3f|%.3f] — stretch [%.3f|%.3f] seconds" % args
# get audio
vecin = track.data[offset:int(beats[-1].start * track.sampleRate),:]
# time stretch
if verbose:
print "\nTime stretching..."
vecout = dirac.timeScale(vecin, rates, track.sampleRate, 0)
# build timestretch AudioData object
ts = AudioData(ndarray=vecout, shape=vecout.shape,
sampleRate=track.sampleRate, numChannels=vecout.shape[1],
verbose=verbose)
# initial and final playback
pb1 = Playback(track, 0, beats[0].start)
pb2 = Playback(track, beats[-1].start, track.analysis.duration-beats[-1].start)
return [pb1, ts, pb2]
def main():
usage = "usage: %s [options] <one_single_mp3>" % sys.argv[0]
parser = OptionParser(usage=usage)
parser.add_option("-s", "--swing", default=0.33, help="swing factor default=0.33")
parser.add_option("-v", "--verbose", action="store_true", help="show results on screen")
(options, args) = parser.parse_args()
if len(args) < 1:
parser.print_help()
return -1
verbose = options.verbose
track = None
track = LocalAudioFile(args[0], verbose=verbose)
if verbose:
print "Computing swing . . ."
# this is where the work takes place
actions = do_work(track, options)
if verbose:
display_actions(actions)
# Send to renderer
name = os.path.splitext(os.path.basename(args[0]))
sign = ('-','+')[float(options.swing) >= 0]
name = name[0] + '_swing' + sign + str(int(abs(float(options.swing))*100)) +'.mp3'
name = name.replace(' ','')
name = os.path.join(os.getcwd(), name) # TODO: use sys.path[0] instead of getcwd()?
if verbose:
print "Rendering... %s" % name
render(actions, name, verbose=verbose)
if verbose:
print "Success!"
return 1
if __name__ == "__main__":
try:
main()
except Exception, e:
print e