-odac /* EXAMPLE 7 A rudimentary pitch tracking concatenative resynthesizer as such This creates a sqlite database in memory, and then uses the opcode getpitches to scan through a sound file in non-realtime, storing detected pitches and the relevant offset time of the pitch in the database. The example file is a descending violin glissando. When done, the instrument "playmatches" is scheduled twice which plays an oscillator varying in pitch, which is pitch tracked and the nearest frequency is found in the database, prompting the "segment" instrument to be scheduled with the relevant offset. 1. for 20s in which nearest matches are played only if the detected pitch has changed outside of a threshold 2. as above but with matches played continuously The result is that the violin segments picked should moreorless match what the oscillator is doing. */ sr = 44100 kr = 4410 nchnls = 2 0dbfs = 1 ; create an in-memory sqlite database and create a table gidb dbconnect "sqlite", ":memory:" dbexec gidb, "CREATE TABLE pitches (time REAL, cps REAL)" ; file of ascending piano pitches gSfile = "sounds/violin.wav" gifn ftgen 0, 0, 0, 1, gSfile, 0, 0, 0 ; detect pitches and insert time/cps to database given a filename, in non-realtime opcode getpitches, 0, S Sfile xin ktimek timeinstk ktime timeinsts klast init -1 kcount init 0 ; run in the first single k-rate cycle if (ktimek == 0) then ; get file length in k-cycles ilen filelen Sfile kcycles = ilen * kr loop: ; read file and track pitch ain diskin2 Sfile, 1 ;ain butterbp ain, 500, 250 koct, kamp pitch ain, 0.01, 6, 12, 6, 12, 60 ; only take action if pitch has changed kchanged changed2 koct if (kchanged == 1) then ; only store if cps is reasonably different from the last value kcps = cpsoct(koct) if (1==1) then ; insert to database: the dbexec_kb opcode is k-rate but blocking/synchronous. ; simpler to use for the non-realtime operation as regular _k opcodes are threaded/asynchronous ktime = kcount / kr Squery sprintfk "INSERT INTO pitches (time, cps) VALUES (%f, %f)", ktime, kcps dbexec_kb gidb, Squery klast = kcps endif endif loop_lt kcount, 1, kcycles, loop endif endop ; begin the example: find pitches and then schedule the next step instr start_example getpitches gSfile schedkwhen 1, 0, 0, "playmatches", 0, 20, 0 schedkwhen 1, 0, 0, "playmatches", 20, 20, 1 turnoff endin ; pitch follow a descending oscillator and attempt to find a similar cps in the database, then schedule segment accordingly instr playmatches kdone init 0 ; for when the select query is done klast init 0 ; last pitch played, in order to avoid repeats ; oscillator k1 linseg 400, p3, 200 ktime linseg 0.001, p3, 2 k2 oscil k1, ktime ain oscil 1, abs(k2)+550, 1 ; track it koct, kamp pitch ain, 0.01, 5, 10, 6, 12 ; only take action when the tracked pitch has changed kchanged changed2 koct if (kchanged == 1) then ; (very roughly) get a near frequency match from the database kcps = cpsoct(koct) ; fairly nasty looking query for obtaining the nearest value SquerySource = {{ SELECT time FROM ( SELECT time, cps FROM ( SELECT time, cps FROM pitches WHERE cps >= %f ORDER BY cps ASC LIMIT 1 ) UNION SELECT time, cps FROM ( SELECT time, cps FROM pitches WHERE cps < %f ORDER BY cps DESC LIMIT 1 ) ) x ORDER BY ABS(cps - %f) ASC LIMIT 1 }} Squery sprintfk SquerySource, kcps, kcps, kcps kdone, kpos dbscalar_k gidb, Squery, kchanged ;schedule the notes if (p4 == 1) then schedkwhen kdone, 0, 0, "segment", 0, 0.2, kpos else if (kpos < klast*0.8 || kpos > klast*1.2) then schedkwhen kdone, 0, 0, "segment", 0, 0.2, kpos klast = kpos endif endif endif ; uncomment to hear the oscillator as well as pitch matched output ;outs ain*0.01, ain*0.01 endin ; play part of the sound file given a skip time with a basic envelope instr segment il = ftlen(gifn) isec = il/sr ist = sr*p4 icps = 1/isec aphs phasor icps andx = aphs * il aout tablei andx+ist, gifn kamp linseg 0, p3*0.3, 1, p3*0.4, 1, p3*0.3, 0 outs aout*0.1*kamp, aout*0.1*kamp endin f1 0 16384 10 1 0 0.3 0 0.2 0 0.14 0 .111 ; square wave i"start_example" 0 1