<UVI4>
    <Program Name="Program" Bypass="0" Gain="1" Pan="0" DisplayName="MonoSynth" TransposeOctaves="0" TransposeSemiTones="0" OutputName="" Polyphony="16" NotePolyphony="0" ProgramPath="C:/Users/otristan/codeM5v4/MachFiveScript/documentation/examples/MonoSynth.M5p" LoopProgram="0" Streaming="1" BypassInsertFX="0">
        <ControlSignalSources>
            <LFO Name="LFO 1" Bypass="0" SyncToHost="0" DisplayName="LFO 1" DelayTime="0" RiseTime="0" Freq="0.5" Depth="1" Phase="0" WaveFormType="0" Retrigger="1" Bipolar="1" Smooth="0"/>
        </ControlSignalSources>
        <EventProcessors>
            <ScriptProcessor Name="EventProcessor0" Bypass="0" Osc="1" Sub="0" Noise="0" Attack="0.0089999996" Decay="0.17399999" Sustain="1" Release="0.050000001" Cutoff="1" Reso="0" lfoToCutoff="0" Resolution="2" Gate="1" API_version="13">
                <Properties ScriptPath="./monoBassLine.lua"/>
                <script><![CDATA[--------------------------------------------------------------------------------
--! @example monoBassLine.lua
--! @brief This is a complete example showing how to create a custom instrument script.
--!
--! It shows how to control the engine and how to create a structured interface
--! with sub-panels and different kind of widgets.
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- init direct access to most used engine nodes
--------------------------------------------------------------------------------
local keygroup = Program.layers[1].keygroups[1]
local ampEnv = Program.layers[1].keygroups[1].modulations["Amp. Env"]
local oscillators = keygroup.oscillators
local filter = keygroup.inserts[1]

--------------------------------------------------------------------------------
-- Oscilllator
--------------------------------------------------------------------------------
local oscPanel = Panel("Oscs")

function formatGainInDb(value)
	if value == 0 then
		return "-inf"
	else
		local dB = 20 * math.log10(value)
		return string.format("%0.1f dB", dB)
	end
end

local oscVolume = oscPanel:Knob("Osc", 1, 0, 1)
oscVolume.fillColour = "lightgrey"
oscVolume.outlineColour = "orange"
oscVolume.mapper = Mapper.Cubic
oscVolume.changed = function(self)
	oscillators[1]:setParameter("Gain", self.value)
	self.displayText = formatGainInDb(self.value)
end
oscVolume:changed() -- force update

local subVolume = oscPanel:Knob("Sub", 0, 0, 1)
subVolume.fillColour = "lightgrey"
subVolume.outlineColour = "orange"
oscVolume.mapper = Mapper.Cubic
subVolume.changed = function(self)
	oscillators[2]:setParameter("Gain", self.value)
	self.displayText = formatGainInDb(self.value)
end
subVolume:changed() -- force update

local noiseVolume = oscPanel:Knob("Noise", 0, 0, 1)
noiseVolume.fillColour = "lightgrey"
noiseVolume.outlineColour = "orange"
oscVolume.mapper = Mapper.Cubic
noiseVolume.changed = function(self)
	oscillators[3]:setParameter("Gain", self.value)
	self.displayText = formatGainInDb(self.value)
end
noiseVolume:changed() -- force update


--------------------------------------------------------------------------------
-- Amplitude ADSR
--------------------------------------------------------------------------------

function formatTimeInSeconds(value)
	if value < 1 then
		return string.format("%0.1f ms", value*1000)
	else
		return string.format("%0.1f s", value)
	end
end

local adsrPanel = Panel("AmpEnv")

local attack = adsrPanel:Knob("Attack", 0.009, 0.009, 1.06)
attack.outlineColour = "magenta"
attack.changed = function(self)
	ampEnv:setParameter("AttackTime", self.value)
	self.displayText = formatTimeInSeconds(self.value)
end
attack:changed() -- force update

local decay = adsrPanel:Knob("Decay", 0.174, 0.174, 2.477)
decay.outlineColour = "magenta"
decay.changed = function(self)
	ampEnv:setParameter("DecayTime", self.value)
	self.displayText = formatTimeInSeconds(self.value)
end
decay:changed() -- force update

local sustain = adsrPanel:Knob("Sustain", 1, 0, 1)
sustain.outlineColour = "magenta"
sustain.changed = function(self)
	ampEnv:setParameter("SustainLevel", self.value)
	self.displayText = string.format("%0.1f %%", self.value*100.)
end
sustain:changed() -- force update

local release = adsrPanel:Knob("Release", 0.05, 0.05, 5.028)	
release.outlineColour = "magenta"
release.changed = function(self)
	ampEnv:setParameter("ReleaseTime", self.value)
	self.displayText = formatTimeInSeconds(self.value)
end
release:changed() -- force update

--------------------------------------------------------------------------------
-- Filter
--------------------------------------------------------------------------------

-- logarithmic mapping for filter cutoff
local filterMax = 20000.
local filterMin = 20.
local filterlogmax = math.log(filterMax)
local filterlogmin = math.log(filterMin)
local filterlogrange = filterlogmax-filterlogmin
function filterMapValue(value)
	local newValue = (value * filterlogrange) + filterlogmin
	value = math.exp(newValue)
	return value
end

local filterPanel = Panel("Filter")

local cutoff = filterPanel:Knob("Cutoff", 1, 0, 1)
cutoff.fillColour = "lightgrey"
cutoff.outlineColour = "yellow"
cutoff.changed = function(self)
	local value = filterMapValue(self.value)
	filter:setParameter("Freq", value)
	if value < 1000 then
		self.displayText = string.format("%0.1f Hz", value)
	else
		self.displayText = string.format("%0.1f kHz", value/1000.)
	end
end
cutoff:changed() -- force update

local reso = filterPanel:Knob("Reso", 0, 0, 1)
reso.fillColour = "lightgrey"
reso.outlineColour = "yellow"
reso.changed = function(self)
	filter:setParameter("Q", self.value)
	self.displayText = string.format("%0.1f %%", self.value*100.)
end
reso:changed() -- force update


local lfoToCutoff = filterPanel:Knob("lfoToCutoff", 0, -1, 1)
lfoToCutoff.fillColour = "lightgrey"
lfoToCutoff.outlineColour = "yellow"
lfoToCutoff.changed = function(self)
	filter:getParameterConnections("Freq")[1]:setParameter("Ratio", self.value)
	self.displayText = string.format("%0.1f %%", self.value*100.)
end
lfoToCutoff:changed() -- force update


--------------------------------------------------------------------------------
-- Mini bass line Sequencer
--------------------------------------------------------------------------------
local seqPanel = Panel("Sequencer")

local resolutions = {0.5, 0.25, 0.125}
local resolutionNames = {"1/8", "1/16", "1/32"}

local numSteps = 8
local steps = seqPanel:Table("pitch", numSteps, 0, -12, 12, true)
local res = seqPanel:Menu{"Resolution", resolutionNames, selected=2}
local gate = seqPanel:Knob("Gate", 1, 0, 1)
gate.changed = function(self)
	self.displayText = string.format("%0.1f %%", self.value*100.)
end
gate:changed() -- force update

res.backgroundColour = "black"
res.textColour = "cyan"
res.arrowColour = "grey"
res.outlineColour = "#1fFFFFFF" -- transparent white

local positionTable = seqPanel:Table("steps"..tostring(i), numSteps, 0, 0, 1, true)
positionTable.enabled = false
positionTable.persistent = false

function clearPosition()
	for i = 1, numSteps do
		positionTable:setValue(i, 0)
	end
end

local arpId = 0
local heldNotes = {}

function arpeg(arpId_)
	local index = 0
	while arpId_ == arpId do
		local e = heldNotes[#heldNotes] 
		local p = resolutions[res.value]
		local note = e.note + steps:getValue(index+1)
		playNote(note, e.velocity, beat2ms(gate.value*p))
		positionTable:setValue((index - 1 + numSteps) % numSteps + 1, 0)
		positionTable:setValue((index % numSteps)+1, 1)
		index = (index+1) % numSteps
		waitBeat(p)
	end
end

--------------------------------------------------------------------------------
-- callbacks
--------------------------------------------------------------------------------
function onNote(e)   
	table.insert(heldNotes, e)
	if #heldNotes == 1 then
		arpeg(arpId)
	end
end

function onRelease(e)
  for i,v in ipairs(heldNotes) do
    if v.note == e.note then
      table.remove(heldNotes, i)
      if #heldNotes == 0 then 
		clearPosition()
		arpId = arpId + 1
      end
      break
    end
  end
end


--------------------------------------------------------------------------------
-- UI positioning
--------------------------------------------------------------------------------
local margin = 10

oscPanel.x = margin
oscPanel.y = margin
oscPanel.width = 400
oscPanel.height = 60

filterPanel.x = oscPanel.x
filterPanel.y = oscPanel.y + oscPanel.height + margin
filterPanel.width = 400
filterPanel.height = 60

adsrPanel.x = filterPanel.x
adsrPanel.y = filterPanel.y + filterPanel.height + margin
adsrPanel.width = 500
adsrPanel.height = 60

seqPanel.x = adsrPanel.x
seqPanel.y = adsrPanel.y + adsrPanel.height + margin
seqPanel.width = 630
seqPanel.height = 150
steps.y = steps.y + 10
steps.width = 500
steps.height = 130
positionTable.x = steps.x
positionTable.y = steps.y - 10
positionTable.width = steps.width
positionTable.height = 10
res.x = steps.x + steps.width + margin
res.y = steps.y
gate.x = steps.x + steps.width + margin
gate.y = steps.y + 70

setSize(650, 380)
makePerformanceView()
]]></script>
                <ScriptData pitch="0 0 0 0 0 0 0 0"/>
            </ScriptProcessor>
        </EventProcessors>
        <Layers>
            <Layer Name="Layer 0" Bypass="0" Gain="1" Pan="0" Mute="0" MidiMute="0" Solo="0" DisplayName="Layer 1" OutputName="" LowKey="0" HighKey="127" CustomPolyphony="0" PlayMode="3" PortamentoTime="0.029999999" PortamentoMode="0" NumVoicesPerNote="1" VelocityCurve="0" BypassInsertFX="0">
                <Properties Color="ffff0000"/>
                <ControlSignalSources/>
                <BusRouters/>
                <Keygroups>
                    <Keygroup Name="Keygroup 0" Bypass="0" Gain="1" Pan="0" DisplayName="Keygroup 1" OutputName="" ExclusiveGroup="0" LowKey="0" HighKey="127" LowVelocity="1" HighVelocity="127" LowKeyFade="0" HighKeyFade="0" LowVelocityFade="0" HighVelocityFade="0" TriggerMode="0" TriggerSync="0" TriggerRule="0" LatchTrigger="0" FXPostGain="1" BypassInsertFX="0">
                        <Connections>
                            <SignalConnection Name="AmpEnvMod" Ratio="1" Source="Amp. Env" Destination="Gain" Mapper="" ConnectionMode="0" Bypass="0" Inverted="0"/>
                        </Connections>
                        <ControlSignalSources>
                            <DAHDSR Name="Amp. Env" Bypass="0" DelayTime="0" AttackTime="0.0089999996" AttackCurve="0.5" HoldTime="0" DecayTime="0.17399999" DecayCurve="-0.97000003" SustainLevel="1" ReleaseTime="0.050000001" ReleaseCurve="-0.97000003" DisplayName="Amp. Env" VelocityAmount="0.82999998" VelocitySens="0.75" Retrigger="1" NoteOffRetrigger="0"/>
                        </ControlSignalSources>
                        <Inserts>
                            <XpanderFilter Name="Filter" Bypass="0" Freq="20000" Q="0" Mode="3" DistortionType="0" Drive="0">
                                <Connections>
                                    <SignalConnection Name="SignalConnection 0" Ratio="0" Source="$Program/LFO 1" Destination="Freq" Mapper="" ConnectionMode="0" Bypass="0" Inverted="0"/>
                                </Connections>
                            </XpanderFilter>
                        </Inserts>
                        <BusRouters/>
                        <Oscillators>
                            <MinBlepGenerator Name="Oscillator" Bypass="0" Waveform="1" Pwm="0.5" StartPhase="0" Polarity="0" HardSync="0" HardSyncShift="0" NumOscillators="1" MultiOscSpread="0.1" Stereo="0" PhaseSpread="0" DetuneMode="0" StereoSpread="0.1" StereoSpreadMode="0" CoarseTune="0" FineTune="0" Gain="1" Pitch="0" NoteTracking="1" BaseNote="60" DisplayName="Oscillator 1">
                                <Connections>
                                    <SignalConnection Name="PitchBendMod" Ratio="2" Source="@PitchBend" Destination="Pitch" Mapper="" ConnectionMode="0" Bypass="0" Inverted="0"/>
                                </Connections>
                            </MinBlepGenerator>
                            <MinBlepGenerator Name="Oscillator 2" Bypass="0" Waveform="4" Pwm="0.5" StartPhase="0" Polarity="0" HardSync="0" HardSyncShift="0" NumOscillators="1" MultiOscSpread="0.1" Stereo="0" PhaseSpread="0" DetuneMode="0" StereoSpread="0.1" StereoSpreadMode="0" CoarseTune="-12" FineTune="0" Gain="0" Pitch="0" NoteTracking="1" BaseNote="60" DisplayName="Oscillator 2">
                                <Connections>
                                    <SignalConnection Name="PitchBendMod" Ratio="2" Source="@PitchBend" Destination="Pitch" Mapper="" ConnectionMode="0" Bypass="0" Inverted="0"/>
                                </Connections>
                            </MinBlepGenerator>
                            <NoiseOscillator Name="Oscillator 3" Bypass="0" CoarseTune="0" FineTune="0" Gain="0" Pitch="0" NoteTracking="1" BaseNote="60" DisplayName="Oscillator 3" Value="0" NoiseType="6">
                                <Connections>
                                    <SignalConnection Name="PitchBendMod" Ratio="2" Source="@PitchBend" Destination="Pitch" Mapper="" ConnectionMode="0" Bypass="0" Inverted="0"/>
                                </Connections>
                            </NoiseOscillator>
                        </Oscillators>
                    </Keygroup>
                </Keygroups>
            </Layer>
        </Layers>
    </Program>
</UVI4>
