-odac /* EXAMPLE 9 A rudimentary pitch tracking concatenative resynthesizer 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 series of ascending piano notes. When done, the instrument "playmatches" is scheduled. This plays a descending oscillator which is pitch tracked and then a similar pitch is found from the database, prompting the "segment" instrument to be scheduled with the relevant offset. The result is that the piano notes/sound file 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/piano.wav" ; 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 koct, kamp pitch ain, 0.01, 7, 11, 6, 12, 10, 8 ; 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 (kcps > klast*1.1 || kcps < klast*0.9) 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 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 ; descending oscillator k1 linseg 2000, p3, 200 ain oscil 1, k1, 1 ; track it koct, kamp pitch ain, 0.01, 7, 11, 6, 12, 10, 8 ; 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) Squery sprintfk "SELECT time FROM pitches WHERE cps >= %f LIMIT 1", kcps kdone, kpos dbscalar_k gidb, Squery, kchanged ; don't repeat notes (try the schedkwhen outside of the if block for continuous play) if (kpos != klast) then schedkwhen kdone, 0, 0, "segment", 0, 0.2, kpos klast = kpos 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 fade in/out instr segment iskip = p4 kamp linseg 0, p3*0.1, 1, p3*0.8, 1, p3*0.1, 0 a1 diskin2 gSfile, 1, iskip aout = a1*kamp * 0.1 outs aout, aout endin f1 0 16384 10 1 0 0.3 0 0.2 0 0.14 0 .111 ; square wave i"start_example" 0 1