Build a perfect Virtual Piano using buttons only

Wow, that’s cool. Like you said: simply the perfect looking piano widget.

You’re right - I didn’t know about the negative margin/spacing technique back then. I think credit goes to afta8 for discovering that?

And, just to confirm since I heard a few concerned whispers: negative margins are a valid design/layout technique - also in other languages. It’s NOT going away from the Renoise API :slight_smile:

Yes. I know CSS3 quite well. Here you can use negative margins too.I suppose it is an almost universal method for any type of code used to represent a GUI.But, certainly, I was surprised to see no piano tool with this aspect until now. And he told me over and over, that there had to be some way.

I invite anyone to take the code and wring the maximum.I would like to see tools with a more professional look in general.I have seen some really amazing, looking tools, one of them was of afta8, that managed dynamic points, I believe.

You indeed must use bitmaps for the text, as far as I know. Just the fact that you can’t set text color makes this the only good option for predictable results independent of color theme. Rotating vb:text or button text certainly isn’t possible.

I will explain the procedure I follow to do it. Build vertical text :

  1. Create a button with large height and width and pure white color.
  2. Type in the text of the button the word you want to use.
  3. Capture the image and paste it into an image editor. I use paint.net
  4. Rotate the image 90º, invert the color, reduce the distance between letters and crop to create an icon in bmp or png format.
  5. Finally use it inside a :button or a :bitmap using bitmap…

Too bad there is no better way to control the text. This method is a bit prehistoric. :slight_smile:

If you’re building scroll buttons, you should imo make a piano widget class. It’s much easier and more compact to handle dynamic behavior like that in a “self-contained structure” that a class is.

I will take this topic to develop it later.The idea I have is to share several examples of different virtual pianos, to have examples available in the forums.

You’re right - I didn’t know about the negative margin/spacing technique back then. I think credit goes to afta8 for discovering that?

I believe that these details should be well explained in the documentation in ViewBuilder.API.lua…

  • For colum: .spacing → [positive or negative number]
  • For row: .spacing → [positive or negative number]
  • etc

Or also in practical basic examples.

I believe that these details should be well explained in the documentation in ViewBuilder.API.lua…

Well, maybe. I think it’s fine that we’re sharing these tips here in the scripting forum.

Another tip : did you know that printing the following in the console yields colored text?

print("*** Oops! An error occurred") -- this will be grey
print(">>> Processing....") -- this will be green

Pretty useful if you’re doing a lot of console tracing while developing :ph34r:

Well, maybe. I think it’s fine that we’re sharing these tips here in the scripting forum.

Another tip : did you know that printing the following in the console yields colored text?

print("*** Oops! An error occurred") -- this will be grey
print(">>> Processing....") -- this will be green

Pretty useful if you’re doing a lot of console tracing while developing :ph34r:

:smiley: :smiley: :Dthanks! True!!! I had seen it in some of your tools, that the green text came out on the console, but I did not bother to see how it was done.Then there are 3 colors, right?

print("info!") - white text
print("*** error!") - grey text
print(">>> applying!") - green text

What would be very useful is to create a timer in milliseconds to determine how long a particular function takes, and to display the value printed on the console at the end of the process:

  • Function “XXX” processed in 450 ms

or

  • Function “YYY” processed in 6213 ms

This would be very useful!!!

Timing your functions can be done by using os.clock(). Example: https://forum.renoise.com/t/renoise-tools-speed-optimization-initiative/45510

Timing your functions can be done by using os.clock(). Example: https://forum.renoise.com/t/renoise-tools-speed-optimization-initiative/45510

:blink:I read over the whole topic to tools speed optimization, but I did not remember that you just used that, thanks!I will definitely use this os.clock() to optimize my tools…

local x = os.clock()

function name_func()
  --content  
end

print(string.format("elapsed time: %.2f\n", os.clock() - x))

This is very interesting!!!

Then there are 3 colors, right?

You could probably get more colors out of strings if you were to run Renoise under a color terminal and output (debugging) strings directly to stdout using ANSI escape sequences:

io.stdout:write("\27[33;1mThis is yellow!\n")
io.stdout:write("\27[31;1mThis is red!\n")
io.stdout:write("\27[35;1mThis is magenta!\n")
io.stdout:write("\27[34;1mThis is blue!\n")

Thinking more Linux here though, don’t know about Windows :slight_smile:

Good little piano ‘button’ keyboard btw Raul :slight_smile:

You could probably get more colors out of strings if you were to run Renoise under a color terminal and output (debugging) strings directly to stdout using ANSI escape sequences:

io.stdout:write("\27[33;1mThis is yellow!\n")
io.stdout:write("\27[31;1mThis is red!\n")
io.stdout:write("\27[35;1mThis is magenta!\n")
io.stdout:write("\27[34;1mThis is blue!\n")

Thinking more Linux here though, don’t know about Windows :slight_smile:

Good little piano ‘button’ keyboard btw Raul :slight_smile:

Thanks 4Tey!I am using windows at all times and linux for boot with USB.So I do not have a proper machine to try Renoise under Linux or Mac.

I keep thinking things with colors.I have not seen the specific documentation, but there should be some way to detect that the selected instrument uses samples (not VSTi).Considering the keyzones.There should be a simple way to colorize gray (or active = false) the keys that do not have any associated samples.Disable non-serving keys…With this, in addition to adjusting the size of the piano, and being able to control the keys through MIDI (especially), USB keyboard and mouse, would be a frankly useful and attractive tool.

MIDI pressed and MIDI released to control the “note buttons”???I mean all the notes of the 10 octaves, the 120 notes.The virtual piano would control the 120 notes, but visually only the necessary octaves would appear. I think 3 octaves would be enough.

I have used to call a function:

renoise.tool():add_midi_mapping {
  name = THE_NAME,
  invoke = function(message)
    if (message:is_trigger()) then
      name_func()
    end
  end
}

MIDI is_trigger() is equal to button released… InRenoise.ScriptingTool.API.lua:

class "renoise.ScriptingTool.MidiMessage"
    
      -- returns if action should be invoked
      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 undefined
      property: int_value

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

is_swith() act like a button, with pressed, and released?What I would like to build is a way to control the virtual keyboard with the MIDI keyboard, all notes.Since there are 120 notes, you really only need to deal with one note.The rest is repeat.My final idea is to build a virtual piano that can control notes with the midi keyboard, but also to be able to edit in the pattern editor the notes directly with the mouse on the virtual keyboard.

  • With the mouse the sound can use the osc server.
  • With the midi keyboard I understand that the sound should “go alone”.

If you want to do a virtual-midi keyboard alike thing with MIDI input, you’ll need for the tool to configure it’s own MIDI input.

There is an example on how to do this here:

https://github.com/renoise/xrnx/blob/master/Snippets/Midi.lua

It lets you receive raw MIDI data, which you can then do whatever with you want.

All the basic MIDI messages (so, not sysex and such stuff) is really just three bytes of data - so quite simple to process.

(check the MIDI input panel to investigate incoming messages)

  • With the mouse the sound can use the osc server.
  • With the midi keyboard I understand that the sound should “go alone”.

If you want to trigger notes in realtime, this is done through the OSC server, no matter what

Remember, you tool defines it’s own MIDI input port - independently of Renoise.

You can send messages using the OSC server in two ways:

  1. “Raw” midi message which is received by whichever instrument is selected

  2. “Extended” message which targets a specific instrument and/or track

The “extended” message is dealing with triggering notes only, so if you want to pass CC data from the keyboard (mod wheel?) to Renoise (or other tools), then you need to send as “raw” midi

Raw MIDI works like incoming midi message and can be MIDI mapped to any element that light up when pressing CTRL+M (MIDI mapping mode).

Hope this helps!

@Raul,
Here is the way to build your widget programatically, done for the fun of it :slight_smile: This will also let you decide how many octaves to build.

(A version that would render from any key to any key would be a bit more complex/different than this, but this is a first step)

Click to view contents
function pw_build_octave(octave)
  local intervals = { 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1 }
  local black_spaces = { [2] = 8, [4] = 22, [7] = 7, [9] = 7 }
    
  local white_keys = vb:row { spacing = -3 }
  local black_keys = vb:row { }
  local octave_content = vb:row {
    spacing = -131,
    white_keys,
    black_keys }
  
  for key = 1, 12 do
    if intervals[key] == 1 then
      white_keys:add_child(
        vb:button {
          height = 66, width = 23,
          color = { 255, 255, 255 },
          id = "KEY_" .. (key - 1) + (12 * octave),
        })
    else
      black_keys:add_child(
        vb:button {
          height = 40, width = 15,
          color = { 1, 1, 1 },
          id = "KEY_" .. (key - 1) + (12 * octave),
        })   
      if key <= 9 then
        black_keys:add_child(vb:space { width = black_spaces[key] })
      end      
    end
  end
  return octave_content
end

function pw_build(octaves)
  local piano_widget = vb:row { margin = 5, spacing = -2 }
  for octave = 0, octaves-1 do
    piano_widget:add_child( pw_build_octave(octave) )
  end
  return piano_widget
end
local my_pianowidget_content = pw_build(4)

@Joule, thank you very much! :slight_smile:

I added a bit of layout:

Click to view contents
function white_tooltip (key, octave)
  if key == 1 then return "C-"..octave.."" end
  if key == 3 then return "D-"..octave.."" end
  if key == 5 then return "E-"..octave.."" end
  if key == 6 then return "F-"..octave.."" end
  if key == 8 then return "G-"..octave.."" end
  if key ==10 then return "A-"..octave.."" end
  if key ==12 then return "B-"..octave.."" end
end
---
function black_tooltip (key, octave)
  if key == 2 then return "C#"..octave.."" end
  if key == 4 then return "D#"..octave.."" end
  if key == 7 then return "F#"..octave.."" end
  if key == 9 then return "G#"..octave.."" end
  if key ==11 then return "A#"..octave.."" end
end
---
function white_bitmap (key, octave)
  if key == 1 then
    for i = 0, 9 do
      if octave == i then return "icons/c-"..i..".png" end
    end
  end
  --if key == 1 then return "icons/c.png" end
  if key == 3 then return "icons/d-.png" end
  if key == 5 then return "icons/e-.png" end
  if key == 6 then return "icons/f-.png" end
  if key == 8 then return "icons/g-.png" end
  if key ==10 then return "icons/a-.png" end
  if key ==12 then return "icons/b-.png" end
end

  function pw_build_octave(octave)
  
    local intervals = { 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1 }
    local black_spaces = { [2] = 8, [4] = 22, [7] = 7, [9] = 7 }
      
    local white_keys = vb:row { spacing = -3 }
    local black_keys = vb:row { }
    local octave_content = vb:row {
      spacing = -131,
      white_keys,
      black_keys
    }
    
    for key = 1, 12 do
      if intervals[key] == 1 then
        white_keys:add_child(
          vb:button {
            height = 66, width = 23,
            color = { 255, 255, 255 },
            id = "KEY_" .. (key - 1) + (12 * octave),
            tooltip = white_tooltip(key, octave), --<<
            bitmap = white_bitmap(key, octave), --<<
            pressed = function(key, octave) end, --<<
            released = function(key, octave) end --<<
          })
      else
        black_keys:add_child(
          vb:button {
            height = 40, width = 15,
            color = { 1, 1, 1 },
            id = "KEY_" .. (key - 1) + (12 * octave),
            tooltip = black_tooltip(key, octave), --<<
            pressed = function(key, octave) end, --<<
            released = function(key, octave) end --<<
          })   
        if key <= 9 then
          black_keys:add_child(vb:space { width = black_spaces[key] })
        end      
      end
    end
  
    return octave_content
  end
  
  
  function pw_build(octaves)
    local piano_widget = vb:row { margin = 5, spacing = -2 }
  
    for octave = 0, octaves-1 do
      piano_widget:add_child( pw_build_octave(octave) )
    end
  
    return piano_widget
  end
  
  my_pianowidget_content = pw_build(4)
tooltip = white_tooltip(key, octave), --<<
bitmap = white_bitmap(key, octave), --<<
pressed = function(key, octave) end, --<<
released = function(key, octave) end --<<

...

tooltip = black_tooltip(key, octave), --<<
pressed = function(key, octave) end, --<<
released = function(key, octave) end --<<

7449 vb-perfect-piano-2.png

I think it would be all set to have a virtual piano as a base.From here, on the 10 octave piano, it is possible to add 2 side scroll buttons and display only 3 or 4 octaves to make a more compact tool. Another button on the left to deploy a bottom line with more buttons to trigger functions.

For now, I think I would be able to include 3 things:

  1. OSC Server for sound.
  2. Control writing in the pattern editor using the mouse with the virtual piano.
  3. Control writing in the pattern editor using the USB keyboard with the virtual piano, according the “Oct” value of Renoise.

I find it an unknown world yet for include control via MIDI input.This involves connecting a MIDI keyboard and pressing the keyboard notes that the virtual piano only can illuminate the keys (pressed and released).

Yes. By making a class you can include smart properties (“first_octave”, “last_octave” et c) and make sure that these will refresh the whole piano view whenever they are changed.

… allowing a syntax that would look something like this:

local piano_widget = PianoWidget { first_octave = 2, last_octave = 7 } -- creates an object by the PianoWidget class
piano_widget.first_octave = 3 -- set a new value for first_octave. in the class, a property setter function is used to refresh the piano view

A class can be used as an interface that makes dynamic behavior like this a breeze. You can imagine how simple it would be to scroll, increase or decrease number of octaves by just pressing a button, and with this kind of automatic rebuilding.

PS. I noticed that you linked a tutorial on LUA classes previously. However, Renoise allows a nicer syntax than the one found in conventional lua tutorials.

If you want to do a virtual-midi keyboard alike thing with MIDI input, you’ll need for the tool to configure it’s own MIDI input.

There is an example on how to do this here:

https://github.com/renoise/xrnx/blob/master/Snippets/Midi.lua

It lets you receive raw MIDI data, which you can then do whatever with you want.

All the basic MIDI messages (so, not sysex and such stuff) is really just three bytes of data - so quite simple to process.

(check the MIDI input panel to investigate incoming messages)

If you want to trigger notes in realtime, this is done through the OSC server, no matter what

Remember, you tool defines it’s own MIDI input port - independently of Renoise.

You can send messages using the OSC server in two ways:

  1. “Raw” midi message which is received by whichever instrument is selected

  2. “Extended” message which targets a specific instrument and/or track

The “extended” message is dealing with triggering notes only, so if you want to pass CC data from the keyboard (mod wheel?) to Renoise (or other tools), then you need to send as “raw” midi

Raw MIDI works like incoming midi message and can be MIDI mapped to any element that light up when pressing CTRL+M (MIDI mapping mode).

Hope this helps!

I have not gone into depth to control a specific tool with MIDI input.Thinking about a virtual piano, the only thing that would need to be controlled is the reaction of a button according to the note pressed with the MIDI keyboard.That’s 120 buttons on the tool related keys 120 notes of the MIDI keyboard.In the end, the code is summarized in configuring a single button with the MIDI input code. Then add the remaining 119.

The MIDI code that is needed to run the tool, will serve for any model MIDI keyboard?

Yes. By making a class you can include smart properties (“first_octave”, “last_octave” et c) and make sure that these will refresh the whole piano view whenever they are changed.

… allowing a syntax that would look something like this:

local piano_widget = PianoWidget { first_octave = 2, last_octave = 7 } -- creates an object by the PianoWidget class
piano_widget.first_octave = 3 -- set a new value for first_octave. in the class, a property setter function is used to refresh the piano view

A class can be used as an interface that makes dynamic behavior like this a breeze. You can imagine how simple it would be to scroll, increase or decrease number of octaves by just pressing a button, and with this kind of automatic rebuilding.

PS. I noticed that you linked a tutorial on LUA classes previously. However, Renoise allows a nicer syntax than the one found in conventional lua tutorials.

I’ve been looking at this.It is also possible to do it as follows:

function pw_build(first_octave, last_octave) --range 0 to 9 (10 octaves)
    local piano_widget = vb:row { margin = 5, spacing = -2 }
  
    for octave = first_octave, last_octave do
      piano_widget:add_child( pw_build_octave(octave) )
    end
  
    return piano_widget
  end
  
  my_pianowidget_content = pw_build(3,6)

Withpw_build(3,6) octaves 3, 4, 5, 6 appear.It is thus possible to easily change or move it.Move one octave to the left would bepw_build(2,5).Maximize:pw_build(0,9).Only one octave (octave 4): pw_build(4,4)

Yes, of course you can use function oriented programming for anything if you want to, but it’s not a very good (flexible and future friendly) way of designing the code.

So I take it you won’t be touching Clojure then Joule? :wink:

C
    You shoot yourself in the foot. 

Assembly Language
    You crash the OS and overwrite the root disk. The system administrator arrives and shoots you in the foot.
    After a moment of contemplation, the administrator shoots himself in the foot and then hops around the room rabidly shooting at everyone in sight. 

C++
    You accidentally create a dozen instances of yourself and shoot them all in the foot. 
    Providing emergency medical care is impossible since you can't tell which are bitwise copies and which are just 
    pointing at others and saying "that's me, over there."

I like good ol’ assembly language :slight_smile:

Lately I’ve been busy with another tool. I leave here a screenshot that shows the appearance of a tool with two virtual pianos, one horizontal and one vertical.

7549 VPDpro_v1.0.png

Both pianos have been constructed using the methodology of this topic.The vertical virtual piano could be used in any tool similar to a piano roll.

It looks so complicated. You must have used a lot of selfmade bitmaps.

This tool is for users with midicontrollers, right?

It looks so complicated. You must have used a lot of selfmade bitmaps.

No, actually I’ve only used a couple of templates to create the images. As it is a repetitive process it is very simple.On the other hand, I have a hobby to create new icons. I like it.

This tool is for users with midicontrollers, right?

Yes. With a Midi controller keyboard you can compose with live recording, but not edit.This tool allows editing with the mouse and with the midi Input.In fact, you could compose a song without using an alphanumeric USB keyboard.Also, I added the ChordPad with the same capacity. You can add chords with the mouse or with any Midi pad.The tool is designed to be compact.You can select the columns you want to display.

It may seem a bit complex. But the compressed tool does not occupy more than 100KB.

The tool is designed to be compact

Did you try it on tablets?