2016-08-20 18:23:55 -04:00
|
|
|
ardour {
|
|
|
|
["type"] = "dsp",
|
|
|
|
name = "a-Pong",
|
2016-08-21 06:34:15 -04:00
|
|
|
category = "Toy",
|
2016-08-20 18:23:55 -04:00
|
|
|
license = "MIT",
|
|
|
|
author = "Ardour Lua Task Force",
|
2016-08-21 06:34:15 -04:00
|
|
|
description = [[A console classic for your console]]
|
2016-08-20 18:23:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
-- return possible i/o configurations
|
|
|
|
function dsp_ioconfig ()
|
|
|
|
-- -1, -1 = any number of channels as long as input and output count matches
|
|
|
|
return { [1] = { audio_in = -1, audio_out = -1}, }
|
|
|
|
end
|
|
|
|
|
2016-08-21 06:34:15 -04:00
|
|
|
-- control port(s)
|
2016-08-20 18:23:55 -04:00
|
|
|
function dsp_params ()
|
|
|
|
return
|
|
|
|
{
|
|
|
|
{ ["type"] = "input", name = "Bar", min = 0, max = 1, default = 0.5 },
|
2016-08-21 06:34:15 -04:00
|
|
|
{ ["type"] = "input", name = "Reset", min = 0, max = 1, default = 0, toggled = true },
|
|
|
|
{ ["type"] = "input", name = "Difficulty", min = 1, max = 10, default = 3},
|
2016-08-20 18:23:55 -04:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2016-08-21 06:34:15 -04:00
|
|
|
|
2016-08-21 11:43:24 -04:00
|
|
|
-- Game State (for this instance)
|
2016-08-21 06:34:15 -04:00
|
|
|
-- NOTE: these variables are for the DSP part (not shared with the GUI instance)
|
2016-08-21 16:57:18 -04:00
|
|
|
local sample_rate -- sample-rate
|
2016-08-20 19:36:50 -04:00
|
|
|
local fps -- audio samples per game-step
|
|
|
|
local game_time -- counts up to fps
|
2016-08-21 06:34:15 -04:00
|
|
|
local game_score
|
2016-08-20 19:36:50 -04:00
|
|
|
local ball_x, ball_y -- ball position [0..1]
|
|
|
|
local dx, dy -- current ball speed
|
|
|
|
local lost_sound -- audio-sample counter for game-over [0..3*fps]
|
|
|
|
local ping_sound -- audio-sample counter for ping-sound [0..fps]
|
2016-08-21 11:43:24 -04:00
|
|
|
local ping_phase -- ping note phase-difference per sample
|
2016-08-20 19:36:50 -04:00
|
|
|
local ping_pitch
|
2016-08-20 18:23:55 -04:00
|
|
|
|
|
|
|
function dsp_init (rate)
|
2016-08-21 06:34:15 -04:00
|
|
|
-- allocate a "shared memory" area to transfer state to the GUI
|
|
|
|
self:shmem ():allocate (3)
|
2016-08-20 18:23:55 -04:00
|
|
|
self:shmem ():clear ()
|
2016-08-21 06:34:15 -04:00
|
|
|
-- initialize some variables
|
2016-08-21 16:57:18 -04:00
|
|
|
sample_rate = rate
|
2016-08-20 18:23:55 -04:00
|
|
|
fps = rate / 25
|
2016-08-20 19:36:50 -04:00
|
|
|
ping_pitch = 752 / rate
|
2016-08-20 18:23:55 -04:00
|
|
|
ball_x = 0.5
|
|
|
|
ball_y = 0
|
2016-08-21 06:34:15 -04:00
|
|
|
dx = 0.00367
|
|
|
|
dy = 0.01063
|
|
|
|
game_score = 0
|
2016-08-21 11:43:24 -04:00
|
|
|
game_time = fps -- start the ball immediately (notify GUI)
|
2016-08-20 19:36:50 -04:00
|
|
|
ping_sound = fps -- set to end of synth cycle
|
|
|
|
lost_sound = 3 * fps
|
2016-08-20 18:23:55 -04:00
|
|
|
end
|
|
|
|
|
2016-08-21 16:57:18 -04:00
|
|
|
function queue_beep ()
|
|
|
|
-- queue 'ping' sound (unless one is already playing to prevent clicks)
|
|
|
|
if (ping_sound >= fps) then
|
2016-08-21 21:26:14 -04:00
|
|
|
-- major scale, 2 octaves
|
|
|
|
local scale = { 0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 17, 19, 21, 23, 24 }
|
|
|
|
local midi_note = 60 + scale[1 + math.floor (math.random () * 14)]
|
|
|
|
ping_pitch = (440 / 32) * 2^((midi_note - 10.0) / 12.0) / sample_rate
|
2016-08-21 16:57:18 -04:00
|
|
|
ping_sound = 0
|
|
|
|
ping_phase = 0
|
|
|
|
end
|
|
|
|
end
|
2016-08-21 11:43:24 -04:00
|
|
|
|
|
|
|
-- callback: process "n_samples" of audio
|
|
|
|
-- ins, outs are http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
|
|
|
|
-- pointers to the audio buffers
|
2016-08-20 18:23:55 -04:00
|
|
|
function dsp_run (ins, outs, n_samples)
|
|
|
|
local ctrl = CtrlPorts:array () -- get control port array (read/write)
|
|
|
|
|
2016-08-20 19:36:50 -04:00
|
|
|
local changed = false -- flag to notify GUI on every game-step
|
|
|
|
game_time = game_time + n_samples
|
2016-08-20 18:23:55 -04:00
|
|
|
|
2016-08-21 06:34:15 -04:00
|
|
|
-- reset (allow to write automation from a given start-point)
|
2016-08-21 11:43:24 -04:00
|
|
|
-- ctrl[2] corresponds to the "Reset" input control
|
2016-08-21 06:34:15 -04:00
|
|
|
if ctrl[2] > 0 then
|
|
|
|
game_time = 0
|
|
|
|
ball_x = 0.5
|
|
|
|
ball_y = 0
|
|
|
|
dx = 0.00367
|
|
|
|
dy = 0.01063
|
|
|
|
game_score = 0
|
|
|
|
end
|
|
|
|
|
2016-08-20 18:23:55 -04:00
|
|
|
-- simple game engine
|
2016-08-21 06:34:15 -04:00
|
|
|
while game_time > fps and ctrl[2] <= 0 do
|
2016-08-20 18:23:55 -04:00
|
|
|
changed = true
|
2016-08-20 19:36:50 -04:00
|
|
|
game_time = game_time - fps
|
2016-08-20 18:23:55 -04:00
|
|
|
|
2016-08-20 19:36:50 -04:00
|
|
|
-- move the ball
|
2016-08-21 06:34:15 -04:00
|
|
|
ball_x = ball_x + dx * ctrl[3]
|
|
|
|
ball_y = ball_y + dy * ctrl[3]
|
2016-08-20 18:23:55 -04:00
|
|
|
|
2016-08-20 19:36:50 -04:00
|
|
|
-- reflect left/right
|
2016-08-21 16:57:18 -04:00
|
|
|
if ball_x >= 1 or ball_x <= 0 then
|
|
|
|
dx = -dx
|
|
|
|
queue_beep ()
|
|
|
|
end
|
2016-08-20 19:36:50 -04:00
|
|
|
|
2016-08-21 06:34:15 -04:00
|
|
|
-- single player (reflect top) -- TODO "stereo" version, 2 ctrls :)
|
2016-08-21 16:57:18 -04:00
|
|
|
if ball_y <= 0 then
|
|
|
|
dy = - dy y = 0
|
|
|
|
queue_beep ()
|
|
|
|
end
|
|
|
|
|
|
|
|
-- keep the ball in the field at all times
|
|
|
|
if ball_x >= 1 then ball_x = 1 end
|
|
|
|
if ball_x <= 0 then ball_x = 0 end
|
2016-08-20 18:23:55 -04:00
|
|
|
|
2016-08-21 06:34:15 -04:00
|
|
|
-- bottom edge
|
2016-08-20 18:23:55 -04:00
|
|
|
if ball_y > 1 then
|
2016-08-20 19:36:50 -04:00
|
|
|
local bar = ctrl[1] -- get bar position
|
2016-08-20 18:23:55 -04:00
|
|
|
if math.abs (bar - ball_x) < 0.1 then
|
2016-08-21 06:34:15 -04:00
|
|
|
-- reflect the ball
|
2016-08-20 18:23:55 -04:00
|
|
|
dy = - dy
|
|
|
|
ball_y = 1.0
|
2016-08-21 06:34:15 -04:00
|
|
|
dx = dx - 0.04 * (bar - ball_x)
|
|
|
|
-- make sure it's moving (not stuck on borders)
|
|
|
|
if math.abs (dx) < 0.0001 then dx = 0.0001 end
|
|
|
|
game_score = game_score + 1
|
2016-08-21 16:57:18 -04:00
|
|
|
queue_beep ()
|
2016-08-20 18:23:55 -04:00
|
|
|
else
|
2016-08-21 11:43:24 -04:00
|
|
|
-- game over, reset game
|
|
|
|
lost_sound = 0 -- re-start noise
|
2016-08-20 18:23:55 -04:00
|
|
|
ball_y = 0
|
2016-08-21 06:34:15 -04:00
|
|
|
game_score = 0
|
2016-08-21 11:43:24 -04:00
|
|
|
dx = 0.00367
|
2016-08-20 18:23:55 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-08-21 06:34:15 -04:00
|
|
|
-- forward audio if processing is not in-place
|
|
|
|
for c = 1,#outs do
|
|
|
|
-- check if output and input buffers for this channel are identical
|
|
|
|
-- http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
|
2017-03-16 15:55:41 -04:00
|
|
|
if ins[c] ~= outs[c] then
|
2016-08-21 11:43:24 -04:00
|
|
|
-- fast (accelerated) copy
|
2016-08-21 06:34:15 -04:00
|
|
|
-- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP
|
2016-08-21 21:26:14 -04:00
|
|
|
ARDOUR.DSP.copy_vector (outs[c], ins[c], n_samples)
|
2016-08-21 06:34:15 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-08-20 18:23:55 -04:00
|
|
|
-- simple synth -- TODO Optimize
|
2016-08-20 19:36:50 -04:00
|
|
|
if ping_sound < fps then
|
2016-08-21 06:34:15 -04:00
|
|
|
-- cache audio data buffers for direct access, later
|
2016-08-20 19:36:50 -04:00
|
|
|
local abufs = {}
|
|
|
|
for c = 1,#outs do
|
2016-08-21 06:34:15 -04:00
|
|
|
abufs[c] = outs[c]:array()
|
2016-08-20 19:36:50 -04:00
|
|
|
end
|
2016-08-21 06:34:15 -04:00
|
|
|
-- simple sine synth with a sine-envelope
|
2016-08-20 18:23:55 -04:00
|
|
|
for s = 1, n_samples do
|
2016-08-20 19:36:50 -04:00
|
|
|
ping_sound = ping_sound + 1
|
|
|
|
ping_phase = ping_phase + ping_pitch
|
|
|
|
local snd = 0.7 * math.sin(6.283185307 * ping_phase) * math.sin (3.141592 * ping_sound / fps)
|
2016-08-21 06:34:15 -04:00
|
|
|
-- add synthesized sound to all channels
|
2016-08-20 18:23:55 -04:00
|
|
|
for c = 1,#outs do
|
2016-08-20 19:36:50 -04:00
|
|
|
abufs[c][s] = abufs[c][s] + snd
|
2016-08-20 18:23:55 -04:00
|
|
|
end
|
2016-08-21 06:34:15 -04:00
|
|
|
-- break out of the loop when the sound finished
|
2016-08-20 19:36:50 -04:00
|
|
|
if ping_sound >= fps then goto ping_end end
|
2016-08-20 18:23:55 -04:00
|
|
|
end
|
2016-08-20 19:36:50 -04:00
|
|
|
::ping_end::
|
2016-08-20 18:23:55 -04:00
|
|
|
end
|
|
|
|
|
2016-08-20 19:36:50 -04:00
|
|
|
if lost_sound < 3 * fps then
|
|
|
|
local abufs = {}
|
|
|
|
for c = 1,#outs do
|
2016-08-21 06:34:15 -04:00
|
|
|
abufs[c] = outs[c]:array()
|
2016-08-20 19:36:50 -04:00
|
|
|
end
|
2016-08-20 18:23:55 -04:00
|
|
|
for s = 1, n_samples do
|
2016-08-20 19:36:50 -04:00
|
|
|
lost_sound = lost_sound + 1
|
|
|
|
-- -12dBFS white noise
|
2016-08-20 18:23:55 -04:00
|
|
|
local snd = 0.5 * (math.random () - 0.5)
|
|
|
|
for c = 1,#outs do
|
2016-08-20 19:36:50 -04:00
|
|
|
abufs[c][s] = abufs[c][s] + snd
|
2016-08-20 18:23:55 -04:00
|
|
|
end
|
2016-08-20 19:36:50 -04:00
|
|
|
if lost_sound >= 3 * fps then goto noise_end end
|
2016-08-20 18:23:55 -04:00
|
|
|
end
|
2016-08-20 19:36:50 -04:00
|
|
|
::noise_end::
|
2016-08-20 18:23:55 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
if changed then
|
2016-08-21 11:43:24 -04:00
|
|
|
-- notify the GUI
|
|
|
|
local shmem = self:shmem () -- get the shared memory region
|
|
|
|
local state = shmem:to_float (0):array () -- "cast" into lua-table
|
|
|
|
-- update data..
|
2016-08-20 18:23:55 -04:00
|
|
|
state[1] = ball_x
|
|
|
|
state[2] = ball_y
|
2016-08-21 06:34:15 -04:00
|
|
|
state[3] = game_score
|
2016-08-21 11:43:24 -04:00
|
|
|
-- ..and wake up the UI
|
2016-08-20 18:23:55 -04:00
|
|
|
self:queue_draw ()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2016-08-21 06:34:15 -04:00
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
--- inline display
|
|
|
|
|
2016-08-21 11:43:24 -04:00
|
|
|
local txt = nil -- cache font description (in GUI context)
|
2016-08-21 06:34:15 -04:00
|
|
|
|
2016-08-20 18:23:55 -04:00
|
|
|
function render_inline (ctx, w, max_h)
|
2016-08-21 06:34:15 -04:00
|
|
|
local ctrl = CtrlPorts:array () -- control port array
|
2016-08-21 11:43:24 -04:00
|
|
|
local shmem = self:shmem () -- shared memory region (game state from DSP)
|
2016-08-21 06:34:15 -04:00
|
|
|
local state = shmem:to_float (0):array () -- cast to lua-table
|
2016-08-20 18:23:55 -04:00
|
|
|
|
|
|
|
if (w > max_h) then
|
|
|
|
h = max_h
|
|
|
|
else
|
|
|
|
h = w
|
|
|
|
end
|
|
|
|
|
2016-08-21 06:34:15 -04:00
|
|
|
-- prepare text rendering
|
|
|
|
if not txt then
|
|
|
|
-- allocate PangoLayout and set font
|
|
|
|
--http://manual.ardour.org/lua-scripting/class_reference/#Cairo:PangoLayout
|
|
|
|
txt = Cairo.PangoLayout (ctx, "Mono 10px")
|
|
|
|
end
|
|
|
|
|
2016-08-21 11:43:24 -04:00
|
|
|
-- ctx is-a http://manual.ardour.org/lua-scripting/class_reference/#Cairo:Context
|
|
|
|
-- 2D vector graphics http://cairographics.org/
|
|
|
|
|
2016-08-20 18:23:55 -04:00
|
|
|
-- clear background
|
|
|
|
ctx:rectangle (0, 0, w, h)
|
|
|
|
ctx:set_source_rgba (.2, .2, .2, 1.0)
|
|
|
|
ctx:fill ()
|
|
|
|
|
2016-08-21 11:43:24 -04:00
|
|
|
-- print the current score
|
2016-08-21 06:34:15 -04:00
|
|
|
if (state[3] > 0) then
|
|
|
|
txt:set_text (string.format ("%.0f", state[3]));
|
|
|
|
local tw, th = txt:get_pixel_size ()
|
|
|
|
ctx:set_source_rgba (1, 1, 1, 1.0)
|
|
|
|
ctx:move_to (w - tw - 3, 3)
|
|
|
|
txt:show_in_cairo_context (ctx)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- prepare line and dot rendering
|
|
|
|
ctx:set_line_cap (Cairo.LineCap.Round)
|
|
|
|
ctx:set_line_width (3.0)
|
|
|
|
ctx:set_source_rgba (.8, .8, .8, 1.0)
|
|
|
|
|
2016-08-20 18:23:55 -04:00
|
|
|
-- display bar
|
|
|
|
local bar_width = w * .1
|
|
|
|
local bar_space = w - bar_width
|
|
|
|
|
|
|
|
ctx:move_to (bar_space * ctrl[1], h - 3)
|
|
|
|
ctx:rel_line_to (bar_width, 0)
|
|
|
|
ctx:stroke ()
|
|
|
|
|
|
|
|
-- display ball
|
2016-08-21 06:34:15 -04:00
|
|
|
ctx:move_to (1 + state[1] * (w - 3), state[2] * (h - 5))
|
2016-08-20 18:23:55 -04:00
|
|
|
ctx:close_path ()
|
|
|
|
ctx:stroke ()
|
|
|
|
|
|
|
|
return {w, h}
|
|
|
|
end
|