aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--esp32/lego-piano.ino138
1 files changed, 89 insertions, 49 deletions
diff --git a/esp32/lego-piano.ino b/esp32/lego-piano.ino
index 01b3bb7..6a8ff09 100644
--- a/esp32/lego-piano.ino
+++ b/esp32/lego-piano.ino
@@ -1,19 +1,7 @@
/*
- matrix-scan a set of LEDs
+ matrix-scan a set of QRD1114 or similar, each maps to a note, play
+ those notes via a soundfont
- pins:
-
- "rows" 12 & 13 go to a 220Ω resistor, then to 2 LEDs each (positive
- / long stem side); also to a 10kΩ resistor then to 2
- phototransistors (collector / long stem side)
-
- also, from between the 10kΩ and the phototransistors, wire goes to
- A1 & A2
-
- "columns"
- 14 & 15 go to 2 LEDs each (negative / short stem side)
-
- so that given one of 12|13 and one of 14|15, one LED is identified
*/
#include <driver/dac.h>
@@ -24,38 +12,66 @@
#include "../ESP8266Audio/src/libtinysoundfont/tsf.h"
#include "../RingBuffer/src/RingBuf.h"
-#include "font.h"
-int currentLed = 0;
+// this is the soundfont, we embed it in the program's memory
+#include "font.h"
#define DEBUG 0
+// how many rows & columns does the matrix have?
const size_t row_count = 5;
const size_t col_count = 5;
+/*
+ which pins to use
+
+ each "row" pins goes to a current-limiting resistor (~220Ω) and then
+ to the LED anodes, also to a pull-up resistor (~10kΩ) and then to
+ the transistor collectors
+
+ each "column" pin goes to LED cathodes and transistor emitters
+
+ each "adc" pin goes between pull-up and collector
+*/
const int rows[row_count] = { 5, 23, 19, 18, 26 };
const int cols[col_count] = { 17, 33, 16, 21, 22 };
const int adc[row_count] = { 2, 4, 12, 27, 14 };
+// this turns on the amplifier
const int ampEnable = 32;
-const int octave_shift = 2;
-
+// sample rate to render the soundfont at; higher means theoretically
+// better quality, but slows down everything, so not worth it
const int sampleRate = 11025;
+// nothing to customise below this point
+
+const i2s_port_t i2sPort = I2S_NUM_0;
+
+// state of the matrix
char pressed[row_count*col_count] = { 0 };
+int currentSensor = 0;
+
+// buffers to exchange data between soundfont renderer and I2S driver
+const size_t bufferSize = 1000;
+RingBuf<short, bufferSize> buffer;
+short intermediateBuffer[bufferSize];
+// the soundfont renderer
tsf* g_TinySoundFont = 0;
-const i2s_port_t i2sPort = I2S_NUM_0;
void setup() {
+ // for debugging
Serial.begin(115200);
+ // turn the amplifier off while we set things up
pinMode(ampEnable, OUTPUT);
digitalWrite(ampEnable, LOW);
+ // set up the DAC and the I2S driver
dac_i2s_enable();
dac_output_enable(DAC_CHANNEL_1);
+ // 16-bit single channel I2S using the internal DAC
static const i2s_config_t i2s_config = {
.mode = (i2s_mode_t)( I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN ),
.sample_rate = sampleRate,
@@ -66,10 +82,12 @@ void setup() {
.dma_buf_len = 64,
.use_apll = false
};
-
i2s_driver_install(i2sPort, &i2s_config, 0, NULL);
- i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN); // only dac1
+ // only dac1: we don't need stereo, also we're using the dac2 pin as
+ // GPIO
+ i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN);
+ // set pins' role
for (int i=0;i<row_count;++i) {
pinMode(rows[i], OUTPUT|PULLUP);
pinMode(adc[i], INPUT);
@@ -78,39 +96,47 @@ void setup() {
pinMode(cols[i], OUTPUT|PULLDOWN);
}
- currentLed = 0;
+ currentSensor = 0;
+ // load the soundfont
g_TinySoundFont = tsf_load_memory(SoundFont, sizeof(SoundFont));
if (g_TinySoundFont) {
- // render at 5kHz
tsf_set_output(g_TinySoundFont, TSF_MONO, sampleRate, 0);
}
else {
Serial.println("failed to start tsf");
}
+ // ok, turn the amplifier on now
digitalWrite(ampEnable, HIGH);
}
+/**** Pin handling functions
+ */
+
+// set a column pin to "high impedance"
void tristate(int pin) {
pinMode(pin,OUTPUT|PULLDOWN);
digitalWrite(pin,LOW);
pinMode(pin,INPUT);
}
+// set a row pin to 3.3V
void power(int pin) {
pinMode(pin,OUTPUT|PULLUP);
digitalWrite(pin,HIGH);
}
+// set a row or column pin to ground
void ground(int pin) {
pinMode(pin,OUTPUT|PULLDOWN);
digitalWrite(pin,LOW);
}
-void enableLed(int led) {
- int row = (led/col_count) % row_count ;
- int col = led%col_count;
+// turn a sensor on
+void enableSensor(int sensor) {
+ int row = (sensor/col_count) % row_count ;
+ int col = sensor%col_count;
#if DEBUG & 0x02
Serial.print("enabling ");
@@ -129,8 +155,9 @@ void enableLed(int led) {
}
}
-int sense(int led) {
- int row = (led/col_count)%row_count;
+// read a sensor
+int sense(int sensor) {
+ int row = (sensor/col_count)%row_count;
int value = analogRead(adc[row]);
@@ -141,21 +168,24 @@ int sense(int led) {
return value < 500;
}
-const size_t bufferSize = 1000;
-RingBuf<short, bufferSize> buffer;
-short intermediateBuffer[bufferSize];
-
void loop() {
#if DEBUG & 0x01
- Serial.print("current led ");
- Serial.println(currentLed);
+ Serial.print("current sensor ");
+ Serial.println(currentSensor);
#endif
- enableLed(currentLed);
+ enableSensor(currentSensor);
unsigned long t = millis();
+
+ // the sensor needs at least a millisecond to see anything, let's do
+ // work in the meantime
+
size_t written=100;
short value;
- // fill the I2S buffer
+ // fill the I2S buffer from our ringbuffer; the I2S API doesn't tell
+ // us how much space it has in its internal buffers, so we must send
+ // one short at a time, without blocking, until it fails (which
+ // means the buffers are full)
while (written > 0) {
// we don't want to drop an element from the buffer if we then
// can't write it! so we peek at the buffer, try to write, and pop
@@ -165,42 +195,52 @@ void loop() {
if (written > 0) buffer.pop(value);
}
+ // fill the ringbuffer from the renderer
int toFill = buffer.maxSize() - buffer.size();
if (toFill > 100) {
+ // our buffer tells us how much space it has, but it can't give us
+ // a contiguous segment of RAM to write into; we need an
+ // intermediate buffer
tsf_render_short(g_TinySoundFont, intermediateBuffer, toFill, 0);
for (int i=0;i<toFill;++i) {
buffer.push(intermediateBuffer[i]);
}
}
+ /* I'm sure there's a better / smarter / faster way of doing that
+ data transfer, but this works and I understand it
+ */
+
+ // did our work take less than 1 millisecond? wait a bit more
while (millis() == t) {
delayMicroseconds(100);
}
- if (sense(currentLed)) { // currently pressed
- if (!pressed[currentLed]) { // was not pressed previously?
- Serial.print(currentLed);
- Serial.println(" proximity!");
+ // now we can read the sensor
+ if (sense(currentSensor)) { // currently pressed
+ if (!pressed[currentSensor]) { // was not pressed previously?
+ Serial.print(currentSensor);
+ Serial.println(" pressed!");
- pressed[currentLed]=1; // mark it pressed
+ pressed[currentSensor]=1; // mark it pressed
if (g_TinySoundFont) {
// start the note
- tsf_note_on(g_TinySoundFont, 0, 36 + currentLed, 1.0f);
+ tsf_note_on(g_TinySoundFont, 0, 36 + currentSensor, 1.0f);
}
}
}
- // not pressed currently
- else if (pressed[currentLed]) { // was pressed previously?
- Serial.print(currentLed);
+ // not currently pressed
+ else if (pressed[currentSensor]) { // was pressed previously?
+ Serial.print(currentSensor);
Serial.println(" released!");
- pressed[currentLed]=0; // mark it no longer pressed
+ pressed[currentSensor]=0; // mark it no longer pressed
if (g_TinySoundFont) {
// stop the note
- tsf_note_off(g_TinySoundFont, 0, 36 + currentLed);
+ tsf_note_off(g_TinySoundFont, 0, 36 + currentSensor);
}
}
- currentLed = (currentLed+1)%(row_count*col_count);
-
+ // go to the next sensor
+ currentSensor = (currentSensor+1)%(row_count*col_count);
} \ No newline at end of file