diff options
Diffstat (limited to 'esp32/lego-piano.ino')
-rw-r--r-- | esp32/lego-piano.ino | 138 |
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 |