ardour { ["type"] = "dsp", name = "ACE DTMF Phone", category = "Instrument", license = "MIT", author = "Ardour Team", description = [[Ardour phone home]] } local r = 48000 local p1 = 0 local p2 = 0 local tau = 2 * math.pi local active_note = -1 local ringsize = 8 function dsp_ioconfig () return { { midi_in = 1, audio_in = 0, audio_out = 1}, { midi_in = 1, audio_in = 0, audio_out = 2} } end function dsp_init (rate) r = rate -- allocate DSP -> GUI ringbuffer self:shmem():allocate (1 + ringsize) self:shmem():atomic_set_int (0, 0) local buffer = self:shmem():to_int(1):array() for i = 1, ringsize do buffer[i] = -1 -- empty slot end end -- https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling#Keypad local dtmf = { ["1"] = {1209, 697}, ["2"] = {1336, 697}, ["3"] = {1447, 697}, ["A"] = {1633, 697}, ["4"] = {1209, 770}, ["5"] = {1336, 770}, ["6"] = {1447, 770}, ["B"] = {1633, 770}, ["7"] = {1209, 852}, ["8"] = {1336, 852}, ["9"] = {1447, 852}, ["C"] = {1633, 852}, ["*"] = {1209, 941}, ["0"] = {1336, 941}, ["#"] = {1447, 941}, ["D"] = {1633, 941}, [" "] = {0, 0}, } local map = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "*", "#", "A", "B", "C", "D"} function midi_note_to_pad (n) -- map [1..9 0 * #] to 12TET, ignore A..D -- start at C == 1 return map [1 + n % 12] end function dsp_run (ins, outs, n_samples) -- clear output buffer local a = {} for s = 1, n_samples do a[s] = 0 end local function synth (s_start, s_end) if active_note == -1 then return end local f = dtmf[midi_note_to_pad (active_note)] if f[1] == 0 then return end local i1 = f[1] / r local i2 = f[2] / r for s = s_start,s_end do a[s] = 0.5 * (math.sin (p1 * tau) + math.sin (p2 * tau)) p1 = p1 + i1 p2 = p2 + i2 end end local newdata = false local tme = 1 for _,b in pairs (midiin) do local t = b["time"] -- t = [ 1 .. n_samples ] -- synth sound until event synth (tme, t) tme = t + 1 -- process MIDI events (ignore MIDI channel) local d = b["data"] -- get midi-event if (#d == 3 and (d[1] & 240) == 144) then -- note on --if active_note == -1 then active_note = d[2] p1, p2 = 0, 0 -- inform UI local pos = self:shmem():atomic_get_int (0) local buffer = self:shmem():to_int(1):array() buffer[1 + pos] = active_note pos = (pos + 1) % ringsize self:shmem():atomic_set_int (0, pos) newdata = true --end end if (#d == 3 and (d[1] & 240) == 128) then -- note off if active_note == d[2] then active_note = -1 end end if (#d == 3 and (d[1] & 240) == 176) then -- CC if (d[2] == 120 or d[2] == 123) then -- panic active_note = -1 -- clear UI self:shmem():atomic_set_int (0, 0) local buffer = self:shmem():to_int(1):array() for i = 1, ringsize do buffer[i] = -1 -- empty slot end newdata = true end end end -- synth rest of cycle synth (tme, n_samples) -- keep phase between 0..tau p1 = math.fmod (p1, 1) p2 = math.fmod (p2, 1) -- set all outputs for c = 1,#outs do outs[c]:set_table (a, n_samples) end if newdata then self:queue_draw () end end ----------------- -- inline display ----------------- local txt = nil -- a pango context local vpadding = 2 local displayheight = 0 local linewidth = 0 function render_inline (ctx, displaywidth, max_h) local pos = self:shmem():atomic_get_int(0) local buffer = self:shmem():to_int(1):array() local str = "" for i = 1, ringsize do local p = (i + pos + ringsize - 1) % ringsize local n = buffer[1 + p] if n ~= -1 and " " ~= midi_note_to_pad (n) then str = str .. midi_note_to_pad (n) end end if not txt then txt = Cairo.PangoLayout (ctx, "Mono 10") -- compute the size of the display local siz = "" for i = 1, ringsize do siz = siz .. "0" end txt:set_text (siz) local lineheight linewidth, lineheight = txt:get_pixel_size() displayheight = math.min (2 * vpadding + lineheight, max_h) end -- clear background ctx:rectangle (0, 0, displaywidth, displayheight) ctx:set_source_rgba (.2, .2, .2, 1.0) ctx:fill () -- show dialed number ctx:set_source_rgba (1.0, 1.0, 1.0, 1.0) txt:set_text (str) ctx:move_to (math.floor (.5 * (displaywidth - linewidth)), vpadding) txt:show_in_cairo_context (ctx) return {displaywidth, displayheight} end