"Render Selection" "Sample Record Pattern Mode" sizes do not match

on macOS, during 20ms latency with no sync offset, and with these settings:
Näyttökuva 2025-01-16 kello 8.21.21

If I have a 137 bpm LPB 4 speed 64 row pattern, and i select the whole track (0-64) and render, i get 336000 sample frames. if i use sample recorder with pattern mode, and record the pattern, the result is 336960.

now, i can compensate for this by resizing the sample to 336000. (i need the sample to be full pattern length, but no more, no extra padding).

now, my question is this, if i have a 128 row pattern or 256 row pattern, the numbers also seem to fluctuate, which is terribly tiresome. I need the sample to end at the last “tick” of the last row, so it will get retriggered. but is there a known thing that i can compensate with, or will this fluctuate depending on latency ms, maybe different platforms (macOS Windows Linux), etc?

I’ve tried to make it so that if the framesize is larger or smaller than 336000-336960, it removes 3500 frames from the end of the sample. but this doesn’t help with a “recorded 2 pattern lengths of 64 row each”.

i’m wondering, is this a known issue within Renoise that the sample frames recorded by sample editor don’t match the sample frames recorded by “selection in pattern from beginning to end of pattern”? is there an easy math that i can apply for this? i need the sample to stop at the end of the pattern, and i’m alright with trying to compensate for it with a script, but i’d prefer not to have to do that.

is this a known bug? surely the recordings should be of identical length. i can create some examples of this fluctuation with 16, 32, 64, 128, 256, 512, this evening (render selection in pattern, record with pattern mode) and repost in this thread.

Yes, the rendered and recorded sample size should match exactly whatever the latency is. Will check and fix that…

2 Likes

another thing that seems strange is, during the process of creating a sample and finishing a sample, the number of frames is 2. whereas it should be 333690
how do i force the sample’s number_of_frames to update during the process of creating a sample? edit to clarify: the sample has definitely been saved and exists, i.e. the samplebuffer is complete. but the number is still 2 for no apparent reason.

during the process of creating a sample and finishing a sample, the number of frames is 2. whereas it should be 333690

This occurs when you have a sample loaded and want to record over it. Right after stopping the recording, the API will report the previous number_of_frames, one app idle frame later, the length will be reported correctly.

Below is a snippet to test and reproduce, it adds an entry to the Sample context menu to run the test.

function test() 
	local s = renoise.song()
	local t = renoise.tool()

	local step = 0

	function update() 
	  print("step " .. step)
	  if step == 50 then
	    s.transport:start_stop_sample_recording()
	    print("stopped recording")
	  elseif step == 51 then
	    t.app_idle_observable:remove_notifier(update)
	  end
	  
	  print("number_of_frames " .. s.selected_sample.sample_buffer.number_of_frames)

	  step = step + 1
	end

	s.selected_instrument:insert_sample_at(1)
	s.selected_sample.sample_buffer:create_sample_data(8000, 32, 2, 2)

	renoise.app().window.sample_record_dialog_is_visible = true
	s.transport:start_stop_sample_recording()
	print("started recording")

	t.app_idle_observable:add_notifier(update)
end

renoise.tool():add_menu_entry({
  name = "Sample Editor:test record length bug",
  invoke = test
})

is it possible that this “one app idle frame” does not actually happen during the script? i mean, i even tried this

add keybinding
run script
print number of frames

and i’d get number 2. so i need to do a delay of 1, i guess? i can try it.

Yes, starting/stopping happens asynchronously in the player. The GUI and document get updated later when everything finished. You’ll need to attach to the renoise.Sample.sample_buffer_observable to track those changes. Don’t use a timer here.

1 Like

err alright, this is going great:

    ----------------------------------------------------------------------------
    -- Trim sample
    ----------------------------------------------------------------------------
    local sample = s.selected_sample
    
    local function handle_buffer_change()
      local frames = sample.sample_buffer.number_of_frames
      print("Buffer changed - new frame count: " .. frames)
      
      if frames > 0 then
        sample.sample_buffer:prepare_sample_data_changes()
        
        local new_length = frames

        if frames == 336960 then
          new_length = 336000
        else

          new_length = frames - 3500
        end
        
        if new_length > 0 then
          local sample_buffer = sample.sample_buffer
          local sample_rate = sample_buffer.sample_rate
          local bit_depth = sample_buffer.bit_depth
          local num_channels = sample_buffer.number_of_channels
          
          local temp_data = {}
          for channel = 1, num_channels do
            temp_data[channel] = {}
            for frame = 1, new_length do
              temp_data[channel][frame] = sample_buffer:sample_data(channel, frame)
            end
          end
          
          sample_buffer:delete_sample_data()
          local success = sample_buffer:create_sample_data(
            sample_rate,
            bit_depth,
            num_channels,
            new_length)
            
          if success and sample_buffer.has_sample_data then
            for channel = 1, num_channels do
              for frame = 1, new_length do
                sample_buffer:set_sample_data(channel, frame, temp_data[channel][frame])
              end
            end
          end
        end
        
        sample.sample_buffer:finalize_sample_data_changes()
        
        if sample.sample_buffer_observable:has_notifier(handle_buffer_change) then
          sample.sample_buffer_observable:remove_notifier(handle_buffer_change)
        end
      end
    end
    
    sample.sample_buffer_observable:add_notifier(handle_buffer_change)
    
    sample.autofade = true
    local amf = renoise.app().window.active_middle_frame
    renoise.app().window.active_middle_frame = amf

hmm.

The issue here might be that both delete_sample_data and create_sample_data fires the notifier before it has a chance to delete itself, it leads to a loop where Renoise hangs.

On a side-note, don’t call prepare_sample_data_changes around deleting and creating new sample data, only do prepare_ and finalize_ around the loop that uses set_sample_data.

1 Like

thanks, i’ve set it now to be around the set_sample_data loop. i’ll also look at the rest of your suggestions, thanks! your “this causes the endless loop” makes a lot of sense :+1: thanks much