# Copyright (c) 2015-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
#
# This is free software: you can redistribute it and/or modify
# it under the terms of the Artistic License 2.0 as published by
# The Perl Foundation.
#
# This source is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Artistic License 2.0 for more details.
#
# You should have received a copy of the Artistic License 2.0
# along the source as a COPYING file. If not, obtain it from
# http://www.perlfoundation.org/artistic_license_2_0.

@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix pset: <http://lv2plug.in/ns/ext/presets#> .
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix state: <http://lv2plug.in/ns/ext/state#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

@prefix moony: <http://open-music-kontrollers.ch/lv2/moony#> .
@prefix lic: <http://opensource.org/licenses/> .
@prefix stateR: <urn:uuid:ecc34531-c5a5-4fd4-9edf-14f2d8cf7b57#> .

# to please sord_validate
moony:code
	a lv2:Parameter ;
	rdfs:range atom:String ;
	rdfs:label "Lua code chunk" .
moony:state
	a lv2:Parameter ;
	rdfs:range atom:Atom ;
	rdfs:label "Lua code state" .
moony:editorHidden
	a lv2:Parameter ;
	rdfs:range atom:Bool ;
	rdfs:label "Editor tab hidden state" .
moony:logHidden
	a lv2:Parameter ;
	rdfs:range atom:Bool ;
	rdfs:label "Log tab hidden state" .
moony:logFollow
	a lv2:Parameter ;
	rdfs:range atom:Bool ;
	rdfs:label "Log follow state" .
moony:logReset
	a lv2:Parameter ;
	rdfs:range atom:Bool ;
	rdfs:label "Log reset state" .
moony:paramHidden
	a lv2:Parameter ;
	rdfs:range atom:Bool ;
	rdfs:label "Parameter tab hidden state" .
moony:paramCols
	a lv2:Parameter ;
	rdfs:range atom:Int ;
	rdfs:label "Parameter tab columns" .
moony:paramRows
	a lv2:Parameter ;
	rdfs:range atom:Int ;
	rdfs:label "Parameter tab rows" .

stateR:pBool
	a lv2:Parameter ;
	rdfs:label "A Bool" ;
	rdfs:range atom:Bool .
stateR:pChunk
	a lv2:Parameter ;
	rdfs:label "A Chunk" ;
	rdfs:range atom:Chunk .
stateR:pEnum
	a lv2:Parameter ;
	rdfs:label "An Enum" ;
	rdfs:range atom:Int .
stateR:pFloat
	a lv2:Parameter ;
	rdfs:label "A Float" ;
	rdfs:range atom:Float .
stateR:pInt
	a lv2:Parameter ;
	rdfs:label "An Int" ;
	rdfs:range atom:Int .
stateR:pString
	a lv2:Parameter ;
	rdfs:label "A String" ;
	rdfs:range atom:String .
stateR:pURID
	a lv2:Parameter ;
	rdfs:label "A URID" ;
	rdfs:range atom:URI .

moony:bank-through_control-through
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """function run(n, control, notify, ...)
	return ...
end

-- vim: set syntax=lua:"""
	] .

moony:bank-through_atom-through
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """function run(n, control, notify, seq, forge, ...)
	for frames, atom in seq:foreach() do
		forge:time(frames):atom(atom)
	end

	return ...
end

-- vim: set syntax=lua:"""
	] .

moony:bank-through_atom-through2
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """function run(n, control, notify, seq1, forge1, seq2, forge2)
	for frames, atom in seq1:foreach() do
		forge1:time(frames):atom(atom)
	end

	for frames, atom in seq2:foreach() do
		forge2:time(frames):atom(atom)
	end
end

-- vim: set syntax=lua:"""
	] .

moony:bank-through_atom-through4
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """function run(n, control, notify, seq1, forge1, seq2, forge2, seq3, forge3, seq4, forge4)
	for frames, atom in seq1:foreach() do
		forge1:time(frames):atom(atom)
	end

	for frames, atom in seq2:foreach() do
		forge2:time(frames):atom(atom)
	end

	for frames, atom in seq3:foreach() do
		forge3:time(frames):atom(atom)
	end

	for frames, atom in seq4:foreach() do
		forge4:time(frames):atom(atom)
	end
end

-- vim: set syntax=lua:"""
	] .

moony:bank-multiplex_atom-multiplex2
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """function run(n, control, notify, seq1, forge1, seq2, forge2)
	for frames, atom in seq1:foreach(seq2) do
		forge1:time(frames):atom(atom)
		forge2:time(frames):atom(atom)
	end
end

-- vim: set syntax=lua:"""
	] .

moony:bank-multiplex_atom-multiplex4
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """function run(n, control, notify, seq1, forge1, seq2, forge2, seq3, forge3, seq4, forge4)
	for frames, atom in seq1:foreach(seq2, seq3, seq4) do
		forge1:time(frames):atom(atom)
		forge2:time(frames):atom(atom)
		forge3:time(frames):atom(atom)
		forge4:time(frames):atom(atom)
	end
end

-- vim: set syntax=lua:"""
	] .

moony:bank-midi_midi-responder
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """local midiR = MIDIResponder({
	[MIDI.NoteOn] = function(self, frames, forge, chan, note, vel)
		forge:time(frames):midi(MIDI.NoteOn | chan, note, vel)
	end,
	[MIDI.NoteOff] = function(self, frames, forge, chan, note, vel)
		forge:time(frames):midi(MIDI.NoteOff | chan, note, vel)
	end
})

function run(n, control, notify, seq, forge)
	for frames, atom in seq:foreach() do
		local handled = midiR(frames, forge, atom)
	end
end

-- vim: set syntax=lua:"""
	] .

moony:bank-time_midi-sequencer
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """local timeR = TimeResponder({
  [Time.speed] = function(self, frames, forge, speed)
    self.rolling = speed > 0.0
    if not self.rolling and forge then
      forge:time(frames):midi(MIDI.Controller, MIDI.AllNotesOff)
    end
  end,
  [Time.barBeat] = function(self, frames, forge, barBeat)
    if self.rolling and forge and math.tointeger(barBeat) then
      forge:time(frames):midi(MIDI.NoteOff, 24, 0x7f)
      forge:time(frames):midi(MIDI.NoteOn, 24, 0x7f)
    end
  end,
  [Time.bar] = function(self, frames, forge, bar)
    if self.rolling and forge and math.tointeger(bar) then
      forge:time(frames):midi(MIDI.NoteOff, 48, 0x7f)
      forge:time(frames):midi(MIDI.NoteOn, 48, 0x7f)
    end
  end,
  rolling = false
})

function stash(forge)
  timeR:stash(forge)
end

function apply(atom)
  timeR:apply(atom)
end

function run(n, control, notify, seq, forge)
  local from = 0
  for frames, atom in seq:foreach() do
    timeR(from, frames, forge, atom)
    from = frames
  end
  timeR(from, n, forge)
end

-- vim: set syntax=lua:"""
	] .

moony:bank-time_lindenmayer-system
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden false ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """local mtointeger = math.tointeger -- reference locally

local rolling = false -- transport state
local oldNote = nil -- currently palying note
local velocity = 0x7f -- default note-on velocity value

-- Lindenmayer pattern-rule iterator
local function iterate(pattern, rules, n)
  for i = 1, n do -- iterate n-times
    pattern = string.gsub(pattern, '.', function(c) -- replace every single character with ...
      return rules[c] or c -- ... matching rule or itself
    end)
  end
  return pattern -- resulting iterated over pattern
end

local rules = {
  ['n'] = 'n.[.n..]', -- replacement string for character 'n'
  ['.'] = '[[..n]]' -- replacement string for character '.'
}

local start = 'n' -- initial pattern
local pattern = iterate(start, rules, 4) -- pattern after 4 iterations

local refNote = Note['C#+4'] -- initial reference note
local incNote = 3 -- reference note increment in MIDI semitones

local actions = {
  ['n'] = function() -- action for character 'n'
    coroutine.yield(refNote) -- return current reference note
  end,
  ['.'] = function() -- action for character '.'
    coroutine.yield(nil) -- return nil, e.g. this note is a pause
  end,
  ['['] = function() -- action for character '['
    refNote = (refNote + incNote) & 0x7f -- increase reference note by increment
  end,
  [']'] = function() -- action for character ']'
    refNote = (refNote - incNote) & 0x7f -- decrease reference note by increment
  end
}

local nextNote = coroutine.wrap(function() -- create iterator to get action from pattern
	while true do -- enter infinite loop
    for c in string.gmatch(pattern, '.') do -- iteratore over each single character in pattern
      local action = actions[c] -- look up action for character
      if action then
        action() -- execute action if defined
      end
    end
  end
end)

local timeR = TimeResponder({
  [Time.speed] = function(self, frames, forge, speed)
    rolling = speed ~= 0.0 -- update transport state

    if not rolling and oldNote then -- if at transport stop and currently playing a note
      forge:time(frames):midi(MIDI.NoteOff, oldNote, 0x0) -- send note-off
      oldNote = nil -- invalidate oldNote
    end
  end,
  [Time.barBeat] = function(self, frames, forge, barBeat)
    if rolling and mtointeger(barBeat) then -- if transport is rolling and barBeat being a whole integer
      if oldNote then -- if currently playing a note
        forge:time(frames):midi(MIDI.NoteOff, oldNote, 0x0) -- send note-off
        oldNote = nil -- invalidate oldNote
      end

      local newNote = nextNote() -- get new note or nil from pattern iterator
      if newNote then -- if not a pause
        forge:time(frames):midi(MIDI.NoteOn, newNote, velocity) -- send note-on
        oldNote = newNote -- update currently playing note
      end
    end
  end
}, 16) -- run 16x faster than host time

function run(n, control, notify, seq, forge)
  local from = 0 -- initialize reference frame time

  for frames, atom in seq:foreach() do -- iterate over input event sequence
    timeR(from, frames, forge, atom) -- let time responder handle the event
    from = frames -- advance reference frame time
  end

  timeR(from, n, forge) -- tell time responder about remaining frames of cycle
end

-- vim: set syntax=lua:"""
	] .

moony:bank-osc_osc-responder
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """local oscR = OSCResponder({
	['/ping'] = function(self, frames, forge, fmt, ...)
		forge:time(frames):message('/pong', fmt, ...)
	end,
	['/pong'] = function(self, frames, forge, fmt, ...)
		forge:time(frames):message('/ping', fmt, ...)
	end
})

function run(n, control, notify, seq, forge)
  for frames, atom in seq:foreach() do
    local handled = oscR(frames, forge, atom)
  end
end

-- vim: set syntax=lua:"""
	] .

moony:bank-state_state-responder
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden false ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """local urn = Mapper('urn:uuid:ecc34531-c5a5-4fd4-9edf-14f2d8cf7b57#')
local sync = false

local pLong
local pInt = Parameter{
  [RDFS.label] = 'Integer',
  [RDFS.comment] = 'This is an Integer',
  [RDFS.range] = Atom.Int,
  [Units.unit] = Units.hz,
  [LV2.minimum] = 0,
  [LV2.maximum] = 10,
	_value = 5,
	[Patch.Get] = function(self)
		return self._value
	end,
	[Patch.Set] = function(self, value)
		self._value = value
		pLong(value * 2)
		sync = true
  end
}

pLong = Parameter{
  [RDFS.label] = 'Long',
  [RDFS.comment] = 'This is a Long',
  [RDFS.range] = Atom.Long,
  [Units.unit] = Units.hz,
  [LV2.minimum] = pInt[LV2.minimum] * 2,
  [LV2.maximum] = pInt[LV2.maximum] * 2,
  [RDF.value] = pInt[RDF.value] * 2
}

local pDouble
local pFloat = Parameter{
  [RDFS.label] = 'Float',
  [RDFS.comment] = 'This is a Float',
  [RDFS.range] = Atom.Float,
  [Units.unit] = Units.m,
  [LV2.minimum] = -1.0,
  [LV2.maximum] = 1.0,
	_value = 0.0,
	[Patch.Get] = function(self)
		return self._value
	end,
	[Patch.Set] = function(self, value)
		self._value = value
		pDouble(value * 2)
		sync = true
	end
}

pDouble = Parameter{
  [RDFS.label] = 'Double',
  [RDFS.comment] = 'This is a Double',
  [RDFS.range] = Atom.Double,
  [Units.unit] = Units.m,
  [LV2.minimum] = pFloat[LV2.minimum] * 2,
  [LV2.maximum] = pFloat[LV2.maximum] * 2,
  [RDF.value] = pFloat[RDF.value] * 2
}

local pBool = Parameter{
  [RDFS.label] = 'Bool',
  [RDFS.comment] = 'This is a Boolean',
  [RDFS.range] = Atom.Bool,
  [RDF.value] = true
}

local pURID = Parameter{
  [RDFS.label] = 'URID',
  [RDFS.comment] = 'This is an URID',
  [RDFS.range] = Atom.URID,
	[RDF.value] = MIDI.MidiEvent
}

local pString = Parameter{
  [RDFS.label] = 'String',
  [RDFS.comment] = 'This is a String',
  [RDFS.range] = Atom.String,
  [RDF.value] = 'hello world'
}

local pChunk = Parameter{
  [RDFS.label] = 'Chunk',
  [RDFS.comment] = 'This is a Chunk',
  [RDFS.range] = Atom.Chunk,
  _value = string.char(0x1, 0x2, 0x3),
  [Patch.Get] = function(self)
		return self._value
	end,
	[Patch.Set] = function(self, value)
		self._value = value
    for i = 1, #value do
      print(i, string.byte(value, i))
    end
  end
}

local pEnum = Parameter{
  [RDFS.label] = 'Enum',
  [RDFS.comment] = 'This is an Enum',
  [RDFS.range] = Atom.Int,
  [RDF.value] = 0,
  [LV2.minimum] = 0,
  [LV2.maximum] = 2,
  [LV2.scalePoint] = {
    zero = 0,
    one = 1,
    two = 2
  }
}

local stateR = StateResponder{
  [Patch.writable] = {
    [urn.pInt] = pInt,
    [urn.pFloat] = pFloat,
    [urn.pBool] = pBool,
    [urn.pURID] = pURID,
    [urn.pString] = pString,
    [urn.pChunk] = pChunk,
    [urn.pEnum] = pEnum
  },
  [Patch.readable] = {
    [urn.pLong] = pLong,
    [urn.pDouble] = pDouble
  }
}

function save(forge)
  stateR:stash(forge)
end

function restore(atom)
  stateR:apply(atom)
end

function once(n, control, notify)
  stateR:register(0, notify)
end

function run(n, control, notify)
  for frames, atom in control:foreach() do
    if stateR(frames, notify, atom) and sync then
			stateR:sync(frames, notify)
			sync = false
		end
  end
end""" ;
		moony:state [
			stateR:pBool true ;
			stateR:pChunk "AQIDBAUGBw=="^^xsd:base64Binary ;
			stateR:pEnum 0 ;
			stateR:pFloat "8e-1"^^xsd:float ;
			stateR:pInt 9 ;
			stateR:pString "world hello" ;
			stateR:pURID <http://lv2plug.in/ns/ext/midi#MidiEvent>
		]
	] .

moony:bank-canvas_lv2-logo
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden false ;
		moony:paramCols 1 ;
		moony:paramRows 2 ;
		moony:code """local srate = Options[Param.sampleRate]
srate = srate and srate.body or 48000 -- get DSP sample rate or fallback

local urate = Options[Ui.updateRate]
urate = urate and urate.body or 30 -- get UI update rate or fallback

local spu = srate // urate -- samples per frame update

local next_color = coroutine.wrap(function()
	local step = 0x10

	while true do
		for i = 0x0, 0xff, step do -- black -> red
			local col = (i << 24) | 0x000000ff
			coroutine.yield(col)
		end

		for i = 0x0, 0xff, step do -- red -> yellow
			local col = (i << 16) | 0xff0000ff
			coroutine.yield(col)
		end

		for i = 0xff, 0x0, -step do -- yellow -> green
			local col = (i << 24) | 0x00ff00ff
			coroutine.yield(col)
		end

		for i = 0xff, 0x0, -step do -- green -> black
			local col = (i << 16) | 0x000000ff
			coroutine.yield(col)
		end
	end
end)

local bg = 0x00000000 -- transparent black
local fg = next_color() -- first color from generator

local ctx = Stash() -- stash to contain vector graphics

local function render()
	ctx:write() -- put stash into writing mode

	ctx:tuple() -- create tuple container

	-- draw background
	ctx:beginPath()
	ctx:rectangle(0.0, 0.0, 1.0, 1.0):style(bg):fill()

	-- draw 'L'
	ctx:beginPath()
	ctx:polyLine(0.05, 0.275,
		0.05, 0.73463521816969,
		0.39996786383766, 0.73463521816969,
		0.35805418792799, 0.61981755929103,
		0.16950515672412, 0.61981755929103,
		0.16950515672412, 0.275,
		0.05, 0.275)
	ctx:closePath():style(fg):stroke()

	-- draw 'V'
	ctx:beginPath()
	ctx:polyLine(0.44035674587458, 0.73463521816969,
		0.27321237521861, 0.275,
		0.39612954205777, 0.275,
		0.5215250619933, 0.61980400005209,
		0.64678627651808, 0.275,
		0.76999411666921, 0.275,
		0.60269884777111, 0.73463521816969,
		0.44035674587458, 0.73463521816969)
	ctx:closePath():style(fg):stroke()

	-- draw '2'
	ctx:beginPath()
	ctx:moveTo(0.92679577564592, 0.33745757758451)
	ctx:curveTo(0.95, 0.37544661222032,
		0.9486097413556, 0.42890103900541, 0.91866073788306, 0.46581025262318)
	ctx:curveTo(0.87662774067075, 0.51761178520021,
		0.84865149155459, 0.52351773004551, 0.8188709443895, 0.55088574387747)
	ctx:lineTo(0.93798338878322, 0.55088574387747)
	ctx:lineTo(0.93798338878322, 0.61972641362727)
	ctx:lineTo(0.68857649440815, 0.61972641362727)
	ctx:curveTo(0.70410821191941, 0.57897193773781,
		0.71568706655441, 0.55649255812279, 0.73505227967577, 0.53436493734023)
	ctx:curveTo(0.78431409785481, 0.47807598612821,
		0.88073913173375, 0.44149338929647, 0.87483180798279, 0.39074363998918)
	ctx:curveTo(0.8731729385169, 0.37649219041461,
		0.86900905711197, 0.34385128732334, 0.80655313421425, 0.34385128732334)
	ctx:lineTo(0.7834998081023, 0.34385128732334)
	ctx:lineTo(0.80849192152801, 0.275)
	ctx:curveTo(0.88098903540187, 0.275,
		0.90879494370618, 0.30798728419169, 0.92679577564592, 0.33745757758451)
	ctx:closePath():style(fg):stroke()

	ctx:pop() -- close tuple container

	return ctx:read() -- return graph tuple
end

-- define canvas graph parameter
local graph = Parameter({
	[RDFS.label] = 'Graph',
	[RDFS.comment] = 'Canvas of graph as atom:Tuple',
	[RDFS.range] = Atom.Tuple,
	[Patch.Get] = function(self)
		return render() -- whenever the parameter is requested, render graph
	end
})

-- define a StateResponder object
local stateR = StateResponder({
	[Patch.readable] = {
		[Canvas.graph] = graph
	}
})

-- register parameters to UI
function once(n, control, notify)
	stateR:register(0, notify)
end

local count = 0 -- count samples until next frame update

function run(n, control, notify)
	for frames, atom in control:foreach() do -- iterate over incoming events
		local handled = stateR(frames, notify, atom)
	end

	if count <= 0 then -- check for frame update counter underflow
		fg = next_color() -- get next color
		stateR:sync(n - 1, notify) -- sync graph to UI (calls render function)

		count = count + spu  -- schedule next frame update
	end

	count = count - n  -- update next frame update
end

-- vim: set syntax=lua:"""
	] .

moony:bank-tutorial_part-1
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """-- Tutorial 1: MIDI Channel Blocker

-- define table which holds active MIDI channels
local channels = {0, 2, 4, 6}

-- derive channel mask based on active channels
local mask = 0 -- block everything
for i, v in ipairs(channels) do
	mask = mask | (1 << v) -- toggle bit of active channel
end

function run(n, control, notify, seq, forge)
	for frames, atom in seq:foreach() do -- iterate over incoming events
		local block = false -- assume a not-to-be-blocked event by default

		if atom.type == MIDI.MidiEvent then -- check for MIDI message
			local msg = atom[1] -- get first MIDI byte
			local cmd = msg & 0xf0 -- get MIDI command nibble
			local chn = msg & 0x0f -- get MIDI channel nibble

			if (cmd ~= 0xf0) and ((1 << chn) & mask == 0) then
				block = true -- if not a system message and channel not part of mask
			end
		end

		if not block then -- let atom through
			forge:time(frames):atom(atom) -- serialize atom as-is
		end
	end
end

-- vim: set syntax=lua:"""
	] .

moony:bank-tutorial_part-2
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """-- Tutorial 2: MIDI Chorder

-- define table that holds number of chord notes and their offsets
local chord = {0, 12, 24, 36} -- octaves, obviously

-- define a general note responder factory
local function noteResponder(cmd)
	return function (self, frames, forge, chan, note, vel)
		for i, v in ipairs(chord) do -- iterate over chord offsets
			local chanNew = i - 1 -- set MIDI channel to chord index - 1
			local noteNew = note + v -- set MIDI note to chord offset
			if noteNew >= 0 and noteNew <= 0x7f then -- handle note under/overflows
				forge:time(frames):midi(cmd | chanNew, noteNew, vel) -- serialize event
			end
		end
	end
end

-- define a MIDIResponder object configured to pass-through unmatched messages
local midiR = MIDIResponder({
	[MIDI.NoteOn] = noteResponder(MIDI.NoteOn), -- create responder for NoteOn
	[MIDI.NoteOff] = noteResponder(MIDI.NoteOff), -- and NoteOff
	[MIDI.NotePressure] = noteResponder(MIDI.NotePressure) -- and NotePressure
}, true)

function run(n, control, notify, seq, forge)
	for frames, atom in seq:foreach() do -- iterate over incoming events
		local handled = midiR(frames, forge, atom) -- call responder for event
	end
end

-- vim: set syntax=lua:"""
	] .

moony:bank-tutorial_part-3
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """-- Tutorial 3: MIDI Sample & Hold

local urn = Mapper('urn:uuid:03340863-7f87-4f67-9fc9-9cac49c2dfba3#') -- prefix of this presets URIs
local sample = false -- flag to tell whether we are sampling or not
local noteOffs = {} -- table to store NoteOff events while sampling

-- MIDI responder for NoteOff events
local function noteOff(self, frames, forge, chan, note, vel)
	local msg = string.char(MIDI.NoteOff | chan, note, vel) -- encode to byte string

	if sample then -- are we sampling?
		table.insert(noteOffs, msg) -- add event to table, e.g. put it on-hold
	else
		forge:time(frames):midi(msg) -- let event through as-is
	end
end

-- MIDI responder for Controller events
local function controller(self, frames, forge, chan, control, value)
	if control == MIDI.SustainPedal then -- check for SustainPedal
		local newSample = value > 0 -- derive new sampling state from Controller value

		if newSample ~= sample then -- is new sampling state different from current one?
			if not newSample then -- sustain pedal has been released
				for i, msg in ipairs(noteOffs) do -- release NoteOff events on-hold
					forge:time(frames):midi(msg)
				end
				noteOffs = {} -- clear table as events have been released
			end

			sample = newSample -- update sampling state
		end
	else -- let through non-SustainPedal Controller events
		forge:time(frames):midi(MIDI.Controller | chan, control, value)
	end
end

-- define a MIDIResponder object configured to pass-through unmatched messages
local midiR = MIDIResponder({
	[MIDI.NoteOff] = noteOff, -- register NoteOff responder
	[MIDI.Controller] = controller -- register Controller responder
}, true)

-- push sampling state and events on-hold on stash
function stash(forge)
	local obj = forge:object() -- create object

	-- add boolean property for sampling state to object
	obj:key(urn.sample):bool(sample)

	-- add tuple property for events on-hold to object
	local tup = forge:key(urn.noteOffs):tuple()
	for i, msg in ipairs(noteOffs) do -- add events on-hold to tuple
		tup:midi(msg)
	end

	tup:pop() -- finalize tuple
	obj:pop() -- finalize object
end

-- pop sampling state and events on-hold from stash
function apply(atom)
	-- get sampling state from object
	if atom[urn.sample] then
		sample = atom[urn.sample].body
	end

	-- get events on-hold from object
	if atom[urn.noteOffs] then
		noteOffs = {} -- clear table
		for i, msg in atom[urn.noteOffs]:foreach() do -- iterate tuple
			noteOffs[i] = msg -- add events on-hold to table
		end
	end
end

function run(n, control, notify, seq, forge)
	for frames, atom in seq:foreach() do -- iterate over incoming events
		local handled = midiR(frames, forge, atom) -- call responder for event
	end
end

-- vim: set syntax=lua:"""
	] .

moony:bank-tutorial_part-4
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """-- Tutorial 4: MIDI Arpeggiator

local schar = string.char -- local variable is more efficient to look up

local chord = {0, 3, 7, 11} -- table with chord note offsets
local offset = 0.1 -- time offset between notes in seconds

local schedule = {} -- table to store Note events
local dur = math.floor(offset * Options[Param.sampleRate].body) -- time offset in frames

-- compare function to sort scheduled messages according to frame time
local function cmp(a, b)
	return a[1] < b[1]
end

local function note_responder(cmd)
	return function(self, frames, forge, chan, note, vel)
		for i, v in ipairs(chord) do
			local chanNew = i - 1 -- set new channel to chord note index - 1
			local noteNew = note + v -- set new note to chord note offset
			local msg = schar(cmd | chanNew, noteNew, vel) -- serialize message
			local off = frames + (i-1)*dur
			table.insert(schedule, {off, msg}) -- schedule message with offset
		end
		table.sort(schedule, cmp) -- sort table
	end
end

-- define a MIDIResponder object configured to pass-through unmatched messages
local midiR = MIDIResponder({
	[MIDI.NoteOn] = note_responder(MIDI.NoteOn), -- register responder for NoteOn
	[MIDI.NoteOff] = note_responder(MIDI.NoteOff), -- and NoteOff
	[MIDI.NotePressure] = note_responder(MIDI.NotePressure) -- and NotePressure
}, true)

-- push scheduled events stash
function stash(forge)
	local seq= forge:sequence() -- create atom sequence
	for i, v in ipairs(schedule) do -- iterate over scheduled events
		seq:time(v[1]):midi(v[2]) -- add events to atom sequence
	end
	seq:pop() -- finalize atom sequence
end

-- pop scheduled events from stash
function apply(atom)
	if atom.type == Atom.Sequence then -- check for correct atom type
		schedule = {} -- clear table with scheduled events
		for off, itm in atom:foreach() do -- iteratore over sequence events
			table.insert(schedule, {off, itm.body}) -- insert event into table
		end
		table.sort(schedule, cmp) -- sort events
	end
end

-- are there any scheduled events to dispatch?
local function dispatch(n, forge)
::loop::
	for i, v in ipairs(schedule) do
		if v[1] < n then
			forge:time(v[1]):midi(v[2]) -- send message
			table.remove(schedule, i) -- remove message from scheduled events
			goto loop -- restart loop as we have removed an item
		else
			v[1] = v[1] - n -- decrease timestamp by period size
		end
	end
end

function run(n, control, notify, seq, forge)
	for frames, atom in seq:foreach() do -- iterate over incoming events
		local handled = midiR(frames, forge, atom) -- call responder for event
	end

	dispatch(n, forge) -- dispatch scheduled events
end

-- vim: set syntax=lua:"""
	] .

moony:bank-tutorial_part-5
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """-- Tutorial 5: MIDI Velocity Curve Interpolator

local X = {0, 15, 112, 127} -- X-coordinates of curve
local Y = {0,  7, 120, 127} -- Y-coordinates of curve
local N = #X -- number of points to interpolate over
local Ymin = 0 -- clip minimum
local Ymax = 127 -- clip maximum

-- Lagrange Polynomial Interpolation
local function P(x)
	local sum = 0
	for j = 1, N do
		local prod = Y[j]
		for k = 1, N do
			prod = prod * (k == j and 1.0 or (x - X[k]) / (X[j] - X[k]))
		end
		sum = sum + prod
	end
	sum = math.floor(sum) -- round to lower integer
	return sum < Ymin and Ymin or (sum > Ymax and Ymax or sum) -- clip to [Ymin, Ymax]
end

-- fill velocity curve lookup table
local curve = {}
for i = 0, 127 do
	curve[i+1] = P(i)
end

-- note responder function factory
local function note_responder(cmd)
	return function(self, frames, forge, chan, note, vel)
		local velNew = curve[vel+1] -- index velocity curve lookup table
		forge:time(frames):midi(cmd | chan, note, velNew) -- send event
	end
end

-- define a MIDIResponder object configured to pass-through unmatched messages
local midiR = MIDIResponder({
	[MIDI.NoteOn] = note_responder(MIDI.NoteOn), -- register responder for NoteOn
	[MIDI.NoteOff] = note_responder(MIDI.NoteOff) -- and NoteOff
}, true)

function run(n, control, notify, seq, forge)
	for frames, atom in seq:foreach() do -- iterate over incoming events
		local handled = midiR(frames, forge, atom) -- call responder for event
	end
end

-- vim: set syntax=lua:"""
	] .

moony:bank-tutorial_part-6
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """-- Tutorial 6: MIDI Sequencer


local urn = Mapper('urn:uuid:4cc65393-869d-4ca1-8ac4-fcbe902b36d6#')

-- table with MIDI notes for 8 beats
local notes = {
	Note['C+4'],  -- beat 1
	Note['D#+4'], -- beat 2
	false,        -- beat 3
	Note['C+4'],  -- beat 4
	Note['G+4'],  -- beat 5 
	false,        -- beat 6
	Note['G+3'],  -- beat 7
	false         -- beat 8
}

local vel = 0x7f -- MIDI note velocity

local active = nil -- active MIDI note

-- register Time responder object
local timeR = TimeResponder({
	rolling = false, -- flag to signal whether we are rolling or not
	[Time.speed] = function(self, frames, forge, speed) -- transport start/stop
		self.rolling = speed > 0 and true or false

		-- send note off event at transport stop with active note
		if not self.rolling and forge and active then
			forge:time(frames):midi(MIDI.NoteOff, active, vel)
			active = nil
		end
	end,
	[Time.barBeat] = function(self, frames, forge, barBeat)
		if self.rolling and forge and math.tointeger(barBeat) then
			-- send note off event for active note
			if active then
				forge:time(frames):midi(MIDI.NoteOff, active, vel)
				active = nil
			end

			-- get new note
			local note = notes[barBeat % #notes + 1]
			-- send note on event for new note
			if note then
				forge:time(frames):midi(MIDI.NoteOn, note, vel)
				active = note
			end
		end
	end
})

-- push time responder state and active note to stash
function stash(forge)
	local obj = forge:object()

	obj:key(Time.Position)
	timeR:stash(obj)

	if active then
		obj:key(urn.active):int(active)
	end

	obj:pop()
end

-- pop time responder state and active note from stash
function apply(atom)
	if atom[Time.Position] then
		timeR:apply(atom[Time.Position])
	end

	if atom[urn.active] then
		active = atom[urn.active].body
	end
end

function run(n, control, notify, seq, forge)
	local from = 0

	for to, atom in seq:foreach() do -- iterate over incoming events
		timeR(from, to, forge, atom) -- call responder for event

		from = to
	end

	timeR(from, n, forge)
end

-- vim: set syntax=lua:"""
	] .

moony:bank-template_part-1
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """-- Template 1: MIDI Responder

local block = false -- route unmatched messages as-is

-- define a MIDIResponder object
local midiR = MIDIResponder({
	-- callbacks featuring a channel parameter
	[MIDI.NoteOn] = function(self, frames, forge, chan, note, vel)
		--TODO
	end,
	[MIDI.NoteOff] = function(self, frames, forge, chan, note, vel)
		--TODO
	end,
	[MIDI.NotePressure] = function(self, frames, forge, chan, note, vel)
		--TODO
	end,
	[MIDI.Bender] = function(self, frames, forge, chan, lsb, msb)
		--TODO
	end,
	[MIDI.Controller] = function(self, frames, forge, chan, cntrl, val)
		--TODO
	end,
	[MIDI.ProgramChange] = function(self, frames, forge, chan, lsb, msb)
		--TODO
	end,
	[MIDI.ChannelPressure] = function(self, frames, forge, chan, val)
		--TODO
	end,

	-- callbacks featuring no channel parameter
	[MIDI.SystemExclusive] = function(self, frames, forge, _, ...)
		--TODO
	end,
	[MIDI.QuarterFrame] = function(self, frames, forge, _, type, val)
		--TODO
	end,
	[MIDI.SongPosition] = function(self, frames, forge, _, lsb, msb) 
		--TODO
	end,
	[MIDI.SongSelect] = function(self, frames, forge, _, val)
		--TODO
	end,
	[MIDI.TuneRequest] = function(self, frames, forge)
		--TODO
	end,
	[MIDI.Clock] = function(self, frames, forge)
		--TODO
	end,
	[MIDI.Start] = function(self, frames, forge)
		--TODO
	end,
	[MIDI.Continue] = function(self, frames, forge)
		--TODO
	end,
	[MIDI.Stop] = function(self, frames, forge)
		--TODO
	end,
	[MIDI.ActiveSense] = function(self, frames, forge)
		--TODO
	end,
	[MIDI.Reset] = function(self, frames, forge)
		--TODO
	end
}, not block)

function run(n, control, notify, seq, forge)
	for frames, atom in seq:foreach() do -- iterate over incoming events
		local handled = midiR(frames, forge, atom) -- call responder for event
	end
end

-- vim: set syntax=lua:"""
	] .

moony:bank-template_part-2
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """-- Template 2: Time Responder

-- define a TimeResponder object
local timeR = TimeResponder({
	[Time.speed] = function(self, frames, forge, speed)
		--TODO
	end,
	[Time.bar] = function(self, frames, forge, bar)
		--TODO
	end,
	[Time.barBeat] = function(self, frames, forge, barBeat)
		--TODO
	end,
	[Time.beatUnit] = function(self, frames, forge, beatUnit)
		--TODO
	end,
	[Time.beatsPerBar] = function(self, frames, forge, beatsPerBar)
		--TODO
	end,
	[Time.beatsPerMinute] = function(self, frames, forge, beatsPerMinute)
		--TODO
	end,
	[Time.framesPerSecond] = function(self, frames, forge, framesPerSecond)
		--TODO
	end,
	[Time.frame] = function(self, frames, forge, frame)
		--TODO
	end
})

-- push time responder state to stash
function stash(forge)
	timeR:stash(forge)
end

-- pop time responder state from stash
function apply(atom)
	timeR:apply(atom)
end

function run(n, control, notify, seq, forge)
	local from = 0 -- initial frame offset

	for to, atom in seq:foreach() do -- iterate over incoming events
		timeR(from, to, forge, atom) -- call responder for event

		from = to -- update to new frame offset
	end

	timeR(from, n, forge) -- call responder for remaining frames
end

-- vim: set syntax=lua:"""
	] .

moony:bank-template_part-3
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden true ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """-- Template 3: OSC Responder

-- define an OSCResponder object
local oscR = OSCResponder({
	['/ping'] = function(self, frames, forge, fmt, ...)
		--TODO
	end,
	['/pong'] = function(self, frames, forge, fmt, ...)
		--TODO
	end
})

function run(n, control, notify, seq, forge)
	for frames, atom in seq:foreach() do -- iterate over incoming events
		local handled = oscR(frames, forge, atom) -- call responder for event
	end
end

-- vim: set syntax=lua:"""
	] .

moony:bank-template_part-4
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden false ;
		moony:paramCols 3 ;
		moony:paramRows 4 ;
		moony:code """-- Template 4: State Responder

-- define URI prefix for state parameters
local urn = Mapper('urn:uuid:3473b33a-e6d2-471c-89d3-4dea1a0d8feb#')

-- define single parameter
local param = Parameter({
	[RDFS.label] = 'Awesome Parameter',
	[RDFS.comment] = 'does this and that...',
	[RDFS.range] = Atom.Int,
	[LV2.minimum] = 0,
	[LV2.maximum] = 10,
	[Units.unit] = Units.hz,
	[RDF.value] = 5
})

-- define a StateResponder object
local stateR = StateResponder({
	[Patch.readable] = {
		--TODO
	},
	[Patch.writable] = {
		[urn.param] = param -- register parameter to writable group
	}
})

-- push parameter values to disk
function save(forge)
	stateR:stash(forge)
end

-- pop parameter values from disk
function restore(atom)
	stateR:apply(atom)
end

-- register parameters to UI
function once(n, control, notify)
	stateR:register(0, notify)
end

function run(n, control, notify)
	for frames, atom in control:foreach() do -- iterate over incoming events
		local handled = stateR(frames, notify, atom)
	end
end

-- vim: set syntax=lua:"""
	] .

moony:bank-template_part-5
	a pset:Preset ;
	doap:license lic:Artistic-2.0 ;
	state:state [
		moony:editorHidden false ;
		moony:logHidden true ;
		moony:logFollow true ;
		moony:logReset false ;
		moony:paramHidden false ;
		moony:paramCols 1 ;
		moony:paramRows 1 ;
		moony:code """-- Template 5: Code Injection

-- define URI prefix for state parameters
local urn = Mapper('urn:uuid:1ad928a1-e050-4380-be39-8dca9bc18f44#')

-- pretty print error message from injection
local function perr(err)
  return string.gsub(err, '%b[]:', '')
end

-- define injection environment
local env = {}

-- define injection environment metatable
local mt = {
	__index = _ENV
}

-- define injection parameter
local injection = Parameter({
  [RDFS.label] = 'Injection',
  [RDFS.comment] = 'enter your code for injection here',
  [RDFS.range] = Atom.String,
  [Moony.syntax] = Lua.lang,
  _value = '',
  [Patch.Get] = function(self)
    return self._value
  end,
  [Patch.Set] = function(self, value)
    self._value = value

		env = {} -- create new environment
		setmetatable(env, mt)
    local fn, err = load(value, 'inj', 't', env) -- compile injection code
    if fn then -- compilation succeded
      local stat, err = pcall(fn) -- run injection code
      if not stat then -- running code failed
        print(perr(err)) -- report error
      end
    else -- compilation failed
      print(perr(err)) -- report error
    end
  end
})

-- define a StateResponder object
local stateR = StateResponder({
  [Patch.writable] = {
    [urn.injection] = injection -- register parameter to writable group
  }
})

-- push parameter values to disk
function save(forge)
  stateR:stash(forge)
end

-- pop parameter values from disk
function restore(atom)
  stateR:apply(atom)
end

-- register parameters to UI
function once(n, control, notify)
  stateR:register(0, notify)
end

function run(n, control, notify)
  for frames, atom in control:foreach() do -- iterate over incoming events
    local handled = stateR(frames, notify, atom)
  end
end

-- vim: set syntax=lua:"""
	] .
