Control-Surface
Control-Surface copied to clipboard
Transposing a "Bankable::ManyAddresses::NoteButtonMatrix"
Hi, Is it possible to transpose a "Bankable::ManyAddresses::NoteButtonMatrix" using Control Surface? If so, I very much appreciate if you can show me an example as to how you assign the transposer object to the matrix. I also need to implement both a semitone and octave transposer to the same matrix, is this even possible? Here is how the matrix is currently defined:
Bank<5> bank = {16}; // 5 banks, 16 addresses per banks
Bankable::ManyAddresses::NoteButtonMatrix<5, 4, 4> buttons {
bank,
{0, 1, 2, 3}, // row pins (outputs, driven low-Z low !)
{10, 9, 7, 4}, // column pins (inputs, hi-Z)
// Now the list of address matrices, one for each bank
{{
{{{60,61,62,63},{64,65,66,67},{68,69,70,71},{72,73,74,75}}}, // Bank 0, Chromatic
{{{60,62,64,65},{67,69,71,72},{74,76,77,79},{81,83,84,86}}}, // Bank 1, Major
{{{60,62,63,65},{67,68,70,72},{74,75,77,79},{80,82,84,86}}}, // Bank 2, Minor
{{{60,63,65,67},{70,72,75,77},{79,82,84,87},{89,91,94,96}}}, // Bank 3, Minor Pentatonic
{{{60,63,65,67},{70,72,75,77},{79,82,84,87},{89,91,94,96}}}, // Bank 4, To be defined
}},
// Finally, the list of MIDI channels, one for each bank
{
CHANNEL_1, // first bank
CHANNEL_1, // second bank
CHANNEL_1, // third bank
},
};
Yes, this is possible, most of the components are there, but you have to write an adapter to combine them. Something like this should work:
#include <Control_Surface.h>
// ----------------------------------------------------------------------------------------------------- //
// Note: By address, I mean the note number and the channel. The channel doesn't change in
// this example, but the note number changes when changing to a different scale or by
// transposing.
//
// You need to combine three different addresses:
// 1. Select the address corresponding to a given row and column in the address matrix,
// for one of the 5 scales.
// 2. Add the required number of semitones to transpose this address.
// 3. Add the required number of octaves to transpose this address.
//
// The following class keeps track of these three addresses. One bank is used to select the
// scale, and two transposers are used to change the transposition (one for semitones, one
// for octaves)
//
template <uint8_t NumAddresses, uint8_t Rows, uint8_t Cols>
class TransposeManyAddresses {
public:
// Constructor
template <int8_t MinSemitone, int8_t MaxSemitone,
int8_t MinOctave, int8_t MaxOctave>
TransposeManyAddresses(const Bank<NumAddresses> &addressBank,
const Array<AddressMatrix<Rows, Cols>, NumAddresses> &addresses,
const Array<MIDIChannelCN, NumAddresses> &channelCNs,
Transposer<MinSemitone, MaxSemitone> &semitoneTransposer,
Transposer<MinOctave, MaxOctave> &octaveTransposer)
: addressMatrix(addressBank, addresses, channelCNs),
semitoneTransposer(semitoneTransposer),
octaveTransposer(octaveTransposer) {}
// Prevents changing the address while a button in the matrix is pressed.
void lock() {
addressMatrix.lock();
semitoneTransposer.lock();
octaveTransposer.lock();
}
// Allow changing of the address again.
void unlock() {
addressMatrix.unlock();
semitoneTransposer.unlock();
octaveTransposer.unlock();
}
// This function computes the effective address, looking up the note number for
// the given row and column in the matrix, taking into account the selected scale
// and then adding the transposition.
MIDIAddress getActiveAddress(uint8_t row, uint8_t col) const {
return addressMatrix.getActiveAddress(row, col)
+ int8_t(semitoneTransposer.getSelection())
+ 12 * int8_t(octaveTransposer.getSelection());
}
private:
Bankable::ManyAddresses::ManyMatrixAddresses<NumAddresses, Rows, Cols> addressMatrix;
ManyAddresses_Base semitoneTransposer;
ManyAddresses_Base octaveTransposer;
};
// This is an adapter to the Bankable::MIDIButtonMatrix for convenience and consistency
// with the built-in Bankable::ManyAddresses::NoteButtonMatrix class.
template <uint8_t NumAddresses, uint8_t Rows, uint8_t Cols>
struct TransposeManyAddressesNoteButtonMatrix
: Bankable::MIDIButtonMatrix<TransposeManyAddresses<NumAddresses, Rows, Cols>,
DigitalNoteSender, Rows, Cols> {
using MIDIButtonMatrix = Bankable::MIDIButtonMatrix<TransposeManyAddresses<NumAddresses, Rows, Cols>,
DigitalNoteSender, Rows, Cols>;
template <int8_t MinSemitone, int8_t MaxSemitone,
int8_t MinOctave, int8_t MaxOctave>
TransposeManyAddressesNoteButtonMatrix(const Bank<NumAddresses> &addressBank,
Transposer<MinSemitone, MaxSemitone> &semitoneTransposer,
Transposer<MinOctave, MaxOctave> &octaveTransposer,
const PinList<Rows> &rowPins, const PinList<Cols> &colPins,
const Array<AddressMatrix<Rows, Cols>, NumAddresses> &addresses,
const Array<MIDIChannelCN, NumAddresses> &channelCNs,
uint8_t velocity = 0x7F)
: MIDIButtonMatrix({addressBank, addresses, channelCNs, semitoneTransposer, octaveTransposer},
rowPins, colPins,
{velocity}) {}
};
// ----------------------------------------------------------------------------------------------------- //
USBDebugMIDI_Interface midi;
Bank<5> scaleBank;
Transposer<-11, +11> semitoneTransposer;
Transposer<-2, +2> octaveTransposer;
IncrementSelector<5> scaleSelector { scaleBank, A0 };
IncrementSelector<23> semitoneSelector { semitoneTransposer, A1 };
IncrementSelector<5> octaveSelector { octaveTransposer, A2 };
TransposeManyAddressesNoteButtonMatrix<5, 4, 4> buttons {
scaleBank, // Select which one of the 5 scales is selected
semitoneTransposer, // Transposition (one semitone at a time)
octaveTransposer, // Transposition (one octave at a time)
{0, 1, 2, 3}, // row pins (outputs, driven low-Z low !)
{4, 5, 6, 7}, // column pins (inputs, hi-Z)
// Now the list of address matrices, one for each bank
{{
{{{60,61,62,63},{64,65,66,67},{68,69,70,71},{72,73,74,75}}}, // Bank 0, Chromatic
{{{60,62,64,65},{67,69,71,72},{74,76,77,79},{81,83,84,86}}}, // Bank 1, Major
{{{60,62,63,65},{67,68,70,72},{74,75,77,79},{80,82,84,86}}}, // Bank 2, Minor
{{{60,63,65,67},{70,72,75,77},{79,82,84,87},{89,91,94,96}}}, // Bank 3, Minor Pentatonic
{{{60,63,65,67},{70,72,75,77},{79,82,84,87},{89,91,94,96}}}, // Bank 4, To be defined
}},
// Finally, the list of MIDI channels, one for each bank
{
CHANNEL_1, // Bank 0
CHANNEL_1, // Bank 1
CHANNEL_1, // Bank 2
CHANNEL_1, // Bank 3
CHANNEL_1, // Bank 4
},
};
void setup() {
Control_Surface.begin();
}
void loop() {
Control_Surface.loop();
}
I don't have much time to add more comments right now, but if you have any questions, just let me know.
First of all, I just have to say that the support for this library is simply amazing!! Your example works perfectly.., however, there is a small issue that you might be able to clarify.
In my project I'm not using separate controllers for selecting scale, semitone and octave transpose. Instead I'm using a common rotary encoder where I select each function individually. The values are changed within the main loop using scaleBank.select(), semitoneTransposer.select() and octaveTransposer.select(). scaleBank.select() works fine when providing the scaleBank index. However, when selecting semitone and octave the notes drops a couple of octaves. I would have guessed that semitoneTransposer.select(1) would raise the notes by one semitone, but it also drops the note by a couple of octaves.
Could you please clarify how the values should be provided to the transposer.select() function?
Bank and transposer settings start at 0, because of the way the library evolved. This means that semitoneTransposer.select(0)
selects the lowest transposition, i.e. -11 semitones in the example above. I don't think this is a clear API, so I'll probably fix that in the 2.0 release. For now, you'll have to add the offset yourself:
int8_t transposition = -5;
semitoneTransposer.select(semitoneTransposer.getInitialSelection() + transposition);
I'm not able to test it right now, so if you encounter any problems, please let me know.
Thanks, figured it out on my own so all is good. Probably a good idea to let 0 be no transpose and then use positive and negative values. More natural and user friendly. Thanks for all your help