[Fixed 2.6 Beta] Undo / Redo From Midi Callbacks

Hi,

I’m facing some Undo/Redo difficulties when setting DeviceParameter values and adding points to an envelope.

Whenever I receive a fader value change from my external FaderPort controller I update a DeviceParameter.value e.g.

 renoise.song().selected_track.prefx_volume.value = myvalue  

That works greatly and the Renoise mixer faders move accordingly to the received updates. Only Problem: it seems that no undo/redo data is beeing written. Interestingly that problem doesn’t occure when using Duplex virtual UI faders or moving the Renoise faders directly. Couldn’t find out why.

Also I don’t understand the behaviour of the following code.

  
-- precondition: a new song has been created and no sliders were moved, the prefx vol slider is set to Renoise 0 dB  
function __init()  
 renoise.song().selected_track.prefx_volume.value = 1.2 -- set to something new  
 renoise.song().selected_track.prefx_volume.value = 0.0 -- set to -INF  
end  
  

When calling __init() the sliders are correctly moved to -INF. Now, when pressing undo the value is astonishingly reset to 0 dB and not to 1.2 as expected. The value change to 1.2 seems to not have been recorded into undo/redo queue.

Is this maybe a wanted performance related behaviour ? I’m aware that it doesn’t make sense to add undo data for every
value change while fader beeing moved, but it seems there is no explicit function to force writing of undo-data like e.g.

song().instruments[].samples[].sample_buffer.finalize_sample_data_changes()  
``` provides. Said that Renoise mixer faders seem to write undo-data whenever fader has been moved and mouse-button is released, right ?  
  
Since my faderport provides a fader-is-touched state I'd like to call a finalize_devpara_value_change() or something  
whenever the touched-state changes to "untouched". [b]Is there another solution I've overseen, or maybe could such a function be added to song API ?[/b]  
  
I also tried to use the following function, but it didn't help. BTW: what is it actually good for ?  
  
```lua  
  
renoise.song().tracks[].devices[].parameters[].record_value(value)  
  

Moreover, I experience the same problems with:

  
renoise.song().patterns[].tracks[].automation[]:add_point_at(time, value)  
  

I can’t undo/redo multiple added or changed point values. It’s the same principle here as described above.

Hey Airmann,

changes which are done from MIDI IO in scripts are right now indeed not correctly undone. This already is fixed for the next beta. Actually I wonder why we haven’t noticed this before. This was broken in all 2.6 betas so far.


renoise.song().tracks[].devices[].parameters[].record_value(value)
records parameter changes either to envelope automation or into the pattern, and is bound to the “Record MIDI mapped Parameter Changes to Automation” option in Renoises MIDI mapping dialog.
This is used by the GlobalMidiMappings.lua script to record automation. Should maybe rename this to “record_midi_mapped_value” or add a more generic version for scripts, moving the option out of the MIDI mapping dialog…

Really glad to hear this. The missing undo/redo is right now a real showstopper for the FaderPort driver.

Otherwise I have to say: so far I could implement almost everything (including support for bidirectional fader automation read/write etc.) as I’ve planned by using the scripting framework. It’s absolutely useable, awesome and quick. Really great job man :guitar:. I love it and I promise there are good things to come.

But again regarding redo/undo: I have an important design question here:

I handle the incoming fader values from the FaderPort right now in two ways:

  1. I set the device parameter values (prefx_volume etc.) directly
  2. I record the values into an automation envelope

In first case there’s the problem that it just doesn’t make sense to create undo-data for all intermediate fader positions.
Only for the final release position after all movement. But that’s exactly the problem: this can not be detected automatically, right ?
An explicit event like “mouse button released” or “fader untouched” is necessary to trigger the creation of the undo-data.
But I couldn’t find such a function in the framework. Is there maybe a function included for next beta release ?

Another question: how to handle undo-data creation of controllers like a fader without “touched”-support ? Using time measurement, maybe ?

Had another weired undo/redo experience with 2.6 beta 5:

I added some points into an automation envelope by using the scripting framework (add_point), afterwards I added some more points by hand, and did some undo/redo calls. Finally I wanted to clear/cut the whole envelope by pressing on the “scissor”-button in automation editor. And whoops: the automation lane wasn’t empty as expected. Instead old points of the lane were restored or so.
After clicking on the “scissor”-button again the lane changed again but wasn’t empty. That happened three times in a row or so
and afterwards the lane was really empty. Felt like some undo/redo was automatically applied.

The problem: I don’t know how to reproduce this. but it happened several times.

From the B6 thread:

Not sure if this would be possible but would some kind of timer for MIDI recorded Undos work? Two closely related ways that may be possible.

Renoise now seems to know that a MIDI value has changed, so it creates a Undo point. Currently it is creating one at each point (or is it even sub-points/ticks as I’ve read happening in the past as MIDI updates/ppqn is higher value than the Automation Envelope?)

Anyway what I was thinking is some way of coding a limit on the time between which MIDI related Undo points can be created. Say every second or maybe longer? Renoise knows a MIDI value has changed, while this is changing it can compare current and previous value and if different record the Undo point on polling every second. Second option would be the exact opposite. On MIDI value change do not record an Undo but flag CC, poll every second and on detecting two subsequent value as being the same record the Undo point with all changes in it.

Not sure if that makes sense but hopefully something to think about…

Yes, thats something I had in mind as well, but we will have to solve this in Renoise internally. We right now have the same problem when recording MIDI without scripts, so this needs a general solution.
Allowing scripts to skip or merge some undo steps is something we should not allow, because this can easily lead into big troubles: some undo step will rely on other undo steps being applied first in order to work.

Yeah I was thinking it’s really a Glocal Renoise fix and glad to think you have some possible solutions in your sketchbook and are seriously giving the problem all due consideration.

So not a 2.6 fix? Let’s hope for some time soon then :)

As always thanks for the good work and providing us with such an awesome program. Keep it up! Hopefully I’ll have time to actually start using it again soon ;)

At least for Fader/Pan movement the problem is solved right now in Renoise by waiting until the mouse button is released, right ? A thing like a timer came also in my mind, but only for faders/controls that don’t provide a “release” event. But IMO the timer-solution feels more like a “hack” (???). IMO the right solution was to listen to events where possible.

Right now, the massively recorded undo-data is a big showstopper for me, FaderPort driver (2000 LOCs) is almost finished,
but when you use it, it makes undo/redo of Renoise almost unuseable :( sig. Guess it’s the same for all Duplex stuff

Update:

checked out how undo/redo mechanism is solved in Reaper. They use time measurement in Reaper Core. The FaderPort driver itself just sends the values to Reaper.
So, if you move the fader very very slowly, multiple undo events are created - despite the fact that the fader wasn’t untouched/released during the movement.
That works of course also for the pan-control which is not touch-sensitive. Probably such a mechanism would work in Renoise, too.

Right now I’m developing a workaround with my own time measurement, but the problem when doing this outside Renoise:

  1. every time I change a value an undo event/data is created
  2. in order to avoid too much undo data creation I could 1) skip setting values 2) call undo() before setting new values (HACK)
  • Skipping setting new values leads to the problem of missing acoustical and visual feedback
  • Undoing" last changed value before setting the new one is a hack, because further actions could accidentally be undone, too.

Nonetheless this is my solution at the moment (Pseudocode)

  
  
local control_moved = false  
local control_touch_state = false  
  
local control_last_movement_time = nil  
local time_frame = 2000 // 2 seconds  
  
on_receive_new_value(value)  
  
 // if we're in a move-action then undo the last value change to avoid massive undo data creation   
 if (control_moved) then  
 undo() // this is a dangerous hack !!!  
 end  
  
 XXXXXXX.value = value  
 control_moved = true // value received means: control was moved !  
 control_last_movement_time = os.clock()   
end  
  
on_idle()  
  
 // if control is released clear "moved" flag  
 if (not control_touched() then  
 control_moved = false  
 end  
end  
  
control_touched()  
  
 if (control_is_touch_sensitive) then   
  
 return control_touch_state   
  
 // compute fake touch by using time measurement.  
 // This is done for all non touch-sensitive controls which provide no "release/untouched" event  
 else   
 if (control_last_movement_time) then   
 if (os.clock() - control_last_movment_time <= time_frame)  
 return true // within time frame  
 end  
 end  
  
 return false  
 end   
end  
  
on_receive_midi(message)  
  
 // value received  
 if (message[2] == XX1) then  
 on_receive_value(message[3])  
  
 // control released  
 elseif (message[2] == XX2) then  
 control_touch_state = (message[3] == 1)  
 end  
end  
  
  

ok, implemented a similar code like the pseudo-code above and now it works.
But there are two problems when undoing the values:

  1. parallel changes are undone, too (e.g. change pan and volume at the same time)
  2. calling undo() before every new value change causes slight (!) clicking in realtime

Despite these drawbacks at least it seems to work.
It’s pretty ugly but I think there is probably no other external solution right now.

ok I understand that skipping undo steps might be dangerous for composite structures / copy paste etc… But why not allow it for atomar / leafs data types like device parameter values ? Setting an absolute parameter value should always work, or ?

About the Renoise-internal solution: as I’ve mentioned above e.g. Reaper does it also internally. But I guess they don’t have he “super-duper universal solution”, but instead explicit “timing” code for volume/pan changes. In case of Renoise I guess this was code for device parameter changes.
But what about adjustable ballistics / timing then ? In Reaper it seems it’s one-timing-fits-it-all