Feature-driven Sampling
This tutorial shows how to build k-dimensional trees to efficiently perform feature-based search on buffers. In this case, we use it to find the best buffer match for each pitch value in a MIDI file. In this tutorial, we do the following:
- Slice a trumpet buffer into multiple onset-based segments, and analyze each.
- Build a tree based on those segments, using pitch as the search metric/feature.
- Load a MIDI file as a list of events.
- For each event, we search the tree for the best pitch-wise buffer match.
- We get the deviation between the matched buffer segment and the target pitch.
- We transcribe the buffer match, using the MIDI event's onset and apply the pitch deviation.
- Finally, we render the entire sequence and apply post-rendering reverb and gain normalization.
feature-driven_sampling.bell
## load source buffer
$source = importaudio('trumpet.wav');
## perform onset detection analysis on buffer
$source = $source.analyze(onsets());
## get list of transient-based onset times
$markers = $source.getkey('onsets');
## split buffers into different segments, based on markers
$segments = $source.splitbuf(@split $markers @mode 2);
## initialize data
$data = null;
## iterate through segments to filter unpitched ones
$segments = for $seg in $segments collect (
## analyze current segment for pitch detection
$seg = $seg.analyze(pitchmelodia());
## get detected results
$pitch = $seg.getkey('pitchmelodia');
## reject segments with no detected pitch
if $pitch > 0 then (
## append pitch as list to data list
$data _= [$pitch];
## return segment to collect
$seg
)
);
## create a k-dimensional tree on segments' pitch values.
$tree = createtree($data);
## import MIDI file
$events = importmidi('bach.mid');
## MIDI output speed
$speed = 1.33;
## tolerance for cent deviation when searching for pitch matches in tree
$tolerance = 300;
## gain envelope for every buffer match
$gainenv = [0 0 0] [1 1 0.25] [10 0 -.25];
## iterate through MIDI events
for $event in $events do (
## get pitch, onset, and duration info from event
$pitch = $event.getkey('pitch');
$onset = $event.getkey('onset');
$duration = $event.getkey('duration');
## query tree for index of nearest pitch in tree.
$matchindex = querytree($tree, $pitch + rand(-$tolerance/2, $tolerance/2));
## extract segment based on match's index
$match = $segments:$matchindex;
## get cents deviation between target pitch and buffer match
$detune = pitchdiff($match.getkey('pitchmelodia'), $pitch);
## adjust duration to make up for resampling-based detuning
$duration /= c2r($detune);
## modify match's duration via lambda function
$match = $match.mapkey('duration', $x -^ $duration -> min($x, $duration));
## transcribe match using MIDI event's onset and pitch deviation
$match.transcribe(
@onset $onset / $speed
@detune $detune
@gain $gainenv
@pan rand()
@pitchkey 'pitchmelodia'
)
);
## trigger rendering
render(
@play 1 @process (
## apply reverb and normalization
freeverb() normalize()
)
)