Interested in this also… don’t you need a device plugged in to generate the midi/osc note?
Interested in this also… don’t you need a device plugged in to generate the midi/osc note?
Hmm… No. The user must enable the OSC server in the Renoise preferences. When that is done, it’s quite simple sending notes to the server from a script.
I don’t know if my way is the pretties, but I use “OscClient.lua” which provides a very simple syntax. One simple line for initializing a “connection” object (client), and then one simple line for sending a note value. It’s documented quite clearly in the file mentioned so it should be difficult to go wrong with it.
The “tricky” part when implementing it is that you have to keep a table of active notes, because they have to be note-offed with a separate transmission, of course depending on for how long you want to hold your note(s). So you might need to use some timers and tailor-made scheme for triggering the following note-off… However, I strongly suspect there might even be a better class available, providing a “duration” parameter when triggering notes. I haven’t looked that deep into what’s available.
(Danoise?)
Hmm so in theory you could build a sequencer using just the scripting side of things and not rely on writing notes to the pattern editor?
Will have a look at the code you mentioned, I had a few tool ideas that never went anywhere because I didn’t think you could do this
Hmm so in theory you could build a sequencer using just the scripting side of things and not rely on writing notes to the pattern editor?
Will have a look at the code you mentioned, I had a few tool ideas that never went anywhere because I didn’t think you could do this
I suggest checking the accuracy of the renoise.app() timer. I can’t find the forum thread now, but IIRC taktik stated somewhere that the timer only roughly guarantees an accuracy of XYZ milliseconds. That’s probably why it’s best to rely on the native playback. Perhaps it could at least be “good enough” for previewing a sequence, though…
EDIT, PS: Except for what’s mentioned above, I have noticed that there might be some small additional irregular timing factor with OSC, on a millisecond(s) level. Possibly due to network latency, even when it’s local? It was noticable in a function that 1) turns off edit mode, 2) sends OSC, 3) turns on edit mode. (notes could get recorded). So, a little bit of timer management might also be needed if you want to prevent recording by this scheme.
Hmm so in theory you could build a sequencer using just the scripting side of things and not rely on writing notes to the pattern editor?
Will have a look at the code you mentioned, I had a few tool ideas that never went anywhere because I didn’t think you could do this
In theory, yes. It would be wonky like hell, due to the reasons mentioned by joule.
But definitely good enough for something like a preview… something like a chord navigator hint
I don’t know if my way is the pretties, but I use “OscClient.lua” which provides a very simple syntax. One simple line for initializing a “connection” object (client), and then one simple line for sending a note value. It’s documented quite clearly in the file mentioned so it should be difficult to go wrong with it.
The “tricky” part when implementing it is that you have to keep a table of active notes, because they have to be note-offed with a separate transmission, of course depending on for how long you want to hold your note(s). So you might need to use some timers and tailor-made scheme for triggering the following note-off… However, I strongly suspect there might even be a better class available, providing a “duration” parameter when triggering notes. I haven’t looked that deep into what’s available.
(Danoise?)
That’s right, today I would use the xOscClient(part of xLib).
Also, to keep track of active voices you could use thexVoiceManagerclass
Here is a simple player class that should hopefully work in most cases.
I’ve included all that it needed (including the duplex oscclient) but nothing more. If you make a tool out of it, it will play a Cmaj7 chord for 3 seconds, extremely pleasing to your ears.
In Rauls’ case this might not be needed, since I’m guessing he’ll just start and stop on button press / button release. Then it would be sufficient to use the OscClient class only.
Click to view contents
-- Simple OSC voice player supporting duration
-- client: OscClient object
-- voices: table (!) of voices, one voice being { instrument_index, track_index, note_value, velocity }
-- duration: play duration in milliseconds
-- no_autoplay: if false, voices will play automatically when object is created
class 'OscPlayer'
function OscPlayer:__init(client, voices, duration, no_autoplay)
self.client = client
self.voices = voices
self.duration = duration
self._playing_voices = { }
self._release_func = function() self:stop() end
-- don't autoplay if true
if not no_autoplay then
self:play()
end
end
-- bloating the syntax for dubious legibility
function OscPlayer:get_parameters(voice_index)
local params = self._playing_voices[voice_index]
return params[1], params[2], params[3], params[4]
end
function OscPlayer:play()
-- cut notes if already playing
if (#self._playing_voices > 0) then self:stop() end
-- play and memorize voices
for k = 1, #self.voices do
local voice = self.voices[k]
self._playing_voices[k] = table.rcopy(self.voices[k])
self.client:trigger_instrument(true, self:get_parameters(k))
end
-- add "release" timer
renoise.tool():add_timer(self._release_func, self.duration)
end
function OscPlayer:stop()
-- remove the "release" timer
if renoise.tool():has_timer(self._release_func) then
renoise.tool():remove_timer(self._release_func)
end
-- send note-offs
for k = 1, #self._playing_voices do
local voice = self._playing_voices[k]
self.client:trigger_instrument(false, self:get_parameters(k))
end
self._playing_voices = { }
end
--[[============================================================================
-- Duplex.OscClient
============================================================================]]--
--[[--
OscClient is a simple OSC client that connect to the built-in OSC server in Renoise, producing realtime messages that trigger notes or send MIDI messages
--]]
--==============================================================================
class 'OscClient'
--------------------------------------------------------------------------------
--- Initialize the OscClient class
-- @param osc_host (string) the host-address name (can be an IP address)
-- @param osc_port (int) the host port
function OscClient:__init(osc_host,osc_port, protocol)
-- the socket connection, nil if not established
self._connection = nil
local client, socket_error = renoise.Socket.create_client(osc_host, osc_port, protocol)
if (socket_error) then
renoise.app():show_warning("Warning: Chord Tracker failed to start the internal OSC client")
self._connection = nil
else
self._connection = client
end
end
--------------------------------------------------------------------------------
--- Trigger instrument-note
-- @param note_on (bool), true when note-on and false when note-off
-- @param instr (int), the Renoise instrument index
-- @param track (int) the Renoise track index
-- @param note (int), the desired pitch, 0-120
-- @param velocity (int), the desired velocity, 0-127
function OscClient:trigger_instrument(note_on,instr,track,note,velocity)
-- TRACE("OscClient:trigger_instrument()",note_on,instr,track,note,velocity)
if not self._connection then
return false
end
local osc_vars = { }
osc_vars[1] = {tag = "i",value = instr}
osc_vars[2] = {tag = "i",value = track}
osc_vars[3] = {tag = "i",value = note}
local header = nil
if (note_on) then
header = "/renoise/trigger/note_on"
osc_vars[4] = {tag = "i",value = velocity}
else
header = "/renoise/trigger/note_off"
end
self._connection:send(renoise.Osc.Message(header,osc_vars))
return true
end
--------------------------------------------------------------------------------
--- END OF CLASSES. TESTING CODE BELOW
local my_osc_client = OscClient("127.0.0.1", 8000, 2)
local voices = {
{ 1, 1, 60, 100 },
{ 1, 1, 64, 100 },
{ 1, 1, 67, 100 },
{ 1, 1, 71, 100 }
}
local my_player = OscPlayer(my_osc_client, voices, 3000)
my_player:stop()
my_player:play() -- a bit of stress testing
my_player:stop()
my_player:play()
Ok Joule,I am using the upper code provided.For now, I can shoot the note from the button. And stop.
The steps I’ve taken:
I used the whole code, canceling the following lines:
...
...
--- END OF CLASSES. TESTING CODE BELOW
local my_osc_client = OscClient("127.0.0.1", 8000, 2)
local voices = {
{ 1, 1, 60, 100 },
{ 1, 1, 64, 100 },
{ 1, 1, 67, 100 },
{ 1, 1, 71, 100 }
}
local my_player = OscPlayer(my_osc_client, voices, 3000)
my_player:stop()
my_player:play() -- a bit of stress testing
my_player:stop()
my_player:play()
Then I add the functions in the button:
--The button
-------------------------------(1)
presd()
OscPlayer(my_osc_client, { { 1, 1, 60, 50 }, }, 3000) --<<----- 3000??? (delete duration)
end
-------------------------------(2)
reled()
stop()
end
-------------------------------(the button)
WMP_KEY_00 = vb:button {
id = 'WMP_KEY_00',
width = 14,
height = 50,
pressed = function() presd() end, --(1)
notifier = function() reled() end, --(2)
--released = function() end, --released is similar to notifier
}
Now I have to look if I can eliminate time.And fix the code to use the selected instrument at any time.I have to find a way to not repeat the same code for the 120 buttons.
@Danoise. If something should be added in the API are the functions that will allow all this, without having to activate the OSC Server.Maybe it’s not available, it’s because it’s hard to implement.But apparently, I am not the only interested. Many things can be done with this.
To all, thank you very much!
If I have more doubts about this I will ask here.This is the first time I use the OSC Server.
…
Attached a code for a timer, in case it serves anything.:
function amic_CH04_4A() --This loop will stop itself after 1.5 seconds. Use a clock
local starttime = os.clock()
print("I'm about to spam you horribly for 1.5 seconds")
while os.clock() - starttime < 1.5 do
print("SPAM!")
end
print("All done :P")
end
…
One last question:
Is it possible to remove the automatic color selection or marked?For example, pressing a button lights up a color related to the skin of Renoise.Is it possible to replace it?
@Danoise. If something should be added in the API are the functions that will allow all this, without having to activate the OSC Server.Maybe it’s not available, it’s because it’s hard to implement.
It’s rarely the case that something is too hard to implement. More critical is,does it make sense to do so?
In this case, I think it would be nice. Perhaps not for the obvious reason (I’ve worked a lot with the current implementation and think it’s great - you can route messages to specific instrument, tracks, etc.) but rather, for these two reasons:
- We don’t actually have a way in the API to check if the OSC server is enabled, and at which port it is listening. So right now, as a tool author, you basically need to display a one-time message to the user that (s)he needs to enable the OSC server in Renoise.
And obviously, being able to control the OSC server via scripting would then be a first step… no need to display this message. But even this would have to be done carefully, because the OSC server allows you to send messages to containing lua code, which Renoise will then evaluate. I think Renoise will sandbox the code, protecting you against running certain methods (so you can’t wipe someones harddisk through the network!!), but it’s still something to be aware of. So I would basically still want to display a warning (“you’re going to enable the OSC server, are you sure you want to continue?”)
- The note that you create is a “note proper”. As a result, it might end up getting recorded somewhere in the project. This is not necessarily what you want. Workaround? Temporarily disable edit-mode if it was enabled, and re-enable after note has been triggered (ugly, but that should work).
Are you just gonna play and stop on pressed/release? In that case you don’t need the intermediate player I did. That was just for duration support.
Otherwise you can simply do something like:
local audition_client = OscClient("127.0.0.1", 8000, 2)
function create_button(instrument, note_value, velocity)
local button = vb:button {
text = "my_button",
pressed = audition_client:trigger_instrument(
true, instrument, note_value, velocity),
released = audition_client:trigger_instrument(
false, instrument, note_value, velocity)
}
return button
end
function create_row()
local instrument = 1
local velocity = 100
local row = vb:row { }
for note_value = 1, 10 do
row:add_child(
create_button(instrument, note_value, velocity)
)
end
-- do what you want with the row here
end
I made the example a bit extensive since your approach seemed a little bit strange.
It’s rarely the case that something is too hard to implement. More critical is,does it make sense to do so?
In this case, I think it would be nice. Perhaps not for the obvious reason (I’ve worked a lot with the current implementation and think it’s great - you can route messages to specific instrument, tracks, etc.) but rather, for these two reasons:
- We don’t actually have a way in the API to check if the OSC server is enabled, and at which port it is listening. So right now, as a tool author, you basically need to display a one-time message to the user that (s)he needs to enable the OSC server in Renoise.
And obviously, being able to control the OSC server via scripting would then be a first step… no need to display this message. But even this would have to be done carefully, because the OSC server allows you to send messages to containing lua code, which Renoise will then evaluate. I think Renoise will sandbox the code, protecting you against running certain methods (so you can’t wipe someones harddisk through the network!!), but it’s still something to be aware of. So I would basically still want to display a warning (“you’re going to enable the OSC server, are you sure you want to continue?”)
- The note that you create is a “note proper”. As a result, it might end up getting recorded somewhere in the project. This is not necessarily what you want. Workaround? Temporarily disable edit-mode if it was enabled, and re-enable after note has been triggered (ugly, but that should work).
1)Maybe a checkbox (activate/deactivate) would suffice → Active OSC Server [v], with tooltip: “warning, bla bla bla”.What would be the function to activate?
- Wooow this has been a disappointment.!!! This is creating me problems. I need the edit-mode activated, to write the notes in the pattern editor while the note is being played.
At the point where I am, everything works:
- Timer deleted. OK!
- Note associated with each button (each key of virtual piano), with play/stop, OK!
- Track selection, OK! (in pattern editor)
- Instrument selection, OK! (in instruments box)
- Volume control with a valuebox OK! (in tool)
- edit-mode activated writte the notes in pattern editor with keys of virtual piano OK!
- edit-mode activated with OSC activate, writte notes also, NOT OK! ← problem!!!
I just need to avoid your point 2 (to prevent notes being written to the pattern editor when playing the notes):
This should not happen(should not write any notes, should only sound the associated sample of the selected instrument).In the gif image capture, my functions about write notes in the pattern editor are disabled.It is caused by OSC related functions.
Is there any way to avoid this?
Are you just gonna play and stop on pressed/release? In that case you don’t need the intermediate player I did. That was just for duration support.
Otherwise you can simply do something like:
local audition_client = OscClient("127.0.0.1", 8000, 2) function create_button(instrument, note_value, velocity) local button = vb:button { text = "my_button", pressed = audition_client:trigger_instrument( true, instrument, note_value, velocity), released = audition_client:trigger_instrument( false, instrument, note_value, velocity) } return button end function create_row() local instrument = 1 local velocity = 100 local row = vb:row { } for note_value = 1, 10 do row:add_child( create_button(instrument, note_value, velocity) ) end -- do what you want with the row here end
I made the example a bit extensive since your approach seemed a little bit strange.
Thanks joule!I have it organized differently, maybe I will change it. I will try to experiment with this code.The “track selection” is missing.It should also be.
As mentioned earlier in the thread, you have to prevent the recording the notes by using some workaround. It can be done with a one-shot timer, delaying edit_mode=on for maybe 40ms or so. (one-shot = timer self destructs after one run)
EDIT: Yeah I forgot the track parameter in the osc triggering. For you to correct
As mentioned earlier in the thread, you have to prevent the recording the notes by using some workaround. It can be done with a one-shot timer, delaying edit_mode=on for maybe 40ms or so. (one-shot = timer self destructs after one run)
I do not know how to add it. I am attaching the code I currently use (the note duration timer is off, overridden lines):
--------------------------------------------------------------------------------
-- OSC SERVER
--------------------------------------------------------------------------------
class 'OscPlayer'
function OscPlayer:__init( client, voices, duration, no_autoplay )
self.client = client
self.voices = voices
--self.duration = duration
self._playing_voices = { }
self._release_func = function() self:stop() end
--don't autoplay if true
if not no_autoplay then
self:play()
end
end
-- bloating the syntax for dubious legibility
function OscPlayer:get_parameters( voice_index )
local params = self._playing_voices[voice_index]
return params[1], params[2], params[3], params[4]
end
function OscPlayer:play()
--cut notes if already playing
if ( #self._playing_voices > 0 ) then self:stop() end
--play and memorize voices
for k = 1, #self.voices do
local voice = self.voices[k]
self._playing_voices[k] = table.rcopy( self.voices[k] )
self.client:trigger_instrument( true, self:get_parameters( k ) )
end
--add "release" timer
--renoise.tool():add_timer( self._release_func, self.duration )
end
function OscPlayer:stop()
--remove the "release" timer
--if renoise.tool():has_timer( self._release_func ) then
--renoise.tool():remove_timer( self._release_func )
--end
--send note-offs
for k = 1, #self._playing_voices do
local voice = self._playing_voices[k]
self.client:trigger_instrument( false, self:get_parameters( k ) )
end
self._playing_voices = { }
end
class 'OscClient' --Initialize the OscClient class
-- osc_host (string) the host-address name (can be an IP address)
-- osc_port (int) the host port
function OscClient:__init( osc_host, osc_port, protocol )
-- the socket connection, nil if not established
self._connection = nil
local client, socket_error = renoise.Socket.create_client( osc_host, osc_port, protocol )
if ( socket_error ) then
renoise.app():show_warning( "Warning: Chord Tracker failed to start the internal OSC client" )
self._connection = nil
else
self._connection = client
end
end
--------------------------------------------------------------------------------
-- Trigger instrument-note
--- note_on (bool), true when note-on and false when note-off
--- instr (int), the Renoise instrument index 1-254
--- track (int), the Renoise track index
--- note (int), the desired pitch, 0-120
--- velocity (int), the desired velocity, 0-127
function OscClient:trigger_instrument( note_on, instr, track, note, velocity )
if not self._connection then
return false
end
local osc_vars = { }
osc_vars[1] = { tag = "i", value = instr } --wmp_instr()
osc_vars[2] = { tag = "i", value = track }
osc_vars[3] = { tag = "i", value = note }
local header = nil
if ( note_on ) then
header = "/renoise/trigger/note_on"
osc_vars[4] = { tag = "i", value = velocity }
else
header = "/renoise/trigger/note_off"
end
self._connection:send( renoise.Osc.Message( header,osc_vars ) )
return true
end
local my_osc_client = OscClient( "127.0.0.1", 8000, 2 )
--my_player = OscPlayer( my_osc_client, { { 1, 1, 60, 50 } }, 10000 )
--my_player:stop()
function wmp_instr( song, instr )
song = renoise.song()
instr = song.selected_instrument_index
return instr
end
function wmp_track( song, track )
song = renoise.song()
track = song.selected_track_index
return track
end
--function wmp_note() end
function wmp_vel( vel )
vel = vb.views['WMP_CTRL_VEL'].value
return vel
end
--------------------------------------------------------------------------------
-- A BUTTON: (C-0)
--------------------------------------------------------------------------------
function wmp_fn_key_00_pre()
OscPlayer( my_osc_client, { { wmp_instr(), wmp_track(), 0, wmp_vel() } } ) --0 is the note
end
function wmp_fn_key_00_rel()
OscPlayer( my_osc_client, { { wmp_instr(), wmp_track(), 0, wmp_vel() } } ):stop()
end
function wmp_fn_key_00( song, spi, sti, sli, snci ) --To write parameters in the pattern editor
song = renoise.song()
spi, sti, sli, snci = song.selected_pattern_index, song.selected_track_index, song.selected_line_index, song.selected_note_column_index
if ( song.transport.edit_mode == true and song.selected_note_column ) then --condition for edit_mode == true
song.patterns[spi].tracks[sti].lines[sli].note_columns[snci].note_value = 0 --note
ins_inst()
ins_vol()
ins_pan()
ins_dly()
step_length()
end
end
--the vb:button (C-0 note)
WMP_KEY_00 = vb:button {
id = 'WMP_KEY_00',
tooltip = '',
width = 14,
height = 50,
color = { 0xFF,0xFF,0xFF },
pressed = function() wmp_fn_key_00_pre() end,
--notifier = function() wmp_fn_key_00() end, --<----Disabled to test the OSC server in isolation
released = function() wmp_fn_key_00_rel() end,
bitmap = 'icons/c-0.png',
}
How do I add the “one_short timer” as you mention?
Hmm… I can’t verify my code now, but the principle looks pretty much like this:
local stored_edit_mode = renoise.song().transport.edit_mode
-- we only need the workaround if recording mode was on. let's not clutter memory/cpu otherwise ;)
if stored_edit_mode then
renoise.song().transport.edit_mode = false
end
-- put playing trigger here!
-- reset to edit_mode = true after some delay
if stored_edit_mode then
local timer_func
timer_func = function()
renoise.song().transport.edit_mode = true
renoise.tool():remove_timer(timer_func) -- works?
end
renoise.tool():add_timer(timer_func, 40)
end
Not sure if it would fire an error if you press the key repeatedly very fast, but I don’t think so. Try it and we’ll work it out.
PS. Do you really need duration support? If not, ditch the OscPlayer class and use OscClient:trigger_instrument directly.
Where do I put the code exactly?
I’ve pasted it inside the function "function OscPlayer:__init( client, voices, duration, no_autoplay )"But the effect is strange. The red frame of the edit_mode flashes, and if very fast pulse ends off.
in,renoise.tool():add_timer(timer_func, 40)I’ve tested it with 10, 20, 40, 60, 80, and I keep typing values.
However, it does not return any errors, even shredding the mouse.
It should flash, very briefly, since it’s temporarily switching off edit_mode to prevent recording any notes. If edit_mode was on before, it should be on after.
The reason why you’re getting a strange behavior is likely due to the dubious structure of your code, specifically the way you handle the player object (rather creating two separate objects for pressed/released, which is not optimal).
Create one player object (with no_autoplay = true) in the scope above the button creation, then call my_player:play() and my_player.stop() from the pressed/released notifiers.
I think (!) it is enough if the workaround goes inside the OscPlayer:play() function. IIRC “orphan” note-offs won’t get recorded anyway.
I true this:
function OscPlayer:__init( client, voices, duration, no_autoplay )
self.client = client
self.voices = voices
--self.duration = duration
self._playing_voices = { }
self._release_func = function() self:stop() end
--don't autoplay if true
if not no_autoplay then
self:play()
end
----delay edit_mode 40ms
local stored_edit_mode = renoise.song().transport.edit_mode
-- we only need the workaround if recording mode was on. let's not clutter memory/cpu otherwise ;)
if stored_edit_mode then
renoise.song().transport.edit_mode = false
end
-- put playing trigger here!
-- reset to edit_mode = true after some delay
if stored_edit_mode then
local timer_func
timer_func = function()
renoise.song().transport.edit_mode = true
renoise.tool():remove_timer(timer_func) -- works?
end
renoise.tool():add_timer(timer_func, 40)
end
end
Keep writing one note (or two notes, depending the timer), when press the mouse.
What exactly happens (in slow process):
- Pressing click: write note A in note column 01
- Releasing click: write note B (the same note A) in note column 02
That is, as the gif shows.
This seems to work perfectly fine here…
local button = vb:button {
text = tostring(note_value),
pressed = function()
local stored_edit_mode = renoise.song().transport.edit_mode
if stored_edit_mode then
renoise.song().transport.edit_mode = false
end
osc_client:trigger_instrument(true, instrument, track, note_value, velocity)
if stored_edit_mode then
local timer_func
timer_func = function()
renoise.song().transport.edit_mode = true
renoise.tool():remove_timer(timer_func)
end
renoise.tool():add_timer(timer_func, 40)
end
end,
released = function()
osc_client:trigger_instrument(false, instrument, track, note_value, velocity)
end,
}
A practical example is attached.
This seems to work perfectly fine here…
local button = vb:button { text = tostring(note_value), pressed = function() local stored_edit_mode = renoise.song().transport.edit_mode if stored_edit_mode then renoise.song().transport.edit_mode = false end osc_client:trigger_instrument(true, instrument, track, note_value, velocity) if stored_edit_mode then local timer_func timer_func = function() renoise.song().transport.edit_mode = true renoise.tool():remove_timer(timer_func) end renoise.tool():add_timer(timer_func, 40) end end, released = function() osc_client:trigger_instrument(false, instrument, track, note_value, velocity) end, }
A practical example is attached.
Is there a problem with setting the value “15” or “10” or “5” instead of 40?Why 40?With value <16 edit_modenot blinking, but I do not know if it depends on the hardware.
The “left click repeat rate” in preferences/Keys/Mouse Wheel has something to do?
I’ll try to build a new piano based on your code, even if it means starting over. I want the tool to work as fine as possible. I believe that with all this I can move forward.I also have another option in mind, edit the notes without using edit_mode.In this case, osc server would be canceled, and the notes would not sound if active edit_mode. As I have my code, I have a separate configuration for each button.I think I understand all your code, and I can use it to compress it to the maximum (within my possibilities).
Thank you very much for all this!I think it’s the best solution for this case.
…withmy_player:play() and my_player.stop(),I had an error in the terminal, by using them separately in “pressed” and “released” of the button.I was doing something wrong.
I will continue to build, and when I have something fine I will comment here, maybe this weekend.
Yeah, I could use 10ms to avoid any noticable flickering, but that’s on an I7 processor. To be safe on slower computers, I would guess that 40ms would be OK. I’d suggest making a stress test in 1 core mode while using a couple of heavy VST/VSTi:s.