Using the Akai APC mini to control Sonic Pi
A collection of utility functions to use the Akai APC mini MIDI controller with Sonic Pi.
Photo credit: <a href="https://commons.wikimedia.org/wiki/File:APC_Mini_and_other_Music_Tools(15387729761).jpg">I G, CC BY 2.0, via Wikimedia Commons_.
Important note
This is work in progress and, while it mostly works as described, its performance is not great and it is not completely stable. It can make the controller crash* sometimes when there is too much going on, and even Sonic Pi itself (this is more rare and I have only seen it once, but it's fair to mention).
You should probably not use this yet for live performances, at least without having reproduced similar loads to what you plan to do.
*I don't know if this crashes happen in the controller itself, or in the software in the computer (be it drivers or Sonic Pi itself), but the way it manifests is: you can still control the sounds using the board, but its lights stop updating. I haven't found a solution to this crashes that is not restarting Sonic PI + plugging and unplugging the controller.
Installation
Download, clone the code or install the gem, then add this to the top of your Sonic Pi buffer (or your ~/.sonic-pi/config/init.rb
):
require '<path-to-sonic-pi-akai-apc-mini>/init.rb'
# for example: require '~/sonic-pi-akai-apc-mini/init.rb'
Finding out where the code is, when installed as a gem
Sonic Pi ships with its own Ruby, meaning that in principle it has no access to the gems installed in your system. To find out where is the file you need to require, run sonic-pi-akai-apc-mini
in a terminal and the path will be printed.
Usage
If you want to skip this reference, feel free to load example.rb
on Sonic Pi and start jamming. Otherwise:
First of all, call initialize_akai(<model>)
at the top of your buffer. That will make all the features available.
A small set of functions get added to the Sonic Pi API, in order to use the controls in the APC mini in different ways.
Supported models
:apc_mini
:apc_key_25
(experimental; please contact the author if you use it, either successfully or not. in any case, only the grid and the knobs are supported, not the keyboard --yet)
Faders
fader(n, [target-values])
This function lets you use any of the faders to control the value of anything in Sonic Pi. n
is the fader number (starting from 0, left to right). target-values
is the range of values the fader will map to (and defaults to (0..1)
*). Some examples:
play :c4, amp: fader(0)
sample :bd_haus, cutoff: fader(1, (60..127))
target-values
is typically a range, but it can also be an array or a ring. In that case, the range of the fader is divided into discrete regions, each of them mapped to a value:
with_fx :slicer, phase: fader(0, [0.125, 0.25, 0.5]) do
play fader(1, chord(:c4, :major))
end
fader
also accepts the special value :pan
, which maps to (-1..1)
, for that very obvious usecase:
play :c4, pan: fader(0, :pan)
Finally, it is possible to use the same fader for two different things, with two different target values, if that makes sense for your music:
play :c4, amp: fader(0, (0.8..1.5)), pan: fader(0, :pan)
*In reality, (0..0.999)
. The reason is that there are many parameters with range [0, 1), that is, between 0 and 1 but not 1, for example a synth's res
(resonance). This weird default helps with this case while making no difference for the normal case. If you really need to be able to get to 1, then pass (0..1)
explicitly.
attach_fader(n, node, property, [target-values])
All this is fine and good and works great with short synth notes or samples, but sometimes you want to control a sound with a fader while it is playing. That's what attach_fader
is for. Apart from the already known n
and target-values
, which work the same, it expects a node
(a synth node, a sample node, or a fx node) and a property
, which will be attached to the fader and updated in real-time:
with_fx :slicer do |fx|
attach_fader(0, fx, :mix)
... # while these sounds play, you can control how much the slicer can be heard using fader 0
end
Or:
live_loop :drums do
drums = sample :loop_amen, beat_stretch: 4
attach_fader(0, drums, :cutoff, (60..120))
sleep 4
end
attach_fader
uses control
under the hood, which means:
- The property needs to be one that can be changed while the sound is playing. Refer to the documentation of each synth and fx.
- It will be affected by the corresponding
_slide
options. It could be said that it doesn't play very well with any non-zero value in the corresponding_slide
option, but in reality pretty cool effects can be created by mixing them.
set_fader(n, [target-values]) { |value| ... }
There is another variant, lower level, which can be used for anything, but the most typical use case is to connect the fader to some general option like set_volume!
or set_mixer_control!
.
set_fader(8) { |v| set_volume! v }
Important notes about faders
- Because MIDI works with events, it is not possible for Sonic Pi to know the initial position of a fader until it is moved and its new value is sent. Until then, it is assumed it is set to zero. So, two little advices:
- Start your performances with the faders physically set to zero, to match that assumption. Move them to the desired position before evaluating the code that will read them.
- The lights above the faders are used as a hint to avoid this problem: they will be off when Sonic Pi thinks they're set at zero, and on when it thinks they're set at non-zero. If you see a fader physically not at zero but with the light off, move it slightly, so that Sonic Pi learns where it is :)
- At the moment it is not possible to apply
attach_fader
/set_fader
to the same fader twice (the second definition wins). But you can combine oneattach_fader
/set_fader
with as manyfader
as you want.
Switches
Each button in the grid can be used as a boolean switch, for any purpose (typically, triggering a sound or not). You could use a fader to map amp
(and set it to zero when you don't want to hear it), but faders are scarce and there are 64 buttons in the grid :)
switch?(row, col)
Returns the current value (true
or false
) of the specified switch. Columns and rows start from 0, 0 at the lower left corner.
live_loop :music do
sample "some_noisy_sample" if switch?(0, 0)
... # some nice music
end
The buttons will light green when they're on.
Selectors
NOTE: This feature is experimental. It mostly works, but its performance is quite bad and is one of the things that incresases the chance of crashes.
Selectors are a special kind of switches. You can map a series of consecutive buttons in the grid, to different values. Only one of them will be active at the time (lighting green, while the others light red).
selector(row, col, target-values)
row
and col
points to the first button you want to assign, and values
is an array/ring with the possible values. As many buttons as possible values will be mapped, but the end of the row is a hard limit.
live_loop :notes do
use_synth selector(7, 0, [:fm, :beep, :tb303])
play scale(:c3, :minor_pentatonic).choose
sleep 0.5
end
Or:
play_chord selector(6, 0, [chord(:e3, :minor), chord(:g3, :major), chord(:d3, :major)])
As you can see, the use case is very similar to using fader
with an array, but it is a better UI for many cases. Sadly, it doesn't work perfectly at the moment, so you might prefer to stick with fader
.
Looping with the grid
One of the most useful uses of the grid is looping. You can set it up so that you can punch notes in the grid, that will be played in loop. This is great (but not only) for drum loops.
loop_rows(duration, rows)
duration
is the number of beats the loop lasts. It will always divided by the 8 columns of the grid. rows
is a hash which maps the row number to a block with the sound to play. For example:
live_loop :drums do
loop_rows(4, {
7 => -> { sample :drum_heavy_kick },
6 => -> { sample :drum_snare_hard },
5 => -> { synth :noise, release: 0.1 } # sketchy hi-hat
})
end
This will assign the top 3 rows of the grid to punch a drum pattern. Notes will be shown as green, and there will be a hinting yellow light showing which column is being played as the loop progresses.
loop_rows_synth(duration, rows, notes, [options])
A typical use case of looping is calling a synth (always the same) with different notes (e.g. for basslines). This function makes it a bit less verbose:
live_loop :bassline do
loop_rows_synth(8, (0..2), chord(:c2, :minor))
end
This will assign each of the three notes of the chord to each row, and now you can punch your baseline.
You can pass options, that will be applied to each note:
live_loop :bassline do
loop_rows_synth(8, (0..2), chord(:c2, :minor), amp: 0.8)
end
And, if you need those options to be evaluated separately for each note (because you call random values, or maybe fader
), you can wrap it in a lambda:
live_loop :bassline do
loop_rows_synth(8, (0..2), chord(:c2, :minor), -> {{ pan: rrand(-1..1), cutoff: fader(5, (60..120)) }}
end
Something to note, is that there is no problem to run more than one loop, with different durations, as long as they don't use the same rows.
Free play
The APC mini is a MIDI device, so you can... play! Be aware that this is of limited usefulness for several reasons (1. there is some latency that is ok for faders and such but makes playing quite difficult, and 2. it is not a keyboard, which makes it even more difficult), but it can be ok for very simple things.
free_play(row, col, notes, [options])
Assigns a series of consecutive buttons starting at row
, col
, to play notes
with the current synth (and the given options
, if any). The mapped buttons with light yellow. This call should live in its own live_loop
.
live_loop :bass do
use_synth :fm
free_play 0, 0, scale(:c3, :major), amp: 0.8
end
reset_free_play(row, col, notes, [options])
If you want to remove a free play mapping (so that the buttons are again available as switches), you need to call reset_free_play
. It has the same signature so you can just prepend reset_
to the previous call.
Roadmap of planned features
- A
selector
that actually works - Free play with samples
- Make it possible to attach/set the same fader more than once
- Better performance and stability in general
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/porras/sonic-pi-akai-apc-mini. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
This code is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the sonic-pi-akai-apc-mini project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
Acknowledgments
Apart from the excellent documentation of the Sonic Pi project, this wonderful summary by Tomáš Hübelbauer took me from barely knowing what MIDI is to a functional prototype in a couple of hours. Cheers!