Backing Track Device with MIDI Controller

Having learnt SPi I’ve been happily using it as the main way of making and playing BTs for live work. I’ve shied away from controlling them via MIDI, opting to use the coding interface as Sam our leader intended :slight_smile:

That said, I’ve got such a repeatable pattern now I thought I’d give it a look. I have a useful device the Akai Midimix which has lots of buttons, knobs and faders. I’m thinking the system as a more like a hardwre device.

I thought I’d share my code for using this. In one buffer I have the following code, which runs two live loops to pick up knob changes and toggle buttons. They set values using the set function. Then some utility functions: iscc() cc() trackvol() that I can use in my music loops to pick up volumes and switch loops on and off.

live_loop :midiccin do
  use_real_time
  n,v=sync "/midi*/control_change"
  set "cc_"+n.to_s,v
end

live_loop :midicctoggle do
  use_real_time
  n,v=sync "/midi*/control_change"
  s = "cc_toggle_"+n.to_s
  x=get[s]
  x=1 if x==nil
  if v==127 and n>0 and n<25
    if x==0
      x=1
    else
      x=0
    end
    set s,x
    puts s,x
  end
end

define :iscc do |n|
  x=get["cc_toggle_"+n.to_s]
  x==nil or x==1
end

define :cc do |n,min,max,default|
  v=get["cc_"+n.to_s]
  if v==nil
    x=default
  else
    x=min+(v/128.0)*(max-min)
  end
  puts "cc "+n.to_s + "," + x.to_s
  x
end

define :trackvol do |n|
  case n
  when 1
    button=1
    fader=19
  when 2
    button=4
    fader=23
  when 3
    button=7
    fader=27
  when 4
    button=10
    fader=31
  end
  v=0
  if iscc(button)
    v=cc(fader,0.0,1.0,0.0)
  end
  puts "trackvol "+n.to_s+" "+v.to_s
  v
end

Here’s an example applied to a drum machine, where the sounds are assigned to ‘tracks’ 1-4, each of which can be toggled on/off, and the volume modified with a fader.

#Drumkit V14
#With CC track controls
use_bpm 80

live_loop :drumtest do
  #stop
  #midiclock 4
  set :trigdrum, tick
  sleep 4
end

live_loop :drums do
  n=sync :trigdrum
  print n
  #stop
  a = 0.5
  s = [:bd_gas,
       :sn_dolf,
       :drum_cymbal_closed,
       :tabla_na,
       :tabla_na,
       :tabla_na,
       :tabla_na]
  
  sample :bd_gas if false
  
  define :p do |i|
    case i
    when 0
      sample s[i], rate: 2, finish: 0.1, amp: a*trackvol(1)
    when 1
      sample s[i], rate: (ring 1,1.2,1.4,1.6).tick(:s1), finish: 0.2, amp: a*0.2*trackvol(2)
    when 2
      sample s[i], rate: 1, amp: a*0.6*trackvol(3)
    when 3
      sample s[i], rate: 3, finish: 0.1, amp: a*0.5*trackvol(4)
    when 4
      sample s[i], rate: 2, finish: 0.1, amp: a*0.5*trackvol(4)
    when 5
      sample s[i], rate: 3, finish: 0.1, amp: a*0.5*trackvol(4)
    when 6
      sample s[i], rate: 4, finish: 0.1, amp: a*0.5*trackvol(4)
    else
      sample s[i], rate: 5, finish: 0.1, amp: a*0.1
    end
  end
  
  with_fx :echo, mix: 0.2, phase: 0.75, decay: 6 do
    in_thread do
      16.times do
        tick
        if n%6<6
          p(0) if ("x---x---x---x---"[look]=="x")
        end
        if n%6<4
          p(1) if ("----x-------x---"[look]=="x")
        else
          p(1) if ("----x-----------"[look]=="x")
        end
        if n%4==0
          p(1) if ("-----------x----"[look]=="x")
        end
        if n%4<4
          p(2) if ("--x---x---x---x-"[look]=="x")
        else
          p(2) if ("-xx--xx--xx--xx-"[look]=="x")
        end
        if n%6==0 then
          p(3) if ("xxx---xxxx---xxx"[look]=="x")
        else
          p(3) if ("------x-----x---"[look]=="x")
        end
        if n%5<2 then
          p(4) if ("-----x----------"[look]=="x") ^ one_in(6)
          p(5) if ("-------x---x--x-"[look]=="x") ^ one_in(6)
          p(6) if ("----x----x-----x"[look]=="x") ^ one_in(6)
        end
        p(7) if ("--x---x---x---x---"[look]=="x")
        sleep 1.0/4
      end
    end
  end
end
2 Likes

Here’s the Akai MidiMix, the drum machine here is controlled with the first 4 buttons and the faders 1-4

Very lean and convincing, I’d love to try that myself. Did you experience any lags/delays between the loops reading the device and the live_loop syncing and controling the drums? I mean, if you press a button you would feel such delays if they existed to a noticeable extend.

Thanks. No lags really, I think the setting values from the midi CC’s is pretty instant (like pressing a midi key). But where it takes effect - that’s about where I use the functions to read them.

For instance, I could test them at the start of a loop - in which case it will stick for the whole loop duration typically one bar in my code. Or put them at the ‘play’ level in which case they will operate straight away. In my drum machine example, they are at the ‘play’ level, so the changes will happen at the next ‘play’ - 16th notes in this case.

I’m thinking like the Ableton Live Sessions mode, I could use them to bring loops in/out but that will be at the bar (or phrase) boundary rather than the middle of a phrase. If that makes sense.

I’ve not convinced myself this is the way to go yet - I’ll have to try it for a bit. More kit = more code = more likely to go wrong.

Every now and then I go all, “I could control all this from midi”. I did the same with VCV Rack and with PureData, but have gone back to using the keyboard, screen, mouse with those too.

Hi @soxsa,

i’m trying to use your interesting set up.

Some questions :

  • your toggle buttons from your AKAI device send messages midi of this kind : /midi*/control_change ? what are their fully midi messages ?
  • toggles are numbered from 1 to 24 ?
  • your faders sends the same kind of midi messages : /midi*/control_change right ?

So you set track 1 to the button 1 and fader 19, track 2 to button 4 and fader 23

  • ::midicctoggle handles the state of the toggle button
  • :cc method is to handle the faders
  • :iscc to check if it’s a toggle

the double == is an affectation ??! Actually, i don’t understand the line x==nil or x==1

Another question

Could you tell me more about this v alone ?


To test i add volForTesting = 0.15 to be sure something happens and set to 0 then.
Crow is my friend to test too :slight_smile:

##

#Drumkit V14
#With CC track controls


use_bpm 80

volForTesting = 0.25

live_loop :drumtest do
  #stop
  #midiclock 4
  set :trigdrum, tick
  sleep 4
end

live_loop :drums do
  n=sync :trigdrum
  print n
  #stop
  a = 0.5
  s = [:bd_gas,
       :sn_dolf,
       :drum_cymbal_closed,
       :tabla_na,
       :tabla_na,
       :tabla_na,
       :tabla_na]
  
  sample :misc_crow if true
  
  define :p do |i|
    case i
    when 0
      sample s[i], rate: 2, finish: 0.1, amp: a*trackvol(1) + volForTesting
    when 1
      sample s[i], rate: (ring 1,1.2,1.4,1.6).tick(:s1), finish: 0.2, amp: a*0.2*trackvol(2) + volForTesting
    when 2
      sample s[i], rate: 1, amp: a*0.6*trackvol(3) + volForTesting
    when 3
      sample s[i], rate: 3, finish: 0.1, amp: a*0.5*trackvol(4) + volForTesting
    when 4
      sample s[i], rate: 2, finish: 0.1, amp: a*0.5*trackvol(4) + volForTesting
    when 5
      sample s[i], rate: 3, finish: 0.1, amp: a*0.5*trackvol(4) + volForTesting
    when 6
      sample s[i], rate: 4, finish: 0.1, amp: a*0.5*trackvol(4) + volForTesting
    else
      sample s[i], rate: 5, finish: 0.1, amp: a*0.1 + volForTesting
      
    end
  end
  
  with_fx :echo, mix: 0.2, phase: 0.75, decay: 6 do
    in_thread do
      16.times do
        tick
        if n%6<6
          p(0) if ("x---x---x---x---"[look]=="x")
        end
        if n%6<4
          p(1) if ("----x-------x---"[look]=="x")
        else
          p(1) if ("----x-----------"[look]=="x")
        end
        if n%4==0
          p(1) if ("-----------x----"[look]=="x")
        end
        if n%4<4
          p(2) if ("--x---x---x---x-"[look]=="x")
        else
          p(2) if ("-xx--xx--xx--xx-"[look]=="x")
        end
        if n%6==0 then
          p(3) if ("xxx---xxxx---xxx"[look]=="x")
        else
          p(3) if ("------x-----x---"[look]=="x")
        end
        if n%5<2 then
          p(4) if ("-----x----------"[look]=="x") ^ one_in(6)
          p(5) if ("-------x---x--x-"[look]=="x") ^ one_in(6)
          p(6) if ("----x----x-----x"[look]=="x") ^ one_in(6)
        end
        p(7) if ("--x---x---x---x---"[look]=="x")
        sleep 1.0/4
      end
    end
  end
end

So i will be happy if you tell me more about your scripts ?
Cheers

1 Like

Hi @nlb I’ve simplified it after trying it for a bit. I’ve removed the code for the toggle buttons. The code works, but the default setup for the MidiMix has some of the buttons to have the same CC number as the faders. I could edit that of course on the device but decided to go for just faders. I’ll paste the new code at the end. But to answer your questions…

The full cue is e.g.: /midi:midi_mix_midi_mix_midi_1_24_0:1/control_change [16, 27] - the new midi cue names are pretty long!!!

Toggle numbers - yes 1-24, but as I say, I thought they would be all different from the faders but they aren’t. I guess it’s because the midimix by default is setup for working with Ableton as a track/recording controller. And yes the buttons send the same CC messages as the faders. Down=127, Up=0.

In the :iscc function (now changed, but to explain the original) the purpose is to return ‘true’ if either the cc_toggle has been set to 1, or hasn’t been set yet. So the line x==nil or x==1 reads as ‘if x doesn’t have value or has a value of 1’. In Ruby == is a comparison operator, whereas = is for assignment.

In Ruby, to return value from a function, you put the value as the last statement. In other languages you’d write something like return (if x==nil or x==1)

define :iscc do |n|
  x=get["cc_toggle_"+n.to_s]
  x==nil or x==1
end

That also explains the lonely v as the end of the trackvol() function

Here’s the new, simpler code. Faders only. But I still want an on/off function so I’ve changed :iscc to be ‘true’ if the fader is more than half way on (>63)

For the :trackvol function, that is specific to the MidiMix device, referring to the row of faders labelled 1-9 on the device. I’ve made the function return between 0.0 and 1.0 for convenience in the playing code e.g.

play :C4, amp: 0.5*trackvol(1)

Likewise :ccvol returns 0.0 to 1.0, but using the raw CC number. e.g.

play :C4, amp: 0.5*ccvol(16)

And to use a fader as an on/off switch e.g.

play :C4, amp:0.5 if iscc(16)

live_loop :midiccin_midimix do
  use_real_time
  n,v=sync "/midi*/control_change"
  s= "cc_"+n.to_s
  set s,v
  puts s,v
end

define :iscc do |n|
  x=get["cc_"+n.to_s]
  x==nil or x>63
end

define :cc do |n,min,max,default|
  v=get["cc_"+n.to_s]
  if v==nil
    x=default
  else
    x=min+(v/128.0)*(max-min)
  end
  puts "cc "+n.to_s + "," + x.to_s
  x
end

define :ccvol do |n|
  cc(n,0.0,1.0,1.0)
end

define :trackvol do |trackno|
  #Convert track number 1-10 to CC
  n=[19,23,27,31,49,53,57,61,62][trackno-1]
  cc(n,0.0,1.0,1.0)
end

Hi @soxsa
Thanks for your answer :-).

“ah d’accord” it’s good to know :slight_smile: I wasn’t aware about this “shortcut” but could have guessed.

Well i try to adapt your script to apc key25 from AKAI,
no faders available but knobs
/midi:apc_key_25_apc_key_25_midi_1_20_0:1/control_change [51, 91] 51 the knob number and 91 the midi value returned
So i want to use 48, 49, 50, 51 as faders to control the volume as this :
48 >> track 1
49 >> track 2
50 >> track 3
51 >> track 4

For the toggles, apckey 25 works with note_on and note_off :

note_on button pressed
note_off button released
so i guess your script doesn’t cover this behaviour.

So another solution will be to use Introduction - Open Stage Control and create the same behaviour as your device. I will see.

btw toggles are good to stop right now samples so may i ask you to add this feature to your script ?
Again thanks to share with us your scripts.

Using note_on is a lot simpler, because there’s only one event every time you press a key. With the CC button like I have, there’s two events (v=127 on keydown, v=0 on keyup). It’s possible I could reprogram the Akai Midi Mix to change to a note.

Here’s some example code. The new function is iskeytoggle which returns true/false.

live_loop :midi_toggle_key do
  use_real_time
  n,v=sync "/midi*/note_on"
  s="key_toggle_"+n.to_s
  x=get[s]
  x=1 if x==nil
  if x==1
    x=0
  else
    x=1
  end
  puts s,x
  set s,x
end

define :iskeytoggle do |n|
  x=get["key_toggle_"+n.to_s]
  x==nil or x==1
end

live_loop :test do
  play :C4, amp: 0.1 if iskeytoggle(60)
  sleep 0.25
end

not really because the midi message when released is not note_on but not_off

 /midi:apc_key_25_apc_key_25_midi_1_20_0:1/note_on   [3, 127]
 /midi:apc_key_25_apc_key_25_midi_1_20_0:1/note_off  [3, 127]

so we have to listen to two midi addresses.

No, because I’m using it as a toggle. Press the key once to toggle ON, press again to toggle OFF. Just ignore the note_off cue. The difference I’m pointing out here is that SPi’s sync function can differentiate between note_on and note_off. Whereas with the midmix button, it gets two control_change cues which only differ because v=127 then v=0 and our code has to do the filtering.