Skip to content

Commit

Permalink
fix error when combining onsets/notes; fixes #255
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Böck committed Mar 2, 2017
1 parent a379b03 commit 95d9f7f
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 42 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Bug fixes:
* Fix error loading unicode filenames (#223)
* Fix ffmpeg unicode filename handling (#236)
* Fix smoothing for peak_picking (#247)
* Fix combining onsets/notes (#255)

API relevant changes:

Expand Down
30 changes: 13 additions & 17 deletions madmom/features/notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from .onsets import peak_picking, OnsetPeakPickingProcessor
from ..processors import SequentialProcessor, ParallelProcessor
from ..utils import suppress_warnings
from ..utils import suppress_warnings, combine_events


@suppress_warnings
Expand Down Expand Up @@ -366,30 +366,26 @@ def process(self, activations, **kwargs):
self.pre_max, self.post_max]) * self.fps
timings = np.round(timings).astype(int)
# detect the peaks (function returns int indices)
detections = peak_picking(activations, self.threshold, *timings)
notes = peak_picking(activations, self.threshold, *timings)
# split onsets and pitches
onsets = detections[0].astype(np.float) / self.fps
pitches = detections[1] + 21
onsets = notes[0].astype(np.float) / self.fps
pitches = notes[1] + 21
# shift if necessary
if self.delay:
onsets += self.delay
# combine notes
if self.combine > 0:
detections = []
# iterate over each detected note separately
notes = []
# iterate over each detected note pitch separately
for pitch in np.unique(pitches):
# get all note detections
note_onsets = onsets[pitches == pitch]
# always use the first note
detections.append((note_onsets[0], pitch))
# filter all notes which occur within `combine` seconds
combined_note_onsets = note_onsets[1:][np.diff(note_onsets) >
self.combine]
# get all onsets for this pitch
onsets_ = onsets[pitches == pitch]
# combine onsets
onsets_ = combine_events(onsets_, self.combine, 'left')
# zip onsets and pitches and add them to list of detections
detections.extend(list(zip(combined_note_onsets,
[pitch] * len(combined_note_onsets))))
notes.extend(list(zip(onsets_, [pitch] * len(onsets_))))
else:
# just zip all detected notes
detections = list(zip(onsets, pitches))
notes = list(zip(onsets, pitches))
# sort the detections and return as numpy array
return np.asarray(sorted(detections))
return np.asarray(sorted(notes))
20 changes: 8 additions & 12 deletions madmom/features/onsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from ..processors import Processor, SequentialProcessor, ParallelProcessor
from ..audio.signal import smooth as smooth_signal
from ..utils import combine_events

EPSILON = np.spacing(1)

Expand Down Expand Up @@ -1138,22 +1139,17 @@ def process(self, activations, **kwargs):
self.pre_max, self.post_max]) * self.fps
timings = np.round(timings).astype(int)
# detect the peaks (function returns int indices)
detections = peak_picking(activations, self.threshold, *timings)
# convert detections to a list of timestamps
detections = detections.astype(np.float) / self.fps
onsets = peak_picking(activations, self.threshold, *timings)
# convert to timestamps
onsets = onsets.astype(np.float) / self.fps
# shift if necessary
if self.delay:
detections += self.delay
onsets += self.delay
# combine onsets
if self.combine and detections.size > 1:
# always use the first onset
# filter all onsets which occur within `combine` seconds
combined_detections = detections[1:][np.diff(detections) >
self.combine]
# append them after the first onset
detections = np.append(detections[0], combined_detections)
if self.combine:
onsets = combine_events(onsets, self.combine, 'left')
# return the detections
return detections
return onsets

@staticmethod
def add_arguments(parser, threshold=THRESHOLD, smooth=None, pre_avg=None,
Expand Down
21 changes: 17 additions & 4 deletions madmom/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ def write_events(events, filename, fmt='%.3f', delimiter='\t', header=''):
return events


def combine_events(events, delta):
def combine_events(events, delta, combine='mean'):
"""
Combine all events within a certain range.
Expand All @@ -328,8 +328,13 @@ def combine_events(events, delta):
events : list or numpy array
Events to be combined.
delta : float
Combination delta. All events within this `delta` are combined, i.e.
replaced by the mean of the two events.
Combination delta. All events within this `delta` are combined.
combine : {'mean', 'left', 'right'}
How to combine two adjacent events:
- 'mean': replace by the mean of the two events
- 'left': replace by the left of the two events
- 'right': replace by the right of the two events
Returns
-------
Expand All @@ -352,7 +357,15 @@ def combine_events(events, delta):
for right in events[1:]:
if right - left <= delta:
# combine the two events
left = events[idx] = 0.5 * (right + left)
if combine == 'mean':
left = events[idx] = 0.5 * (right + left)
elif combine == 'left':
left = events[idx] = left
elif combine == 'right':
left = events[idx] = right
else:
raise ValueError("don't know how to combine two events with "
"%s" % combine)
else:
# move forward
idx += 1
Expand Down
1 change: 1 addition & 0 deletions tests/data/detections/sample.onset_detector_ll.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
0.460
0.620
0.770
0.810
0.990
1.130
1.480
Expand Down
1 change: 1 addition & 0 deletions tests/data/detections/sample.super_flux_nn.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
0.440
0.600
0.750
0.800
0.970
1.100
1.460
Expand Down
47 changes: 38 additions & 9 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,31 +278,60 @@ def test_write_and_read_events(self):

class TestCombineEventsFunction(unittest.TestCase):

def test_combine_000(self):
def test_combine_mean(self):
# EVENTS = [1, 1.02, 1.5, 2.0, 2.03, 2.05, 2.5, 3]
comb = combine_events(EVENTS, 0.)
correct = np.asarray([1, 1.02, 1.5, 2.0, 2.03, 2.05, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))

def test_combine_001(self):
comb = combine_events(EVENTS, 0.01)
correct = np.asarray([1, 1.02, 1.5, 2.0, 2.03, 2.05, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))

def test_combine_003(self):
comb = combine_events(EVENTS, 0.03)
correct = np.asarray([1.01, 1.5, 2.015, 2.05, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))

def test_combine_0035(self):
comb = combine_events(EVENTS, 0.035)
correct = np.asarray([1.01, 1.5, 2.0325, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))

def test_combine_short(self):
comb = combine_events([1], 0.035)
correct = np.asarray([1])
self.assertTrue(np.allclose(comb, correct))

def test_combine_left(self):
# EVENTS = [1, 1.02, 1.5, 2.0, 2.03, 2.05, 2.5, 3]
comb = combine_events(EVENTS, 0., 'left')
correct = np.asarray([1, 1.02, 1.5, 2.0, 2.03, 2.05, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))
comb = combine_events(EVENTS, 0.01, 'left')
correct = np.asarray([1, 1.02, 1.5, 2.0, 2.03, 2.05, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))
comb = combine_events(EVENTS, 0.03, 'left')
correct = np.asarray([1, 1.5, 2, 2.05, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))
comb = combine_events(EVENTS, 0.035, 'left')
correct = np.asarray([1, 1.5, 2, 2.05, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))
comb = combine_events(EVENTS, 0.05, 'left')
correct = np.asarray([1, 1.5, 2, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))

def test_combine_right(self):
# EVENTS = [1, 1.02, 1.5, 2.0, 2.03, 2.05, 2.5, 3]
comb = combine_events(EVENTS, 0., 'right')
correct = np.asarray([1, 1.02, 1.5, 2.0, 2.03, 2.05, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))
comb = combine_events(EVENTS, 0.01, 'right')
correct = np.asarray([1, 1.02, 1.5, 2.0, 2.03, 2.05, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))
comb = combine_events(EVENTS, 0.03, 'right')
correct = np.asarray([1.02, 1.5, 2.05, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))
comb = combine_events(EVENTS, 0.035, 'right')
correct = np.asarray([1.02, 1.5, 2.05, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))
comb = combine_events(EVENTS, 0.05, 'right')
correct = np.asarray([1.02, 1.5, 2.05, 2.5, 3])
self.assertTrue(np.allclose(comb, correct))


class TestQuantizeEventsFunction(unittest.TestCase):

Expand Down

0 comments on commit 95d9f7f

Please sign in to comment.