How to implement a "Run" button

This forum is for developers of Rack Extensions to discuss the RE SDK, share code, and offer tips to other developers.
Post Reply
User avatar
pongasoft
RE Developer
Posts: 478
Joined: 21 Apr 2016
Location: Las Vegas
Contact:

08 Apr 2023

This is a repost from the internal Reason dev forum, in case somebody here knows the answer...

I want to implement a button that behaves exactly like the Run button found in devices like Dr Rex or Chord Sequencer:

Screen Shot 2023-04-08 at 10.30.27.png
Chord Sequencer Screenshot
Screen Shot 2023-04-08 at 10.30.27.png (37.06 KiB) Viewed 6505 times


1) the user can press the Run button, to toggle play/stop
2) the user can use the transport Play/Stop button to also toggle the Run button


I just don't know how to implement such a behavior:


If the user can interact with the button directly, then it has to be tied to a document_owner property (and for the rendering, a toggle_button widget should do the trick)
But because the transport can dictate the state as well, that means the real time code, which is the one receiving the transport information, needs to update the property. Which means the property needs to be rt_owner property.

Since a property cannot belong to 2 different owners, I am not sure how to do this. Can somebody explain the trick to make it work?


Thank you

User avatar
buddard
RE Developer
Posts: 1245
Joined: 17 Jan 2015
Location: Stockholm
Contact:

08 Apr 2023

The trick is to implement the Run button as a custom display.
You can hide that fact either by drawing fairly realistic display graphics (the RS approach) or by covering up the display with static decorations that change based on visibility switches (the Robotic Bean approach).

As you've already concluded, you have to use a realtime property to keep track of the "is_running" state, which is calculated in the realtime code by looking at the transport state in combination with various other internal states depending on the requirements of the device.

For the player devices that I wrote (Euclidean, Sequences, Beatmap, Pattern Mutator and Bassline Generator), I have a custom boolean property to indicate whether the Run button is being pressed. When that property turns true it means that the user wants to toggle whatever playback state is active right now, and the realtime code acts accordingly.

Apart from the Reason transport and pressing the Run button, some devices also trigger Run on note on or a pulse on a CV input, so the logic can get pretty complicated. :-)
I created a Transport class that manages all these inputs and either follows the Reason song clock, or generates its own clock, depending on the situation.

I hope this helps!

User avatar
pongasoft
RE Developer
Posts: 478
Joined: 21 Apr 2016
Location: Las Vegas
Contact:

09 Apr 2023

Thank you @buddard. The description makes a lot of sense. I wished we didn't have to jump through hoops to implement what seems to be a trivial behavior.

Reading your description, I realized I ended up doing something similar for my AB12 switch (I guess I am getting old, not even remembering what I did a few years ago), in particular for the A and B button and I used your approach.

User avatar
Murf
RE Developer
Posts: 656
Joined: 21 Jun 2019
Location: Brisbane, Australia
Contact:

30 Jul 2023

pongasoft wrote:
09 Apr 2023
Thank you @buddard. The description makes a lot of sense. I wished we didn't have to jump through hoops to implement what seems to be a trivial behavior.

Reading your description, I realized I ended up doing something similar for my AB12 switch (I guess I am getting old, not even remembering what I did a few years ago), in particular for the A and B button and I used your approach.
You guys saved me creating a new topic thanks!
I need to implement a mixer "solo" button that turns the "mute" on all other channels on, and I guess this is exactly the way it needs to be done :)
Murf

User avatar
Murf
RE Developer
Posts: 656
Joined: 21 Jun 2019
Location: Brisbane, Australia
Contact:

11 Aug 2023

Guys I am having trouble with this but I am pretty sure I have implemented it properly.
Here is my hdguid_2D.lua entry:

Code: Select all

-- CH1_SOLO_PB
front_widgets[#front_widgets + 1] = jbox.custom_display {
  graphics = { node = "CH1_SOLO_PB" },
  background = jbox.image{ path = "solo_off" },
  display_width_pixels = 50,
  display_height_pixels = 50,
  draw_function = "drawSwitch",
  gesture_function = "SwitchGesture",
  values = { "/custom_properties/ch1_solo_pb", "/custom_properties/ch1_solo_pb_rt" }
}
Here is my motherboard_def.lua entries:

Code: Select all

custom_properties = jbox.property_set{
	document_owner = {
		properties = {
			ch1_solo_pb=jbox.boolean{default=false,ui_name=ui_text_ch1_solo_pb,ui_type=ui_selecter_switch,},
		}
	},
rt_owner = {
		properties = {
			ch1_solo_pb_rt=jbox.boolean{default=false,ui_name=ui_text_ch1_solo_pb,ui_type=ui_selecter_switch,},
		}
	},
Here is my display.lua entries (Similar to what Yan used in AB switch:

Code: Select all

function handleSwitchTap(props)
  local changes = {}
  changes[1] = not props[1];
  return { gesture_ui_name = jbox.ui_text("ch1_solo_pb"),property_changes = changes }
end
function drawSwitch(props, di, dr)
  local sw = props[2] -- rt version
  local switchRect = { left = 0, top = 0, right = di.width, bottom = di.height }
  if (sw) then
    jbox_display.draw_rect(switchRect, colorOn)
  else
    jbox_display.draw_rect(switchRect, colorOff)
  end
end
And finally my renderBatch code

Code: Select all

.h def:
   fCH1_Knob_Solo_PropertyRef = JBox_MakePropertyRef(fCustomProperties, "ch1_solo_pb"); 
   fCH1_Knob_Solo_RT_PropertyRef = JBox_MakePropertyRef(fCustomProperties, "ch1_solo_pb_rt"); 
 .cpp (renderBatch):
  fCH1_Knob_SoloSelected = JBox_GetBoolean(JBox_LoadMOMProperty(fCH1_Knob_Solo_PropertyRef)); //BM t or f
  JBox_StoreMOMProperty(fCH1_Knob_Solo_RT_PropertyRef,JBox_MakeBoolean(fCH1_Knob_SoloSelected));
The idea here is when the button gets tapped it returns the rt as true in display.lua then the renderbatch sets the document_owner custom display with that value.
Nothing happen though...
in renderBatch the "fCH1_Knob_SoloSelected" var is not receiving the changes returned by the handleSwitchTap function in display.lua?
I have even logged handleSwitchTap to see if it changes in the cpp code when the button is tapped and it doesnt

Any insights would be appreciated!

Murf

User avatar
buddard
RE Developer
Posts: 1245
Joined: 17 Jan 2015
Location: Stockholm
Contact:

11 Aug 2023

Murf wrote:
11 Aug 2023

The idea here is when the button gets tapped it returns the rt as true in display.lua then the renderbatch sets the document_owner custom display with that value.
Nothing happen though...
in renderBatch the "fCH1_Knob_SoloSelected" var is not receiving the changes returned by the handleSwitchTap function in display.lua?
I have even logged handleSwitchTap to see if it changes in the cpp code when the button is tapped and it doesnt

Any insights would be appreciated!

Murf
What does the code in SwitchGesture look like, does it delegate properly to handleSwitchTap?
I would probably add some traces in both the display code and the realtime code to see how far you get.

User avatar
Murf
RE Developer
Posts: 656
Joined: 21 Jun 2019
Location: Brisbane, Australia
Contact:

11 Aug 2023

buddard wrote:
11 Aug 2023


What does the code in SwitchGesture look like, does it delegate properly to handleSwitchTap?
I would probably add some traces in both the display code and the realtime code to see how far you get.

Code: Select all

--------------- Definition ----------------------
function gestureFunction(handlers)
  return function(props, di, sp)
    local gesture_definition = {
      custom_data = {},
      handlers = handlers
    }
    return gesture_definition
  end
end
SwitchGesture = gestureFunction{ on_tap = "SwitchTap" }
SwitchTap = switchTapFunction()
I have traced in renderBatch but it shows the doc property as never changing when I tap.
NOTE: If I look at the document proprty in the draw function rather than the RT one then clicking the button performs the draw function fine.
The only problem here is I need to "light up" the buttons from renderBatch, not display code.
Actually... If I put all the "which other buttons do what logic when others are pressed" code in the display.lua instead of renderBatch that may work....

User avatar
Murf
RE Developer
Posts: 656
Joined: 21 Jun 2019
Location: Brisbane, Australia
Contact:

12 Aug 2023

Ok I am almost there, I have the switches (buttons) logic working in display.lua, and the buttons turn on and off properly when I click them.
I am not using an RT property anymore, just the doc ones for the UI elements (custom display buttons)

The only problem is.... renderBatch is not seeing the properties change!!!

I have 8 channels each with a solo button, the hdgui_2D.lua is like this (just showing the ch1 here)

Code: Select all

-- CH1_SOLO_PB
front_widgets[#front_widgets + 1] = jbox.custom_display {
  graphics = { node = "CH1_SOLO_PB" },
  background = jbox.image{ path = "solo_off" },
  display_width_pixels = 50,
  display_height_pixels = 50,
  draw_function = "SwitchDraw1",
  gesture_function = "SwitchGesture1",
  values = {  "/custom_properties/ch1_solo_pb",
              "/custom_properties/ch2_solo_pb",
              "/custom_properties/ch3_solo_pb",
              "/custom_properties/ch4_solo_pb",
              "/custom_properties/ch5_solo_pb",
              "/custom_properties/ch6_solo_pb",
              "/custom_properties/ch7_solo_pb",
              "/custom_properties/ch8_solo_pb"
            }
}
Motherboard is as before ( x 8)

Code: Select all

custom_properties = jbox.property_set{
	document_owner = {
		properties = {
			ch1_solo_pb=jbox.boolean{default=false,ui_name=ui_text_ch1_solo_pb,ui_type=ui_selecter_switch,},
display.lua:

Code: Select all

format_version = "1.0"

--------------- Definition ----------------------
function gestureFunction(handlers)
  return function(props, di, sp)
    local gesture_definition = {
      custom_data = {},
      handlers = handlers
    }
    return gesture_definition
  end
end

function switchTapFunction(input)
  return function(props, di, gi, cd) return handleSwitchTap(props,input) end
end

function switchDrawFunction(input)
  return function(props, di, dr) return handleSwitchDraw(props,di,input) end
end

SwitchGesture1 = gestureFunction{ on_tap = "SwitchTap1" }
SwitchTap1 = switchTapFunction(1)
SwitchDraw1 = switchDrawFunction(1)
SwitchGesture2 = gestureFunction{ on_tap = "SwitchTap2" }
SwitchTap2 = switchTapFunction(2)
SwitchDraw2 = switchDrawFunction(2)
SwitchGesture3 = gestureFunction{ on_tap = "SwitchTap3" }
SwitchTap3 = switchTapFunction(3)
SwitchDraw3 = switchDrawFunction(3)
SwitchGesture4 = gestureFunction{ on_tap = "SwitchTap4" }
SwitchTap4 = switchTapFunction(4)
SwitchDraw4 = switchDrawFunction(4)
SwitchGesture5 = gestureFunction{ on_tap = "SwitchTap5" }
SwitchTap5 = switchTapFunction(5)
SwitchDraw5 = switchDrawFunction(5)
SwitchGesture6 = gestureFunction{ on_tap = "SwitchTap6" }
SwitchTap6 = switchTapFunction(6)
SwitchDraw6 = switchDrawFunction(6)
SwitchGesture7 = gestureFunction{ on_tap = "SwitchTap7" }
SwitchTap7 = switchTapFunction(7)
SwitchDraw7 = switchDrawFunction(7)
SwitchGesture8 = gestureFunction{ on_tap = "SwitchTap8" }
SwitchTap8 = switchTapFunction(8)
SwitchDraw8 = switchDrawFunction(8)

local colorOn = { r = 0, g = 255, b = 0, a = 200 }
local colorOff = { r = 0, g = 0, b = 0, a = 0 }

function handleSwitchTap(props,input)
  local changes = {}
  changes[input] = not props[input]
  return { gesture_ui_name = jbox.ui_text("ch1_solo_pb"),property_changes = changes }
end

function handleSwitchDraw(props,di, input)
  local switchRect = { left = 0, top = 0, right = di.width, bottom = di.height }
  if (props[input]) then
    jbox_display.draw_rect(switchRect, colorOn)
  else
    jbox_display.draw_rect(switchRect, colorOff)
  end
end

-- nothing to do
NoOpDraw = function(props, di, dr)
end
and this renderBatch code does not log anything when any of the first 3 channel solo buttons are pressed:

Code: Select all

fCH1_Knob_SoloSelected = JBox_GetBoolean(JBox_LoadMOMProperty(fCH1_Knob_Solo_PropertyRef)); //BM t or f ( x8)

if (fCH1_Knob_SoloSelected || fCH2_Knob_SoloSelected || fCH3_Knob_SoloSelected ) {
	TJBox_Value aVal = JBox_MakeNumber(fCH1_Knob_SoloSelected);
	TJBox_Value bVal = JBox_MakeNumber(fCH2_Knob_SoloSelected);
	TJBox_Value cVal = JBox_MakeNumber(fCH3_Knob_SoloSelected);
	TJBox_Value values[] = {aVal,bVal,cVal};
	JBOX_TRACEVALUES("fCH1_Knob_SoloSelected: ^0,fCH2_Knob_SoloSelected: ^1,fCH3_Knob_SoloSelected: ^2", values, 3);
}
Sorry for such a big post, I think I am missing something simple here...
Murf.

User avatar
Murf
RE Developer
Posts: 656
Joined: 21 Jun 2019
Location: Brisbane, Australia
Contact:

12 Aug 2023

Ok.. so this apparently doesnt work!:
fCH1_Knob_SoloSelected = JBox_GetBoolean(JBox_LoadMOMProperty(fCH1_Knob_Solo_PropertyRef)); //BM t or f
when I cast to a float everything works :)

User avatar
buddard
RE Developer
Posts: 1245
Joined: 17 Jan 2015
Location: Stockholm
Contact:

15 Aug 2023

Murf wrote:
12 Aug 2023
Ok.. so this apparently doesnt work!:
fCH1_Knob_SoloSelected = JBox_GetBoolean(JBox_LoadMOMProperty(fCH1_Knob_Solo_PropertyRef)); //BM t or f
when I cast to a float everything works :)
I'm not sure why that wouldn't work? I use GetBoolean all the time... But I'm glad you found a way forward!

User avatar
Murf
RE Developer
Posts: 656
Joined: 21 Jun 2019
Location: Brisbane, Australia
Contact:

15 Aug 2023

buddard wrote:
15 Aug 2023
Murf wrote:
12 Aug 2023
Ok.. so this apparently doesnt work!:
fCH1_Knob_SoloSelected = JBox_GetBoolean(JBox_LoadMOMProperty(fCH1_Knob_Solo_PropertyRef)); //BM t or f
when I cast to a float everything works :)
I'm not sure why that wouldn't work? I use GetBoolean all the time... But I'm glad you found a way forward!
Bool does work, it turned out my code was not even being reached due to some poorly implemented silence detection foo.

User avatar
buddard
RE Developer
Posts: 1245
Joined: 17 Jan 2015
Location: Stockholm
Contact:

15 Aug 2023

Murf wrote:
15 Aug 2023
buddard wrote:
15 Aug 2023


I'm not sure why that wouldn't work? I use GetBoolean all the time... But I'm glad you found a way forward!
Bool does work, it turned out my code was not even being reached due to some poorly implemented silence detection foo.
Ah, I see! Haha, yes, that's a classic, I've been there too a couple of times. :lol:

User avatar
Murf
RE Developer
Posts: 656
Joined: 21 Jun 2019
Location: Brisbane, Australia
Contact:

15 Aug 2023

buddard wrote:
15 Aug 2023


Ah, I see! Haha, yes, that's a classic, I've been there too a couple of times. :lol:
:lol: :lol: :thumbup:

Post Reply
  • Information
  • Who is online

    Users browsing this forum: No registered users and 0 guests