Push2: Implement a vertical interval for "tuning" the pads

The implementation is generic, but the actual call is hardcoded to 4ths here.
UI to follow.
This commit is contained in:
David Robillard 2021-06-19 00:52:29 -04:00 committed by Paul Davis
parent b35796af75
commit d5ca05edff
2 changed files with 210 additions and 102 deletions

View File

@ -17,6 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <algorithm>
#include <bitset>
#include <stdlib.h>
#include <pthread.h>
@ -1330,126 +1333,184 @@ Push2::set_pad_note_kind (Pad& pad, const PadNoteKind kind)
write (pad.state_msg ());
}
void
Push2::set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey)
/** Return a bitset of notes in a musical mode.
*
* The returned bitset has a bit for every possible MIDI note number, which is
* set if the note is in the mode in any octave.
*/
static std::bitset<128>
mode_notes_bitset (const int scale_root,
const int octave,
const MusicalMode::Type mode)
{
MusicalMode m (mode);
std::vector<float>::iterator interval;
int note;
const int original_root = root;
std::bitset<128> notes_bitset;
interval = m.steps.begin();
root += (octave*12);
note = root;
const std::vector<float> mode_steps = MusicalMode (mode).steps;
int root = scale_root - 12;
const int root_start = root;
std::set<int> mode_map; /* contains only notes in mode, O(logN) lookup */
std::vector<int> mode_vector; /* sorted in note order */
mode_map.insert (note);
mode_vector.push_back (note);
/* build a map of all notes in the mode, from the root to 127 */
while (note < 128) {
if (interval == m.steps.end()) {
/* last distance was the end of the scale,
so wrap, adding the next note at one
octave above the last root.
*/
interval = m.steps.begin();
// Repeatedly loop through the intervals in an octave
for (std::vector<float>::const_iterator i = mode_steps.begin ();;) {
if (i == mode_steps.end ()) {
// Reached the end of the scale, continue with the next octave
root += 12;
mode_map.insert (root);
mode_vector.push_back (root);
if (root > 127) {
break;
}
notes_bitset.set (root);
i = mode_steps.begin ();
} else {
note = (int) floor (root + (2.0 * (*interval)));
interval++;
mode_map.insert (note);
mode_vector.push_back (note);
const int note = (int)floor (root + (2.0 * (*i)));
if (note > 127) {
break;
}
if (note > 0) {
notes_bitset.set (note);
}
++i;
}
}
return notes_bitset;
}
/** Return a sorted vector of all notes in a musical mode.
*
* The returned vector has every possible MIDI note number (0 through 127
* inclusive) that is in the mode in any octave.
*/
static std::vector<int>
mode_notes_vector (const int scale_root,
const int octave,
const MusicalMode::Type mode)
{
std::vector<int> notes_vector;
const std::vector<float> mode_steps = MusicalMode (mode).steps;
int root = scale_root - 12;
// Repeatedly loop through the intervals in an octave
for (std::vector<float>::const_iterator i = mode_steps.begin ();;) {
if (i == mode_steps.end ()) {
// Reached the end of the scale, continue with the next octave
root += 12;
if (root > 127) {
break;
}
notes_vector.push_back (root);
i = mode_steps.begin ();
} else {
const int note = (int)floor (root + (2.0 * (*i)));
if (note > 127) {
break;
}
if (note > 0) {
notes_vector.push_back (note);
}
++i;
}
}
return notes_vector;
}
void
Push2::set_pad_scale_in_key (const int scale_root,
const int octave,
const MusicalMode::Type mode,
const int ideal_vertical_semitones)
{
const std::vector<int> notes = mode_notes_vector (scale_root, octave, mode);
for (int row = 0; row < 8; ++row) {
// The ideal leftmost note in a row is based only on the "tuning"
const int ideal_leftmost_note = 36 + (ideal_vertical_semitones * row);
// If that's in the scale, use it, otherwise use the closest higher note
std::vector<int>::const_iterator n =
std::lower_bound (notes.begin (), notes.end (), ideal_leftmost_note);
// Set up the the following columns in the row using the scale
for (int col = 0; col < 8 && n != notes.end (); ++col) {
const int note = *n++;
const int index = 36 + (row * 8) + col;
const boost::shared_ptr<Pad>& pad = _nn_pad_map[index];
pad->filtered = note; // Generated note number
_fn_pad_map.insert (std::make_pair (note, pad));
if ((note % 12) == scale_root) {
set_pad_note_kind (*pad, RootNote);
} else {
set_pad_note_kind (*pad, InScaleNote);
}
}
}
}
void
Push2::set_pad_scale_chromatic (const int scale_root,
const int octave,
const MusicalMode::Type mode,
const int vertical_semitones)
{
const std::bitset<128> notes = mode_notes_bitset (scale_root, octave, mode);
for (int row = 0; row < 8; ++row) {
// The leftmost note in a row is just based only on the "tuning"
const int leftmost_note = 36 + (vertical_semitones * row);
// Set up the the following columns in the row using the scale
for (int col = 0; col < 8; ++col) {
const int note = leftmost_note + col;
const int index = 36 + (row * 8) + col;
const boost::shared_ptr<Pad>& pad = _nn_pad_map[index];
pad->filtered = note; // Generated note number
_fn_pad_map.insert (std::make_pair (note, pad));
if (!notes.test (note)) {
set_pad_note_kind (*pad, OutOfScaleNote);
} else if ((note % 12) == scale_root) {
set_pad_note_kind (*pad, RootNote);
} else {
set_pad_note_kind (*pad, InScaleNote);
}
}
}
}
void
Push2::set_pad_scale (const int scale_root,
const int octave,
const MusicalMode::Type mode,
const bool inkey)
{
// Clear the pad map and call the appropriate method to set them up again
_fn_pad_map.clear ();
if (inkey) {
std::vector<int>::iterator notei;
int row_offset = 0;
for (int row = 0; row < 8; ++row) {
/* Ableton's grid layout wraps the available notes in the scale
* by offsetting 3 notes per row (from the bottom)
*/
notei = mode_vector.begin();
notei += row_offset;
row_offset += 3;
for (int col = 0; col < 8; ++col) {
int index = 36 + (row*8) + col;
boost::shared_ptr<Pad> pad = _nn_pad_map[index];
int notenum;
if (notei != mode_vector.end()) {
notenum = *notei;
pad->filtered = notenum;
_fn_pad_map.insert (make_pair (notenum, pad));
if ((notenum % 12) == original_root) {
set_pad_note_kind(*pad, RootNote);
} else {
set_pad_note_kind(*pad, InScaleNote);
}
notei++;
} else {
pad->filtered = -1;
set_pad_note_kind(*pad, OutOfScaleNote);
}
}
}
set_pad_scale_in_key(scale_root, octave, mode, 5);
} else {
/* chromatic: all notes available, but highlight those in the scale */
for (note = 36; note < 100; ++note) {
boost::shared_ptr<Pad> pad = _nn_pad_map[note];
/* Chromatic: all pads play, half-tone steps. Light
* those in the scale, and highlight root notes
*/
pad->filtered = root_start + (note - 36);
_fn_pad_map.insert (make_pair (pad->filtered, pad));
if (mode_map.find (note) == mode_map.end()) {
pad->filtered = -1;
set_pad_note_kind(*pad, OutOfScaleNote);
} else if ((note % 12) == original_root) {
set_pad_note_kind(*pad, RootNote);
} else {
set_pad_note_kind(*pad, InScaleNote);
}
}
set_pad_scale_chromatic(scale_root, octave, mode, 5);
}
/* store state */
// Store state
bool changed = false;
if (_scale_root != original_root) {
_scale_root = original_root;
if (_scale_root != scale_root) {
_scale_root = scale_root;
changed = true;
}
if (_root_octave != octave) {

View File

@ -331,6 +331,53 @@ class Push2 : public ARDOUR::ControlProtocol
/// Set up a pad to represent a "kind" of note
void set_pad_note_kind(Pad& pad, PadNoteKind kind);
/** Set an "in-key" scale on the pads.
*
* "In-key" mode shows only notes which are in the scale, so every pad is
* in the scale and there are no "spaces". This provides access to a wide
* range of notes in the scale, but no access to notes outside the scale at
* all.
*
*
* @param root The root note in the lowest octave (at most 11).
*
* @param octave The octave number of the "actual" root (at most 10).
*
* @param mode The active musical mode (scale).
*
* @param ideal_vertical_semitones The ideal interval between rows in
* semitones. This is an "ideal" because it may not be possible to use
* exactly this interval for every row depending on the scale. It may be
* bumped up to the next note in the scale if necessary, so with this mode,
* rows are not guaranteed to all have the same vertical interval.
*/
void set_pad_scale_in_key (int root,
int octave,
MusicalMode::Type mode,
int ideal_vertical_semitones);
/** Set a "chromatic" scale on the pads.
*
* "Chromatic" mode is chromatic from left to right, and "tuned" to some
* interval from bottom up, like a stringed instrument. The default
* vertical interval is 5 semitones, or a perfect 4th, like strings on a
* bass guitar.
*
* @param root The root note in the lowest octave (at most 11).
*
* @param octave The octave number of the "actual" root (at most 10).
*
* @param mode The active musical mode (scale).
*
* @param vertical_semitones The interval between rows in semitones. This
* mode guarantees that the vertical interval for all rows is always
* exactly this.
*/
void set_pad_scale_chromatic (int root,
int octave,
MusicalMode::Type mode,
int vertical_semitones);
void set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey);
PBD::Signal0<void> ScaleChange;