diff --git a/share/scripts/a_dtmf_phone.lua b/share/scripts/a_dtmf_phone.lua new file mode 100644 index 0000000000..ccfea1a15c --- /dev/null +++ b/share/scripts/a_dtmf_phone.lua @@ -0,0 +1,198 @@ +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