concertina

Elecronic concertina
git clone https://git.woozle.org/neale/concertina.git

concertina / src
Neale Pickett  ·  2026-02-10

concertina.ino

  1#include <U8g2lib.h>
  2#include <Adafruit_Keypad.h>
  3#include <Adafruit_DotStar.h>
  4#include "layouts.h"
  5#include "ruby.h"
  6
  7#define VS1053_RESET 2 // This is the pin that connects to the RESET pin on VS1053
  8
  9// See http://www.vlsi.fi/fileadmin/datasheets/vs1053.pdf Pg 31
 10#define VS1053_BANK_DEFAULT 0x00
 11#define VS1053_BANK_DRUMS1 0x78
 12#define VS1053_BANK_DRUMS2 0x7F
 13#define VS1053_BANK_MELODY 0x79
 14
 15// See http://www.vlsi.fi/fileadmin/datasheets/vs1053.pdf Pg 32 for more!
 16#define VS1053_GM1_OCARINA 21
 17
 18#define MIDI_NOTE_ON  0x90
 19#define MIDI_NOTE_OFF 0x80
 20#define MIDI_CHAN_MSG 0xB0
 21#define MIDI_CHAN_BANK 0x00
 22#define MIDI_CHAN_VOLUME 0x07
 23#define MIDI_CHAN_PROGRAM 0xC0
 24
 25// Serial1 on the ItsyBitsy M0 Express is marked on the silkscreen
 26#define VS1053_MIDI Serial1
 27
 28// There's only one DotStar
 29Adafruit_DotStar DotStar(DOTSTAR_NUM, PIN_DOTSTAR_DATA, PIN_DOTSTAR_CLK, DOTSTAR_BRG);
 30
 31// An itsy bitsy OLED display
 32U8G2_SSD1306_64X32_1F_F_HW_I2C Display(U8G2_R0);
 33
 34/* Maps grid positions to layout positions.
 35
 36   It's in octal: "0%d%d%d" % (side, row, col)
 37
 38   There are four possible sides: left, right, meta, and undefined.
 39   There are eight possible rows.
 40   There are eight possible columns.
 41*/
 42#define ROWS 6
 43#define COLS 6
 44byte keyMap[ROWS][COLS] = {
 45  { 0020, 0010, 0000, 0104, 0114, 0124 },
 46  { 0021, 0011, 0001, 0103, 0113, 0123 },
 47  { 0022, 0012, 0002, 0102, 0112, 0122 },
 48  { 0023, 0013, 0003, 0101, 0111, 0121 },
 49  { 0024, 0014, 0004, 0100, 0110, 0120 },
 50  { 0200, 0201, 0202, 0203, 0204, 0205 },
 51};
 52byte rowPins[ROWS] = {14, 15, 16, 17, 18, 19};
 53byte colPins[COLS] = { 5,  7,  9, 10, 11, 12};
 54Adafruit_Keypad Keypad((byte *)keyMap, rowPins, colPins, ROWS, COLS);
 55
 56int currentLayoutIdx = 0;
 57layout_t currentLayout;
 58
 59// Load a layout into our button mapping
 60void nextLayout(int adj) {
 61  int nlayouts = sizeof(layouts) / sizeof(*layouts);
 62  currentLayoutIdx = (currentLayoutIdx + nlayouts + adj) % nlayouts;
 63  currentLayout = layouts[currentLayoutIdx];
 64}
 65
 66void error() {
 67  bool led = false;
 68  for (;;) {
 69    led = !led;
 70    digitalWrite(LED_BUILTIN, led);
 71    delay(250);
 72  }
 73}
 74
 75void setup() {
 76  Serial.begin(9600);
 77  Serial.println("Ruby MIDI Concertina");
 78
 79  pinMode(LED_BUILTIN, OUTPUT);
 80  pinMode(VS1053_RESET, OUTPUT);
 81
 82  // Reset VS1053
 83  digitalWrite(VS1053_RESET, LOW);
 84  delay(10);
 85  digitalWrite(VS1053_RESET, HIGH);
 86  delay(10);
 87  VS1053_MIDI.begin(31250); // MIDI uses a 'strange baud rate'
 88
 89  midiSetChannelBank(0, VS1053_BANK_MELODY);
 90  midiSetInstrument(0, VS1053_GM1_OCARINA);
 91  midiSetChannelVolume(0, 127);
 92
 93  // Set up keyboard
 94  Keypad.begin();
 95  nextLayout(0);
 96
 97  // Use the display
 98  Display.begin();
 99  Display.firstPage();
100  Display.setFont(u8g2_font_helvR08_tf);
101  Display.drawUTF8(0, 10, currentLayout.name);
102  Display.nextPage();
103
104  // Turn off the DotStar
105  DotStar.begin();
106  DotStar.clear();
107  DotStar.show();
108}
109
110void displayNoteName(int8_t note) {
111  char base = 0;
112  int accidental = 0;
113  char out[32] = {};
114  int pos = 0;
115
116  switch (note % 12) {
117  case 10:
118    accidental = -1;
119  case 11:
120    base = 'B';
121    break;
122  case 9:
123    base = 'A';
124    break;
125  case 8:
126    accidental = 1;
127  case 7:
128    base = 'G';
129    break;
130  case 6:
131    accidental = 1;
132  case 5:
133    base = 'F';
134    break;
135  case 4:
136    base = 'E';
137    break;
138  case 3:
139    accidental = 1;
140  case 2:
141    base = 'D';
142    break;
143  case 1:
144    accidental = 1;
145  case 0:
146    base = 'C';
147    break;
148  }
149
150  if (note > 71) {
151    base += 32;
152  }
153  out[pos++] = base;
154
155  switch (accidental) {
156  case -1:
157    out[pos++] = '_';
158    break;
159  case 1:
160    out[pos++] = '^';
161    break;
162  }
163
164  while (note > 83) {
165    out[pos++] = '\'';
166    note -= 12;
167  }
168  while (note < 60) {
169    out[pos++] = ',';
170    note += 12;
171  }
172  out[pos++] = ' ';
173
174  Display.print((char *)out);
175}
176
177void loop() {
178  Keypad.tick();
179
180  if (Keypad.available()) {
181    int push = Keypad.isPressed(0205) ? 1 : 0;
182
183    Display.setFont(u8g2_font_helvR08_tr);
184    Display.setDrawColor(0);
185    Display.drawBox(0, 10, 64, 12);
186    Display.setDrawColor(1);
187    Display.setCursor(0, 20);
188    while (Keypad.available()) {
189      keypadEvent k = Keypad.read();
190
191      int side = (k.bit.KEY >>6) & 0b11;
192      int row = (k.bit.KEY >> 3) & 0b111;
193      int col = (k.bit.KEY >> 0) & 0b111;
194      bool pressed = (k.bit.EVENT == KEY_JUST_PRESSED);
195
196      // Now look up the note
197      int8_t note = currentLayout.notes[side][row][col][push];
198
199      Serial.printf("%03o %d,%d,%d %d %d\n", k.bit.KEY, side, col, row, note, k.bit.EVENT);
200      //Display.fillRect(0, 16, 40, 8, BLACK);
201      if (pressed) {
202        displayNoteName(note);
203      }
204      VS1053_MIDI.write(pressed ? MIDI_NOTE_ON : MIDI_NOTE_OFF);
205      VS1053_MIDI.write(note);
206      VS1053_MIDI.write(127);
207    }
208    Display.nextPage();
209  }
210}
211
212void midiSetInstrument(uint8_t chan, uint8_t inst) {
213  if (chan > 15) return;
214  inst --; // page 32 has instruments starting with 1 not 0 :(
215  if (inst > 127) return;
216  
217  VS1053_MIDI.write(MIDI_CHAN_PROGRAM | chan);  
218  VS1053_MIDI.write(inst);
219}
220
221
222void midiSetChannelVolume(uint8_t chan, uint8_t vol) {
223  if (chan > 15) return;
224  if (vol > 127) return;
225  
226  VS1053_MIDI.write(MIDI_CHAN_MSG | chan);
227  VS1053_MIDI.write(MIDI_CHAN_VOLUME);
228  VS1053_MIDI.write(vol);
229}
230
231void midiSetChannelBank(uint8_t chan, uint8_t bank) {
232  if (chan > 15) return;
233  if (bank > 127) return;
234  
235  VS1053_MIDI.write(MIDI_CHAN_MSG | chan);
236  VS1053_MIDI.write((uint8_t)MIDI_CHAN_BANK);
237  VS1053_MIDI.write(bank);
238}
239
240void midiNoteOn(uint8_t chan, uint8_t n, uint8_t vel) {
241  if (chan > 15) return;
242  if (n > 127) return;
243  if (vel > 127) return;
244  
245  VS1053_MIDI.write(MIDI_NOTE_ON | chan);
246  VS1053_MIDI.write(n);
247  VS1053_MIDI.write(vel);
248}
249
250void midiNoteOff(uint8_t chan, uint8_t n, uint8_t vel) {
251  if (chan > 15) return;
252  if (n > 127) return;
253  if (vel > 127) return;
254  
255  VS1053_MIDI.write(MIDI_NOTE_OFF | chan);
256  VS1053_MIDI.write(n);
257  VS1053_MIDI.write(vel);
258}