Old 10-02-2016, 10:56 AM   #1
Mr. PC
Human being with feelings
 
Mr. PC's Avatar
 
Join Date: Apr 2010
Location: Cloud 37
Posts: 1,071
Default Humanize CC

So sometimes when I draw in CC data, I end up with some straight lines, and I'd rather they were a bit bumpy.

Could we add a CC humanize feature, so that my cc11 time selection could be 10% randomized, the same way velocity is?
__________________
AlbertMcKay.com
SoundCloud BandCamp
ReaNote Hotkeys to make Reaper notation easy/fast
Mr. PC is offline   Reply With Quote
Old 10-02-2016, 11:39 AM   #2
jmorel33
Human being with feelings
 
Join Date: Jun 2015
Posts: 217
Default

I'd add CC humanize, and by note change also, not on every CC.
jmorel33 is offline   Reply With Quote
Old 10-09-2016, 04:20 AM   #3
juliansader
Human being with feelings
 
Join Date: Jul 2009
Posts: 3,714
Default

It would indeed be useful - and more intuitive - if CC humanization could be incorporated into the current "Humanize" function.

I find it curious that many of REAPER's niftiest functions are limited to notes, and exclude CCs. Several of my own FRs have also asked for CC equivalents of note functions.

Since this FR is unlikely to be implemented any time soon, here is a script that may help. Each time that the script is run, it randomizes the CC values a little:

Code:
--[[
ReaScript name: js_Humanize the values of selected CC events.lua
Version: 1.00
Author: juliansader
About:
  # Description
  Quick script to humanize (slightly randomize) the values of selected CC events.
]]

-- USER AREA
-- Settings that the user can cuztomize

randomType = "abs" -- "%", "percentage", "abs" or "absolute"
randomMaxChange = 1 -- Maximum change, as percentage or absolute value (integer)

-- End of USER AREA
-------------------

-----------------------------------
function newRandomValue(value, max)
    if randomType == "%" or randomType:lower() == "percentage" then
        value = value * (math.random(100-randomMaxChange, 100+randomMaxChange))/100
    else
        value = value + math.random(-randomMaxChange, randomMaxChange)
    end
    value = math.max(0, math.min(max, math.floor(value+0.5))) -- Ensure that value is within limits, and integer

    return value
end

--------------------------------------
-- function main()
editor = reaper.MIDIEditor_GetActive()
if editor == nil then return end
take = reaper.MIDIEditor_GetTake(editor)
if not reaper.ValidatePtr2(0, take, "MediaItem_Take*") then return end

reaper.Undo_BeginBlock()

countCCs = 0
i = -1
repeat
    i = reaper.MIDI_EnumSelCC(take, i)
    if i ~= -1 then
        ccOK, _, _, _, chanmsg, channel, msg2, msg3 = reaper.MIDI_GetCC(take, i)
        if ccOK then
            countCCs = countCCs + 1
            local eventType = chanmsg>>4

            -- Different CC types use different bytes to carry their values, so each must be handled separately
            if eventType == 11 then -- 7-bit CC
                reaper.MIDI_SetCC(take, i, nil, nil, nil, nil, nil, nil, newRandomValue(msg3, 127), true)
            elseif eventType == 13 then -- Channel pressure
                reaper.MIDI_SetCC(take, i, nil, nil, nil, nil, nil, newRandomValue(msg2, 127), nil, true)
            elseif eventType == 14 then -- Pitch
                reaper.MIDI_SetCC(take, i, nil, nil, nil, nil, nil,  newRandomValue(msg2, 127), newRandomValue(msg3, 127), true)
            end
        end
    end
until i == -1

reaper.Undo_EndBlock2(0, "Humanized the values of " .. tostring(countCCs) .. " CC events", -1)
juliansader is offline   Reply With Quote
Old 10-12-2016, 05:37 AM   #4
Mr. PC
Human being with feelings
 
Mr. PC's Avatar
 
Join Date: Apr 2010
Location: Cloud 37
Posts: 1,071
Default

Oh wow, you've just introduced me to the whole new world of ReaScripts. Now I'm getting deep down the rabbit hole..


So... I copied this code into notepad, saved it as a .eel, load it into my action list, but when I try to run the action, I get an error.

Is this the wrong way to do it; I was following online ReaScript tutorials.
__________________
AlbertMcKay.com
SoundCloud BandCamp
ReaNote Hotkeys to make Reaper notation easy/fast

Last edited by Mr. PC; 10-12-2016 at 08:13 AM.
Mr. PC is offline   Reply With Quote
Old 10-12-2016, 11:06 AM   #5
juliansader
Human being with feelings
 
Join Date: Jul 2009
Posts: 3,714
Default

Quote:
Originally Posted by Mr. PC View Post
So... I copied this code into notepad, saved it as a .eel, load it into my action list, but when I try to run the action, I get an error.
It is actually a Lua script, not EEL, so if you save it as a .lua file, it should hopefully work OK.
juliansader is offline   Reply With Quote
Old 06-23-2019, 03:47 AM   #6
juliansader
Human being with feelings
 
Join Date: Jul 2009
Posts: 3,714
Default

A request from another thread:

Quote:
Originally Posted by Dafarkias View Post
Quick question about this https://forum.cockos.com/showthread.php?t=182146 script, it doesn't really work well with large amounts of CC data. What I want to do is use your "js_Insert linear or shaped ramps between selected CCs or pitches in lane under mouse.lua" script to basically virtualize half pedaling for recorded midi piano "64 Hold Pedal" data. It works great. But after that I want to run the (thousands of) CC's through a slight bit of randomization to complete the effect, but it kinda chokes and crashes most of the time.

Would you be interested, by chance, in compiling a CC randomization script using your blazing-fast chunk techniques and superior programming?
Here is an updated version of the humanize script that uses the new, fast MIDI_Get/SetAllEvts functions:
Code:
--[[
ReaScript name: js_Humanize the values of selected CC events.lua
Version: 2.00
Author: juliansader
Changelog:
  - Much faster execution when many thousands of events are selected.
About:
  # Description
  Simple script to humanize (slightly randomize) the values of selected CC events or velocities.
]]

-- USER AREA
-- Settings that the user can customize

randomType = "abs" -- "%", "percentage", "abs" or "absolute"
randomMaxChange = 1 -- Maximum change, as percentage or absolute value (integer)

-- End of USER AREA
-------------------


local isRandomPercentage
-----------------------------------
function newRandomValue(value, min, max)
    if isRandomPercentage then
        value = value * (math.random(100-randomMaxChange, 100+randomMaxChange))/100
    else
        value = value + math.random(-randomMaxChange, randomMaxChange)
    end
    if value > max then value = max
    elseif value < min then value = min
    else value = (value+0.5)//1 -- Ensure that value is within limits, and integer
    end
    
    return value
end

--------------------------------------
-- function main()
isRandomPercentage = (randomType == "%" or randomType:lower():match("per"))

editor = reaper.MIDIEditor_GetActive()
if editor == nil then reaper.MB("Could not find any active MIDI editor.", "ERROR", 0) return end
take = reaper.MIDIEditor_GetTake(editor)
if not reaper.ValidatePtr2(0, take, "MediaItem_Take*") then reaper.MB("Active take is invalid.", "ERROR", 0) return end
item = reaper.GetMediaItemTake_Item(take)
if not reaper.ValidatePtr2(0, item, "MediaItem*") then reaper.MB("Could not determine valid item.", "ERROR", 0) return end

--reaper.Undo_BeginBlock()

local MIDIOK, MIDI = reaper.MIDI_GetAllEvts(take, "")
if not MIDIOK then reaper.MB("Could not load MIDI string.", "ERROR", 0) return end
local pos, savePos, countEvts = 1, 1, 0 -- pos is position in MIDI while parsing, savePos is next position from which substrings must be stored in tMIDI
local tMIDI = {} -- MIDI substrings will be stored in tMIDI, to be concatenated again later

while pos < #MIDI do
    offset, flags, msg, pos = string.unpack("i4Bs4", MIDI, pos)
    if flags&1 == 1 then
        local eventType = (msg:byte(1))>>4
        -- Different CC types use different bytes to carry their values, so each must be handled separately
        if eventType == 11 then -- 7-bit CC
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg+1) .. string.char(newRandomValue(msg:byte(3), 0, 127)) .. msg:sub(4,nil)
            savePos = pos
            countEvts = countEvts + 1
        elseif eventType == 13 then -- Channel pressure
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg) .. string.char(newRandomValue(msg:byte(2), 0, 127)) .. msg:sub(3,nil)
            savePos = pos
            countEvts = countEvts + 1
        elseif eventType == 14 then -- Pitch
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg) .. string.char(newRandomValue(msg:byte(2), 0, 127)) 
                                                          .. string.char(newRandomValue(msg:byte(3), 0, 127))
                                                          .. msg:sub(4,nil)
            savePos = pos
            countEvts = countEvts + 1
        elseif eventType == 9 and msg:byte(3) ~= 0 then -- Note-on velocity
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg+1) .. string.char(newRandomValue(msg:byte(3), 1, 127)) .. msg:sub(4,nil)
            savePos = pos
            countEvts = countEvts + 1
        end
    end
end
tMIDI[#tMIDI+1] = MIDI:sub(savePos, nil)

reaper.MIDI_SetAllEvts(take, table.concat(tMIDI))

--reaper.Undo_EndBlock2(0, "Randomized the values of " .. tostring(countEvts) .. " events", -1)
reaper.Undo_OnStateChange_Item(0, "Randomized the values of " .. tostring(countEvts) .. " events", item)
juliansader is offline   Reply With Quote
Old 06-29-2019, 03:24 AM   #7
Dafarkias
Human being with feelings
 
Dafarkias's Avatar
 
Join Date: Feb 2019
Location: Southern Vermont
Posts: 864
Default

You are the bees knees!

Thank you!

[Postscript] There are a lot of awesome programmers in this forum I probably owe more than gratitude for their work, but at my earliest convenience I will attempt to PayPal you a few beers. It's the least I can do.

Last edited by Dafarkias; 06-29-2019 at 10:18 AM.
Dafarkias is offline   Reply With Quote
Old 07-02-2019, 11:26 AM   #8
Dafarkias
Human being with feelings
 
Dafarkias's Avatar
 
Join Date: Feb 2019
Location: Southern Vermont
Posts: 864
Default

Quote:
Originally Posted by juliansader View Post
A request from another thread:
Here is an updated version of the humanize script that uses the new, fast MIDI_Get/SetAllEvts functions:
Quick question if you don't mind...

I'm trying to slightly tweak this script so that it will randomize midi CC over time within a certain allotted range, but I'm getting an error message:
Randomize Value of Selected CC (by Time).lua:88: bad argument #1 to 'char' (value out of range)

Here's the code:

Code:
--[[
ReaScript name: js_Humanize the values of selected CC events.lua
Version: 2.00
Author: juliansader
Changelog:
  - Much faster execution when many thousands of events are selected.
About:
  # Description
  Simple script to humanize (slightly randomize) the values of selected CC events or velocities.
]]

local isRandomPercentage
local randomType = "absolute"
local percent = 0
local range = 0
local number = 0


::incorrect::
local _, dummy = reaper.GetUserInputs( "Randomize Chance:", 1, "", "1-100%" ) 
if dummy ~= "1-100%" then dummy = tonumber(dummy)
  if type(dummy) ~= 'number' then reaper.MB( "Ensure that input is 1-100%", "[error]", 0 ) goto incorrect end
  if dummy < 1 or dummy > 100 then reaper.MB( "Ensure that input is 1-100%", "[error]", 0 ) goto incorrect end
  percent = dummy
else reaper.MB( "Script canceled.", "[message]", 0 ) goto exit end

::incorrect2::
_, dummy = reaper.GetUserInputs( "Randomize Range:", 1, "", "1-126" )
if dummy ~= "1-126" then dummy = tonumber(dummy)
  if type(dummy) ~= 'number' then reaper.MB( "Ensure that input 1-126", "[error]", 0 ) goto incorrect2 end
  if dummy < 1 or dummy > 126 then reaper.MB( "Ensure that input 1-126", "[error]", 0 ) goto incorrect2 end
  range = dummy
else reaper.MB( "Script canceled.", "[message]", 0 ) goto exit end

-----------------------------------
function newRandomValue(value, min, max)
    if math.random(0, 100) <= percent
  then
      if math.random(0, 100) > 50
    then
        if number < (range/2) and (value+number+1) < max
      then
        number = number + 1
      end
    else
      if number > (0-(range/2)) and (value+number-1) > min
        then
          number = number - 1
        end
    end
  end
  
  return (value+number)
  
end

--------------------------------------
-- function main()
isRandomPercentage = (randomType == "%" or randomType:lower():match("per"))

editor = reaper.MIDIEditor_GetActive()
if editor == nil then reaper.MB("Could not find any active MIDI editor.", "ERROR", 0) return end
take = reaper.MIDIEditor_GetTake(editor)
if not reaper.ValidatePtr2(0, take, "MediaItem_Take*") then reaper.MB("Active take is invalid.", "ERROR", 0) return end
item = reaper.GetMediaItemTake_Item(take)
if not reaper.ValidatePtr2(0, item, "MediaItem*") then reaper.MB("Could not determine valid item.", "ERROR", 0) return end

--reaper.Undo_BeginBlock()

local MIDIOK, MIDI = reaper.MIDI_GetAllEvts(take, "")
if not MIDIOK then reaper.MB("Could not load MIDI string.", "ERROR", 0) return end
local pos, savePos, countEvts = 1, 1, 0 -- pos is position in MIDI while parsing, savePos is next position from which substrings must be stored in tMIDI
local tMIDI = {} -- MIDI substrings will be stored in tMIDI, to be concatenated again later

while pos < #MIDI do
    offset, flags, msg, pos = string.unpack("i4Bs4", MIDI, pos)
    if flags&1 == 1 then
        local eventType = (msg:byte(1))>>4
        -- Different CC types use different bytes to carry their values, so each must be handled separately
        if eventType == 11 then -- 7-bit CC
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg+1) .. string.char(newRandomValue(msg:byte(3), 0, 127)) .. msg:sub(4,nil)
            savePos = pos
            countEvts = countEvts + 1
        elseif eventType == 13 then -- Channel pressure
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg) .. string.char(newRandomValue(msg:byte(2), 0, 127)) .. msg:sub(3,nil)
            savePos = pos
            countEvts = countEvts + 1
        elseif eventType == 14 then -- Pitch
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg) .. string.char(newRandomValue(msg:byte(2), 0, 127)) 
                                                          .. string.char(newRandomValue(msg:byte(3), 0, 127))
                                                          .. msg:sub(4,nil)
            savePos = pos
            countEvts = countEvts + 1
        elseif eventType == 9 and msg:byte(3) ~= 0 then -- Note-on velocity
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg+1) .. string.char(newRandomValue(msg:byte(3), 1, 127)) .. msg:sub(4,nil)
            savePos = pos
            countEvts = countEvts + 1
        end
    end
end
tMIDI[#tMIDI+1] = MIDI:sub(savePos, nil)

reaper.MIDI_SetAllEvts(take, table.concat(tMIDI))

--reaper.Undo_EndBlock2(0, "Randomized the values of " .. tostring(countEvts) .. " events", -1)
reaper.Undo_OnStateChange_Item(0, "Randomized the values of " .. tostring(countEvts) .. " events", item)

::exit::

[ADD., 7/2/19]

Argghhhhh!

I figured out why I was getting the message right after posting this... Darnit. Sorry!

The issue was in the return value of the NewRandomValue function...

Corrected code:

Code:
function newRandomValue(value, min, max)
    if math.random(0, 100) <= percent
  then
      if math.random(0, 100) > 50
    then
        if number < (range/2)
      then
        number = number + 1
      end
    else
      if number > (0-(range/2))
        then
          number = number - 1
        end
    end
  end
  
  if (value+number) >= max then return max
  elseif (value+number) <= min then return min
  else return (value+number) end
  
end
__________________

Support my feature request!

Last edited by Dafarkias; 07-02-2019 at 11:50 AM.
Dafarkias is offline   Reply With Quote
Old 11-30-2019, 03:28 AM   #9
RCJacH
Human being with feelings
 
Join Date: Apr 2016
Location: Beijing, China
Posts: 215
Default

I have a question regarding humanization with CC curves.

I've been looking for ways to humanize CC positions as well as curve tension. I noticed that the way you alter the original CC value is by using string.sub() function to directly replace the original value with new string.char(). However if we want to alter all other parameters or creating new events, it might be necessary to add new events using string.pack(). What would be the format for packing event data into MIDI event message, with the new meta-event of CC curves?
RCJacH is offline   Reply With Quote
Old 09-19-2020, 10:43 PM   #10
Nightowl4272
Human being with feelings
 
Nightowl4272's Avatar
 
Join Date: Feb 2013
Location: Chicago, IL
Posts: 33
Default

Wow, this is amazing...really. Thank you for this! This community is really something else.
Nightowl4272 is offline   Reply With Quote
Old 04-11-2021, 12:46 AM   #11
Antoine Portes
Human being with feelings
 
Antoine Portes's Avatar
 
Join Date: Jan 2021
Location: the sweet spot
Posts: 22
Default

Quote:
Originally Posted by Dafarkias View Post
Code:
--[[
ReaScript name: js_Humanize the values of selected CC events.lua
Version: 2.00
Author: juliansader
Changelog:
  - Much faster execution when many thousands of events are selected.
About:
  # Description
  Simple script to humanize (slightly randomize) the values of selected CC events or velocities.
]]

local isRandomPercentage
local randomType = "absolute"
local percent = 0
local range = 0
local number = 0


::incorrect::
local _, dummy = reaper.GetUserInputs( "Randomize Chance:", 1, "", "1-100%" ) 
if dummy ~= "1-100%" then dummy = tonumber(dummy)
  if type(dummy) ~= 'number' then reaper.MB( "Ensure that input is 1-100%", "[error]", 0 ) goto incorrect end
  if dummy < 1 or dummy > 100 then reaper.MB( "Ensure that input is 1-100%", "[error]", 0 ) goto incorrect end
  percent = dummy
else reaper.MB( "Script canceled.", "[message]", 0 ) goto exit end

::incorrect2::
_, dummy = reaper.GetUserInputs( "Randomize Range:", 1, "", "1-126" )
if dummy ~= "1-126" then dummy = tonumber(dummy)
  if type(dummy) ~= 'number' then reaper.MB( "Ensure that input 1-126", "[error]", 0 ) goto incorrect2 end
  if dummy < 1 or dummy > 126 then reaper.MB( "Ensure that input 1-126", "[error]", 0 ) goto incorrect2 end
  range = dummy
else reaper.MB( "Script canceled.", "[message]", 0 ) goto exit end

-----------------------------------
function newRandomValue(value, min, max)
    if math.random(0, 100) <= percent
  then
      if math.random(0, 100) > 50
    then
        if number < (range/2) and (value+number+1) < max
      then
        number = number + 1
      end
    else
      if number > (0-(range/2)) and (value+number-1) > min
        then
          number = number - 1
        end
    end
  end
  
  return (value+number)
  
end

--------------------------------------
-- function main()
isRandomPercentage = (randomType == "%" or randomType:lower():match("per"))

editor = reaper.MIDIEditor_GetActive()
if editor == nil then reaper.MB("Could not find any active MIDI editor.", "ERROR", 0) return end
take = reaper.MIDIEditor_GetTake(editor)
if not reaper.ValidatePtr2(0, take, "MediaItem_Take*") then reaper.MB("Active take is invalid.", "ERROR", 0) return end
item = reaper.GetMediaItemTake_Item(take)
if not reaper.ValidatePtr2(0, item, "MediaItem*") then reaper.MB("Could not determine valid item.", "ERROR", 0) return end

--reaper.Undo_BeginBlock()

local MIDIOK, MIDI = reaper.MIDI_GetAllEvts(take, "")
if not MIDIOK then reaper.MB("Could not load MIDI string.", "ERROR", 0) return end
local pos, savePos, countEvts = 1, 1, 0 -- pos is position in MIDI while parsing, savePos is next position from which substrings must be stored in tMIDI
local tMIDI = {} -- MIDI substrings will be stored in tMIDI, to be concatenated again later

while pos < #MIDI do
    offset, flags, msg, pos = string.unpack("i4Bs4", MIDI, pos)
    if flags&1 == 1 then
        local eventType = (msg:byte(1))>>4
        -- Different CC types use different bytes to carry their values, so each must be handled separately
        if eventType == 11 then -- 7-bit CC
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg+1) .. string.char(newRandomValue(msg:byte(3), 0, 127)) .. msg:sub(4,nil)
            savePos = pos
            countEvts = countEvts + 1
        elseif eventType == 13 then -- Channel pressure
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg) .. string.char(newRandomValue(msg:byte(2), 0, 127)) .. msg:sub(3,nil)
            savePos = pos
            countEvts = countEvts + 1
        elseif eventType == 14 then -- Pitch
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg) .. string.char(newRandomValue(msg:byte(2), 0, 127)) 
                                                          .. string.char(newRandomValue(msg:byte(3), 0, 127))
                                                          .. msg:sub(4,nil)
            savePos = pos
            countEvts = countEvts + 1
        elseif eventType == 9 and msg:byte(3) ~= 0 then -- Note-on velocity
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg+1) .. string.char(newRandomValue(msg:byte(3), 1, 127)) .. msg:sub(4,nil)
            savePos = pos
            countEvts = countEvts + 1
        end
    end
end
tMIDI[#tMIDI+1] = MIDI:sub(savePos, nil)

reaper.MIDI_SetAllEvts(take, table.concat(tMIDI))

--reaper.Undo_EndBlock2(0, "Randomized the values of " .. tostring(countEvts) .. " events", -1)
reaper.Undo_OnStateChange_Item(0, "Randomized the values of " .. tostring(countEvts) .. " events", item)

::exit::
[...]
Code:
function newRandomValue(value, min, max)
    if math.random(0, 100) <= percent
  then
      if math.random(0, 100) > 50
    then
        if number < (range/2)
      then
        number = number + 1
      end
    else
      if number > (0-(range/2))
        then
          number = number - 1
        end
    end
  end
  
  if (value+number) >= max then return max
  elseif (value+number) <= min then return min
  else return (value+number) end
  
end
Hi Dafarkias, I am deeply interested by this script but I keep failing at having it work!

I've saved it with as a .lua file (with the latter correction), imported it in the "Midi editor" section of the action list and given it a keyboard shortcut.

When I select CC events and press the shortcut, I'm asked to confirm that radomise chance is "1-100%", I click "OK" and get the message "Script canceled."

Am I doing something wrong?

Thanks.
Antoine Portes is offline   Reply With Quote
Old 04-11-2021, 01:48 PM   #12
Dafarkias
Human being with feelings
 
Dafarkias's Avatar
 
Join Date: Feb 2019
Location: Southern Vermont
Posts: 864
Default

Yeah. It's strange, a little.

First input you enter the percentage chance in which a note will be randomized, e.g., 50

Then you enter the total amount of drift you want to be randomize, e.g., 10

Then the script will randomize the selected notes by the percentage given, either plus one or negative one, but within the given range.

So there would be a 50% that any given note would be increased or decreased by an amount no less than -5 and no greater than 5, but never by an incremental jump of more than 1.

This creates a smoother humanization
__________________

Support my feature request!
Dafarkias is offline   Reply With Quote
Old 04-12-2021, 01:52 AM   #13
Antoine Portes
Human being with feelings
 
Antoine Portes's Avatar
 
Join Date: Jan 2021
Location: the sweet spot
Posts: 22
Default

It works indeed! A thousands thanks!

(I was stupidly inputting "1-100%" instead of any value inside this range...)
Antoine Portes is offline   Reply With Quote
Old 04-14-2021, 08:08 AM   #14
marik
Human being with feelings
 
Join Date: Nov 2016
Posts: 8
Default

This is exactly what I was looking for! Big thank you!!

Let's say I wanted to make an action to apply the script at probability 50%, value 10 without it asking me for the numbers. How would you suggest I edit the script?
marik is offline   Reply With Quote
Old 04-14-2021, 05:15 PM   #15
Dafarkias
Human being with feelings
 
Dafarkias's Avatar
 
Join Date: Feb 2019
Location: Southern Vermont
Posts: 864
Default

Feel free to PM me. I'd prefer not to continue to distract this thread (which is something I have a bad habit doing)
__________________

Support my feature request!
Dafarkias is offline   Reply With Quote
Old 02-15-2022, 10:38 PM   #16
richardj
Human being with feelings
 
Join Date: Nov 2012
Posts: 178
Default

Quote:
Originally Posted by juliansader View Post
It would indeed be useful - and more intuitive - if CC humanization could be incorporated into the current "Humanize" function.

I find it curious that many of REAPER's niftiest functions are limited to notes, and exclude CCs. Several of my own FRs have also asked for CC equivalents of note functions.

Since this FR is unlikely to be implemented any time soon, here is a script that may help. Each time that the script is run, it randomizes the CC values a little:

Code:
--[[
ReaScript name: js_Humanize the values of selected CC events.lua
Version: 1.00
Author: juliansader
About:
  # Description
  Quick script to humanize (slightly randomize) the values of selected CC events.
]]

-- USER AREA
-- Settings that the user can cuztomize

randomType = "abs" -- "%", "percentage", "abs" or "absolute"
randomMaxChange = 1 -- Maximum change, as percentage or absolute value (integer)

-- End of USER AREA
-------------------

-----------------------------------
function newRandomValue(value, max)
    if randomType == "%" or randomType:lower() == "percentage" then
        value = value * (math.random(100-randomMaxChange, 100+randomMaxChange))/100
    else
        value = value + math.random(-randomMaxChange, randomMaxChange)
    end
    value = math.max(0, math.min(max, math.floor(value+0.5))) -- Ensure that value is within limits, and integer

    return value
end

--------------------------------------
-- function main()
editor = reaper.MIDIEditor_GetActive()
if editor == nil then return end
take = reaper.MIDIEditor_GetTake(editor)
if not reaper.ValidatePtr2(0, take, "MediaItem_Take*") then return end

reaper.Undo_BeginBlock()

countCCs = 0
i = -1
repeat
    i = reaper.MIDI_EnumSelCC(take, i)
    if i ~= -1 then
        ccOK, _, _, _, chanmsg, channel, msg2, msg3 = reaper.MIDI_GetCC(take, i)
        if ccOK then
            countCCs = countCCs + 1
            local eventType = chanmsg>>4

            -- Different CC types use different bytes to carry their values, so each must be handled separately
            if eventType == 11 then -- 7-bit CC
                reaper.MIDI_SetCC(take, i, nil, nil, nil, nil, nil, nil, newRandomValue(msg3, 127), true)
            elseif eventType == 13 then -- Channel pressure
                reaper.MIDI_SetCC(take, i, nil, nil, nil, nil, nil, newRandomValue(msg2, 127), nil, true)
            elseif eventType == 14 then -- Pitch
                reaper.MIDI_SetCC(take, i, nil, nil, nil, nil, nil,  newRandomValue(msg2, 127), newRandomValue(msg3, 127), true)
            end
        end
    end
until i == -1

reaper.Undo_EndBlock2(0, "Humanized the values of " .. tostring(countCCs) .. " CC events", -1)
Is this a destructive script or an FX plugin? I don't want to actually change my CC values
richardj is offline   Reply With Quote
Old 06-22-2022, 06:14 AM   #17
Ikary
Human being with feelings
 
Ikary's Avatar
 
Join Date: Sep 2018
Posts: 34
Default

I've been waiting to have this feature in Reaper for years, where you could set how much the notes to humanize both in value and in time, and it would apply the humanization to all the selected notes across different MIDI items, like we have in the case of notes (which isn't possible with the scripts above).

Hope the team (or someone great at scripting) can implement this soon.
__________________
www.gonzalovarela.com

Last edited by Ikary; 06-22-2022 at 06:34 AM.
Ikary is offline   Reply With Quote
Old 06-22-2022, 06:36 AM   #18
Ikary
Human being with feelings
 
Ikary's Avatar
 
Join Date: Sep 2018
Posts: 34
Default

Quote:
Originally Posted by juliansader View Post
A request from another thread:



Here is an updated version of the humanize script that uses the new, fast MIDI_Get/SetAllEvts functions:
Code:
--[[
ReaScript name: js_Humanize the values of selected CC events.lua
Version: 2.00
Author: juliansader
Changelog:
  - Much faster execution when many thousands of events are selected.
About:
  # Description
  Simple script to humanize (slightly randomize) the values of selected CC events or velocities.
]]

-- USER AREA
-- Settings that the user can customize

randomType = "abs" -- "%", "percentage", "abs" or "absolute"
randomMaxChange = 1 -- Maximum change, as percentage or absolute value (integer)

-- End of USER AREA
-------------------


local isRandomPercentage
-----------------------------------
function newRandomValue(value, min, max)
    if isRandomPercentage then
        value = value * (math.random(100-randomMaxChange, 100+randomMaxChange))/100
    else
        value = value + math.random(-randomMaxChange, randomMaxChange)
    end
    if value > max then value = max
    elseif value < min then value = min
    else value = (value+0.5)//1 -- Ensure that value is within limits, and integer
    end
    
    return value
end

--------------------------------------
-- function main()
isRandomPercentage = (randomType == "%" or randomType:lower():match("per"))

editor = reaper.MIDIEditor_GetActive()
if editor == nil then reaper.MB("Could not find any active MIDI editor.", "ERROR", 0) return end
take = reaper.MIDIEditor_GetTake(editor)
if not reaper.ValidatePtr2(0, take, "MediaItem_Take*") then reaper.MB("Active take is invalid.", "ERROR", 0) return end
item = reaper.GetMediaItemTake_Item(take)
if not reaper.ValidatePtr2(0, item, "MediaItem*") then reaper.MB("Could not determine valid item.", "ERROR", 0) return end

--reaper.Undo_BeginBlock()

local MIDIOK, MIDI = reaper.MIDI_GetAllEvts(take, "")
if not MIDIOK then reaper.MB("Could not load MIDI string.", "ERROR", 0) return end
local pos, savePos, countEvts = 1, 1, 0 -- pos is position in MIDI while parsing, savePos is next position from which substrings must be stored in tMIDI
local tMIDI = {} -- MIDI substrings will be stored in tMIDI, to be concatenated again later

while pos < #MIDI do
    offset, flags, msg, pos = string.unpack("i4Bs4", MIDI, pos)
    if flags&1 == 1 then
        local eventType = (msg:byte(1))>>4
        -- Different CC types use different bytes to carry their values, so each must be handled separately
        if eventType == 11 then -- 7-bit CC
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg+1) .. string.char(newRandomValue(msg:byte(3), 0, 127)) .. msg:sub(4,nil)
            savePos = pos
            countEvts = countEvts + 1
        elseif eventType == 13 then -- Channel pressure
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg) .. string.char(newRandomValue(msg:byte(2), 0, 127)) .. msg:sub(3,nil)
            savePos = pos
            countEvts = countEvts + 1
        elseif eventType == 14 then -- Pitch
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg) .. string.char(newRandomValue(msg:byte(2), 0, 127)) 
                                                          .. string.char(newRandomValue(msg:byte(3), 0, 127))
                                                          .. msg:sub(4,nil)
            savePos = pos
            countEvts = countEvts + 1
        elseif eventType == 9 and msg:byte(3) ~= 0 then -- Note-on velocity
            tMIDI[#tMIDI+1] = MIDI:sub(savePos, pos-#msg+1) .. string.char(newRandomValue(msg:byte(3), 1, 127)) .. msg:sub(4,nil)
            savePos = pos
            countEvts = countEvts + 1
        end
    end
end
tMIDI[#tMIDI+1] = MIDI:sub(savePos, nil)

reaper.MIDI_SetAllEvts(take, table.concat(tMIDI))

--reaper.Undo_EndBlock2(0, "Randomized the values of " .. tostring(countEvts) .. " events", -1)
reaper.Undo_OnStateChange_Item(0, "Randomized the values of " .. tostring(countEvts) .. " events", item)
This implementation will do for me until we have an implementation like the one I described on my post above, but does anybody know how to tweak this so that it applies the humanization to all the selected notes across different MIDI items (instead of just the selected notes on the active MIDI item)?

Thanks in advance
__________________
www.gonzalovarela.com
Ikary is offline   Reply With Quote
Old 12-22-2023, 10:01 AM   #19
keaton
Human being with feelings
 
Join Date: May 2022
Posts: 198
Default

Hi there. +1 for this FR.

Quote:
Originally Posted by Ikary View Post
This implementation will do for me until we have an implementation like the one I described on my post above, but does anybody know how to tweak this so that it applies the humanization to all the selected notes across different MIDI items (instead of just the selected notes on the active MIDI item)?

Thanks in advance
This is exactly what I'm looking for.
keaton is offline   Reply With Quote
Old 01-26-2024, 03:30 AM   #20
DANIELE
Human being with feelings
 
DANIELE's Avatar
 
Join Date: Aug 2015
Location: Florence, Italy
Posts: 463
Default

+1 for me too, I'm using some scripts but it is not the same as a native option.


I don't understand why whe can humanize notes and velocities but not midi CCs.
__________________
Audio: AKG-K240 MKII, Adam A7X, Audient iD22 - Steinberg UR22; Piano: Yamaha P-250 - NI S88 MK1;
!!DANIELE EPIC ORCHESTRAL MUSIC!! |*| STAR WARS SERIES
DANIELE 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 12:08 PM.


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