/*
Project : Tin Wistle Simulator
Date Started: 18 Nov 2025
Date Updated: 18 Nov 2025
Inspired by : https://github.com/VolosR/TinWhistle
https://www.youtube.com/watch?v=4f6NkdOxqLE
*/
// ---------------------------------------------------------
// NOTE DEFINES
// ---------------------------------------------------------
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
// ---------------------------------------------------------
// PIN CONSTANTS
// ---------------------------------------------------------
#define PLAY_PIN 2
#define H1 7
#define H2 6
#define H3 5
#define H4 12
#define H5 11
#define H6 10
#define OCT_PIN 8
#define SPK 17 // A3
// ---------------------------------------------------------
// NOTE TABLES (lower and upper octaves)
// ---------------------------------------------------------
int freLower[7] = { NOTE_D4, NOTE_E4, NOTE_FS4, NOTE_G4, NOTE_A4, NOTE_B4, NOTE_CS5 };
int freUpper[7] = { NOTE_D5, NOTE_E5, NOTE_FS5, NOTE_G5, NOTE_A5, NOTE_B5, NOTE_CS6 };
// ---------------------------------------------------------
// CORRECT D-WHISTLE FINGERING TABLE (6 holes)
// 0 = hole covered, 1 = open
// ---------------------------------------------------------
byte fingering[7][6] = {
{0,0,0,0,0,0}, // D XXX XXX
{0,0,0,0,0,1}, // E XXX XXO
{0,0,0,0,1,1}, // F# XXX XOO
{0,0,0,1,1,1}, // G XXX OOO
{0,0,1,1,1,1}, // A XXO OOO
{0,1,1,1,1,1}, // B XOO OOO
{1,1,1,1,1,1}, // C# OOO OOO
};
// ---------------------------------------------------------
// DEBUG PRINTING
// ---------------------------------------------------------
void printFingering(int note, bool octaveUp, byte f[6]) {
const char* names[7] = {"D", "E", "F#", "G", "A", "B", "C#"};
Serial.print("Note: ");
Serial.print(names[note]);
Serial.print(octaveUp ? " (upper)" : " (lower)");
Serial.print(" F ");
for (int i = 0; i < 6; i++) Serial.print(1 - f[i]);
Serial.println();
}
// ---------------------------------------------------------
// RANDOM TUNE SIMULATOR (unchanged)
// ---------------------------------------------------------
void playRandomTune(int notes, int noteLength) {
int pins[] = { PLAY_PIN, OCT_PIN, H1, H2, H3, H4, H5, H6 };
for (int i = 0; i < 8; i++) pinMode(pins[i], OUTPUT);
for (int i = 0; i < notes; i++) {
int base = random(0, 7);
bool octaveUp = random(0, 2);
byte f[6];
for (int k = 0; k < 6; k++) f[k] = fingering[base][k];
digitalWrite(PLAY_PIN, LOW);
digitalWrite(OCT_PIN, octaveUp ? HIGH : LOW);
digitalWrite(H1, f[0]);
digitalWrite(H2, f[1]);
digitalWrite(H3, f[2]);
digitalWrite(H4, f[3]);
digitalWrite(H5, f[4]);
digitalWrite(H6, f[5]);
printFingering(base, octaveUp, f);
tone(SPK, octaveUp ? freUpper[base] : freLower[base]);
delay(noteLength);
noTone(SPK);
delay(50);
}
for (int i = 0; i < 8; i++) digitalWrite(pins[i], LOW);
for (int i = 0; i < 8; i++) pinMode(pins[i], INPUT_PULLUP);
}
// ---------------------------------------------------------
// DETECT REAL FINGERING
// ---------------------------------------------------------
int detectNote() {
bool f[6];
f[0] = !digitalRead(H1);
f[1] = !digitalRead(H2);
f[2] = !digitalRead(H3);
f[3] = !digitalRead(H4);
f[4] = !digitalRead(H5);
f[5] = !digitalRead(H6);
for (int n = 0; n < 7; n++) {
bool match = true;
for (int k = 0; k < 6; k++) {
if (f[k] != fingering[n][k]) { match = false; break; }
}
if (match) return n;
}
return -1;
}
// ---------------------------------------------------------
// SETUP
// ---------------------------------------------------------
void setup() {
Serial.begin(115200);
pinMode(PLAY_PIN, INPUT_PULLUP);
pinMode(OCT_PIN, INPUT_PULLUP);
pinMode(H1, INPUT_PULLUP);
pinMode(H2, INPUT_PULLUP);
pinMode(H3, INPUT_PULLUP);
pinMode(H4, INPUT_PULLUP);
pinMode(H5, INPUT_PULLUP);
pinMode(H6, INPUT_PULLUP);
pinMode(SPK, OUTPUT);
}
// ---------------------------------------------------------
// MAIN LOOP – LIVE MODE FIXED
// ---------------------------------------------------------
void loop() {
// auto demo
playRandomTune(10, 250);
delay(1000);
// live playing mode
if (!digitalRead(PLAY_PIN)) {
int n = detectNote();
if (n >= 0) {
// ✔ OCT_PIN now controls octave correctly
// HIGH = upper, LOW = lower
bool octaveUp = digitalRead(OCT_PIN);
int freq = octaveUp ? freUpper[n] : freLower[n];
tone(SPK, freq);
}
} else {
noTone(SPK);
}
}