Tool Request: More Effects

im interested in a tool or plugin that gives the following effect:
reverse: so you can reverse the thing in the fx chain maybe an automate able button of whether or not the sample is reversed or not?

As far as I know, the API does not allow to ā€œreverseā€ the sample. It is not possible to do it with a tool. In general, any sample editing operation is not available from the API, unfortunately.

In fact, I tried to integrate this into a tool of my own and I couldnā€™t (reverse the sample and automation capacity).

Curious that you ask for this now. I myself have been investigating this a few days ago.

Sure you can edit sample data, but itā€™s probably not recommended trying to do it in realtime. At least not with bigger samples.

Then there is always the reverse pattern effect.

Surely the user would use something like reverse knowing that the sample does not have many frames. I think it would be very interesting to have this.

How would you do to reverse the two channels of the sample?

  1. Is it possible to copy the buffer to a clipboard, then reverse it and then copy it back over the original sample? Or reverse it when depositing it?
  2. Is it possible to chop the sample and then copy a piece?
  3. Would it be possible to add a silence of x frames at the end or at the beginning of the frame or where the cursor is?

I have never done any of this with a sample. So I would love to investigate it. To my knowledge, with sample_mapping, we have access to the properties of the sample, but not to the manipulation of the buffer (audio wave edition).

Yes, if I remember correctly you should optimize things by copying the sample data to a table if you want to affect many frames. Otherwise it tends to be a bit slow. Lack of speed is the main issue with renoise.Samplebuffer in my experience. Especially if you need to change the size of the sample.

There are quite a bit of old tools dealing with samples. Synths/generators and such.

class: SampleBuffer
 properties:
    bit_depth
    display_length
    display_length_observable
    display_range
    display_range_observable
    display_start
    display_start_observable
    has_sample_data
    number_of_channels
    number_of_frames
    read_only
    sample_rate
    selected_channel
    selected_channel_observable
    selection_end
    selection_end_observable
    selection_range
    selection_range_observable
    selection_start
    selection_start_observable
    vertical_zoom_factor
    vertical_zoom_factor_observable
 methods:
    __STRICT
    create_sample_data
    delete_sample_data
    finalize_sample_data_changes
    load_from
    prepare_sample_data_changes
    sample_data
    save_as
    set_display_range
    set_sample_data

I will look at the methods, see what can be done with all this. Thanks!

  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 ???