Remote controlled ultrasonic granular synthesizer
Devices and components
Arduino Uno Rev3
Ultrasonic sensor - HC-SR04
16x2 LCD screen (I2C)
MAX7219 8x8 LED Matrix
Speaker (generic) 8Ohm
6.35mm audio jacks
PAM-8610 12V DC Audio Amplifier
Potentiometer
push buttons
12V DC power supply
Software and tools
Arduino IDE
Project description
Main features
Hardware setup
Wiring Guide
Facility
Usage
FUTURE IMPROVEMENTS
Improve audio output, using dedicated libraries (e.g. Mozzi)
Add Midi Output functionality
Add configuration functionality, such as creating a new scale using push buttons or a joystick
Add a pitch wheel
Syntermino v 1.0
1// SYNTEREMINO v1.0 Arduino-based granular synthesizer with ultrasonic distance control
2// This code is released under GNU Lesser General Public License https://www.gnu.org/licenses/lgpl-3.0.html
3// The code is by Salvatore Mecca
4// Waveform management is taken from
5// Auduino, the Lo-Fi granular synthesiser
6//
7// by Peter Knight,
8//
9// Help: http://code.google.com/p/tinkerit/wiki/Auduino
10// More help: http://groups.google.com/group/auduino
11//
12// Analog in 1: Grain 2 decay
13// Analog in 2: Grain 1 decay
14// Analog in 3: Grain 2 pitch
15// Analog in 4: Grain repetition frequency
16
17#include <avr/pgmspace.h>
18#include <avr/io.h>
19#include <avr/interrupt.h>
20#include <LedControl.h>
21#include <LiquidCrystal_I2C.h>
22#include <SR04.h>
23
24// LED Matrix configuration
25#define DIN 10
26#define CS 9
27#define CLK 8
28LedControl lc = LedControl(DIN, CLK, CS, 0);
29
30// LCD configuration
31LiquidCrystal_I2C lcd(0x27, 16, 2);
32
33// Note definitions
34const int notes[] = {
35 31, 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, 65, 69, 73, 78, 82, 87, 93, 98, 104, 110, 117, 123, 131,
36 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466,
37 494, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568,
38 1661, 1760, 1865, 1976, 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 4186, 4435, 4699, 4978
39};
40
41const char* notes_name[] = {
42 "B0", "C1", "CS1", "D1", "DS1", "E1", "F1", "FS1", "G1", "GS1", "A1", "AS1", "B1", "C2", "CS2", "D2", "DS2", "E2",
43 "F2", "FS2", "G2", "GS2", "A2", "AS2", "B2", "C3", "CS3", "D3", "DS3", "E3", "F3", "FS3", "G3", "GS3", "A3", "AS3",
44 "B3", "C4", "CS4", "D4", "DS4", "E4", "F4", "FS4", "G4", "GS4", "A4", "AS4", "B4", "C5", "CS5", "D5", "DS5", "E5",
45 "F5", "FS5", "G5", "GS5", "A5", "AS5", "B5", "C6", "CS6", "D6", "DS6", "E6", "F6", "FS6", "G6", "GS6", "A6", "AS6",
46 "B6", "C7", "CS7", "D7", "DS7", "E7", "F7", "FS7", "G7", "GS7", "A7", "AS7", "B7", "C8", "CS8", "D8", "DS8"
47};
48
49// LED matrix configuration
50const int MATRIX_ROWS = 8;
51const int MATRIX_COLS = 8;
52const byte matrix_notes[][MATRIX_ROWS] PROGMEM = {
53 { 0xE7, 0x95, 0xF5, 0x95, 0xE7, 0x00, 0x00, 0x00 }, { 0xF1, 0x83, 0x81, 0x81, 0xF1, 0x00, 0x00, 0x00 },
54 { 0xF1, 0x83, 0x81, 0x81, 0xF1, 0x12, 0x3C, 0x48 }, { 0xE1, 0x93, 0x91, 0x91, 0xE1, 0x00, 0x00, 0x00 },
55 { 0xE1, 0x93, 0x91, 0x91, 0xE1, 0x12, 0x3C, 0x48 }, { 0xF1, 0x83, 0xE1, 0x81, 0xF1, 0x00, 0x00, 0x00 },
56 { 0xF1, 0x83, 0xF1, 0x81, 0x81, 0x00, 0x00, 0x00 }, { 0xF1, 0x83, 0xF1, 0x81, 0x81, 0x12, 0x3C, 0x48 },
57 { 0xF1, 0x83, 0xB1, 0x91, 0xF1, 0x00, 0x00, 0x00 }, { 0xF1, 0x83, 0xB1, 0x91, 0xF1, 0x12, 0x3C, 0x48 },
58 { 0x61, 0x93, 0x91, 0xF1, 0x91, 0x00, 0x00, 0x00 }, { 0x61, 0x93, 0xF1, 0x91, 0x91, 0x12, 0x3C, 0x48 },
59 { 0xE1, 0x93, 0xE1, 0x91, 0xE1, 0x00, 0x00, 0x00 }, { 0xF7, 0x81, 0x87, 0x84, 0xF7, 0x00, 0x00, 0x00 },
60 { 0xF7, 0x81, 0x87, 0x84, 0xF7, 0x12, 0x3C, 0x48 }, { 0xE7, 0x91, 0x97, 0x94, 0xE7, 0x00, 0x00, 0x00 },
61 { 0xE7, 0x91, 0x97, 0x94, 0xE7, 0x12, 0x3C, 0x48 }, { 0xF7, 0x81, 0xE7, 0x84, 0xF7, 0x00, 0x00, 0x00 },
62 { 0xF7, 0x81, 0xF7, 0x84, 0x87, 0x00, 0x00, 0x00 }, { 0xF7, 0x81, 0xF7, 0x84, 0x87, 0x12, 0x3C, 0x48 },
63 { 0xF7, 0x81, 0xB7, 0x94, 0xF7, 0x00, 0x00, 0x00 }, { 0xF7, 0x81, 0xB7, 0x94, 0xF7, 0x12, 0x3C, 0x48 },
64 { 0x67, 0x91, 0x97, 0xF4, 0x97, 0x00, 0x00, 0x00 }, { 0x67, 0x91, 0xF7, 0x94, 0x97, 0x12, 0x3C, 0x48 },
65 { 0xE7, 0x91, 0xE7, 0x94, 0xE7, 0x00, 0x00, 0x00 }, { 0xF7, 0x81, 0x87, 0x81, 0xF7, 0x00, 0x00, 0x00 },
66 { 0xF7, 0x81, 0x87, 0x81, 0xF7, 0x12, 0x3C, 0x48 }, { 0xE7, 0x91, 0x97, 0x91, 0xE7, 0x00, 0x00, 0x00 },
67 { 0xE7, 0x91, 0x97, 0x91, 0xE7, 0x12, 0x3C, 0x48 }, { 0xF7, 0x81, 0xE7, 0x81, 0xF7, 0x00, 0x00, 0x00 },
68 { 0xF7, 0x81, 0xF7, 0x81, 0x87, 0x00, 0x00, 0x00 }, { 0xF7, 0x81, 0xF7, 0x81, 0x87, 0x12, 0x3C, 0x48 },
69 { 0xF7, 0x81, 0xB7, 0x91, 0xF7, 0x00, 0x00, 0x00 }, { 0xF7, 0x81, 0xB7, 0x91, 0xF7, 0x12, 0x3C, 0x48 },
70 { 0x67, 0x91, 0x97, 0xF1, 0x97, 0x00, 0x00, 0x00 }, { 0x67, 0x91, 0xF7, 0x91, 0x97, 0x12, 0x3C, 0x48 },
71 { 0xE7, 0x91, 0xE7, 0x91, 0xE7, 0x00, 0x00, 0x00 }, { 0xF1, 0x83, 0x87, 0x81, 0xF1, 0x00, 0x00, 0x00 },
72 { 0xF1, 0x83, 0x87, 0x81, 0xF1, 0x12, 0x3C, 0x48 }, { 0xE1, 0x93, 0x97, 0x91, 0xE1, 0x00, 0x00, 0x00 },
73 { 0xE1, 0x93, 0x97, 0x91, 0xE1, 0x12, 0x3C, 0x48 }, { 0xF1, 0x83, 0xE7, 0x81, 0xF1, 0x00, 0x00, 0x00 },
74 { 0xF1, 0x83, 0xF7, 0x81, 0x81, 0x00, 0x00, 0x00 }, { 0xF1, 0x83, 0xF7, 0x81, 0x81, 0x12, 0x3C, 0x48 },
75 { 0xF1, 0x83, 0xB7, 0x91, 0xF1, 0x00, 0x00, 0x00 }, { 0xF1, 0x83, 0xB7, 0x91, 0xF1, 0x12, 0x3C, 0x48 },
76 { 0x61, 0x93, 0x97, 0xF1, 0x91, 0x00, 0x00, 0x00 }, { 0x61, 0x93, 0xF7, 0x91, 0x91, 0x12, 0x3C, 0x48 },
77 { 0xE1, 0x93, 0xE7, 0x91, 0xE1, 0x00, 0x00, 0x00 }, { 0xF7, 0x84, 0x87, 0x81, 0xF7, 0x00, 0x00, 0x00 },
78 { 0xF7, 0x84, 0x87, 0x81, 0xF7, 0x12, 0x3C, 0x48 }, { 0xE7, 0x94, 0x97, 0x91, 0xE7, 0x00, 0x00, 0x00 },
79 { 0xE7, 0x94, 0x97, 0x91, 0xE7, 0x12, 0x3C, 0x48 }, { 0xF7, 0x84, 0xE7, 0x81, 0xF7, 0x00, 0x00, 0x00 },
80 { 0xF7, 0x84, 0xF7, 0x81, 0x87, 0x00, 0x00, 0x00 }, { 0xF7, 0x84, 0xF7, 0x81, 0x87, 0x12, 0x3C, 0x48 },
81 { 0xF7, 0x84, 0xB7, 0x91, 0xF7, 0x00, 0x00, 0x00 }, { 0xF7, 0x84, 0xB7, 0x91, 0xF7, 0x12, 0x3C, 0x48 },
82 { 0x67, 0x94, 0x97, 0xF1, 0x97, 0x00, 0x00, 0x00 }, { 0x67, 0x94, 0xF7, 0x91, 0x97, 0x12, 0x3C, 0x48 },
83 { 0xE7, 0x94, 0xE7, 0x91, 0xE7, 0x00, 0x00, 0x00 }, { 0xF4, 0x84, 0x87, 0x85, 0xF7, 0x00, 0x00, 0x00 },
84 { 0xF4, 0x84, 0x87, 0x85, 0xF7, 0x12, 0x3C, 0x48 }, { 0xE4, 0x94, 0x97, 0x95, 0xE7, 0x00, 0x00, 0x00 },
85 { 0xE4, 0x94, 0x97, 0x95, 0xE7, 0x12, 0x3C, 0x48 }, { 0xF4, 0x84, 0xE7, 0x85, 0xF7, 0x00, 0x00, 0x00 },
86 { 0xF4, 0x84, 0xF7, 0x85, 0x87, 0x00, 0x00, 0x00 }, { 0xF4, 0x84, 0xF7, 0x85, 0x87, 0x12, 0x3C, 0x48 },
87 { 0xF4, 0x84, 0xB7, 0x95, 0xF7, 0x00, 0x00, 0x00 }, { 0xF4, 0x84, 0xB7, 0x95, 0xF7, 0x12, 0x3C, 0x48 },
88 { 0x64, 0x94, 0x97, 0xF5, 0x97, 0x00, 0x00, 0x00 }, { 0x64, 0x94, 0xF7, 0x95, 0x97, 0x12, 0x3C, 0x48 },
89 { 0xE4, 0x94, 0xE7, 0x95, 0xE7, 0x00, 0x00, 0x00 }, { 0xF7, 0x81, 0x81, 0x81, 0xF1, 0x00, 0x00, 0x00 },
90 { 0xF7, 0x81, 0x81, 0x81, 0xF1, 0x12, 0x3C, 0x48 }, { 0xE7, 0x91, 0x91, 0x91, 0xE1, 0x00, 0x00, 0x00 },
91 { 0xE7, 0x91, 0x91, 0x91, 0xE1, 0x12, 0x3C, 0x48 }, { 0xF7, 0x81, 0xE1, 0x81, 0xF1, 0x00, 0x00, 0x00 },
92 { 0xF7, 0x81, 0xF1, 0x81, 0x81, 0x00, 0x00, 0x00 }, { 0xF7, 0x81, 0xF1, 0x81, 0x81, 0x12, 0x3C, 0x48 },
93 { 0xF7, 0x81, 0xB1, 0x91, 0xF1, 0x00, 0x00, 0x00 }, { 0xF7, 0x81, 0xB1, 0x91, 0xF1, 0x12, 0x3C, 0x48 },
94 { 0x67, 0x91, 0x91, 0xF1, 0x91, 0x00, 0x00, 0x00 }, { 0x67, 0x91, 0xF1, 0x91, 0x91, 0x12, 0x3C, 0x48 },
95 { 0xE7, 0x91, 0xE1, 0x91, 0xE1, 0x00, 0x00, 0x00 }, { 0xF7, 0x85, 0x87, 0x85, 0xF7, 0x00, 0x00, 0x00 },
96 { 0xF7, 0x85, 0x87, 0x85, 0xF7, 0x12, 0x3C, 0x48 }, { 0xE7, 0x95, 0x97, 0x95, 0xE7, 0x00, 0x00, 0x00 },
97 { 0xE7, 0x95, 0x97, 0x95, 0xE7, 0x12, 0x3C, 0x48 }
98};
99const int NUM_FRAMES = sizeof(matrix_notes) / sizeof(matrix_notes[0]);
100
101// System state variables
102int scale = 0; // Current scale mode (0=OFF, 1=Continuous, etc.)
103bool output_ok = false; // Audio output enable flag
104int octave = 12; // Central octave offset
105int starting_note = 37; // Default: C4 (MIDI note 60)
106bool ON_mode = false; // Synthesis engine active flag
107String scale_name = ""; // Current scale name
108
109// Ultrasonic sensor
110SR04 sensor = SR04(12, 11); // Trig=12, Echo=11
111long distance; // Measured distance
112int echoNote = 220; // Current note frequency
113
114// Distance parameters
115const int starting_distance = 2.5; // higher note
116const int step = 2.5; // changing note step
117
118// Button configuration
119const int buttonOne = 2;
120const int buttonTwo = 4;
121const int buttonThree = 5;
122const int buttonFour = 6;
123const int buttonFive = 13;
124const int buttonSix = 7;
125
126// Button state tracking
127int buttonStates[6] = {0}; // Debounce states
128
129// Sound synthesis parameters
130uint16_t syncPhaseAcc;
131uint16_t syncPhaseInc;
132uint16_t grainPhaseAcc;
133uint16_t grainPhaseInc;
134uint16_t grainAmp;
135uint8_t grainDecay;
136uint16_t grain2PhaseAcc;
137uint16_t grain2PhaseInc;
138uint16_t grain2Amp;
139uint8_t grain2Decay;
140
141// Potentiometer mapping
142#define GRAIN_FREQ_CONTROL 0
143#define GRAIN_DECAY_CONTROL 1
144#define GRAIN2_FREQ_CONTROL 2
145#define GRAIN2_DECAY_CONTROL 3
146
147// PWM configuration
148#if defined(__AVR_ATmega1280__)
149 #define PWM_PIN 3
150 #define PWM_VALUE OCR3C
151 #define PWM_INTERRUPT TIMER3_OVF_vect
152#else
153 #define PWM_PIN 3
154 #define PWM_VALUE OCR2B
155 #define PWM_INTERRUPT TIMER2_OVF_vect
156#endif
157
158// Note buffers
159#define NUM_NOTE_FRAMES 15
160byte notes_name_matrix_to_be_played[NUM_NOTE_FRAMES][MATRIX_ROWS];
161int notes_to_be_played[NUM_NOTE_FRAMES];
162
163// ======================================================================
164// INITIALIZATION
165// ======================================================================
166void setup() {
167 // LED matrix setup
168 lc.shutdown(0, false);
169 lc.setIntensity(0, 6);
170 lc.clearDisplay(0);
171
172 // LCD setup
173 lcd.init();
174 lcd.clear();
175 lcd.backlight();
176
177 // Button setup
178 pinMode(buttonOne, INPUT_PULLUP);
179 pinMode(buttonTwo, INPUT_PULLUP);
180 pinMode(buttonThree, INPUT_PULLUP);
181 pinMode(buttonFour, INPUT_PULLUP);
182 pinMode(buttonFive, INPUT_PULLUP);
183 pinMode(buttonSix, INPUT_PULLUP);
184
185 // Serial setup for debugging
186 //Serial.begin(9600);
187
188 // Initial state
189 scale_name = "OFF";
190 update_display();
191 update_notes(scale, starting_note, octave);
192
193 // Audio system initialization
194 pinMode(PWM_PIN, OUTPUT);
195 audioOn();
196}
197
198// ======================================================================
199// MAIN LOOP
200// ======================================================================
201void loop() {
202 // 1. Process button inputs
203 handleButtons();
204
205 // 2. Read distance sensor
206 distance = sensor.Distance();
207
208 // 3. Determine note based on distance and scale
209 if (scale == 1) { // Continuous mode
210 // up to 15 step (step + step*15)
211 echoNote = map(distance, 0, (starting_distance + (step * 15)), 4978, 31);
212 lc.clearDisplay(0); // Clear matrix in continuous mode
213 }
214 else if (ON_mode) {
215 // Discrete note selection
216 for (int i = 0; i < NUM_NOTE_FRAMES; i++) {
217 int dist_min = starting_distance + (step * i);
218 int dist_max = starting_distance + (step * (i + 1));
219
220 if (distance >= dist_min && distance < dist_max) {
221 echoNote = notes_to_be_played[i];
222
223 // Display note pattern on LED matrix
224 for (int row = 0; row < MATRIX_ROWS; row++) {
225 lc.setRow(0, row, notes_name_matrix_to_be_played[i][row]);
226 }
227 break;
228 }
229 }
230 }
231
232 // 4. Update synthesis parameters if active
233 if (distance < (starting_distance + step * 15) && ON_mode) {
234 syncPhaseInc = echoNote;
235 grainPhaseInc = mapPhaseInc(analogRead(GRAIN_FREQ_CONTROL)) / 2;
236 grainDecay = analogRead(GRAIN_DECAY_CONTROL) / 8;
237 grain2PhaseInc = mapPhaseInc(analogRead(GRAIN2_FREQ_CONTROL)) / 2;
238 grain2Decay = analogRead(GRAIN2_DECAY_CONTROL) / 4;
239 output_ok = true;
240 }
241 else {
242 output_ok = false;
243 lc.clearDisplay(0);
244 }
245}
246
247// ======================================================================
248// BUTTON HANDLING
249// ======================================================================
250void handleButtons() {
251 bool display_updated = false;
252
253 // Button 1: Decrease starting note
254 if (digitalRead(buttonOne) == LOW && buttonStates[0] == 0) {
255 starting_note = constrain(starting_note - 1, 0, 73);
256 update_notes(scale, starting_note, octave);
257 buttonStates[0] = 1;
258 display_updated = true;
259 }
260 else if (digitalRead(buttonOne) == HIGH) {
261 buttonStates[0] = 0;
262 }
263
264 // Button 2: Increase starting note
265 if (digitalRead(buttonTwo) == LOW && buttonStates[1] == 0) {
266 starting_note = constrain(starting_note + 1, 0, 73);
267 update_notes(scale, starting_note, octave);
268 buttonStates[1] = 1;
269 display_updated = true;
270 }
271 else if (digitalRead(buttonTwo) == HIGH) {
272 buttonStates[1] = 0;
273 }
274
275 // Button 3: Decrease octave
276 if (digitalRead(buttonThree) == LOW && buttonStates[2] == 0) {
277 octave = constrain(octave - 12, 0, 48);
278 update_notes(scale, starting_note, octave);
279 buttonStates[2] = 1;
280 display_updated = true;
281 }
282 else if (digitalRead(buttonThree) == HIGH) {
283 buttonStates[2] = 0;
284 }
285
286 // Button 4: Increase octave
287 if (digitalRead(buttonFour) == LOW && buttonStates[3] == 0) {
288 octave = constrain(octave + 12, 0, 48);
289 update_notes(scale, starting_note, octave);
290 buttonStates[3] = 1;
291 display_updated = true;
292 }
293 else if (digitalRead(buttonFour) == HIGH) {
294 buttonStates[3] = 0;
295 }
296
297 // Button 5: Previous scale
298 if (digitalRead(buttonFive) == LOW && buttonStates[4] == 0) {
299 scale = constrain(scale - 1, 0, 6);
300 update_notes(scale, starting_note, octave);
301 buttonStates[4] = 1;
302 display_updated = true;
303 }
304 else if (digitalRead(buttonFive) == HIGH) {
305 buttonStates[4] = 0;
306 }
307
308 // Button 6: Next scale
309 if (digitalRead(buttonSix) == LOW && buttonStates[5] == 0) {
310 scale = constrain(scale + 1, 0, 6);
311 update_notes(scale, starting_note, octave);
312 buttonStates[5] = 1;
313 display_updated = true;
314 }
315 else if (digitalRead(buttonSix) == HIGH) {
316 buttonStates[5] = 0;
317 }
318
319 // Update display only if a button was pressed
320 if (display_updated) {
321 update_display();
322 }
323}
324
325// ======================================================================
326// DISPLAY UPDATE FUNCTION
327// ======================================================================
328void update_display() {
329 // First line: Scale information
330 lcd.setCursor(0, 0);
331 lcd.print(" "); // Clear line
332 lcd.setCursor(0, 0);
333 lcd.print(scale);
334 lcd.print(" ");
335 lcd.print(scale_name);
336
337 // Second line: Conditional display
338 lcd.setCursor(0, 1);
339 lcd.print(" "); // Clear line
340
341 // Only show starting note for scales 2 and above
342 if (scale >= 2) {
343 lcd.setCursor(0, 1);
344 lcd.print("Starting note");
345
346 // Calculate remaining space and display note name
347 int remaining_space = 16 - 13; // "Starting note" is 13 characters
348 const char* note = notes_name[starting_note + octave];
349 int note_length = strlen(note);
350
351 if (note_length <= remaining_space) {
352 lcd.print(note);
353 } else {
354 // If note name is too long, truncate it
355 for (int i = 0; i < remaining_space; i++) {
356 lcd.print(note[i]);
357 }
358 }
359 }
360}
361
362// ======================================================================
363// SOUND ENGINE
364// ======================================================================
365SIGNAL(PWM_INTERRUPT) {
366 uint8_t value;
367 uint16_t output;
368
369 syncPhaseAcc += syncPhaseInc;
370 if (syncPhaseAcc < syncPhaseInc) {
371 // Time to start the next grain
372 grainPhaseAcc = 0;
373 grainAmp = 0x7fff;
374 grain2PhaseAcc = 0;
375 grain2Amp = 0x7fff;
376 PORTB ^= 1 << 5; // Toggle LED on pin 13
377 }
378
379 // Increment the phase of the grain oscillators
380 grainPhaseAcc += grainPhaseInc;
381 grain2PhaseAcc += grain2PhaseInc;
382
383 // Convert phase into a triangle wave
384 value = (grainPhaseAcc >> 7) & 0xff;
385 if (grainPhaseAcc & 0x8000) value = ~value;
386 // Multiply by current grain amplitude to get sample
387 output = value * (grainAmp >> 8);
388
389 // Repeat for second grain
390 value = (grain2PhaseAcc >> 7) & 0xff;
391 if (grain2PhaseAcc & 0x8000) value = ~value;
392 output += value * (grain2Amp >> 8);
393
394 // Make the grain amplitudes decay by a factor every sample
395 grainAmp -= (grainAmp >> 8) * grainDecay;
396 grain2Amp -= (grain2Amp >> 8) * grain2Decay;
397
398 // Scale output to the available range
399 output >>= 9;
400 if (output > 255) output = 255;
401
402 // Output to PWM if enabled
403 if (output_ok) {
404 PWM_VALUE = output;
405 }
406}
407
408void audioOn() {
409 #if defined(__AVR_ATmega1280__)
410 TCCR3A = _BV(COM3C1) | _BV(WGM30);
411 TCCR3B = _BV(CS30);
412 TIMSK3 = _BV(TOIE3);
413 #else
414 TCCR2A = _BV(COM2B1) | _BV(WGM20);
415 TCCR2B = _BV(CS20);
416 TIMSK2 = _BV(TOIE2);
417 #endif
418}
419
420// Frequency mapping functions
421uint16_t mapPhaseInc(uint16_t input) {
422 static const uint16_t antilogTable[] = {
423 64830,64132,63441,62757,62081,61413,60751,60097,59449,58809,58176,57549,56929,56316,55709,55109,
424 54515,53928,53347,52773,52204,51642,51085,50535,49991,49452,48920,48393,47871,47356,46846,46341,
425 45842,45348,44859,44376,43898,43425,42958,42495,42037,41584,41136,40693,40255,39821,39392,38968,
426 38548,38133,37722,37316,36914,36516,36123,35734,35349,34968,34591,34219,33850,33486,33125,32768
427 };
428 return (antilogTable[input & 0x3f]) >> (input >> 6);
429}
430
431// ======================================================================
432// SCALE MANAGEMENT
433// ======================================================================
434void update_notes(int scale_update, int starting_note_update, int octave_update) {
435 // Update note sequences based on selected scale
436 switch (scale_update) {
437 case 0: // OFF
438 ON_mode = false;
439 scale_name = "OFF";
440 lc.clearDisplay(0);
441 break;
442
443 case 1: // Continuous
444 ON_mode = true;
445 scale_name = "Continuous";
446 break;
447
448 case 2: // Chromatic
449 {
450 ON_mode = true;
451 scale_name = "Chromatic";
452 for (int i = 0; i < NUM_NOTE_FRAMES; i++) {
453 int note_index = constrain(starting_note_update + octave_update - i, 0, 87);
454 notes_to_be_played[i] = notes[note_index];
455 for (int row = 0; row < MATRIX_ROWS; row++) {
456 notes_name_matrix_to_be_played[i][row] =
457 pgm_read_byte(&matrix_notes[note_index][row]);
458 }
459 }
460 }
461 break;
462
463 case 3: // Major
464 {
465 ON_mode = true;
466 scale_name = "Major";
467 // Major scale intervals: W-W-H-W-W-W-H
468 int major_offsets[15] = {0, -1, -3, -5, -7, -8, -10, -12, -13, -15, -17, -19, -20, -22, -24};
469 for (int i = 0; i < NUM_NOTE_FRAMES; i++) {
470 int note_index = constrain(starting_note_update + octave_update + major_offsets[i], 0, 87);
471 notes_to_be_played[i] = notes[note_index];
472 for (int row = 0; row < MATRIX_ROWS; row++) {
473 notes_name_matrix_to_be_played[i][row] =
474 pgm_read_byte(&matrix_notes[note_index][row]);
475 }
476 }
477 }
478 break;
479
480 case 4: // Natural Minor
481 {
482 ON_mode = true;
483 scale_name = "Nat Minor";
484 // Natural minor scale intervals: W-H-W-W-H-W-W
485 int minor_offsets[15] = {0, -2, -4, -5, -7, -9, -10, -12, -14, -16, -17, -19, -21, -22, -24};
486 for (int i = 0; i < NUM_NOTE_FRAMES; i++) {
487 int note_index = constrain(starting_note_update + octave_update + minor_offsets[i], 0, 87);
488 notes_to_be_played[i] = notes[note_index];
489 for (int row = 0; row < MATRIX_ROWS; row++) {
490 notes_name_matrix_to_be_played[i][row] =
491 pgm_read_byte(&matrix_notes[note_index][row]);
492 }
493 }
494 }
495 break;
496
497 case 5: // Harmonic Minor
498 {
499 ON_mode = true;
500 scale_name = "Harm Minor";
501 // Harmonic minor scale intervals: W-H-W-W-H-WH-H
502 int harm_offsets[15] = {0, -1, -4, -5, -7, -9, -10, -12, -13, -16, -17, -19, -21, -22, -24};
503 for (int i = 0; i < NUM_NOTE_FRAMES; i++) {
504 int note_index = constrain(starting_note_update + octave_update + harm_offsets[i], 0, 87);
505 notes_to_be_played[i] = notes[note_index];
506 for (int row = 0; row < MATRIX_ROWS; row++) {
507 notes_name_matrix_to_be_played[i][row] =
508 pgm_read_byte(&matrix_notes[note_index][row]);
509 }
510 }
511 }
512 break;
513
514 case 6: // Pentatonic
515 {
516 ON_mode = true;
517 scale_name = "Pentatonic";
518 // Pentatonic scale intervals: W-W-W-WH-W
519 int pentatonic_offsets[15] = {0, -3, -5, -8, -10, -12, -15, -17, -20, -22, -24, -27, -29, -32, -34};
520 for (int i = 0; i < NUM_NOTE_FRAMES; i++) {
521 int note_index = constrain(starting_note_update + octave_update + pentatonic_offsets[i], 0, 87);
522 notes_to_be_played[i] = notes[note_index];
523 for (int row = 0; row < MATRIX_ROWS; row++) {
524 notes_name_matrix_to_be_played[i][row] =
525 pgm_read_byte(&matrix_notes[note_index][row]);
526 }
527 }
528 }
529 break;
530 }
531}
Downloadable files
3D File Right Base
01 Base R.stl
Left Base 3D File
02 Base L.stl
3D File Right Antenna
03 Antenna R.stl
3D File Left Antenna
04 Antenna L.stl
3D File Right Cover
05 Cover R.stl
3D File Left Cover
06 Cover L.stl
3D file push button
You need to print 6 items
07 Push button X6.stl
3D file Power plug shutdown
08 Power socket stop.stl
3D File Letter S
09 Letter S.stl
3D file Letter Y
Letter 10 Y.stl
3D File Letter N
You need to print 2 items
11 N Letter X2.stl
3D file Letter T
12 T Letter.stl
3D File Letter E
You need to print 2 items
13 E Letter X2.stl
3D file Letter R
14 R Letter.stl
3D file Letter M
Letter 15 M.stl
3D file I Letter
16 I Letter.stl
3D file O Letter
17 O Letter.stl
3D file Small letter o
18 o small letter.stl
3D File Small Circle
19 Small circle.stl
Documentation
Schematic diagram
Schemas.png
Note: Content and images are from: https://projecthub.arduino.cc/, with some modifications.
If you want it removed due to copyright reasons, please leave a comment. Thank you.
I want to share this article more widely so that everyone knows about Arduino and your project.