--[[============================================================================

 GlobalMidiActions.lua

 DO NOT EDIT THIS FILE IN THE RENOISE RESOURCE FOLDER! UPDATING RENOISE WILL
 TRASH YOUR MODIFICATIONS!
 
 TO EXTEND MIDI MAPPINGS, COPY THIS FILE TO THE RENOISE PREFERENCES SCRIPT 
 FOLDER, THEN DO YOUR CHANGES AND ADDITIONS THERE.

============================================================================]]--

--[[

List of actions which defines the set of global MIDI mappings in Renoise.
The message parameter, passed to invoke the action, is defined as:

 class "TriggerMessage"
  -- returns if the should be invoked at all
  function is_trigger() -> boolean

  -- check which properties are valid
  function is_switch() -> boolean
  function is_rel_value() -> boolean
  function is_abs_value() -> boolean

  -- [0 - 127] for abs values, [-63 - 63] for relative values
  -- valid when is_rel_value() or is_abs_value() returns true, else nil
  int_value

  -- valid [true OR false] when :is_switch() returns true, else nil
  boolean_value

  -- [0.0 - 1.0] min/max range scaling for parameter values. always valid.
  value_min_scaling
  value_max_scaling

  
---- Adding new mappings

If you want to extend the default MIDI mapping set, first copy this file to the 
"Scripts" folder in your preferences folder. Then do your changes there. The 
file in the Renoise resource folder may be overwritten when updating Renoise. 
Click on "Help" -> "Show Preferences Folder..." in Renoise to locate the folder.

New MIDI actions can not be bound to the Renoise GUI (the highlighted frames in
the GUI that get visible with the MIDI mapping dialog). They will only show
up in the "Available & Active Messages" list in the MIDI mapping dialog.

Any errors that may happen in the MIDI message handlers, are not shown to the 
user. They are only dumped into the scripting terminal. Make sure its open
when changing anything in here to make debugging easier.

When editing this file in Renoises "Scripting Terminal&Editor", any changes 
to the message set will instantly be applied as soon as you save the file.
This way you can quickly test out your changes. 

When working with an external editor, you can manually reload this script and 
apply changes by clicking on the small "refresh" button in Renoise's MIDI
Mapping dialog. Watch out for a small refresh icon next to the "Available & 
Active Messages" list.

Scripting developer tools must be enabled to see the "refresh" button and the 
scripting terminal. Please see http://scripting.renoise.com for more info about
this.


---- Add mappings via XRNX tools 

Alternatively to editing this file, you can also define new midi mappings via
Renoise XRNX tool bundles. This way new mappings can be easily installed (drag 
& drop the xrnx tool bundle) and upgrading Renoise won't remove your mappings. 
Please have a look at the "Renoise.ScriptingTool.API.lua" documentation for more 
info about this. 

]]


--------------------------------------------------------------------------------
-- Globals (interface to Renoise)
--------------------------------------------------------------------------------

-- max mapping counts

MAX_SEQUENCE_MAPPINGS = 256
MAX_TRACK_MAPPINGS = 64
MAX_SEND_TRACK_MAPPINGS = 32
MAX_GENERIC_PARAMETER_MAPPINGS = 32


-- available_actions

local action_names = table.create()

function available_actions()
  return action_names
end


-- invoke_action

local action_map = {}

function invoke_action(name, message)
  local action = action_map[name]

  if (action == nil) then
    error(string.format("trying to invoke undefined action '%s'", name))
  end

  action(message)
end


--------------------------------------------------------------------------------
-- Locals (message registration & helpers)
--------------------------------------------------------------------------------

-- add_action

local function add_action(name, func)
  if (action_map[name] ~= nil) then
    error(string.format("action '%s' was already added", name))
  end

  action_map[name] = func
  action_names:insert(name)
end


--------------------------------------------------------------------------------

-- app

local app = renoise.app


-- song

local song = renoise.song


-- sequencer_track

local function sequencer_track(track_index)
  if (track_index <= song().sequencer_track_count) then
    return song():track(track_index)
  else
    return nil
  end
end


-- send_track

local function send_track(send_index)
  if (send_index <= song().send_track_count) then
    -- send tracks are always behind the master track
    return song():track(song().sequencer_track_count + 1 + send_index)
  else
    return nil
  end
end


-- master_track

local function master_track()
  -- master track is always behind the sequencer tracks
  return song():track(song().sequencer_track_count + 1)
end


-- selected_track

local function selected_track()
  return song().selected_track
end


-- selected_device

local function selected_device()
  return song().selected_device
end



--------------------------------------------------------------------------------

-- clamp_value

local function clamp_value(value, min_value, max_value)
  return math.min(max_value, math.max(value, min_value))
end


-- wrap_value

local function wrap_value(value, min_value, max_value)
  local range = max_value - min_value + 1
  assert(range > 0, "invalid range")

  while (value < min_value) do
    value = value + range
  end

  while (value > max_value) do
    value = value - range
  end

  return value
end


-- quantize_value

local function quantize_value(value, quantum)
  if (value >= 0) then
     value = value + quantum / 2
  else
     value = value - quantum / 2
  end

  return math.floor(value / quantum) * quantum
end


--------------------------------------------------------------------------------

-- boolean_message_value

local function boolean_message_value(message, value)
  if (message:is_switch()) then
    return message.boolean_value
  else
    return value
  end
end


-- toggle_message_value

local function toggle_message_value(message, value)
  if (message:is_trigger()) then
    return not value
  else
    return value
  end
end


-- message_value_with_offset

local function message_value_with_offset(message, value,
  offset, min_value, max_value)

  if (message:is_abs_value()) then
    return clamp_value(message.int_value + offset, min_value, max_value)

  elseif (message:is_rel_value()) then
    return clamp_value(value + message.int_value, min_value, max_value)

  else
    return value
  end
end


-- message_value

local function message_value(message, value, min_value, max_value)
  return message_value_with_offset(message, value, 0, min_value, max_value)
end


-- inc_message_value

local function inc_message_value(message, value, min_value, max_value)
  if (message:is_trigger()) then
    return clamp_value(value + 1, min_value, max_value)
  else
    return value
  end
end


-- dec_message_value

local function dec_message_value(message, value, min_value, max_value)
  if (message:is_trigger()) then
    return clamp_value(value - 1, min_value, max_value)
  else
    return value
  end
end


-- parameter_message_value

local function parameter_message_value(message, parameter)
  local new_value = parameter.value
  local quantum = parameter.value_quantum

  local parameter_min = (parameter.value_max - parameter.value_min) *
    message.value_min_scaling + parameter.value_min

  local parameter_max = (parameter.value_max - parameter.value_min) *
    message.value_max_scaling + parameter.value_min

  if (quantum > 0) then
    new_value = quantize_value(new_value, quantum)
    
    parameter_min = quantize_value(parameter_min, quantum)
    parameter_max = quantize_value(parameter_max, quantum)
  end

  local parameter_range = parameter_max - parameter_min

  if (message:is_abs_value()) then
    new_value = parameter_min + (message.int_value / 127 * parameter_range)

    if (parameter.polarity == renoise.DeviceParameter.POLARITY_BIPOLAR) then
      local center_value = parameter.value_min +
        (parameter.value_max - parameter.value_min) / 2;
      if (math.abs(center_value - new_value) < parameter_range / 128) then
        -- snap to center
        new_value = center_value
      end
    end

  elseif (message:is_rel_value()) then
    if (quantum > 0) then
      if (message.int_value > 0) then
        new_value = new_value + quantum
      else
        new_value = new_value - quantum
      end
    else
      new_value = new_value + parameter_range / 127 * message.int_value;

      if (parameter.polarity == renoise.DeviceParameter.POLARITY_BIPOLAR) then
        local center_value = parameter.value_min + 
          (parameter.value_max - parameter.value_min) / 2;
        if (math.abs(center_value - new_value) < parameter_range / 128) then
          -- snap to center
          new_value = center_value
        end
      end
    end

    new_value = clamp_value(new_value,
      math.min(parameter_min, parameter_max),
      math.max(parameter_min, parameter_max)
    )

  elseif (message:is_switch()) then
    if (message.boolean_value) then
      new_value = parameter_max
    else
      new_value = parameter_min
    end

  elseif (message:is_trigger()) then
    if (quantum > 0) then
      -- walk through quantized values
      if (parameter_max > parameter_min) then
        new_value = new_value + quantum;
        if (new_value > parameter_max) then
          new_value = parameter_min
        end
      elseif (parameter_max < parameter_min) then
        new_value = new_value - quantum
        if (new_value < parameter_max) then
          new_value = parameter_min
        end
      end
    else
      -- toggle between min/max
      if (parameter.value > parameter_min + parameter_range / 2) then
        new_value = parameter_min
      else
        new_value = parameter_max
      end
    end
  end

  return new_value
end


--------------------------------------------------------------------------------
-- Transport
--------------------------------------------------------------------------------

-- Transport:Playback

add_action("Transport:Playback:Panic [Trigger]",
function(message)
  if message:is_trigger() then
    song().transport:panic()
  end
end)

add_action("Transport:Playback:Start Playing [Trigger]",
function(message)
  if message:is_trigger() then
    local play_mode = renoise.Transport.PLAYMODE_RESTART_PATTERN
    song().transport:start(play_mode)
  end
end)

add_action("Transport:Playback:Stop Playing [Trigger]",
function(message)
  if message:is_trigger() then
    song().transport:stop()
  end
end)

add_action("Transport:Playback:Start/Stop Playing [Set]",
function(message)
  local start_playing = boolean_message_value(
    message, song().transport.playing)

  if (start_playing ~= song().transport.playing) then
    if start_playing then
      local play_mode = renoise.Transport.PLAYMODE_RESTART_PATTERN
      song().transport:start(play_mode)
    else
      song().transport:stop()
    end
  end
end)

add_action("Transport:Playback:Loop Pattern [Set]",
function(message)
  song().transport.loop_pattern = boolean_message_value(
    message, song().transport.loop_pattern)
end)

add_action("Transport:Playback:Loop Pattern [Toggle]",
function(message)
  song().transport.loop_pattern = toggle_message_value(
    message, song().transport.loop_pattern)
end)


add_action("Transport:Playback:Loop Block [Set]",
function(message)
  song().transport.loop_block_enabled = boolean_message_value(
    message, song().transport.loop_block_enabled)
end)

add_action("Transport:Playback:Loop Block [Toggle]",
function(message)
  song().transport.loop_block_enabled = toggle_message_value(
    message, song().transport.loop_block_enabled)
end)

add_action("Transport:Playback:Loop Block Range [Set]",
function(message)
  -- reverses the range, so we don't use message_value...
  if message:is_abs_value() then
    song().transport.loop_block_range_coeff = clamp_value(
      message.int_value, 2, 16)

  elseif message:is_rel_value() then
    song().transport.loop_block_range_coeff = clamp_value(
      song().transport.loop_block_range_coeff - message.int_value, 2, 16)
  end
end)

add_action("Transport:Playback:Move Loop Block Backwards [Trigger]",
function(message)
  if message:is_trigger() then
    song().transport:loop_block_move_backwards()
  end
end)

add_action("Transport:Playback:Move Loop Block Forwards [Trigger]",
function(message)
  if message:is_trigger() then
    song().transport:loop_block_move_forwards()
  end
end)


add_action("Transport:Playback:Follow Player [Set]",
function(message)
  song().transport.follow_player = boolean_message_value(
    message, song().transport.follow_player)
end)

add_action("Transport:Playback:Follow Player [Toggle]",
function(message)
  song().transport.follow_player = toggle_message_value(
    message, song().transport.follow_player)
end)


-- Transport:Record

add_action("Transport:Record:Undo Last Action",
function(message)
  if message:is_trigger() then
    if(song():can_undo()) then
      song():undo()
    end
  end
end)

add_action("Transport:Record:Redo Last Action",
function(message)
  if message:is_trigger() then
    if(song():can_redo()) then
      song():redo()
    end
  end
end)

add_action("Transport:Record:Metronome Enabled [Set]",
function(message)
  song().transport.metronome_enabled = boolean_message_value(
    message, song().transport.metronome_enabled)
end)

add_action("Transport:Record:Metronome Enabled [Toggle]",
function(message)
  song().transport.metronome_enabled = toggle_message_value(
    message, song().transport.metronome_enabled)
end)


add_action("Transport:Record:Chord Mode Enabled [Set]",
function(message)
  song().transport.chord_mode_enabled = boolean_message_value(
    message, song().transport.chord_mode_enabled)
end)

add_action("Transport:Record:Chord Mode Enabled [Toggle]",
function(message)
  song().transport.chord_mode_enabled = toggle_message_value(
    message, song().transport.chord_mode_enabled)
end)


add_action("Transport:Record:Quantization Enabled [Set]",
function(message)
  song().transport.record_quantize_enabled = boolean_message_value(
    message, song().transport.record_quantize_enabled)
end)

add_action("Transport:Record:Quantization Enabled [Toggle]",
function(message)
  song().transport.record_quantize_enabled = toggle_message_value(
    message, song().transport.record_quantize_enabled)
end)


add_action("Transport:Record:Quantization Amount [Set]",
function(message)
  song().transport.record_quantize_lines = message_value(
    message, song().transport.record_quantize_lines, 1, 32)
end)

add_action("Transport:Record:Decrease Quantization Amount [Trigger]",
function(message)
  song().transport.record_quantize_lines = dec_message_value(
    message, song().transport.record_quantize_lines, 1, 32)
end)

add_action("Transport:Record:Increase Quantization Amount [Trigger]",
function(message)
  song().transport.record_quantize_lines = inc_message_value(
    message, song().transport.record_quantize_lines, 1, 32)
end)


add_action("Transport:Record:Parameter Record Mode [Set]",
function(message)
  local record_into_automation = (song().transport.record_parameter_mode ==
    renoise.Transport.RECORD_PARAMETER_MODE_AUTOMATION)

  if boolean_message_value(message, record_into_automation) then
    song().transport.record_parameter_mode =
      renoise.Transport.RECORD_PARAMETER_MODE_AUTOMATION
  else
    song().transport.record_parameter_mode =
      renoise.Transport.RECORD_PARAMETER_MODE_PATTERN
  end
end)

add_action("Transport:Record:Parameter Record Mode [Toggle]",
function(message)
  local record_into_automation = (song().transport.record_parameter_mode ==
    renoise.Transport.RECORD_PARAMETER_MODE_AUTOMATION)

  if toggle_message_value(message, record_into_automation) then
    song().transport.record_parameter_mode =
      renoise.Transport.RECORD_PARAMETER_MODE_AUTOMATION
  else
    song().transport.record_parameter_mode =
      renoise.Transport.RECORD_PARAMETER_MODE_PATTERN
  end
end)

add_action("Transport:Record:Start/Stop Sample Recording [Trigger]",
function(message)
  if message:is_trigger() then
    song().transport:start_stop_sample_recording()
  end
end)

add_action("Transport:Record:Cancel Sample Recording [Trigger]",
function(message)
  if message:is_trigger() then
    song().transport:cancel_sample_recording()
  end
end)


-- Transport:Song

add_action("Transport:Song:BPM [Set]",
function(message)
  song().transport.bpm = message_value_with_offset(
    message, song().transport.bpm, 60, 32, 999)
end)

add_action("Transport:Song:Decrease BPM [Trigger]",
function(message)
  song().transport.bpm = dec_message_value(
    message, song().transport.bpm, 32, 999)
end)

add_action("Transport:Song:Increase BPM [Trigger]",
function(message)
  song().transport.bpm = inc_message_value(
    message, song().transport.bpm, 32, 999)
end)


add_action("Transport:Song:LPB [Set]",
function(message)
  song().transport.lpb = message_value(
    message, song().transport.lpb, 1, 256)
end)

add_action("Transport:Song:Decrease LPB [Trigger]",
function(message)
  song().transport.lpb = dec_message_value(
    message, song().transport.lpb, 1, 256)
end)

add_action("Transport:Song:Increase LPB [Trigger]",
function(message)
  song().transport.lpb = inc_message_value(
    message, song().transport.lpb, 1, 256)
end)


add_action("Transport:Song:Shuffle Enabled [Set]",
function(message)
  song().transport.shuffle_enabled = boolean_message_value(
    message, song().transport.shuffle_enabled)
end)

add_action("Transport:Song:Shuffle Enabled [Toggle]",
function(message)
  song().transport.shuffle_enabled = toggle_message_value(
    message, song().transport.shuffle_enabled)
end)


-- Transport:Edit

add_action("Transport:Edit:Edit Mode [Set]",
function(message)
  song().transport.edit_mode = boolean_message_value(
    message, song().transport.edit_mode)
end)

add_action("Transport:Edit:Edit Mode [Toggle]",
function(message)
  song().transport.edit_mode = toggle_message_value(
    message, song().transport.edit_mode)
end)


add_action("Transport:Edit:Edit Mode Step [Set]",
function(message)
  song().transport.edit_step = message_value(
    message, song().transport.edit_step, 0, 64)
end)

add_action("Transport:Edit:Decrease Edit Mode Step [Trigger]",
function(message)
  song().transport.edit_step = dec_message_value(
    message, song().transport.edit_step, 0, 64)
end)

add_action("Transport:Edit:Increase Edit Mode Step [Trigger]",
function(message)
  song().transport.edit_step = inc_message_value(
    message, song().transport.edit_step, 0, 64)
end)


add_action("Transport:Edit:Octave [Set]",
function(message)
  song().transport.octave = message_value(
    message, song().transport.octave, 0, 8)
end)

add_action("Transport:Edit:Decrease Octave [Trigger]",
function(message)
  song().transport.octave = dec_message_value(
    message, song().transport.octave, 0, 8)
end)

add_action("Transport:Edit:Increase Octave [Trigger]",
function(message)
  song().transport.octave = inc_message_value(
    message, song().transport.octave, 0, 8)
end)


add_action("Transport:Edit:Single Track Edit Mode [Toggle]",
function(message)
  song().transport.single_track_edit_mode = toggle_message_value(
    message, song().transport.single_track_edit_mode)
end)

add_action("Transport:Edit:Single Track Edit Mode [Set]",
function(message)
  song().transport.single_track_edit_mode = boolean_message_value(
    message, song().transport.single_track_edit_mode)
end)

add_action("Transport:Edit:Patternwrap Mode [Toggle]",
function(message)
  song().transport.wrapped_pattern_edit = toggle_message_value(
    message, song().transport.wrapped_pattern_edit)
end)

add_action("Transport:Edit:Patternwrap Mode [Set]",
function(message)
  song().transport.wrapped_pattern_edit = boolean_message_value(
    message, song().transport.wrapped_pattern_edit)
end)


--------------------------------------------------------------------------------
-- Seq. Triggering
--------------------------------------------------------------------------------

-- Seq. Triggering:Trigger

add_action("Seq. Triggering:Trigger:Current [Trigger]",
function(message)
  if message:is_trigger() then
    song().transport:trigger_sequence(song().transport.edit_pos.sequence)
  end
end)

add_action("Seq. Triggering:Trigger:Sequence XX [Set]",
function(message)
  if message:is_abs_value() then
    local sequence_pos = clamp_value(
      message.int_value, 1, song().transport.song_length.sequence)

    song().transport:trigger_sequence(sequence_pos)
  end
end)

for sequence_pos = 1,MAX_SEQUENCE_MAPPINGS do
  add_action(string.format(
    "Seq. Triggering:Trigger:Sequence XX:Sequence #%02d [Trigger]",
    sequence_pos),
  function(message)

    if message:is_trigger() then
      if sequence_pos <= #song().sequencer.pattern_sequence then
        song().transport:trigger_sequence(sequence_pos)
      end
    end
  end)
end


-- Seq. Triggering:Schedule

add_action("Seq. Triggering:Schedule:Current [Trigger]",
function(message)
  if message:is_trigger() then
    song().transport:set_scheduled_sequence(song().transport.edit_pos.sequence)
  end
end)

add_action("Seq. Triggering:Schedule:Sequence XX [Set]",
function(message)
  if message:is_abs_value() then
    local sequence_pos = clamp_value(
      message.int_value, 1, song().transport.song_length.sequence)

    song().transport:set_scheduled_sequence(sequence_pos)
  end
end)

for sequence_pos = 1,MAX_SEQUENCE_MAPPINGS do
  add_action(string.format(
    "Seq. Triggering:Schedule:Sequence XX:Sequence #%02d [Trigger]",
    sequence_pos),
  function(message)

    if message:is_trigger() then
      if sequence_pos <= #song().sequencer.pattern_sequence then
        song().transport:set_scheduled_sequence(sequence_pos)
      end
    end
  end)
end


-- Seq. Triggering:Add Scheduled

add_action("Seq. Triggering:Add Scheduled:Current [Trigger]",
function(message)
  if message:is_trigger() then
    song().transport:add_scheduled_sequence(song().transport.edit_pos.sequence)
  end
end)

add_action("Seq. Triggering:Add Scheduled:Sequence XX [Set]",
function(message)
  if message:is_abs_value() then
    local sequence_pos = clamp_value(
      message.int_value, 1, song().transport.song_length.sequence)

    song().transport:add_scheduled_sequence(sequence_pos)
  end
end)

for sequence_pos = 1,MAX_SEQUENCE_MAPPINGS do
  add_action(string.format(
    "Seq. Triggering:Add Scheduled:Sequence XX:Sequence #%02d [Trigger]",
    sequence_pos),
  function(message)

    if message:is_trigger() then
      if sequence_pos <= #song().sequencer.pattern_sequence then
        song().transport:add_scheduled_sequence(sequence_pos)
      end
    end
  end)
end


--------------------------------------------------------------------------------
-- Seq. Muting
--------------------------------------------------------------------------------

-- Seq. Muting: Selected Seq.

for track_index = 1,MAX_TRACK_MAPPINGS do
  add_action(string.format(
    "Seq. Muting:Selected Seq. [Toggle]:Mute Track #%02d [Toggle]", track_index),
  function(message)
    if message:is_trigger() then
      if track_index <= #song().tracks then
        local is_muted = song().sequencer:track_sequence_slot_is_muted(
          track_index, song().selected_sequence_index)

        song().sequencer:set_track_sequence_slot_is_muted(track_index,
          song().selected_sequence_index, toggle_message_value(message, is_muted))
      end
    end
  end)

  add_action(string.format(
    "Seq. Muting:Selected Seq. [Set]:Mute Track #%02d [Set]", track_index),
  function(message)
    if message:is_switch() then
      if track_index <= #song().tracks then
        song().sequencer:set_track_sequence_slot_is_muted(track_index,
          song().selected_sequence_index, message.boolean_value)
      end
    end
  end)
end

-- Seq. Muting: Seq. #XX

for sequence_pos = 1,MAX_SEQUENCE_MAPPINGS do
  for track_index = 1,MAX_TRACK_MAPPINGS do
    add_action(string.format(
      "Seq. Muting:Seq. XX [Toggle]:Seq. #%02d:Mute Track #%02d [Toggle]",
      sequence_pos, track_index),
    function(message)
      if message:is_trigger() then
        if sequence_pos <= #song().sequencer.pattern_sequence then
          if track_index <= #song().tracks then
             local is_muted = song().sequencer:track_sequence_slot_is_muted(
               track_index, sequence_pos)

             song().sequencer:set_track_sequence_slot_is_muted(track_index,
               sequence_pos, toggle_message_value(message, is_muted))
          end
        end
      end
    end)

    add_action(string.format(
      "Seq. Muting:Seq. XX [Set]:Seq. #%02d:Mute Track #%02d [Set]",
      sequence_pos, track_index),
    function(message)
      if message:is_switch() then
        if sequence_pos <= #song().sequencer.pattern_sequence then
          if track_index <= #song().tracks then
             song().sequencer:set_track_sequence_slot_is_muted(track_index,
               sequence_pos, message.boolean_value)
          end
        end
      end
    end)
  end
end


--------------------------------------------------------------------------------
-- Track Muting
--------------------------------------------------------------------------------

-- Mute/Unmute/Solo

add_action("Track Muting:Mute All [Trigger]",
function(message)
  if message:is_trigger() then
    for _,track in ipairs(song().tracks) do
      if track.type ~= renoise.Track.TRACK_TYPE_MASTER then
        track:mute()
      end
    end
  end
end)

add_action("Track Muting:Unmute All [Trigger]",
function(message)

  if message:is_trigger() then
    for _,track in ipairs(song().tracks) do
     track:unmute()
    end
  end

end)

add_action("Track Muting:Mute/Unmute:Current Track [Toggle]",
function(message)

  if message:is_trigger() then
    local track = selected_track()

    if track.mute_state == renoise.Track.MUTE_STATE_ACTIVE then
      if track.type ~= renoise.Track.TRACK_TYPE_MASTER then
        track:mute()
      end
    else
      track:unmute()
    end
  end

end)

add_action("Track Muting:Mute/Unmute:Current Track [Set]",
function(message)

  if message:is_switch() then
    local track = selected_track()

    if track.type ~= renoise.Track.TRACK_TYPE_MASTER then
      if message.boolean_value then
        track:mute()
      else
        track:unmute()
      end
    end
  end

end)

add_action("Track Muting:Solo:Current Track [Trigger]",
function(message)

  if message:is_trigger() then
    selected_track():solo()
  end

end)


-- Mute/Unmute/Solo Track XX

for track_index = 1,MAX_TRACK_MAPPINGS do
  add_action(string.format(
    "Track Muting:Mute/Unmute:Track XX [Toggle]:Track #%02d [Toggle]",
    track_index),
  function(message)

    if message:is_trigger() then
      local track = sequencer_track(track_index)
      if track ~= nil then
        if track.mute_state == renoise.Track.MUTE_STATE_ACTIVE then
          track:mute()
        else
          track:unmute()
        end
      end
    end
  end)

  add_action(string.format(
    "Track Muting:Mute/Unmute:Track XX [Set]:Track #%02d [Set]",
    track_index),
  function(message)

    if message:is_switch() then
      local track = sequencer_track(track_index)
      if track ~= nil then
        if message.boolean_value then
          track:mute()
        else
          track:unmute()
        end
      end
    end
  end)

  add_action(string.format(
    "Track Muting:Solo:Track XX:Track #%02d [Trigger]",
    track_index),
  function(message)

    if message:is_trigger() then
      local track = sequencer_track(track_index)
        if track ~= nil then
        track:solo()
      end
    end
  end)
end



-- Mute/Unmute/Solo Send Track XX

for send_index = 1,MAX_SEND_TRACK_MAPPINGS do
  add_action(string.format(
    "Track Muting:Mute/Unmute:Send Track XX [Toggle]:Send Track #%02d [Toggle]",
    send_index),
  function(message)

    if message:is_trigger() then
      local track = send_track(send_index)
      if track ~= nil then
        if track.mute_state == renoise.Track.MUTE_STATE_ACTIVE then
          track:mute()
        else
          track:unmute()
        end
      end
    end
  end)

  add_action(string.format(
    "Track Muting:Mute/Unmute:Send Track XX [Set]:Send Track #%02d [Set]",
    send_index),
  function(message)

    if message:is_switch() then
      local track = send_track(send_index)
      if track ~= nil then
        if message.boolean_value then
          track:mute()
        else
          track:unmute()
        end
      end
    end
  end)

  add_action(string.format(
    "Track Muting:Solo:Send Track XX:Send Track #%02d [Trigger]",
    send_index),
  function(message)

    if message:is_trigger() then
      local track = send_track(send_index)
      if track ~= nil then
        track:solo()
      end
    end
  end)
end


--------------------------------------------------------------------------------
-- Track Levels
--------------------------------------------------------------------------------


-- Track Levels::Current Track

add_action("Track Levels:Volume:Current Track (Pre) [Set]",
function(message)
  selected_track().prefx_volume:record_value(
    parameter_message_value(message, selected_track().prefx_volume))
end)

add_action("Track Levels:Panning:Current Track (Pre) [Set]",
function(message)
  selected_track().prefx_panning:record_value(
    parameter_message_value(message, selected_track().prefx_panning))
end)

add_action("Track Levels:Width:Current Track [Set]",
function(message)
  selected_track().prefx_width:record_value(
    parameter_message_value(message, selected_track().prefx_width))
end)

add_action("Track Levels:Volume:Current Track (Post) [Set]",
function(message)
  selected_track().postfx_volume:record_value(
    parameter_message_value(message, selected_track().postfx_volume))
end)

add_action("Track Levels:Panning:Current Track (Post) [Set]",
function(message)
  selected_track().postfx_panning:record_value(
    parameter_message_value(message, selected_track().postfx_panning))
end)


-- Track Levels::Master Track

add_action("Track Levels:Volume:Master Track (Pre) [Set]",
function(message)
  master_track().prefx_volume:record_value(
    parameter_message_value(message, master_track().prefx_volume))
end)

add_action("Track Levels:Panning:Master Track (Pre) [Set]",
function(message)
  master_track().prefx_panning:record_value(
    parameter_message_value(message, master_track().prefx_panning))
end)

add_action("Track Levels:Width:Master Track [Set]",
function(message)
  master_track().prefx_width:record_value(
    parameter_message_value(message, master_track().prefx_width))
end)

add_action("Track Levels:Volume:Master Track (Post) [Set]",
function(message)
  master_track().postfx_volume:record_value(
    parameter_message_value(message, master_track().postfx_volume))
end)

add_action("Track Levels:Panning:Master Track (Post) [Set]",
function(message)
  master_track().postfx_panning:record_value(
    parameter_message_value(message, master_track().postfx_panning))
end)


-- Track Levels::Track XX

for track_index = 1,MAX_TRACK_MAPPINGS do

  add_action(string.format(
    "Track Levels:Volume:Track XX (Pre):Track #%02d [Set]",
    track_index),
  function(message)
    local track = sequencer_track(track_index)
    if track ~= nil then
      track.prefx_volume:record_value(
        parameter_message_value(message, track.prefx_volume))
    end
  end)

  add_action(string.format(
    "Track Levels:Volume:Track XX (Post):Track #%02d [Set]",
    track_index),
  function(message)
    local track = sequencer_track(track_index)
    if track ~= nil then
      track.postfx_volume:record_value(
        parameter_message_value(message, track.postfx_volume))
    end
  end)

  add_action(string.format(
    "Track Levels:Panning:Track XX (Pre):Track #%02d [Set]",
    track_index),
  function(message)
    local track = sequencer_track(track_index)
    if track ~= nil then
      track.prefx_panning:record_value(
        parameter_message_value(message, track.prefx_panning))
    end
  end)

  add_action(string.format(
    "Track Levels:Panning:Track XX (Post):Track #%02d [Set]",
    track_index),
  function(message)
    local track = sequencer_track(track_index)
    if track ~= nil then
      track.postfx_panning:record_value(
        parameter_message_value(message, track.postfx_panning))
    end
  end)

  add_action(string.format(
    "Track Levels:Width:Track XX:Track #%02d [Set]", track_index),
  function(message)
    local track = sequencer_track(track_index)
    if track ~= nil then
      track.prefx_width:record_value(
        parameter_message_value(message, track.prefx_width))
    end
  end)
end


-- Track Levels::Send Track XX

for send_index = 1,MAX_SEND_TRACK_MAPPINGS do

  add_action(string.format(
    "Track Levels:Volume:Send Track XX (Pre):Send Track #%02d [Set]",
    send_index),
  function(message)
    local track = send_track(send_index)
    if track ~= nil then
      track.prefx_volume:record_value(
        parameter_message_value(message, track.prefx_volume))
    end
  end)

  add_action(string.format(
    "Track Levels:Volume:Send Track XX (Post):Send Track #%02d [Set]",
    send_index),
  function(message)
    local track = send_track(send_index)
    if track ~= nil then
      track.postfx_volume:record_value(
        parameter_message_value(message, track.postfx_volume))
    end
  end)

  add_action(string.format(
    "Track Levels:Panning:Send Track XX (Pre):Send Track #%02d [Set]",
    send_index),
  function(message)
    local track = send_track(send_index)
    if track ~= nil then
      track.prefx_panning:record_value(
        parameter_message_value(message, track.prefx_panning))
    end
  end)

  add_action(string.format(
    "Track Levels:Panning:Send Track XX (Post):Send Track #%02d [Set]",
    send_index),
  function(message)
    local track = send_track(send_index)
    if track ~= nil then
      track.postfx_panning:record_value(
        parameter_message_value(message, track.postfx_panning))
    end
  end)

  add_action(string.format(
    "Track Levels:Width:Send Track XX:Send Track #%02d [Set]",
    send_index),
  function(message)
    local track = send_track(send_index)
    if track ~= nil then
      track.prefx_width:record_value(
        parameter_message_value(message, track.prefx_width))
    end
  end)
end


--------------------------------------------------------------------------------
-- Track DSP
--------------------------------------------------------------------------------

-- Track DSPs:Selected FX Active

add_action("Track DSPs:Selected FX Active [Toggle]",
function(message)
  local sel_device = selected_device()
  if sel_device and sel_device.name ~= "TrackVolPan" then
    sel_device.is_active = toggle_message_value(
      message, sel_device.is_active)
  end
end)

add_action("Track DSPs:Selected FX Active [Set]",
function(message)
  local sel_device = selected_device()
  if sel_device and sel_device.name ~= "TrackVolPan" then
    sel_device.is_active = boolean_message_value(
      message, sel_device.is_active)
  end
end)


-- Track DSPs:Selected FX

for parameter_index = 1, MAX_GENERIC_PARAMETER_MAPPINGS do
  add_action(string.format(
    "Track DSPs:Selected FX:Parameter #%02d [Set]",
     parameter_index),
  function(message)
    local sel_device = selected_device()
    if sel_device and parameter_index <= #sel_device.parameters then
      local parameter = sel_device.parameters[parameter_index]
      parameter:record_value(parameter_message_value(message, parameter))
    end
  end)
end


-- Track DSP:Selected FX (Mixer Subset)

for parameter_index = 1, MAX_GENERIC_PARAMETER_MAPPINGS do
  add_action(string.format(
    "Track DSPs:Selected FX (Mixer Subset):Parameter #%02d [Set]",
     parameter_index),
  function(message)
    local sel_device = selected_device()
    if sel_device then
      local mixer_parameters = {}
      for _, parameter in pairs(sel_device.parameters) do
        if parameter.show_in_mixer then
          mixer_parameters[#mixer_parameters + 1] = parameter
        end
      end

      if parameter_index <= #mixer_parameters then
        local parameter = mixer_parameters[parameter_index]
        parameter:record_value(parameter_message_value(message, parameter))
      end
    end
  end)
end


--------------------------------------------------------------------------------
-- Navigation
--------------------------------------------------------------------------------

-- Navigation:Sequencer

add_action("Navigation:Sequencer:Current Sequence Pos [Set]",
function(message)

  if message:is_abs_value() then
    local new_edit_pos = song().transport.edit_pos

    new_edit_pos.sequence = clamp_value(
      message.int_value,
      1, song().transport.song_length.sequence)

    song().transport.edit_pos = new_edit_pos

  elseif message:is_rel_value() then
    local new_edit_pos = song().transport.edit_pos

    new_edit_pos.sequence = clamp_value(
      new_edit_pos.sequence + message.int_value,
      1, song().transport.song_length.sequence)

    song().transport.edit_pos = new_edit_pos
  end

end)

add_action("Navigation:Sequencer:Select Previous Sequence Pos [Trigger]",
function(message)

  if message:is_trigger() then
    local new_edit_pos = song().transport.edit_pos

    new_edit_pos.sequence = clamp_value(
      new_edit_pos.sequence - 1,
      1, song().transport.song_length.sequence)

    song().transport.edit_pos = new_edit_pos
  end

end)

add_action("Navigation:Sequencer:Select Next Sequence Pos [Trigger]",
function(message)

  if message:is_trigger() then
    local new_edit_pos = song().transport.edit_pos

    new_edit_pos.sequence = clamp_value(
      new_edit_pos.sequence + 1,
      1, song().transport.song_length.sequence)

    song().transport.edit_pos = new_edit_pos
  end

end)


add_action("Navigation:Sequencer:Current Pattern [Set]",
function(message)
  song().selected_pattern_index = message_value(
    message, song().selected_pattern_index, 1, 1000)
end)

add_action("Navigation:Sequencer:Decrease Current Pattern [Trigger]",
function(message)
  song().selected_pattern_index = dec_message_value(
    message, song().selected_pattern_index, 1, 1000)
end)

add_action("Navigation:Sequencer:Increase Current Pattern [Trigger]",
function(message)
  song().selected_pattern_index = inc_message_value(
    message, song().selected_pattern_index, 1, 1000)
end)


-- Navigation:Tracks

add_action("Navigation:Tracks:Current Track [Set]",
function(message)

  if message:is_abs_value() then
    song().selected_track_index = clamp_value(
      message.int_value, 1, #song().tracks)

  elseif message:is_rel_value() then
    song().selected_track_index = clamp_value(
      song().selected_track_index + message.int_value,
      1, #song().tracks)

  end

end)

add_action("Navigation:Tracks:Select Previous Track [Trigger]",
function(message)

  if message:is_trigger() then
    song().selected_track_index = wrap_value(
      song().selected_track_index - 1,
      1, #song().tracks)
  end

end)

add_action("Navigation:Tracks:Select Next Track [Trigger]",
function(message)

  if message:is_trigger() then
    song().selected_track_index = wrap_value(
      song().selected_track_index + 1,
      1, #song().tracks)
  end

end)


-- Navigation:Columns

add_action("Navigation:Columns:Select Previous Column [Trigger]",
function(message)

  if message:is_trigger() then
    local sel_track_index = song().selected_track_index
    local sel_track = song().tracks[sel_track_index]

    local note_column_count = sel_track.visible_note_columns
    local effect_column_count = sel_track.visible_effect_columns
    local column_count = note_column_count + effect_column_count

    local new_column_index = 0

    if song().selected_note_column_index > 0 then
      new_column_index = song().selected_note_column_index - 1

    elseif song().selected_effect_column_index > 0 then
      new_column_index = note_column_count +
        song().selected_effect_column_index - 1
    end

    -- jump to the previous effect column
    if new_column_index >= 1 and
       new_column_index > note_column_count then
      song().selected_effect_column_index = new_column_index -
        note_column_count

    -- jump to the previous note column
    elseif new_column_index >= 1 then
      song().selected_note_column_index = new_column_index

    -- jump to the previous effect or note column
    else
      sel_track_index = wrap_value(sel_track_index - 1, 1, #song().tracks)
      sel_track = song().tracks[sel_track_index]

      note_column_count = sel_track.visible_note_columns
      effect_column_count = sel_track.visible_effect_columns
      column_count = note_column_count + effect_column_count

      song().selected_track_index = sel_track_index

      if effect_column_count > 0 then
        song().selected_effect_column_index = effect_column_count
      else
        song().selected_note_column_index = note_column_count
      end
    end
  end

end)

add_action("Navigation:Columns:Select Next Column [Trigger]",
function(message)

  if message:is_trigger() then
    local sel_track_index = song().selected_track_index
    local sel_track = song().tracks[sel_track_index]

    local note_column_count = sel_track.visible_note_columns
    local effect_column_count = sel_track.visible_effect_columns
    local column_count = note_column_count + effect_column_count

    local new_column_index = 0

    if song().selected_note_column_index > 0 then
      new_column_index = song().selected_note_column_index + 1

    elseif song().selected_effect_column_index > 0 then
      new_column_index = note_column_count +
        song().selected_effect_column_index + 1
    end

    -- jump to the next note column
    if new_column_index <= note_column_count then
      song().selected_note_column_index = new_column_index

    -- jump to the next effect column
    elseif new_column_index <= column_count then
      song().selected_effect_column_index = new_column_index -
        note_column_count

    -- jump to the next tracks note or effect column
    else
      sel_track_index = wrap_value(sel_track_index + 1, 1, #song().tracks)
      sel_track = song().tracks[sel_track_index]
      note_column_count = sel_track.visible_note_columns

      song().selected_track_index = sel_track_index

      if note_column_count > 0 then
        song().selected_note_column_index = 1
      else
        song().selected_effect_column_index = 1
      end
    end
  end

end)

add_action("Navigation:Columns:Select Previous Note Column [Trigger]",
function(message)

  if message:is_trigger() then
    local sel_track_index = song().selected_track_index
    local sel_track = song().tracks[sel_track_index]

    local note_column_count = sel_track.visible_note_columns

    -- jump to previous note column in the selected track
    if song().selected_note_column_index > 1 then
      song().selected_note_column_index =
        song().selected_note_column_index - 1

    -- jump to a previous track with note columns
    else
      for i = 1,#song().tracks do
        sel_track_index = wrap_value(sel_track_index - 1, 1, #song().tracks)
        sel_track = song().tracks[sel_track_index]
        note_column_count = sel_track.visible_note_columns

        if note_column_count > 0 then
          song().selected_track_index = sel_track_index
          song().selected_note_column_index = note_column_count
          break
        end
      end
    end
  end

end)

add_action("Navigation:Columns:Select Next Note Column [Trigger]",
function(message)

  if message:is_trigger() then
    local sel_track_index = song().selected_track_index
    local sel_track = song().tracks[sel_track_index]

    local note_column_count = sel_track.visible_note_columns

    -- jump to next note column in the selected track
    if song().selected_note_column_index > 0 and
        song().selected_note_column_index <
          note_column_count then

      song().selected_note_column_index =
        song().selected_note_column_index + 1

    -- jump to a next track with note columns
    else
      for i = 1,#song().tracks do
        sel_track_index = wrap_value(sel_track_index + 1, 1, #song().tracks)
        sel_track = song().tracks[sel_track_index]
        note_column_count = sel_track.visible_note_columns

        if note_column_count > 0 then
          song().selected_track_index = sel_track_index
          song().selected_note_column_index = 1
          break
        end
      end
    end
  end

end)

add_action("Navigation:Columns:Show Volume Column [Toggle]",
function(message)
  if message:is_trigger() then
    local selected_track = selected_track()
    if selected_track.max_note_columns > 0 then
      selected_track.volume_column_visible = toggle_message_value(
        message, selected_track.volume_column_visible)
    end
  end
end)

add_action("Navigation:Columns:Show Volume Column [Set]",
function(message)
  if message:is_trigger() then
    local selected_track = selected_track()
    if selected_track.max_note_columns > 0 then
      selected_track.volume_column_visible = boolean_message_value(
        message, selected_track.volume_column_visible)
    end
  end
end)

add_action("Navigation:Columns:Show Panning Column [Toggle]",
function(message)
  if message:is_trigger() then
    local selected_track = selected_track()
    if selected_track.max_note_columns > 0 then
      selected_track.panning_column_visible = toggle_message_value(
        message, selected_track.panning_column_visible)
    end
  end
end)

add_action("Navigation:Columns:Show Panning Column [Set]",
function(message)
  if message:is_trigger() then
    local selected_track = selected_track()
    if selected_track.max_note_columns > 0 then
      selected_track.panning_column_visible = boolean_message_value(
        message, selected_track.panning_column_visible)
    end
  end
end)

add_action("Navigation:Columns:Show Delay Column [Toggle]",
function(message)
  if message:is_trigger() then
    local selected_track = selected_track()
    if selected_track.max_note_columns > 0 then
      selected_track.delay_column_visible = toggle_message_value(
        message, selected_track.delay_column_visible)
    end
  end
end)

add_action("Navigation:Columns:Show Delay Column [Set]",
function(message)
  if message:is_trigger() then
    local selected_track = selected_track()
    if selected_track.max_note_columns > 0 then
      selected_track.delay_column_visible = boolean_message_value(
        message, selected_track.delay_column_visible)
    end
  end
end)


-- Navigation:Track DSPs

add_action("Navigation:Track DSPs:Current Track DSP [Set]",
function(message)

  if message:is_abs_value() then
    song().selected_device_index = clamp_value(
      message.int_value, 1, #selected_track().devices)

  elseif message:is_rel_value() then
    song().selected_device_index = clamp_value(
      song().selected_device_index + message.int_value,
      1, #selected_track().devices)

  end

end)

add_action("Navigation:Track DSPs:Select Previous Track DSP [Trigger]",
function(message)

  if message:is_trigger() then
    song().selected_device_index = clamp_value(
      song().selected_device_index - 1,
      1, #selected_track().devices)
  end

end)

add_action("Navigation:Track DSPs:Select Next Track DSP [Trigger]",
function(message)

  if message:is_trigger() then
    song().selected_device_index = clamp_value(
      song().selected_device_index + 1,
      1, #selected_track().devices)
  end

end)


-- Navigation:Instruments

add_action("Navigation:Instruments:Current Instrument [Set]",
function(message)

  if message:is_abs_value() then
    song().selected_instrument_index = clamp_value(
      message.int_value, 1, #song().instruments)

  elseif message:is_rel_value() then
    song().selected_instrument_index = clamp_value(
      song().selected_instrument_index + message.int_value,
      1, #song().instruments)

  end

end)

add_action("Navigation:Instruments:Decrease Current Instrument [Trigger]",
function(message)

  if message:is_trigger() then
    song().selected_instrument_index = clamp_value(
      song().selected_instrument_index - 1, 1, #song().instruments)
  end

end)

add_action("Navigation:Instruments:Increase Current Instrument [Trigger]",
function(message)

  if message:is_trigger() then
    song().selected_instrument_index = clamp_value(
      song().selected_instrument_index + 1, 1, #song().instruments)
  end

end)


add_action("Navigation:Instruments:Capture Nearest From Pattern [Trigger]",
function(message)

  if message:is_trigger() then
    song():capture_instrument_from_pattern()
  end

end)

add_action("Navigation:Instruments:Capture From Pattern [Trigger]",
function(message)

  if message:is_trigger() then
    song():capture_nearest_instrument_from_pattern()
  end

end)


--------------------------------------------------------------------------------
-- GUI
--------------------------------------------------------------------------------

-- GUI:Window

add_action("GUI:Window:Fullscreen Mode [Toggle]",
function(message)
  song().window.fullscreen = toggle_message_value(
    message, app().window.fullscreen)
end)

add_action("GUI:Window:Fullscreen Mode [Set]",
function(message)
  app().window.fullscreen = boolean_message_value(
    message, app().window.fullscreen)
end)


-- GUI:Dialogs

add_action("GUI:Dialogs:Show Sample Recorder [Set]",
function(message)
  app().window.sample_record_dialog_is_visible = boolean_message_value(
    message, app().window.sample_record_dialog_is_visible)
end)

add_action("GUI:Dialogs:Show Sample Recorder [Toggle]",
function(message)
  app().window.sample_record_dialog_is_visible = toggle_message_value(
    message, app().window.sample_record_dialog_is_visible)
end)


-- GUI:Presets

add_action("GUI:Presets:Activate Preset [Set]",
function(message)
  if message:is_abs_value() then
    app().window:select_preset(clamp_value(message.int_value, 1, 8))
  end
end)

for preset_index = 1,8 do
  add_action(string.format(
    "GUI:Presets:Activate Preset #%02d [Trigger]", preset_index),
  function(message)
    if message:is_trigger() then
      app().window:select_preset(preset_index)
    end
  end)
end


-- GUI:Upper Frame

local upper_frame_views = {}
table.insert(upper_frame_views,
  renoise.ApplicationWindow.UPPER_FRAME_DISK_BROWSER)
table.insert(upper_frame_views,
  renoise.ApplicationWindow.UPPER_FRAME_TRACK_SCOPES)
table.insert(upper_frame_views,
  renoise.ApplicationWindow.UPPER_FRAME_MASTER_SCOPES)
table.insert(upper_frame_views,
  renoise.ApplicationWindow.UPPER_FRAME_MASTER_SPECTRUM)

add_action("GUI:Upper Frame:Show Upper Frame [Toggle]",
function(message)
  app().window.upper_frame_is_visible = toggle_message_value(
    message, app().window.upper_frame_is_visible)
end)

add_action("GUI:Upper Frame:Show Upper Frame [Set]",
function(message)
  app().window.upper_frame_is_visible = boolean_message_value(
    message, app().window.upper_frame_is_visible)
end)

add_action("GUI:Upper Frame:Select Previous [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_upper_frame = wrap_value(
      app().window.active_upper_frame - 1, 1, #upper_frame_views)
  end
end)

add_action("GUI:Upper Frame:Select Next [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_upper_frame = wrap_value(
      app().window.active_upper_frame + 1, 1, #upper_frame_views)
  end
end)

add_action("GUI:Upper Frame:Select [Set]",
function(message)
  app().window.active_upper_frame = message_value(
    message, app().window.active_upper_frame, 1, #upper_frame_views)
end)

add_action("GUI:Upper Frame:Show Disk Browser [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_upper_frame =
      renoise.ApplicationWindow.UPPER_FRAME_DISK_BROWSER
  end
end)

add_action("GUI:Upper Frame:Show Track Scopes [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_upper_frame =
      renoise.ApplicationWindow.UPPER_FRAME_TRACK_SCOPES
  end
end)

add_action("GUI:Upper Frame:Show Master Scopes [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_upper_frame =
      renoise.ApplicationWindow.UPPER_FRAME_MASTER_SCOPES
  end
end)

add_action("GUI:Upper Frame:Show Master Spectrum [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_upper_frame =
      renoise.ApplicationWindow.UPPER_FRAME_MASTER_SPECTRUM
  end
end)


-- GUI:Middle Frame

local middle_frame_views = {}
table.insert(middle_frame_views,
  renoise.ApplicationWindow.MIDDLE_FRAME_PATTERN_EDITOR)
table.insert(middle_frame_views,
  renoise.ApplicationWindow.MIDDLE_FRAME_MIXER)
table.insert(middle_frame_views,
  renoise.ApplicationWindow.MIDDLE_FRAME_KEYZONE_EDITOR)
table.insert(middle_frame_views,
  renoise.ApplicationWindow.MIDDLE_FRAME_SAMPLE_EDITOR)

add_action("GUI:Middle Frame:Select Previous [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_middle_frame = wrap_value(
      app().window.active_middle_frame - 1, 1, #middle_frame_views)
  end
end)

add_action("GUI:Middle Frame:Select Next [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_middle_frame = wrap_value(
      app().window.active_middle_frame + 1, 1, #middle_frame_views)
  end
end)

add_action("GUI:Middle Frame:Select [Set]",
function(message)
  app().window.active_middle_frame = message_value(
    message, app().window.active_middle_frame, 1, #middle_frame_views)
end)

add_action("GUI:Middle Frame:Show Pattern Editor [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_middle_frame =
      renoise.ApplicationWindow.MIDDLE_FRAME_PATTERN_EDITOR
  end
end)

add_action("GUI:Middle Frame:Show Mixer [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_middle_frame =
      renoise.ApplicationWindow.MIDDLE_FRAME_MIXER
  end
end)

add_action("GUI:Middle Frame:Show Key-Zone Editor [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_middle_frame =
      renoise.ApplicationWindow.MIDDLE_FRAME_KEYZONE_EDITOR
  end
end)

add_action("GUI:Middle Frame:Show Sample Editor [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_middle_frame =
      renoise.ApplicationWindow.MIDDLE_FRAME_SAMPLE_EDITOR
  end
end)

add_action("GUI:Middle Frame:Show Pattern Matrix [Set]",
function(message)
  app().window.pattern_matrix_is_visible = boolean_message_value(
    message, app().window.pattern_matrix_is_visible)
end)

add_action("GUI:Middle Frame:Show Pattern Matrix [Toggle]",
function(message)
  app().window.pattern_matrix_is_visible = toggle_message_value(
    message, app().window.pattern_matrix_is_visible)
end)

add_action("GUI:Middle Frame:Show Pattern Advanced Edit [Set]",
function(message)
  app().window.pattern_advanced_edit_is_visible = boolean_message_value(
    message, app().window.pattern_advanced_edit_is_visible)
end)

add_action("GUI:Middle Frame:Show Pattern Advanced Edit [Toggle]",
function(message)
  app().window.pattern_advanced_edit_is_visible = toggle_message_value(
    message, app().window.pattern_advanced_edit_is_visible)
end)


-- GUI:Lower Frame

local lower_frame_views = {}
table.insert(lower_frame_views,
  renoise.ApplicationWindow.LOWER_FRAME_TRACK_DSPS)
table.insert(lower_frame_views,
  renoise.ApplicationWindow.LOWER_FRAME_TRACK_AUTOMATION)
table.insert(lower_frame_views,
  renoise.ApplicationWindow.LOWER_FRAME_INSTRUMENT_PROPERTIES)
table.insert(lower_frame_views,
  renoise.ApplicationWindow.LOWER_FRAME_SONG_PROPERTIES)

add_action("GUI:Lower Frame:Show Lower Frame [Toggle]",
function(message)
  app().window.lower_frame_is_visible = toggle_message_value(
    message, app().window.lower_frame_is_visible)
end)

add_action("GUI:Lower Frame:Show Lower Frame [Set]",
function(message)
  app().window.lower_frame_is_visible = boolean_message_value(
    message, app().window.lower_frame_is_visible)
end)

add_action("GUI:Lower Frame:Select Previous [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_lower_frame = wrap_value(
      app().window.active_lower_frame - 1, 1, #lower_frame_views)
  end
end)

add_action("GUI:Lower Frame:Select Next [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_lower_frame = wrap_value(
      app().window.active_lower_frame + 1, 1, #lower_frame_views)
  end
end)

add_action("GUI:Lower Frame:Select [Set]",
function(message)
  app().window.active_lower_frame = message_value(
    message, app().window.active_lower_frame, 1, #lower_frame_views)
end)

add_action("GUI:Lower Frame:Show Track DSPs [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_lower_frame =
      renoise.ApplicationWindow.LOWER_FRAME_TRACK_DSPS
  end
end)

add_action("GUI:Lower Frame:Show Track Automation [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_lower_frame =
      renoise.ApplicationWindow.LOWER_FRAME_TRACK_AUTOMATION
  end
end)

add_action("GUI:Lower Frame:Show Instrument Properties [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_lower_frame =
      renoise.ApplicationWindow.LOWER_FRAME_INSTRUMENT_PROPERTIES
  end
end)

add_action("GUI:Lower Frame:Show Song Properties [Trigger]",
function(message)
  if message:is_trigger() then
    app().window.active_lower_frame =
      renoise.ApplicationWindow.LOWER_FRAME_SONG_PROPERTIES
  end
end)


