-odac
--m-amps=0
sr = 44100
ksmps = 64
nchnls = 2
0dbfs = 1
/*
* MFCC matching / concatenative resynthesis example
* By Richard Knight 2021
*
* Interactive FLTK interface example
*
* See README.md for overview and usage details.
*
*/
; put your own sounds here and they will be analysed/added to available sounds at startup.
;gSsounds[] fillarray "/path/to/sound1.wav", "/path/to/sound2.wav"
; default is to use any sounds in the sounds subdirectory
gSsounds[] directory "sounds", ".wav"
; FFT size for MFCC analysis (lower = more CPU)
gifftsize = 1024
; Number of MFCC bands to use (^2, ideally 8, 16, 32)
gimfccbands = 16
; instrument numbers for updateui and player, as used in FL opcode calls
iupdateui nstrnum "updateui"
iplayer nstrnum "player"
/*
* Return element after last slash
*/
opcode strfilename, S, S
Sinput xin
xout strsub(Sinput, strrindex(Sinput, "/") + 1)
endop
; the UI
FLpanel "MFCC Matching", 800, 500
gkcorpusindex, gicorpusindex FLcount "Corpus", 0, lenarray(gSsounds)-1, 1, 1, 1, 200, 50, 0, 0, 0, iupdateui, 0, 1, 1 ; updateui item 1
gicorpusbox FLbox strfilename(gSsounds[0]), 1, 1, 18, 300, 50, 0, 100
FLsetTextSize 24, gicorpusindex
FLsetFont 2, gicorpusindex
gkdriverindex, gidriverindex FLcount "Driver", 0, lenarray(gSsounds)-1, 1, 1, 1, 200, 50, 600, 0, 0, iupdateui, 0, 1, 2 ; updateui item 2
gidriverbox FLbox strfilename(gSsounds[0]), 1, 1, 18, 300, 50, 500, 100
FLsetTextSize 24, gidriverindex
FLsetFont 2, gidriverindex
gkxfade, gixfade FLslider "Crossfade", 0, 1, 0, 5, -1, 400, 50, 200, 150
FLsetTextSize 24, gixfade
FLsetFont 2, gixfade
FLsetVal_i 0.5, gixfade
gkblur, giblur FLbutton "Blur", 1, 0, 3, 100, 30, 0, 300, 0, iupdateui, 0, 1, 3 ; updateui item 3
gkblurtime, giblurtime FLslider "Blur time", 0, 1, 0, 5, -1, 300, 30, 100, 300
gkenvelope, gienvelope FLslider "Grain envelope", 0.1, 0.9, 0, 5, -1, 400, 20, 0, 350
FLsetVal_i 0.4, gienvelope
gkgrainsize, gigrainsize FLslider "Grain size", 0.01, 3, 0, 5, -1, 400, 20, 0, 400
FLsetVal_i 1.2, gigrainsize
gkpitchshift, gipitchshift FLbutton "Repitch", 1, 0, 3, 100, 30, 0, 450, -1
gkmatchamps, gimatchamps FLbutton "Match amps", 1, 0, 3, 100, 30, 200, 450, -1
gkreadmode, gireadmode FLcount "Read mode", 0, 1, 1, 1, 1, 150, 30, 0, 250, 0, iupdateui, 0, 1, 4 ; updateui item 4
gkstretch, gistretch FLslider "Stretch", 0, 0.999, 0, 5, -1, 250, 30, 150, 250
gkplay, giplay FLbutton "Play/Stop", 1, 0, 1, 200, 50, 300, 0, 0, iplayer, 0, 200
FLsetTextSize 24, giplay
FLsetColor 40, 140, 40, giplay
giloadingbox FLbox "Analysing", 1, 1, 72, 800, 500, 0, 0
FLpanelEnd
FLrun
; not shown to begin with
FLhide giblurtime
FLhide gistretch
/*
* Alter UI elements according to specified item/mode
*/
instr updateui
item = p4
if (item == 1) then ; corpus sound index changed
FLsetText strfilename(gSsounds[i(gkcorpusindex)]), gicorpusbox
elseif (item == 2) then ; driver sound index changed
FLsetText strfilename(gSsounds[i(gkdriverindex)]), gidriverbox
elseif (item == 3) then ; blur checkbox changed
if (i(gkblur) == 1) then
FLshow giblurtime
else
FLhide giblurtime
endif
elseif (item == 4) then ; read mode changed
if (i(gkreadmode) == 1) then
FLshow gistretch
FLhide gienvelope
FLhide gigrainsize
else
FLhide gistretch
FLshow gienvelope
FLshow gigrainsize
endif
endif
turnoff
endin
; The corpus sounds
gicorpuswave[] init lenarray(gSsounds)
; ftables for MFCC corpus data, initialise to -1 and create in analysis instrument
gicorpus[] init lenarray(gSsounds)
; allow only one playing instance
giplaying = 0
; include analysis and matching opcodes
#include "mfcc_matching.udo"
/*
* Step through gSsounds by index (p4), load to ftable and perform mfcc analysis
*/
instr analyseloop
index = p4
if (index > lenarray(gSsounds) - 1) then
; everything analysed, show UI accordingly
FLhide giloadingbox
else
; wave still to analyse
gicorpuswave[index] ftgen 0, 0, 0, 1, gSsounds[index], 0, 0, 1
event_i "i", "analysecorpus", 0, 1, index
endif
turnoff
endin
/*
* Analyse corpus sound in gicorpuswave, in one k-cycle,
* then invoke the "analyseloop" instrument again to continue
*/
instr analysecorpus
index = p4
ilen = ftlen(gicorpuswave[index]) / ftsr(gicorpuswave[index])
imaxitems = gimfccbands * (ftlen(gicorpuswave[index]) / gifftsize)
gicorpus[index] ftgen 0, 0, -imaxitems, 2, 0
ktimek timeinstk
if (ktimek == 1) then
kcycles = ilen*kr
kcount init 0
loop:
asig loscil 1, 1, gicorpuswave[index], 1
kdx init 0
kmfcc[], ktrig getmfccs asig, gifftsize, gimfccbands
if (ktrig == 1) then
kfb = 0
while (kfb < gimfccbands) do
tabw kmfcc[kfb], kdx, gicorpus[index]
kfb += 1
kdx += 1
od
endif
loop_lt kcount, 1, kcycles, loop
else
schedkwhen 1, 1, 1, "analyseloop", 0, 1, index+1
turnoff
endif
endin
/*
* Play the driver sound, obtain the nearest matching index of the corpus sound, and
* then play that from the relevant starting index with the "segment" instrument.
* Output only the left channel for audible comparison purposes.
*/
instr player
; if playing, turn off, otherwise play
if (giplaying == 1) then
; set play/stop button colour to green and turn off
FLsetColor 40, 140, 40, giplay
giplaying = 0
turnoff2 p1, 0, 0
else
; set play/stop button colour to red
FLsetColor 140, 40, 40, giplay
giplaying = 1
idriverfn = gicorpuswave[i(gkdriverindex)]
iduration = ftlen(idriverfn) / ftsr(idriverfn)
p3 = iduration
; after duration, call same instrument to set giplaying to 0
ktime timeinsts
if (ktime >= iduration) then
event "i", "player", 0, 0.1
endif
; read the driving sound
adriver loscil 1, 1, idriverfn, 1
; do the actual matching to find the best sample point
kdx, ktrig nearest adriver, gifftsize, gimfccbands, gicorpus[i(gkcorpusindex)]
; segment time according to the MFCC analysis FFT size
isegmenttime = (1/sr) * gifftsize
ksegmenttime = isegmenttime * gkgrainsize
if (gkreadmode == 0) then
; each grain is an instrument call that chnmixes to "segments"
schedkwhen ktrig, 0, 0, "segment", random:k(0, isegmenttime), ksegmenttime, gicorpuswave[i(gkcorpusindex)], kdx
amatched chnget "segments"
chnclear "segments"
else
; grains are read using sndwarp
ilen = ftlen(gicorpuswave[i(gkcorpusindex)])
icsr = ftsr(gicorpuswave[i(gkcorpusindex)])
icduration = ilen / icsr
icps = 1/(ilen/icsr)
aphs, a_ syncphasor icps*(1-gkstretch), a(ktrig)
apos = (((aphs * ilen) + kdx) / ilen) * icduration
amatched sndwarp 0.7, apos, 1, gicorpuswave[i(gkcorpusindex)], 0, gifftsize/2, 64, 4, 99, 1
endif
; delay to account for matching
adriver vdelay adriver, (1/sr)*gifftsize, 1
; if pvs modifications are required
if (gkblur == 1 || gkpitchshift == 1) then
ir = 1024
fsegments pvsanal amatched, ir, ir/4, ir, 1
if (gkpitchshift == 1) then
; additional pitch matching; rough at the moment
fsource pvsanal adriver, ir, ir/4, ir, 1
kfrsrc, kasrc pvspitch fsource, 0.1
kfrseg, kaseg pvspitch fsegments, 0.1
kpscale init 1
if (kfrseg != 0) then
kpscale = kfrsrc / kfrseg
endif
fpitchmatched pvscale fsegments, kpscale
if (gkblur == 1) then
fouts pvsblur fpitchmatched, (gkblurtime*ksegmenttime*2)+(ksegmenttime*0.5), isegmenttime*6
else
fouts = fpitchmatched
endif
elseif (gkblur == 1) then
fouts pvsblur fsegments, (gkblurtime*ksegmenttime*2)+(ksegmenttime*0.5), isegmenttime*6
adriver vdelay adriver, (1/sr)*ir, 1
endif
amatched pvsynth fouts
;asegments balance asegmentstemp, asegments
endif
if (gkmatchamps == 1) then
amatched balance amatched, adriver
endif
; output with crossfader
aout = (adriver*gkxfade) + (amatched*(1-gkxfade))
outs aout, aout
endif
endin
/*
* Play sound grain from table p4 with the start index p5, with basic enveloping.
*/
instr segment
imidpoint = i(gkenvelope)
ifadepoint = (1-imidpoint)*0.5
iamp = 1
ifn = p4
il = ftlen(ifn)
isec = il/sr
ist = p5
icps = 1/isec
aphs phasor icps
andx = aphs * il
aout tablei andx+ist, ifn
aout *= linseg:a(0, p3*ifadepoint, iamp, p3*imidpoint, iamp, p3*ifadepoint, 0)
chnmix aout, "segments"
endin
f99 0 512 9 0.5 1 0 ; half sine
f0 3600
i"analyseloop" 0 1 0