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 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
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
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
@@ -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
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sonic-pi-akai-apc-mini.gemspec
4
+ gemspec
5
+
6
+ gem 'rake'
7
+ gem 'rspec'
8
+ gem 'rubocop'
9
+ gem 'rubocop-rake'
10
+ gem 'rubocop-rspec'
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
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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,3 @@
1
+ require_relative './lib/sonic-pi-akai-apc-mini'
2
+
3
+ include SonicPiAkaiApcMini::API
@@ -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
@@ -0,0 +1,3 @@
1
+ require_relative './sonic-pi-akai-apc-mini/core_extensions/range'
2
+ require_relative './sonic-pi-akai-apc-mini/helpers'
3
+ require_relative './sonic-pi-akai-apc-mini/api'
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: []