sonic-pi-akai-apc-mini 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +18 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +61 -0
- data/LICENSE.txt +21 -0
- data/README.md +239 -0
- data/Rakefile +6 -0
- data/akai-apc-mini.jpg +0 -0
- data/bin/console +13 -0
- data/bin/setup +8 -0
- data/exe/sonic-pi-akai-apc-mini +13 -0
- data/init.rb +3 -0
- data/lib/sonic-pi-akai-apc-mini/api.rb +131 -0
- data/lib/sonic-pi-akai-apc-mini/core_extensions/range.rb +13 -0
- data/lib/sonic-pi-akai-apc-mini/helpers.rb +23 -0
- data/lib/sonic-pi-akai-apc-mini.rb +3 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5f1ebf42972f8a9b3d2906047bbbeb2292a1a1a4d8f0ba4de4e74eb809b0c6ea
|
4
|
+
data.tar.gz: 03ab06c2fefd7b6741a303374073404d1b065213734e185b3b0da95a29beda9e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3868b1f5b216126776a0d388eecddc36992d4354c22d3fb519061b59e26dcc0219acae3e1b388102f89ab8dacdbb476bfa11c41dcf8733e0db054fefac8c1043
|
7
|
+
data.tar.gz: d4f0b4c643f33c5e41705376b40beb68eb58ae8801f4e3db006c660128a301637f474cfb26aa0911bc0cd7ef38342e29f233ab1a6add6c98bc87ef0ba4e3415b
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.7
|
3
|
+
NewCops: enable
|
4
|
+
|
5
|
+
Style/FrozenStringLiteralComment:
|
6
|
+
EnforcedStyle: never
|
7
|
+
|
8
|
+
Lint/AssignmentInCondition:
|
9
|
+
Enabled: false
|
10
|
+
|
11
|
+
Metrics/BlockLength:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
# This is a weird gem. This is fine here :)
|
15
|
+
Naming/FileName:
|
16
|
+
Exclude:
|
17
|
+
- 'lib/sonic-pi-akai-apc-mini.rb'
|
18
|
+
- 'spec/sonic-pi-akai-apc-mini_spec.rb'
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.1.0
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
6
|
+
|
7
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
8
|
+
|
9
|
+
## Our Standards
|
10
|
+
|
11
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
12
|
+
|
13
|
+
* Demonstrating empathy and kindness toward other people
|
14
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
15
|
+
* Giving and gracefully accepting constructive feedback
|
16
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
17
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
18
|
+
|
19
|
+
Examples of unacceptable behavior include:
|
20
|
+
|
21
|
+
* The use of sexualized language or imagery, and sexual attention or
|
22
|
+
advances of any kind
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
24
|
+
* Public or private harassment
|
25
|
+
* Publishing others' private information, such as a physical or email
|
26
|
+
address, without their explicit permission
|
27
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
28
|
+
professional setting
|
29
|
+
|
30
|
+
## Enforcement Responsibilities
|
31
|
+
|
32
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
33
|
+
|
34
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
35
|
+
|
36
|
+
## Scope
|
37
|
+
|
38
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
39
|
+
|
40
|
+
## Enforcement
|
41
|
+
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at TODO: Write your email address. All complaints will be reviewed and investigated promptly and fairly.
|
43
|
+
|
44
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
|
+
|
46
|
+
## Enforcement Guidelines
|
47
|
+
|
48
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
49
|
+
|
50
|
+
### 1. Correction
|
51
|
+
|
52
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
53
|
+
|
54
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
55
|
+
|
56
|
+
### 2. Warning
|
57
|
+
|
58
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
59
|
+
|
60
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
61
|
+
|
62
|
+
### 3. Temporary Ban
|
63
|
+
|
64
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
65
|
+
|
66
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
67
|
+
|
68
|
+
### 4. Permanent Ban
|
69
|
+
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
71
|
+
|
72
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
73
|
+
|
74
|
+
## Attribution
|
75
|
+
|
76
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
|
77
|
+
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
78
|
+
|
79
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
80
|
+
|
81
|
+
[homepage]: https://www.contributor-covenant.org
|
82
|
+
|
83
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
84
|
+
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
sonic-pi-akai-apc-mini (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.2)
|
10
|
+
diff-lcs (1.5.0)
|
11
|
+
parallel (1.21.0)
|
12
|
+
parser (3.1.0.0)
|
13
|
+
ast (~> 2.4.1)
|
14
|
+
rainbow (3.0.0)
|
15
|
+
rake (13.0.6)
|
16
|
+
regexp_parser (2.2.0)
|
17
|
+
rexml (3.2.5)
|
18
|
+
rspec (3.10.0)
|
19
|
+
rspec-core (~> 3.10.0)
|
20
|
+
rspec-expectations (~> 3.10.0)
|
21
|
+
rspec-mocks (~> 3.10.0)
|
22
|
+
rspec-core (3.10.1)
|
23
|
+
rspec-support (~> 3.10.0)
|
24
|
+
rspec-expectations (3.10.1)
|
25
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
26
|
+
rspec-support (~> 3.10.0)
|
27
|
+
rspec-mocks (3.10.2)
|
28
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
29
|
+
rspec-support (~> 3.10.0)
|
30
|
+
rspec-support (3.10.3)
|
31
|
+
rubocop (1.24.1)
|
32
|
+
parallel (~> 1.10)
|
33
|
+
parser (>= 3.0.0.0)
|
34
|
+
rainbow (>= 2.2.2, < 4.0)
|
35
|
+
regexp_parser (>= 1.8, < 3.0)
|
36
|
+
rexml
|
37
|
+
rubocop-ast (>= 1.15.1, < 2.0)
|
38
|
+
ruby-progressbar (~> 1.7)
|
39
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
40
|
+
rubocop-ast (1.15.1)
|
41
|
+
parser (>= 3.0.1.1)
|
42
|
+
rubocop-rake (0.6.0)
|
43
|
+
rubocop (~> 1.0)
|
44
|
+
rubocop-rspec (2.7.0)
|
45
|
+
rubocop (~> 1.19)
|
46
|
+
ruby-progressbar (1.11.0)
|
47
|
+
unicode-display_width (2.1.0)
|
48
|
+
|
49
|
+
PLATFORMS
|
50
|
+
x86_64-linux
|
51
|
+
|
52
|
+
DEPENDENCIES
|
53
|
+
rake
|
54
|
+
rspec
|
55
|
+
rubocop
|
56
|
+
rubocop-rake
|
57
|
+
rubocop-rspec
|
58
|
+
sonic-pi-akai-apc-mini!
|
59
|
+
|
60
|
+
BUNDLED WITH
|
61
|
+
2.3.3
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 TODO: Write your name
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,239 @@
|
|
1
|
+
# Using the Akai APC mini to control Sonic Pi ![(build status)](https://github.com/porras/sonic-pi-akai-apc-mini/actions/workflows/main.yml/badge.svg)
|
2
|
+
|
3
|
+
A collection of utility functions to use the [Akai APC mini](https://www.akaipro.com/apc-mini) MIDI controller with [Sonic Pi](https://sonic-pi.net/).
|
4
|
+
|
5
|
+
![Photo of an Akai APC mini](akai-apc-mini.jpg)
|
6
|
+
|
7
|
+
_Photo credit: <a href="https://commons.wikimedia.org/wiki/File:APC_Mini_and_other_Music_Tools_(15387729761).jpg">I G</a>, <a href="https://creativecommons.org/licenses/by/2.0">CC BY 2.0</a>, via Wikimedia Commons_.
|
8
|
+
|
9
|
+
### Important note
|
10
|
+
|
11
|
+
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).
|
12
|
+
|
13
|
+
You should _probably_ not use this yet for live performances, at least without having reproduced similar loads to what you plan to do.
|
14
|
+
|
15
|
+
\*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.
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
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`):
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require '<path-to-sonic-pi-akai-apc-mini>/init.rb'
|
23
|
+
# for example: require '~/sonic-pi-akai-apc-mini/init.rb'
|
24
|
+
```
|
25
|
+
|
26
|
+
### Finding out where the code is, when installed as a gem
|
27
|
+
|
28
|
+
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.
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
First of all, call `initialize_akai` at the top of your buffer. That will make all the features available.
|
33
|
+
|
34
|
+
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.
|
35
|
+
|
36
|
+
### Faders
|
37
|
+
|
38
|
+
#### `fader(n, [target-values])`
|
39
|
+
|
40
|
+
This function lets you use any of the faders to control the value of _anything_ in Sonic Pi. `n` is the fader number (they are 0-8, left to right). `target-values` is the range of values the fader will map to (and defaults to `(0..1)`\*). Some examples:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
play :c4, amp: fader(0)
|
44
|
+
sample :bd_haus, cutoff: fader(1, (60..127))
|
45
|
+
```
|
46
|
+
|
47
|
+
`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:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
with_fx :slicer, phase: fader(0, [0.125, 0.25, 0.5]) do
|
51
|
+
play fader(1, chord(:c4, :major))
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
`fader` also accepts the special value `:pan`, which maps to `(-1..1)`, for that very obvious usecase:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
play :c4, pan: fader(0, :pan)
|
59
|
+
```
|
60
|
+
|
61
|
+
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:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
play :c4, amp: fader(0, (0.8..1.5)), pan: fader(0, :pan)
|
65
|
+
```
|
66
|
+
|
67
|
+
\*In reallity, `(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.
|
68
|
+
|
69
|
+
#### `attach_fader(n, node, property, [target-values])`
|
70
|
+
|
71
|
+
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:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
with_fx :slicer do |fx|
|
75
|
+
attach_fader(0, fx, :mix)
|
76
|
+
... # while these sounds play, you can control how much the slicer can be heard using fader 0
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
Or:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
live_loop :drums do
|
84
|
+
drums = sample :loop_amen, beat_stretch: 4
|
85
|
+
attach_fader(0, drums, :cutoff, (60..120))
|
86
|
+
sleep 4
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
In this case, at the moment it is not possible to attach the same fader to two different controls. But you can combine **one** `attach_fader` with as many `fader` as you want.
|
91
|
+
|
92
|
+
`attach_fader` uses `control` under the hood, which means:
|
93
|
+
|
94
|
+
* The property needs to be one that can be changed while the sound is playing. Refer to the documentation of each synth and fx.
|
95
|
+
* 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.
|
96
|
+
|
97
|
+
#### Important note about faders
|
98
|
+
|
99
|
+
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:
|
100
|
+
|
101
|
+
* 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.
|
102
|
+
* 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 :)
|
103
|
+
|
104
|
+
### Switches
|
105
|
+
|
106
|
+
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 :)
|
107
|
+
|
108
|
+
#### `switch?(row, col)`
|
109
|
+
|
110
|
+
Returns the current value (`true` or `false`) of the specified switch. Columns and rows start from 0, 0 at the lower left corner.
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
live_loop :music do
|
114
|
+
sample "some_noisy_sample" if switch?(0, 0)
|
115
|
+
... # some nice music
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
The buttons will light green when they're on.
|
120
|
+
|
121
|
+
### Selectors
|
122
|
+
|
123
|
+
_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._
|
124
|
+
|
125
|
+
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).
|
126
|
+
|
127
|
+
#### `selector(row, col, target-values)`
|
128
|
+
|
129
|
+
`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.
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
live_loop :notes do
|
133
|
+
use_synth selector(7, 0, [:fm, :beep, :tb303])
|
134
|
+
play scale(:c3, :minor_pentatonic).choose
|
135
|
+
sleep 0.5
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
Or:
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
play_chord selector(6, 0, [chord(:e3, :minor), chord(:g3, :major), chord(:d3, :major)])
|
143
|
+
```
|
144
|
+
|
145
|
+
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`.
|
146
|
+
|
147
|
+
### Looping with the grid
|
148
|
+
|
149
|
+
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.
|
150
|
+
|
151
|
+
#### `loop_rows(duration, rows)`
|
152
|
+
|
153
|
+
`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:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
live_loop :drums do
|
157
|
+
loop_rows(4, {
|
158
|
+
7 => -> { sample :drum_heavy_kick },
|
159
|
+
6 => -> { sample :drum_snare_hard },
|
160
|
+
5 => -> { synth :noise, release: 0.1 } # sketchy hi-hat
|
161
|
+
})
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
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.
|
166
|
+
|
167
|
+
#### `loop_rows_synth(duration, rows, notes, [options])`
|
168
|
+
|
169
|
+
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:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
live_loop :bassline do
|
173
|
+
loop_rows_synth(8, (0..2), chord(:c2, :minor))
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
This will assign each of the three notes of the chord to each row, and now you can punch your baseline.
|
178
|
+
|
179
|
+
You can pass options, that will be applied to each note:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
live_loop :bassline do
|
183
|
+
loop_rows_synth(8, (0..2), chord(:c2, :minor), amp: 0.8)
|
184
|
+
end
|
185
|
+
```
|
186
|
+
|
187
|
+
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:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
live_loop :bassline do
|
191
|
+
loop_rows_synth(8, (0..2), chord(:c2, :minor), -> {{ pan: rrand(-1..1), cutoff: fader(5, (60..120)) }}
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
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.
|
196
|
+
|
197
|
+
### Free play
|
198
|
+
|
199
|
+
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.
|
200
|
+
|
201
|
+
#### `free_play(row, col, notes, [options])`
|
202
|
+
|
203
|
+
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`.
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
live_loop :bass do
|
207
|
+
use_synth :fm
|
208
|
+
free_play 0, 0, scale(:c3, :major), amp: 0.8
|
209
|
+
end
|
210
|
+
```
|
211
|
+
|
212
|
+
#### `reset_free_play(row, col, notes, [options])`
|
213
|
+
|
214
|
+
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.
|
215
|
+
|
216
|
+
### Roadmap of planned features
|
217
|
+
|
218
|
+
* A `selector` that actually works
|
219
|
+
* Free play with samples
|
220
|
+
* Improvements to `attach_fader` so that a couple of things that are currently not possible, are:
|
221
|
+
* Using it to control the mixer (`set_mixer_control!`, etc.)
|
222
|
+
* Attaching the same fader to different nodes, or different properties of the same node
|
223
|
+
* Better performance and stability in general
|
224
|
+
|
225
|
+
## Contributing
|
226
|
+
|
227
|
+
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](https://github.com/porras/sonic-pi-akai-apc-mini/blob/master/CODE_OF_CONDUCT.md).
|
228
|
+
|
229
|
+
## License
|
230
|
+
|
231
|
+
This code is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
232
|
+
|
233
|
+
## Code of Conduct
|
234
|
+
|
235
|
+
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](https://github.com/porras/sonic-pi-akai-apc-mini/blob/master/CODE_OF_CONDUCT.md).
|
236
|
+
|
237
|
+
## Acknowledgments
|
238
|
+
|
239
|
+
Apart from the excellent documentation of the Sonic Pi project, [this wonderful summary](https://github.com/TomasHubelbauer/akai-apc-mini) by Tomáš Hübelbauer took me from barely knowing what MIDI is to a functional prototype in a couple of hours. Cheers!
|
data/Rakefile
ADDED
data/akai-apc-mini.jpg
ADDED
Binary file
|
data/bin/console
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'sonic/pi/akai/apc/mini'
|
4
|
+
|
5
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
6
|
+
# with your gem easier. You can also use a different console, if you like.
|
7
|
+
|
8
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
9
|
+
# require "pry"
|
10
|
+
# Pry.start
|
11
|
+
|
12
|
+
require 'irb'
|
13
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
path_to_init = File.expand_path(File.join(__dir__, '..', 'init'))
|
4
|
+
|
5
|
+
if ARGV.first == 'path'
|
6
|
+
puts path_to_init
|
7
|
+
else
|
8
|
+
puts <<~RUBY
|
9
|
+
# To load sonic-pi-akai-apc-mini into Sonic Pi, add the following to your
|
10
|
+
# buffer, or ~/.sonic-pi/config/init.rb
|
11
|
+
require '#{path_to_init}'
|
12
|
+
RUBY
|
13
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
module SonicPiAkaiApcMini
|
2
|
+
module API
|
3
|
+
def switch?(line, col)
|
4
|
+
n = (line * 8) + col
|
5
|
+
!!get("switch_#{n}")
|
6
|
+
end
|
7
|
+
|
8
|
+
# default `target` is 0-0.999 instead of 0.1 because many parameters have
|
9
|
+
# [0,1) as range and throw an error when passed 1 (e.g. tb303 synth's res).
|
10
|
+
# It's not the most common usecase, but for the common use case it makes no
|
11
|
+
# difference so I think it's a good default.
|
12
|
+
def fader(n, target = (0..0.999), _options = {})
|
13
|
+
# TODO: Try to optimize speed, there is some latency because the
|
14
|
+
# controller send a lot of events (too much granularity)
|
15
|
+
value = get("fader_#{n}", 0)
|
16
|
+
Helpers.normalize(value, target)
|
17
|
+
end
|
18
|
+
|
19
|
+
def attach_fader(n, node, property, target = (0..0.999))
|
20
|
+
control node, property => fader(n, target)
|
21
|
+
set "attached_fader_#{n}", node: node, property: property, target: target
|
22
|
+
end
|
23
|
+
|
24
|
+
def loop_rows(duration, rows)
|
25
|
+
first_row = rows.keys.first
|
26
|
+
8.times do |beat|
|
27
|
+
prev = (beat - 1) % 8
|
28
|
+
midi_note_on (first_row * 8) + prev, get("switch_#{(first_row * 8) + prev}") ? 1 : 0
|
29
|
+
midi_note_on (first_row * 8) + beat, 5
|
30
|
+
rows.each do |row, sound|
|
31
|
+
in_thread(&sound) if get("switch_#{(row * 8) + beat}")
|
32
|
+
end
|
33
|
+
sleep duration / 8.0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def loop_rows_synth(duration, rows, notes, options = {})
|
38
|
+
8.times do |beat|
|
39
|
+
prev = (beat - 1) % 8
|
40
|
+
midi_note_on (rows.first * 8) + prev, get("switch_#{(rows.first * 8) + prev}") ? 1 : 0
|
41
|
+
midi_note_on (rows.first * 8) + beat, 5
|
42
|
+
rows.each.with_index do |row, index|
|
43
|
+
opts = options.respond_to?(:call) ? options.call : options
|
44
|
+
play(notes[index], opts) if get("switch_#{(row * 8) + beat}")
|
45
|
+
end
|
46
|
+
sleep duration / 8.0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def reset_free_play(row, col, size)
|
51
|
+
size = size.size unless size.is_a?(Integer) # so we can pass the same ring
|
52
|
+
Helpers.key_range(row, col, size).each do |key|
|
53
|
+
midi_note_on key, 0
|
54
|
+
set "free_play_#{key}", nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def free_play(row, col, notes, options = {})
|
59
|
+
Helpers.key_range(row, col, notes.size).each.with_index do |key, i|
|
60
|
+
midi_note_on key, 5
|
61
|
+
set "free_play_#{key}", notes[i]
|
62
|
+
end
|
63
|
+
|
64
|
+
use_real_time
|
65
|
+
|
66
|
+
message = sync(:free_play)
|
67
|
+
note_control = play message[:note], { sustain: 9999 }.merge(options)
|
68
|
+
set "free_play_playing_#{message[:key]}", note_control
|
69
|
+
end
|
70
|
+
|
71
|
+
def selector(row, col, values)
|
72
|
+
# TODO: selector is quite messy. It can use a refactor and proper reset/cleanup (like free_play's).
|
73
|
+
krange = Helpers.key_range(row, col, values.size)
|
74
|
+
set "selector_values_#{krange}", values.ring
|
75
|
+
set "selector_current_value_#{krange}", 0 if get("selector_current_value_#{krange}").nil?
|
76
|
+
krange.each.with_index do |key, i|
|
77
|
+
set "selector_keys_#{key}", krange.to_a
|
78
|
+
midi_note_on key, i == get("selector_current_value_#{krange}") ? 1 : 3
|
79
|
+
end
|
80
|
+
values[get("selector_current_value_#{krange}")]
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize_akai
|
84
|
+
# This loop manages faders. Whenever they change, the new value is stored via set,
|
85
|
+
# and the corresponding light is turned on/off.
|
86
|
+
live_loop :faders do
|
87
|
+
use_real_time
|
88
|
+
n, value = sync('/midi:apc_mini_apc_mini_midi_1_20_0:1/control_change')
|
89
|
+
set "fader_#{n - 48}", value
|
90
|
+
midi_note_on n - 48 + 64, value.zero? ? 0 : 1
|
91
|
+
if attachment = get("attached_fader_#{n - 48}")
|
92
|
+
normalized_value = Helpers.normalize(value, attachment[:target])
|
93
|
+
control attachment[:node], attachment[:property] => normalized_value
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Manages the buttons in the grid, both as switches and to "free play". Whenever one is,
|
98
|
+
# pressed, we check if that row is being used to "free play". If it is, we play. If it's
|
99
|
+
# not, we manage it as a switch.
|
100
|
+
live_loop :switches_and_freeplay do
|
101
|
+
use_real_time
|
102
|
+
n, _vel = sync('/midi:apc_mini_apc_mini_midi_1_20_0:1/note_on')
|
103
|
+
if note = get("free_play_#{n}")
|
104
|
+
cue :free_play, note: note, key: n
|
105
|
+
elsif keys = get("selector_keys_#{n}")
|
106
|
+
keys.each do |k|
|
107
|
+
midi_note_on k, 3
|
108
|
+
end
|
109
|
+
midi_note_on n, 1
|
110
|
+
set "selector_current_value_#{keys.first}..#{keys.last}", n - keys.first
|
111
|
+
else
|
112
|
+
new_value = !get("switch_#{n}", false)
|
113
|
+
set "switch_#{n}", new_value
|
114
|
+
midi_note_on n, (new_value ? 1 : 0)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
live_loop :free_play_note_offs do
|
119
|
+
use_real_time
|
120
|
+
n, _vel = sync('/midi:apc_mini_apc_mini_midi_1_20_0:1/note_off')
|
121
|
+
if note_control = get("free_play_playing_#{n}")
|
122
|
+
release = note_control.args['release'] || note_control.info.arg_defaults[:release]
|
123
|
+
control note_control, amp: 0, amp_slide: release
|
124
|
+
at(release) { note_control.kill }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
include SonicPiAkaiApcMini::API
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SonicPiAkaiApcMini
|
2
|
+
module SPThreadSafeRange
|
3
|
+
def __sp_make_thread_safe
|
4
|
+
(self.begin.__sp_make_thread_safe..self.end.__sp_make_thread_safe).freeze
|
5
|
+
end
|
6
|
+
|
7
|
+
def sp_thread_safe?
|
8
|
+
frozen? && self.begin.sp_thread_safe? && self.end.sp_thread_safe?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Range.prepend SonicPiAkaiApcMini::SPThreadSafeRange
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SonicPiAkaiApcMini
|
2
|
+
module Helpers
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def normalize(value, target)
|
6
|
+
value /= 127.0
|
7
|
+
target = (-1..1) if target == :pan
|
8
|
+
case target
|
9
|
+
when Range
|
10
|
+
target.begin + (value * (target.end - target.begin))
|
11
|
+
when Array, SonicPi::Core::RingVector
|
12
|
+
index = ((target.size - 1) * value).round
|
13
|
+
target[index]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def key_range(row, col, max_size)
|
18
|
+
first = (row * 8) + col
|
19
|
+
last = [(row * 8) + 7, first + max_size - 1].min
|
20
|
+
(first..last)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sonic-pi-akai-apc-mini
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sergio Gil
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-01-09 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- sgilperez@gmail.com
|
16
|
+
executables:
|
17
|
+
- sonic-pi-akai-apc-mini
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- ".rspec"
|
22
|
+
- ".rubocop.yml"
|
23
|
+
- ".ruby-version"
|
24
|
+
- CODE_OF_CONDUCT.md
|
25
|
+
- Gemfile
|
26
|
+
- Gemfile.lock
|
27
|
+
- LICENSE.txt
|
28
|
+
- README.md
|
29
|
+
- Rakefile
|
30
|
+
- akai-apc-mini.jpg
|
31
|
+
- bin/console
|
32
|
+
- bin/setup
|
33
|
+
- exe/sonic-pi-akai-apc-mini
|
34
|
+
- init.rb
|
35
|
+
- lib/sonic-pi-akai-apc-mini.rb
|
36
|
+
- lib/sonic-pi-akai-apc-mini/api.rb
|
37
|
+
- lib/sonic-pi-akai-apc-mini/core_extensions/range.rb
|
38
|
+
- lib/sonic-pi-akai-apc-mini/helpers.rb
|
39
|
+
homepage: https://github.com/porras/sonic-pi-akai-apc-mini
|
40
|
+
licenses:
|
41
|
+
- MIT
|
42
|
+
metadata:
|
43
|
+
homepage_uri: https://github.com/porras/sonic-pi-akai-apc-mini
|
44
|
+
source_code_uri: https://github.com/porras/sonic-pi-akai-apc-mini
|
45
|
+
rubygems_mfa_required: 'true'
|
46
|
+
post_install_message: Remember to run sonic-pi-akai-apc-mini to get instructions on
|
47
|
+
how to load the new version into Sonic Pi
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 2.7.0
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubygems_version: 3.3.3
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: Utility functions to use the Akai APC mini MIDI controller with Sonic Pi
|
66
|
+
test_files: []
|