Retrieve from OSC Address Pattern

You can use a function like this , which extracts wild cards from an OSC address

define :parse_sync_address do |address|
  v= get_event(address).to_s.split(",")[6]
  if v != nil
    return v[3..-2].split("/")
  else
    return ["error"]
  end
end

EDITED TO FIT YOUR EXAMPLE. I posted this late last night after a “night out” and thought it needed a bit more detail and explanation this morning!
So the program below works with the example requirement you gave in your post.

define :parse_sync_address do |address|
  v= get_event(address).to_s.split(",")[6]
  if v != nil
    return v[3..-2].split("/")
  else
    return ["error"]
  end
end

live_loop :getModulate do
  use_real_time
  #the * after osc in the next line allows this to work on 3.2dev as well as 3.2
  #you can miss it out on 3.2 if you wish,althouhg it will work with it
  p,q,r,s= sync "/osc*/modulate/*"
  puts "values",p,q,r,s
  #the FIRST * after osc in the next line allows this to work on 3.2dev as well as 3.2
  #you can miss it out on 3.2 if you wish,although it will work with it
  res = parse_sync_address "/osc*/modulate/*"
  puts "channel",res[2].to_i #track activated extracts the 3rd element of the osc address, ie the value of the *
  
end

#sample osc messages now sent to test this
use_osc "localhost",4559
osc "/modulate/1",1,2,3,4
sleep 1
osc "/modulate/2",5,6,7,8

See my project

for a practical demonstration of this.

The function uses an undocumented function get_event from Sonic PI. It may change in the futre, but works at present with all version 3 including latest dev version.
The * following /osc in the address lines ie /osc*/ allows this to work in version 3.2dev as well as 3.1 If you will only use 3.1 then you can miss out this *, although it will work if you leave it there. Currently it IS necessary to be there to work with version 3.2dev.

This gives output.

 ├─ "values" 1 2 3 4
 └─ "channel" 1

 ├─ "values" 5 6 7 8
 └─ "channel" 2

To help understand how the parseAddress function works add some puts statements as below:

define :parse_sync_address do |address|
  puts get_event(address)
  puts get_event(address).to_s.split(",")
  v= get_event(address).to_s.split(",")[6]
  if v != nil
    puts v
    puts v[3..-2].split("/")
    return v[3..-2].split("/")
  else
    return ["error"]
  end
end

With the example based on your requirements the program will then give output

 ├─ "values" 1 2 3 4
 ├─ #<SonicPi::CueEvent:[[1550307484.7874072, 0, #<SonicPi::ThreadId [-1]>, 0, 0.0, 60.0], "/osc/modulate/1", [1, 2, 3, 4]]
 ├─ ["#<SonicPi::CueEvent:[[1550307484.7874072", " 0", " #<SonicPi::ThreadId [-1]>", " 0", " 0.0", " 60.0]", " \"/osc/modulate/1\"", " [1", " 2", " 3", " 4]]"]
 ├─ " \"/osc/modulate/1\""
 ├─ ["osc", "modulate", "1"]
 └─ "channel" 1

and

 ├─ "values" 5 6 7 8
 ├─ #<SonicPi::CueEvent:[[1550307485.7777338, 0, #<SonicPi::ThreadId [-1]>, 0, 0.0, 60.0], "/osc/modulate/2", [5, 6, 7, 8]]
 ├─ ["#<SonicPi::CueEvent:[[1550307485.7777338", " 0", " #<SonicPi::ThreadId [-1]>", " 0", " 0.0", " 60.0]", " \"/osc/modulate/2\"", " [5", " 6", " 7", " 8]]"]
 ├─ " \"/osc/modulate/2\""
 ├─ ["osc", "modulate", "2"]
 └─ "channel" 2

for the two osc messages