forked from echonest/snuGIFy
-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathearworm_support.py
120 lines (90 loc) · 3.87 KB
/
earworm_support.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
117
118
119
120
#!/usr/bin/env python
# encoding: utf-8
"""
earworm_support.py
Created by Tristan Jehan and Jason Sundram.
"""
import numpy as np
from copy import deepcopy
FUSION_INTERVAL = .06 # This is what we use in the analyzer
AVG_PEAK_OFFSET = 0.025 # Estimated time between onset and peak of segment.
def rows(m):
"""returns the # of rows in a numpy matrix"""
return m.shape[0]
def evaluate_distance(mat1, mat2):
return np.linalg.norm(mat1.flatten() - mat2.flatten())
def timbre_whiten(mat):
if rows(mat) < 2: return mat
m = np.zeros((rows(mat), 12), dtype=np.float32)
m[:,0] = mat[:,0] - np.mean(mat[:,0],0)
m[:,0] = m[:,0] / np.std(m[:,0],0)
m[:,1:] = mat[:,1:] - np.mean(mat[:,1:].flatten(),0)
m[:,1:] = m[:,1:] / np.std(m[:,1:].flatten(),0) # use this!
return m
def get_central(analysis, member='segments'):
""" Returns a tuple:
1) copy of the members (e.g. segments) between end_of_fade_in and start_of_fade_out.
2) the index of the first retained member.
"""
def central(s):
return analysis.end_of_fade_in <= s.start and (s.start + s.duration) < analysis.start_of_fade_out
members = getattr(analysis, member)
ret = filter(central, members[:])
index = members.index(ret[0]) if ret else 0
return ret, index
def get_mean_offset(segments, markers):
if segments == markers:
return 0
index = 0
offsets = []
try:
for marker in markers:
while segments[index].start < marker.start + FUSION_INTERVAL:
offset = abs(marker.start - segments[index].start)
if offset < FUSION_INTERVAL:
offsets.append(offset)
index += 1
except IndexError, e:
pass
return np.average(offsets) if offsets else AVG_PEAK_OFFSET
def resample_features(data, rate='tatums', feature='timbre'):
"""
Resample segment features to a given rate within fade boundaries.
@param data: analysis object.
@param rate: one of the following: segments, tatums, beats, bars.
@param feature: either timbre or pitch.
@return A dictionary including a numpy matrix of size len(rate) x 12, a rate, and an index
"""
ret = {'rate': rate, 'index': 0, 'cursor': 0, 'matrix': np.zeros((1, 12), dtype=np.float32)}
segments, ind = get_central(data.analysis, 'segments')
markers, ret['index'] = get_central(data.analysis, rate)
if len(segments) < 2 or len(markers) < 2:
return ret
# Find the optimal attack offset
meanOffset = get_mean_offset(segments, markers)
# Make a copy for local use
tmp_markers = deepcopy(markers)
# Apply the offset
for m in tmp_markers:
m.start -= meanOffset
if m.start < 0: m.start = 0
# Allocate output matrix, give it alias mat for convenience.
mat = ret['matrix'] = np.zeros((len(tmp_markers)-1, 12), dtype=np.float32)
# Find the index of the segment that corresponds to the first marker
f = lambda x: tmp_markers[0].start < x.start + x.duration
index = (i for i,x in enumerate(segments) if f(x)).next()
# Do the resampling
try:
for (i, m) in enumerate(tmp_markers):
while segments[index].start + segments[index].duration < m.start + m.duration:
dur = segments[index].duration
if segments[index].start < m.start:
dur -= m.start - segments[index].start
C = min(dur / m.duration, 1)
mat[i, 0:12] += C * np.array(getattr(segments[index], feature))
index += 1
C = min( (m.duration + m.start - segments[index].start) / m.duration, 1)
mat[i, 0:12] += C * np.array(getattr(segments[index], feature))
except IndexError, e:
pass # avoid breaking with index > len(segments)
return ret