1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
11.1 MIDI In
# MIDI In
In this section we will learn how to connect a MIDI controller to send
events into Sonic Pi to control our synths and sounds. Go and grab a
MIDI controller such as a keyboard or control surface and let's get
physical!
## Connecting a MIDI Controller
In order to get information from an external MIDI device into Sonic Pi
we first need to connect it to our computer. Typically this will be via
a USB connection, although older equipment will have a 5-pin DIN
connector for which you'll need hardware support for your computer (for
example, some sound cards have MIDI DIN connectors). Once you've
connected your device, launch Sonic Pi and take a look at the IO section
of the Preferences panel. You should see your device listed there. If
not, try hitting the 'Reset MIDI' button and see if it appears. If
you're still not seeing anything, the next thing to try is to consult
your operating system's MIDI config to see if it sees your
device. Failing all that, feel free to ask questions in our friendly
forums: https://in-thread.sonic-pi.net
## Receiving MIDI Events
Once your device is connected, Sonic Pi will automatically receive
events. You can see for yourself by manipulating your MIDI device and
looking at the cue logger in the bottom right of the application window
below the log (if this isn't visible go to Preferences->Editor->Show &
Hide and enable the 'Show cue log' tickbox). You'll see a stream of
events such as:
```
/midi:nanokey2_keyboard:0:1/note_off [55, 64]
/midi:nanokey2_keyboard:0:1/note_on [53, 102]
/midi:nanokey2_keyboard:0:1/note_off [57, 64]
/midi:nanokey2_keyboard:0:1/note_off [53, 64]
/midi:nanokey2_keyboard:0:1/note_on [57, 87]
/midi:nanokey2_keyboard:0:1/note_on [55, 81]
/midi:nanokey2_keyboard:0:1/note_on [53, 96]
/midi:nanokey2_keyboard:0:1/note_off [55, 64]
```
Once you can see a stream of messages like this, you've successfully
connected your MIDI device. Congratulations, let's see what we can do
with it!
## MIDI Time State
These events are broken into two sections. Firstly there's the name of
the event such as `/midi:nanokey2_keyboard:0:1/note_on` and secondly
there's the values of the event such as `[18, 62]`. Interestingly, these
are the two things we need to store information in Time State. *Sonic Pi
automatically inserts incoming MIDI events into Time State*. This means
you can `get` the latest MIDI value and also `sync` waiting for the next
MIDI value using everything we learned in section 10 of this tutorial.
## Controlling Code
Now we've connected a MIDI device, seen its events in the cue log and
discovered that our knowledge of Time State is all we need to work with
the events, we can now start having fun. Let's build a simple MIDI
piano:
```
live_loop :midi_piano do
note, velocity = sync "/midi:nanokey2_keyboard:0:1/note_on"
synth :piano, note: note
end
```
There's a few things going on in the code above including some
issues. Firstly, we have a simple `live_loop` which will repeat forever
running the code between the `do`/`end` block. This was introduced in
Section 9.2. Secondly, we're calling `sync` to wait for the next
matching Time State event. We use a string representing the MIDI message
we're looking for (which is the same as was displayed in the cue
logger). Notice that this long string is provided to you by Sonic Pi's
autocompletion system, so you don't have to type it all out by hand. In
the log we saw that there were two values for each MIDI note on event,
so we assign the result to two separate variables `note` and
`velocity`. Finally we trigger the `:piano` synth passing our note.
Now, you try it. Type in the code above, replace the sync key with a
string matching your specific MIDI device and hit Run. Hey presto, you
have a working piano! However, you'll probably notice a couple of
problems: firstly all the notes are the same volume regardless of how
hard you hit the keyboard. This can be easily fixed by using the
velocity MIDI value and converting it to an amplitude. Given that MIDI
has a range of 0->127, to convert this number to a value between 0->1 we
just need to divide it by 127:
```
live_loop :midi_piano do
note, velocity = sync "/midi:nanokey2_keyboard:0:1/note_on"
synth :piano, note: note, amp: velocity / 127.0
end
```
Update the code and hit Run again. Now the velocity of the keyboard is
honoured. Next, let's get rid of that pesky pause.
## Removing Latency
Before we can remove the pause, we need to know why it's there. In order
to keep all the synths and FX well-timed across a variety of differently
capable CPUs, Sonic Pi schedules the audio *in advance* by 0.5s by
default. (Note that this added latency can be configured via the fns
`set_sched_ahead_time!` and `use_sched_ahead_time`). This 0.5s latency
is being added to our `:piano` synth triggers as it is added to all
synths triggered by Sonic Pi. Typically we really want this added
latency as it means all synths will be well timed. However, this only
makes sense for synths triggered by code using `play` and `sleep`. In
this case, we're actually triggering the `:piano` synth with our
external MIDI device and therefore don't want Sonic Pi to control the
timing for us. We can turn off this latency with the command
`use_real_time` which disables the latency for the current thread. This
means you can use real time mode for live loops that have their timing
controlled by `sync`ing with external devices, and keep the default
latency for all other live loops. Let's see:
```
live_loop :midi_piano do
use_real_time
note, velocity = sync "/midi:nanokey2_keyboard:0:1/note_on"
synth :piano, note: note, amp: velocity / 127.0
end
```
Update your code to match the code above and hit Run again. Now we have
a low latency piano with variable velocity coded in just 5 lines. Wasn't
that easy!
## Getting Values
Finally, as our MIDI events are going straight into the Time State, we
can also use the `get` fn to retrieve the last seen value. This doesn't
block the current thread and returns `nil` if there's no value to be
found (which you can override by passing a default value - see the docs
for `get`). Remember that you can call `get` in any thread at any time
to see the latest matching Time State value. You can even use
`time_warp` to jump back in time and call `get` to see past events...
## Now You are in Control
The exciting thing now is that you can now use the same code structures
to `sync` and `get` MIDI information from any MIDI device and do whatever you
want with the values. You can now choose what your MIDI device will do!
|