Old 02-12-2017, 11:32 AM   #1
mpl
Human being with feelings
 
mpl's Avatar
 
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,960
Default JS basic sampler - any work examples?

Hi guys, for new StepSequencer/DrumRack script I searching solution to manipulate sampler sample paths from script. All samplers I`ve seen hold this info in their binary chunks (RS5K also). So maybe it is possible to code very basic JS, which will run sample by MIDI trigger + ADSR? Any code examples much appreciated.
Main reason for test it is what will be if I encode sample path base64 chunk for this JS manually. RS5K understand it when I apply TrackStateChunk first time, but it crashed after second time focusing in FX Chain (example below). I pinged Justin for dedicated RS5K API functions for manipulating this, but if it is possible to not use RS5K, I`ll be very thankful to you if you can help.

Thanks.

Here is RS5K example
Code:
-- @description Move item active audio take to RS5K
-- @version 1.0alpha
-- @author MPL
-- @website http://forum.cockos.com/member.php?u=70694
-- @changelog
--    + init

local pitch_base = 60


-------------------------------------------------------------------
function m(s) reaper.ShowConsoleMsg(s) end
-------------------------------------------------------------------
function enc(data)
  -- character table string
  local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    return ((data:gsub('.', function(x) 
        local r,b='',x:byte()
        for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
        return r;
    end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
        if (#x < 6) then return '' end
        local c=0
        for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
        return b:sub(c+1,c+1)
    end)..({ '', '==', '=' })[#data%3+1])
end
---------------------------------------------------------------------      
  function InsertRS5K(track, samplepath)
    local insert_id
    local gen_guid = reaper.genGuid('' )
    local sample_path_base64 = enc(samplepath)
    local default_data =  -- file is C:\\1234567890
[[    BYPASS 0 0 0
      <VST "VSTi: ReaSamplOmatic5000 (Cockos)" reasamplomatic.dll 0 "RS5K" 1920167789
        bW9zcu5e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAAAqAQAAAQAAAAAAEIA=
        ]]..sample_path_base64..[[AAAAAAAAAPA/AAAAAAAA4D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D+amZmZmZmxP83M
        zMzMzOs/AAAAAAAAAACamZmZmZnJP/yp8dJNYkA//Knx0k1iQD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAOA/AQAAAAAAAAAAAAAAAAAAAAAA
        8D9AAAAAVVVVVVVVxT//////AAAAAAAAAAAAAAAAAADwPwAAAAAAAPA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOpCEhGmWQPwAAAAAAAPA//Knx0k1i
        MD8AAAAAAAAAAA==
        AAAQAAAA
      >
      FLOATPOS 0 0 0 0
      FXID ]]..gen_guid..'\n'..[[
      WAK 0]]
    local def_chain = 
[[<FXCHAIN
WNDRECT 24 52 752 466
SHOW 0
LASTSEL 0
DOCKED 0 
]]..default_data..'>'
    local _, chunk = reaper.GetTrackStateChunk( track, '' )    
    local t = {}
    for line in chunk:gmatch('[^\n\r]+') do
      t[#t+1] = line
      if line:match('DOCKED') then insert_id = #t end
      if line:match('MAINSEND ') then insert_id2 = #t end
    end    
    if insert_id then
      table.insert(t,insert_id+1,default_data )
     else
      table.insert(t,insert_id2+1,def_chain )
    end
    local out_str = table.concat(t, '\n')
    --m(out_str)
    reaper.SetTrackStateChunk( track, out_str )
  end
--------------------------------------------------------------------- 
  
  function main()
    local item =  reaper.GetSelectedMediaItem( 0, 0 )
    if not item then return end
    local track4insert =  reaper.GetMediaItem_Track( item )
    t = {}
    for i = 1,  reaper.CountSelectedMediaItems( 0 ) do
      local sel_item = reaper.GetSelectedMediaItem( 0, i-1 )
      local act_take =  reaper.GetActiveTake( sel_item )
      if not reaper.TakeIsMIDI(act_take)  then  
        local guid = reaper.BR_GetMediaItemGUID( sel_item )
        local src = reaper.GetMediaItemTake_Source( act_take )
        local src_file = reaper.GetMediaSourceFileName( src, '' )           
        t[#t+1] = {guid  =guid,
                  src_file  =src_file}
      end
    end
    if #t < 1 then return end
    for i = #t,1,-1 do
      local item = reaper.BR_GetMediaItemByGUID( 0, t[i].guid )
      local item_tr = reaper.GetMediaItem_Track( item )
      reaper.DeleteTrackMediaItem( item_tr, item )
      InsertRS5K(track4insert, t[i].src_file)
    end
    reaper.UpdateArrange()
  end
  
  main()

Last edited by mpl; 02-12-2017 at 12:09 PM.
mpl is offline   Reply With Quote
Old 02-12-2017, 03:40 PM   #2
Arthur McArthur
Human being with feelings
 
Arthur McArthur's Avatar
 
Join Date: Sep 2016
Location: Toronto
Posts: 744
Default

Big +1 for RS5K API and Media Explorer API. I'm always excited to see someone working on programming for Reaper as I think it is one of the most undeveloped parts.

I can't help with the coding but on the same topic, here's an idea for easy audio step sequencing (I just refined thanks to me2beats' select next hidden track script.)

http://imgur.com/a/XTrn7

The samples are stored in a hidden, indented track:

Custom: Drum mode prepare
Script: X-Raym_Select only tracks of selected items.lua
Xenakios/SWS: Select first items of selected tracks
Script: X-Raym_Select only tracks of selected items.lua
Track: Insert new track
Item edit: Move items/envelope points down one track/a bit
Item properties: Toggle mute
Xenakios/SWS: Select previous tracks, keeping current selection
SWS: Make folder from selected tracks
SWS: Set selected folder(s) collapsed
SWS: Select only children of selected folders
Script: kawa_MAIN_SelectedTrackHideTCP_Type2.lua

Cycle action mouse modifier:

http://imgur.com/a/Jj8OI

I still need to make an action to open the hidden track back up so the samples can be edited if needed.
Arthur McArthur is offline   Reply With Quote
Old 02-12-2017, 06:03 PM   #3
eugen2777
Human being with feelings
 
eugen2777's Avatar
 
Join Date: Aug 2012
Posts: 271
Default

Sorry, I'm writing it in Russian
Миша, провозился несколько часов, интересная штука. Давно в lua ничего не делал, но вопрос с RS5000, вроде, решен.
Короче, так запросто подменять имя, конечно же, не получается(хотя можно поморочиться)
Причина - Чанк непрерывный. Раскодируй или весь чанк(то,что base64, естественно), или основную часть, середину(я брал ее).
Функция для декодинга та же с lua org.
Приклей имя(или замени, если уже было), и закодируй обратно. Работает, проверено. Для красоты разбей на строки, как положено, но работает и так.




===================================
It is a working example:
Code:
-- @description Move item active audio take to RS5K
-- @version 1.0alpha
-- @author MPL
-- @website http://forum.cockos.com/member.php?u=70694
-- @changelog
--    + init

local pitch_base = 60


-------------------------------------------------------------------
function m(s) reaper.ShowConsoleMsg(s) end
-------------------------------------------------------------------
-- character table string
local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

-- encoding
function enc(data)
    return ((data:gsub('.', function(x) 
        local r,b='',x:byte()
        for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
        return r;
    end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
        if (#x < 6) then return '' end
        local c=0
        for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
        return b:sub(c+1,c+1)
    end)..({ '', '==', '=' })[#data%3+1])
end

-- decoding
function dec(data)
    data = string.gsub(data, '[^'..b..'=]', '')
    return (data:gsub('.', function(x)
        if (x == '=') then return '' end
        local r,f='',(b:find(x)-1)
        for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
        return r;
    end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
        if (#x ~= 8) then return '' end
        local c=0
        for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
        return string.char(c)
    end))
end

---------------------------------------------------------------------      
  function InsertRS5K(track, samplepath)
    local insert_id
    local gen_guid = reaper.genGuid('' )
    ------------------------------------   
    local default_data =  -- file is C:\\1234567890
[[
  <VST "VSTi: ReaSamplOmatic5000 (Cockos)" reasamplomatic.dll 0 "RS5K" 1920167789
    bW9zcu5e7f4AAAAAAgAAAAEAAAAAAAAAAgAAAAAAAAD9AAAAAQAAAAAAEAA=
    ]].. enc(samplepath .. dec([[AAAAAAAAAPA/AAAAAAAA4D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D+amZmZmZmxP83MzMzMzOs/AAAAAAAAAACamZmZmZnJP/yp8dJNYkA//Knx0k1iQD8AAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwPwAAAAAAAOA/AQAAAAAAAAAAAAAAAAAAAAAA8D9AAAAAVVVVVVVVxT//////AAAAAAAAAAAAAAAAAADwPwAAAAAAAPA/AAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOpCEhGmWQPwAAAAAAAPA//Knx0k1iMD8AAAAAAAAAAA==]])) ..
    [[AAAQAAAA
  >
  FLOATPOS 0 0 0 0
  FXID ]] .. gen_guid .. '\n' .. [[
  WAK 0]]
    
    ------------------------------------
    local def_chain = 
[[<FXCHAIN
    WNDRECT 341 57 922 742
    SHOW 2
    LASTSEL 1
    DOCKED 0
    BYPASS 0 0 0
]] .. default_data .. '>'
    
    ------------------------------------
    local _, chunk = reaper.GetTrackStateChunk( track, '' )    
    local t = {}
    for line in chunk:gmatch('[^\n\r]+') do
      t[#t+1] = line
      if line:match('DOCKED') then insert_id = #t end
      if line:match('MAINSEND ') then insert_id2 = #t end
    end    
    if insert_id then
      table.insert(t,insert_id+1,default_data )
     else
      table.insert(t,insert_id2+1,def_chain )
    end
    local out_str = table.concat(t, '\n')
    --m(out_str)
    reaper.SetTrackStateChunk( track, out_str )
  end
--------------------------------------------------------------------- 
  
  function main()
    local item =  reaper.GetSelectedMediaItem( 0, 0 )
    if not item then return end
    local track4insert =  reaper.GetMediaItem_Track( item )
    t = {}
    for i = 1,  reaper.CountSelectedMediaItems( 0 ) do
      local sel_item = reaper.GetSelectedMediaItem( 0, i-1 )
      local act_take =  reaper.GetActiveTake( sel_item )
      if not reaper.TakeIsMIDI(act_take)  then  
        local guid = reaper.BR_GetMediaItemGUID( sel_item )
        local src = reaper.GetMediaItemTake_Source( act_take )
        local src_file = reaper.GetMediaSourceFileName( src, '' )           
        t[#t+1] = {guid  =guid,
                  src_file  =src_file}
      end
    end
    if #t < 1 then return end
    for i = #t,1,-1 do
      local item = reaper.BR_GetMediaItemByGUID( 0, t[i].guid )
      local item_tr = reaper.GetMediaItem_Track( item )
      reaper.DeleteTrackMediaItem( item_tr, item )
      InsertRS5K(track4insert, t[i].src_file)
    end
    reaper.UpdateArrange()
  end
  
  main()
__________________
ReaScripts

Last edited by eugen2777; 02-12-2017 at 07:11 PM.
eugen2777 is offline   Reply With Quote
Old 02-12-2017, 08:17 PM   #4
mpl
Human being with feelings
 
mpl's Avatar
 
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,960
Default

Question is more about JS sampler implementation.
Since Justin said today related API coming soon so no need to chunk manipulation already (which are anyway painful and CPU hungry).

Last edited by mpl; 02-12-2017 at 09:43 PM.
mpl is offline   Reply With Quote
Old 02-13-2017, 04:44 AM   #5
eugen2777
Human being with feelings
 
eugen2777's Avatar
 
Join Date: Aug 2012
Posts: 271
Default

As far as I know, JSFX can take audio file only from the program directory - REAPER\Data etc.
As an example of the use of samples - there are DrumReaplacer and some jsfx use impulses.
__________________
ReaScripts
eugen2777 is offline   Reply With Quote
Old 02-13-2017, 08:00 AM   #6
akademie
Human being with feelings
 
Join Date: Mar 2007
Posts: 3,978
Default

Quote:
Originally Posted by eugen2777 View Post
As far as I know, JSFX can take audio file only from the program directory - REAPER\Data etc.
As an example of the use of samples - there are DrumReaplacer and some jsfx use impulses.
Hi,

! BUT !: If you place JSFX Effect to project folder \Effects\file.js
then you can place the data for it the same way , like \Data\somefolder\*.wav etc...

Then the reaper will use the JS Effect from project directory instead of regular (install) dir and also its "data". It works for midi_drumseq and I believe that it works generally (e.g. ampsim "amp-model" with IRs etc..)

So maybe there is some way then? ;-)

akademie
akademie is offline   Reply With Quote
Old 02-16-2017, 06:28 PM   #7
geraintluff
Human being with feelings
 
geraintluff's Avatar
 
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
Default

I don't know if this is relevant or helpful, but I just wrote a basic JSFX sampler that takes incoming audio and stores it for playback on a MIDI trigger. It currently has a fixed zero-length attack and 10ms release (or when the sample runs out, whichever is sooner), but that's simple enough to change.

The code is on GitHub, and it's hopefully fairly readable. To actually run it, you'll need both "ui-lib.jsfx-inc" for the UI and "synth-framework.jsfx-inc" for the polyphonic synth logic (or just install the sampler from ReaPack).

I wrote it because I sometimes found myself hand-arranging drum loops from samples, which ended up with my project being littered with individual hi-hat items. Instead, I wanted to assemble the samples once at the beginning of my project, have a sampler on each track to "learn" all the samples (e.g. one sampler for all the different snare sounds, one for bass drums, etc.), and then play them back later.

The recorded sounds are read/written in @serialise, but it currently doesn't handle sample-rate changes correctly (it will play the samples back at the wrong speed). This isn't a problem for my use-case, because my projects will have the drum loop at the beginning to re-learn. If this becomes an issue, it can be solved by interpolating on playback.

Last edited by geraintluff; 02-17-2017 at 03:57 AM.
geraintluff is offline   Reply With Quote
Old 01-04-2020, 08:10 AM   #8
DarrenH
Human being with feelings
 
Join Date: Mar 2014
Posts: 347
Default

Hi Geraint, not sure if you'll see this but was trying to use your sampler...just wanted to ask how to get it working? It's on my track, sample at the beginning but not able to get it to work. Thanks for any time you may have D


Quote:
Originally Posted by geraintluff View Post
I don't know if this is relevant or helpful, but I just wrote a basic JSFX sampler that takes incoming audio and stores it for playback on a MIDI trigger. It currently has a fixed zero-length attack and 10ms release (or when the sample runs out, whichever is sooner), but that's simple enough to change.

The code is on GitHub, and it's hopefully fairly readable. To actually run it, you'll need both "ui-lib.jsfx-inc" for the UI and "synth-framework.jsfx-inc" for the polyphonic synth logic (or just install the sampler from ReaPack).

I wrote it because I sometimes found myself hand-arranging drum loops from samples, which ended up with my project being littered with individual hi-hat items. Instead, I wanted to assemble the samples once at the beginning of my project, have a sampler on each track to "learn" all the samples (e.g. one sampler for all the different snare sounds, one for bass drums, etc.), and then play them back later.

The recorded sounds are read/written in @serialise, but it currently doesn't handle sample-rate changes correctly (it will play the samples back at the wrong speed). This isn't a problem for my use-case, because my projects will have the drum loop at the beginning to re-learn. If this becomes an issue, it can be solved by interpolating on playback.
DarrenH is offline   Reply With Quote
Old 01-06-2020, 04:30 PM   #9
Time Waster
Human being with feelings
 
Time Waster's Avatar
 
Join Date: Aug 2013
Location: Bowral, Australia
Posts: 1,638
Default

I wrote the JSFX 'Time Machine' sampler in the stash. It also stores audio in memory for playback, triggered by MIDI. I Initially wanted to play audio files directly, but as far as I can tell from the documentation there is no way to do this using JSFX. The Time Machine sampler does have the option of a release envelope, for choking. Later versions may eventually get a full ADSR envelope, but that has not been a priority as I wanted it mainly for drum samples.
__________________
Mal, aka The Wasters of Time
Mal's JSFX: ReaRack2 Modular Synth
Time Waster is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 04:53 AM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.