diff options
author | knyght <q@1bpm.net> | 2021-03-16 01:42:19 +0000 |
---|---|---|
committer | knyght <q@1bpm.net> | 2021-03-16 01:42:19 +0000 |
commit | 7a0e8937baf672a66eafb2da7493c201cc694b01 (patch) | |
tree | cee474db1dc3a768e0b0c4a663fe9de7053627a1 /mfcc_match_fltk_demo.csd | |
download | csd-mfccmatch-7a0e8937baf672a66eafb2da7493c201cc694b01.tar.gz csd-mfccmatch-7a0e8937baf672a66eafb2da7493c201cc694b01.tar.bz2 csd-mfccmatch-7a0e8937baf672a66eafb2da7493c201cc694b01.zip |
initial
Diffstat (limited to 'mfcc_match_fltk_demo.csd')
-rw-r--r-- | mfcc_match_fltk_demo.csd | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/mfcc_match_fltk_demo.csd b/mfcc_match_fltk_demo.csd new file mode 100644 index 0000000..47e7373 --- /dev/null +++ b/mfcc_match_fltk_demo.csd @@ -0,0 +1,330 @@ +<CsoundSynthesizer> +<CsOptions> +-odac +--m-amps=0 +</CsOptions> +<CsInstruments> +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 + fout "D:/mfxx.wav", 14, 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 + + + +</CsInstruments> +<CsScore> +f99 0 512 9 0.5 1 0 ; half sine +f0 3600 +i"analyseloop" 0 1 0 +</CsScore> +</CsoundSynthesizer> |