Tool Request: More Effects

  1. Reverse a sample:
    sample_reverse

  2. Swap stereo chanels:
    sample_swap_stereo

  3. Insert a silence from the cursor (400 frames, from another empty sample):
    sample_insert_silence

It would be very interesting to do these 3 things from a tool. The third one, I don’t know if it’s possible (access to the cursor position).

This doesn’t seem to be available from the API, at a quick glance. That’s very peculiar.

Maybe at the beginning or end of the sample?

Ok, I think I know how to reverse or swap a sample:

-- Read access to samples in a sample data buffer.
renoise.song().instruments[].samples[].sample_buffer:sample_data(
  channel_index, frame_index)
  -> [number -1 to 1]

-- Write access to samples in a sample data buffer. New samples values must be
-- within [-1, 1] and will be clipped automatically. Sample buffers may be 
-- read-only (see property 'read_only'). Attempts to write on such buffers 
-- will result into errors.
-- IMPORTANT: before modifying buffers, call 'prepare_sample_data_changes'.
-- When you are done, call 'finalize_sample_data_changes' to generate undo/redo
-- data for your changes and update sample overview caches!
renoise.song().instruments[].samples[].sample_buffer:set_sample_data(
  channel_index, frame_index, sample_value)

.sample_buffer.sample_data() return the sample_value (-1 to 1: the height of the wave portion).

  • channel_index is the stereo channel (1= left, 2 = right)
  • frame_index is the horizontal position inside the wave.
  • No exist .sample_buffer.selected_frame_index (cursor position). It is a pity.

.sample_buffer:set_sample_data() allows editing the portion of the existing wave (modify the value or empty it (sample_value = 0)).

  • But it does not seem possible to add a new portion, that is, modify the wavelength, by adding a portion at a specific position.
  • Therefore, it is possible to create new waves, edit existing waves without modifying their length, but it is not possible to edit an existing wave by modifying its length, adding portions of silence (or another value between -1 and 1).

To lengthen an existing wave, it would be possible to create a new empty longer wave, and copy the previous wave into it. Then, I deduce that a silence would appear at the end of the wave. It is possible to use a table to deposit the sample_value of each frame_index.

If this is possible, then it would be possible to add a silence in a specific position (frame_index). But we don’t have the cursor position. A window tool would be necessary to set that value previously, which is not very practical. Then, it should at least be possible to add a silence at the beginning and end of the existing wave (add a new number of samples).

That’s correct… but instead of cursor position, you can use the selection start. Sure, it’s not very good UX, but it’s almost as practical… Except when you need to append silence to the end.

I (or anyone) should look into porting this to current Renoise, or recreating it:

runcommandsonselection3

Basically, running arbitrary commands (sox etc.) on parts of a sample by saving them as wav, running the command, loading it back in.

As for access to the cursor position, that used to be possible for sure, so I would be surprised if it isn’t anymore? Here’s another old tool made just to mess around with selections (I never really used it myself heh, I doubt anyone did, I just went overboard making it)

selector03

This is a function that I created to reverse the sample:

--reverse sample
local function reverse_sample()
  --renoise.song().instruments[].samples[].sample_buffer:sample_data     (channel_index, frame_index)
  --renoise.song().instruments[].samples[].sample_buffer:set_sample_data (channel_index, frame_index, sample_value)
  local x=os.clock()
  local sam=renoise.song().selected_sample  
  if (sam) then
    local buf=sam.sample_buffer
    if (buf.has_sample_data) then

      --define empty tables
      local TBL_L_SAM_VAL,REV_TBL_L_SAM_VAL={},{}
      local TBL_R_SAM_VAL,REV_TBL_R_SAM_VAL={},{}
    
      --load sample_value(s)
      for f=1,buf.number_of_frames do
        TBL_L_SAM_VAL[f]=buf:sample_data(1,f)
        TBL_R_SAM_VAL[f]=buf:sample_data(2,f)
      end
      
      --reverse tables
      for _,v in ripairs(TBL_L_SAM_VAL) do
        REV_TBL_L_SAM_VAL[#REV_TBL_L_SAM_VAL+1]=v
      end
      
      for _,v in ripairs(TBL_R_SAM_VAL) do
        REV_TBL_R_SAM_VAL[#REV_TBL_R_SAM_VAL+1]=v
      end

      --[[
      print("================")
      print(TBL_L_SAM_VAL[1])
      print(TBL_L_SAM_VAL[2])
      print("----------------")
      print(REV_TBL_L_SAM_VAL[1])
      print(REV_TBL_L_SAM_VAL[2])
      ]]
      
      --reverse wave
      buf:prepare_sample_data_changes()
      for f=1,buf.number_of_frames do
        buf:set_sample_data(1,f,REV_TBL_L_SAM_VAL[f])
        buf:set_sample_data(2,f,REV_TBL_R_SAM_VAL[f])
      end
      buf:finalize_sample_data_changes()
    end
  end
  print(string.format("reverse_sample: %.2f ms\n",(os.clock()- x)*1000))
end
reverse_sample()

A sample with the following characteristics:

  • 16.065 frames
  • 0.364 s
  • 44100hz, 16 bit, stereo

It takes about 17 ms to reverse, with an i7 processor (6700K). ~ 20 ms is too slow to use in real time. Automate it should require some foresight. Probably, this function could be further optimized. I have needed to iterate 4 times.

Any ideas to speed up this function?

Edit: Ah, I think it is possible to save the tables already inverted. This is much more direct:

--reverse sample
local function reverse_sample()
  local x=os.clock()
  local sam=renoise.song().selected_sample  
  if (sam) then
    local buf=sam.sample_buffer
    if (buf.has_sample_data) then
      --define empty tables
      local TBL_L_SAM_VAL={}
      local TBL_R_SAM_VAL={}
      --load sample_value(s)
      local i=1
      for f=buf.number_of_frames,1,-1 do
        TBL_L_SAM_VAL[i]=buf:sample_data(1,f)
        TBL_R_SAM_VAL[i]=buf:sample_data(2,f)
        i=i+1
      end
      --reverse wave
      buf:prepare_sample_data_changes()
      for f=1,buf.number_of_frames do
        buf:set_sample_data(1,f,TBL_L_SAM_VAL[f])
        buf:set_sample_data(2,f,TBL_R_SAM_VAL[f])
      end
      buf:finalize_sample_data_changes()
    end
  end
  print(string.format("reverse_sample: %.2f ms\n",(os.clock()- x)*1000))
end
reverse_sample()

With only 2 iterations. It is still very slow. It takes about 13 ms. This function, with this case, is writing and reading up to 16000 values in two tables.

Not sure, but you might save a bunch of lookups by localizing the sample_data() and set_sample_data() functions.

local set_sample_data = buf.set_sample_data
–loop
set_sample_data(buf, your_normal_arguments)

If it works, it probably doesn’t do much, but should help a tiny bit.

--reverse sample
local function reverse_sample()
  local x=os.clock()
  local sam=renoise.song().selected_sample  
  if (sam) then
    local buf=sam.sample_buffer
    if (buf.has_sample_data) then
      --define empty tables
      local TBL_L_SAM_VAL={}
      local TBL_R_SAM_VAL={}
      --load sample_value(s)
      local i=1
      local dat=buf.sample_data
      for f=buf.number_of_frames,1,-1 do
        TBL_L_SAM_VAL[i]=dat(buf,1,f)
        TBL_R_SAM_VAL[i]=dat(buf,2,f)
        i=i+1
      end
      --reverse wave
      local ssd=buf.set_sample_data
      buf:prepare_sample_data_changes()
      for f=1,buf.number_of_frames do
        ssd(buf,1,f,TBL_L_SAM_VAL[f])
        ssd(buf,2,f,TBL_R_SAM_VAL[f])
      end
      buf:finalize_sample_data_changes()
    end
  end
  print(string.format("reverse_sample: %.2f ms\n",(os.clock()- x)*1000))
end
reverse_sample()

7ms, remains high. Optimizing the 2 iterations. I think it is not possible to improve further. It would only be possible to accelerate it, if only it were an iteration. But it is not possible to the “reverse”. Anyway, it is a lot of information for such a short sample.

To do this, Renoise should have a method to reverse directly from memory (to have a buffer with the sample already reversed), and that does not seem reasonable.

By the way, good trick! Thank you!

You can further slim it down to just one loop. If all you want to do is reverse it. I won’t provide the exact statements you need, but in principle just save the frame that gets overwritten for later use.

If you need to visualize it, think of going from the edges and towards the center. You’ll end up with a buffer of half the total sample. I think.

But that’s just some lua optimization, and won’t minimize the heavy sample writing per se.

EDIT:
actually it’s just as simple as swapping [n] with [total_n - n] … so all you need to do is localize the value temporarily in the loop, to write it to the ‘other place’… lol. Not even a table buffer is needed.

Yes, it is only “reverse”, it is not necessary to keep the original data. Anyway, even if it was twice as fast, it is still very slow. There are many frames for very short samples. They are a lot of data. It would practically be instantaneous (you would not notice it), in short samples, but it would not serve to automate it.

A song of almost 6 minutes (16.525.242 frames) takes more than 3 seconds with my CPU, from Renoise. I’ve done the test with the DBlue song - Tension.

The conclusion is that the length of the samples (number of frames) should be too short for this to be feasible (5000 frames, 2000 frames, or less).

Probably because of this, these kinds of options do not exist (reverse the sample with automation capability).

I had thought something similar to this, I think. But I ended up deleting the sample, because the function didn’t end in the center. But that would be half the job, it’s a great optimization too. In theory it would last half. Maybe I try it.

Yeah you have to do some little ‘if’ to check if total number of samples is odd or even, and if there is a center sample that should be left as is. But it should be simple.

I am not sure about this. Actually, the number of operations will be similar, because you have to copy each value elsewhere.
As there is no option to do something like this within the loop:
a, b = b, a

--reverse sample
local function reverse_sample()
  local x=os.clock()
  local sam=renoise.song().selected_sample  
  if (sam) then
    local buf=sam.sample_buffer
    --reverse wave
    local dat=buf.sample_data
    local ssd=buf.set_sample_data
    local max=buf.number_of_frames+1
    local mid=math.floor((max-1)/2)
    buf:prepare_sample_data_changes()
    for f=1,mid do
      local s_l_a,s_r_a=dat(buf,1,f),dat(buf,2,f)
      local s_l_b,s_r_b=dat(buf,1,max-f),dat(buf,2,max-f)
      ssd(buf,1,f,s_l_b) ssd(buf,2,f,s_r_b)
      ssd(buf,1,max-f,s_l_a) ssd(buf,2,max-f,s_r_a)
    end
    buf:finalize_sample_data_changes()
  end
  print(string.format("reverse_sample: %.2f ms\n",(os.clock()- x)*1000))
end
reverse_sample()

7ms. This function, with a single iteration, takes the same as the previous one. It even seems to skillfully draw the central frame.

How to invert a table, using itself, without copying it elsewhere?

tbl={a,b,c}
return tbl={c,b,a}

using a, b = b, a ???

You mean table sort? I guess there is some descending-sorting functionality…? Or some lib doing that: https://www.lua.org/pil/19.3.html

table.sort(tbl, function(a, b) return a > b end)

The problem is that the value to be modified is the third one (sample_value):

renoise.song().instruments[].samples[].sample_buffer:set_sample_data(
  channel_index, frame_index, sample_value)

How do you invest that table without losing the opposite value, just with an operation? At the time of creating another table and deposit the values already double the number of operations.

@neinMC Where did you see that it is possible to access the cursor position of the sample from the API?

I have not seen it anywhere. But this seems fundamental to me to be able to build tools to generate waves, or rather, to modify existing waves.

The cursor position is not the same as the start of the sample selection. The cursor position always exists when there is a buffer. Sample selection may or may not exist.

Sorry, I mixed that up, my bad :confused:

Maybe i miss the whole point here, but what is wrong with effect command:

-Bxx - Play phrase backwards (xx = 00) or forwards (xx = 01)

You can even combine this with,

-Sxx - Trigger phrase from line xx

to play the sample backwards at a specific point.

Exactly what I wrote. It’s a small lua optimization that requires only one iteration, but the amount of sample access will be the same.

In effect, that’s the same as doing:

local temp_a = a
a = b
b = temp_a

Do it like that and you’ll optimize it a tad further. It might cut some millisecond off on large samples :wink: Iirc, you’re also cutting a bit by localizing temp_a outside the loop and re-using it all the time, but I can’t vouch for that.

(maybe this topic should be broken-out)