SIDKIT OLED Pages • 128x64 Mono + 256x128 4-bit Greyscale
Full OSC page layout with all controls
Verbatim testFinalSynthPage() - uses setFont() and baseline Y positioning
/**
* SIDKIT Synth Page - Standalone OLED Test
* 128x64 SSD1309 via HW SPI on Teensy 4.1
*/
#include <U8g2lib.h>
#include <SPI.h>
#define OLED_CS 36
#define OLED_DC 37
#define OLED_RST 5
U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R0, OLED_CS, OLED_DC, OLED_RST);
// Current selection (linked to buttons)
int currentSID = 2; // 1-4 (SID1-SID4 buttons)
int currentOSC = 1; // 1-3 (OSC1-OSC3 buttons)
float wavePhase = 0.0f;
void setup() {
pinMode(OLED_RST, OUTPUT);
digitalWrite(OLED_RST, LOW);
delay(50);
digitalWrite(OLED_RST, HIGH);
delay(50);
u8g2.begin();
u8g2.setContrast(255);
}
void loop() {
wavePhase += 0.05f;
if (wavePhase > 2.0f * PI) wavePhase -= 2.0f * PI;
u8g2.clearBuffer();
testFinalSynthPage();
u8g2.sendBuffer();
delay(16);
}
// ============================================================================
// DRAWING FUNCTIONS
// ============================================================================
void drawDottedRect(int x, int y, int w, int h, int spacing) {
for (int i = 0; i <= w; i += spacing) {
u8g2.drawPixel(x + i, y + h);
}
for (int j = 0; j <= h; j += spacing) {
u8g2.drawPixel(x, y + j);
u8g2.drawPixel(x + w, y + j);
}
}
void drawWaveCell(int col, int waveIndex) {
int cx = col * 32 + 16;
int yOffset = 17;
u8g2.setFont(u8g2_font_04b_03b_tr);
const char* label = "WAVE";
int lblW = u8g2.getStrWidth(label);
u8g2.drawStr(cx - lblW / 2, yOffset + 3, label);
int waveY = yOffset + 5;
int waveW = 26;
int waveH = 11;
int waveX = cx - waveW / 2;
float phase = wavePhase; // Global animated phase
int prevY = waveY + waveH / 2;
float prevPhase = 0;
for (int x = 0; x <= waveW; x++) {
float p = fmod((float)x / waveW * 2.0f * PI + phase, 2.0f * PI);
int y = waveY + waveH / 2;
switch (waveIndex) {
case 0: y = waveY + waveH / 2; break;
case 1:
if (p < PI) y = waveY + waveH - (int)((p / PI) * waveH);
else y = waveY + (int)(((p - PI) / PI) * waveH);
break;
case 2:
y = waveY + (int)((p / (2.0f * PI)) * waveH);
if (x > 0 && p < prevPhase) u8g2.drawVLine(waveX + x, waveY, waveH + 1);
break;
case 3:
{
bool high = (p < PI);
y = high ? waveY : waveY + waveH;
if (x > 0 && (p < PI) != (prevPhase < PI))
u8g2.drawVLine(waveX + x, waveY, waveH + 1);
}
break;
case 4:
y = waveY + random(waveH + 1);
break;
}
u8g2.drawPixel(waveX + x, y);
if (x > 0 && (waveIndex == 1 || waveIndex == 2) && !(waveIndex == 2 && p < prevPhase)) {
if (abs(y - prevY) > 1) u8g2.drawLine(waveX + x - 1, prevY, waveX + x, y);
}
prevY = y;
prevPhase = p;
}
}
void drawSwitchCell(int col, bool state, const char* label) {
int cx = col * 32 + 16;
int yOffset = 17;
u8g2.setFont(u8g2_font_04b_03b_tr);
int lblW = u8g2.getStrWidth(label);
u8g2.drawStr(cx - lblW / 2, yOffset + 3, label);
int swW = 22;
int swH = 10;
int swX = cx - swW / 2;
int swY = yOffset + 6;
if (state) {
u8g2.drawBox(swX, swY, swW, swH);
u8g2.setDrawColor(0);
u8g2.setFont(u8g2_font_micro_tr);
u8g2.drawStr(swX + 7, swY + 8, "ON");
u8g2.setDrawColor(1);
} else {
u8g2.drawFrame(swX, swY, swW, swH);
u8g2.setFont(u8g2_font_micro_tr);
u8g2.drawStr(swX + 5, swY + 8, "OFF");
}
}
void drawPWCell(int col, float pwValue, bool isPulseWave) {
int cx = col * 32 + 16;
int yOffset = 17;
if (!isPulseWave) {
u8g2.setFont(u8g2_font_04b_03b_tr);
int w = u8g2.getStrWidth("---");
u8g2.drawStr(cx - w / 2, yOffset + 10, "---");
return;
}
u8g2.setFont(u8g2_font_04b_03b_tr);
u8g2.drawStr(cx - u8g2.getStrWidth("PW") / 2, yOffset + 3, "PW");
u8g2.setFont(u8g2_font_micro_tr);
int val = (int)(pwValue * 100);
char buf[8];
snprintf(buf, sizeof(buf), "%d", val);
u8g2.drawStr(cx - u8g2.getStrWidth(buf) / 2, yOffset + 10, buf);
int barX = cx - 12;
int barY = yOffset + 12;
u8g2.drawFrame(barX, barY, 25, 4);
int fill = (int)(pwValue * 23);
if (fill > 0) u8g2.drawBox(barX + 1, barY + 1, fill, 2);
}
void drawDetuneKnob(int col, int detValue) {
int cx = col * 32 + 16;
int yOffset = 44;
u8g2.setFont(u8g2_font_04b_03b_tr);
u8g2.drawStr(cx - u8g2.getStrWidth("DETN") / 2, yOffset - 1, "DETN");
int knobY = yOffset + 9 - 2;
int radius = 6;
u8g2.drawCircle(cx, knobY, radius);
float angle = (detValue / 50.0f) * (PI / 2);
angle = -PI / 2 + angle;
int px = cx + (int)(cos(angle) * (radius - 1));
int py = knobY + (int)(sin(angle) * (radius - 1));
u8g2.drawLine(cx, knobY, px, py);
u8g2.setFont(u8g2_font_micro_tr);
char buf[8];
if (detValue >= 0) snprintf(buf, sizeof(buf), "+%d", detValue);
else snprintf(buf, sizeof(buf), "%d", detValue);
int tw = u8g2.getStrWidth(buf);
u8g2.drawStr(cx - tw / 2 - 2, yOffset + 20, buf);
}
void drawFMCell(int col, int sourceOsc, int amountPercent) {
int cx = col * 32 + 16;
int yOffset = 44;
u8g2.setFont(u8g2_font_04b_03b_tr);
const char* label = "FM OSC";
int lblW = u8g2.getStrWidth(label);
u8g2.drawStr(cx - lblW / 2, yOffset - 1, label);
if (sourceOsc == 0) {
int swW = 22, swH = 10;
int swX = cx - swW / 2;
int swY = yOffset + 3;
u8g2.drawFrame(swX, swY, swW, swH);
u8g2.setFont(u8g2_font_micro_tr);
u8g2.drawStr(swX + 5, swY + 8, "OFF");
} else {
int btnW = 9, btnH = 10, gap = 1;
int totalW = 3 * btnW + 2 * gap;
int startX = cx - totalW / 2;
int btnY = yOffset + 3;
for (int i = 0; i < 3; i++) {
int bx = startX + i * (btnW + gap);
int oscNum = i + 1;
if (sourceOsc == oscNum) {
u8g2.drawBox(bx, btnY, btnW, btnH);
u8g2.setDrawColor(0);
} else {
u8g2.drawFrame(bx, btnY, btnW, btnH);
}
char numBuf[2];
snprintf(numBuf, sizeof(numBuf), "%d", oscNum);
u8g2.setFont(u8g2_font_4x6_tf);
u8g2.drawStr(bx + 3, btnY + 8, numBuf);
u8g2.setDrawColor(1);
}
u8g2.setFont(u8g2_font_micro_tr);
char amtBuf[6];
snprintf(amtBuf, sizeof(amtBuf), "%d%%", amountPercent);
int amtW = u8g2.getStrWidth(amtBuf);
int pctBoxW = amtW + 4;
int pctBoxH = 7;
int pctBoxX = cx - pctBoxW / 2;
int pctBoxY = yOffset + 14;
u8g2.drawBox(pctBoxX, pctBoxY, pctBoxW, pctBoxH);
u8g2.setDrawColor(0);
u8g2.drawStr(cx - amtW / 2, pctBoxY + 6, amtBuf);
u8g2.setDrawColor(1);
}
}
void drawVolumeCell(int col, bool enabled, int volumePct) {
int cx = col * 32 + 16;
int yOffset = 44;
u8g2.setFont(u8g2_font_04b_03b_tr);
u8g2.drawStr(cx - u8g2.getStrWidth("VOL") / 2, yOffset - 1, "VOL");
int boxW = 22, boxH = 10;
int boxX = cx - boxW / 2;
int boxY = yOffset + 3;
if (!enabled) {
u8g2.drawFrame(boxX, boxY, boxW, boxH);
u8g2.setFont(u8g2_font_micro_tr);
u8g2.drawStr(boxX + 5, boxY + 8, "OFF");
} else {
u8g2.drawBox(boxX, boxY, boxW, boxH);
u8g2.setDrawColor(0);
u8g2.setFont(u8g2_font_micro_tr);
char buf[8];
snprintf(buf, sizeof(buf), "%d%%", volumePct);
int tw = u8g2.getStrWidth(buf);
u8g2.drawStr(cx - tw / 2, boxY + 8, buf);
u8g2.setDrawColor(1);
if (volumePct > 100) {
u8g2.drawPixel(boxX + boxW - 3, boxY + 2);
u8g2.drawPixel(boxX + boxW - 2, boxY + 2);
u8g2.drawPixel(boxX + boxW - 4, boxY + 2);
u8g2.drawPixel(boxX + boxW - 3, boxY + 1);
u8g2.drawPixel(boxX + boxW - 3, boxY + 3);
}
}
}
// ============================================================================
// MAIN PAGE FUNCTION
// ============================================================================
void testFinalSynthPage() {
// === HEADER ===
u8g2.setFont(u8g2_font_NokiaSmallBold_tf);
char header[20];
snprintf(header, sizeof(header), "SYNTH SID%d OSC%d", currentSID, currentOSC);
u8g2.drawStr(0, 9, header);
u8g2.setFont(u8g2_font_micro_tr);
u8g2.drawStr(99, 9, "CPU:25%");
u8g2.drawHLine(0, 11, 128);
// Animation values
int waveIdx = ((int)(millis() / 2000)) % 5;
bool isPulseWave = (waveIdx == 3);
bool syncState = ((millis() / 1500) % 2) == 0;
bool ringState = ((millis() / 1800) % 2) == 0;
int fmSource = ((int)(millis() / 2500)) % 4;
int fmAmountPct = (fmSource == 0) ? 0 : ((int)(millis() / 40) % 101);
int detValue = (int)(sin(millis() / 1000.0f) * 50);
int volCycle = ((int)(millis() / 1500)) % 5;
bool volEnabled = (volCycle > 0);
int volPct = 0;
if (volCycle == 1) volPct = 0;
else if (volCycle == 2) volPct = 50;
else if (volCycle == 3) volPct = 100;
else if (volCycle == 4) volPct = 110;
// === TOP ROW: FREQ / WAVE / PW / SYNC ===
{
int col = 0, cx = col * 32 + 16, yOffset = 17;
u8g2.setFont(u8g2_font_04b_03b_tr);
u8g2.drawStr(cx - u8g2.getStrWidth("FREQ") / 2, yOffset + 3, "FREQ");
u8g2.setFont(u8g2_font_micro_tr);
float freqVal = 0.5f + 0.4f * sin(millis() / 800.0f);
int val = (int)(freqVal * 100);
char buf[8];
snprintf(buf, sizeof(buf), "%d", val);
u8g2.drawStr(cx - u8g2.getStrWidth(buf) / 2, yOffset + 10, buf);
int barX = cx - 12, barY = yOffset + 12;
u8g2.drawFrame(barX, barY, 25, 4);
int fill = (int)(freqVal * 23);
if (fill > 0) u8g2.drawBox(barX + 1, barY + 1, fill, 2);
}
drawWaveCell(1, waveIdx);
drawPWCell(2, 0.5f + 0.3f * sin(millis() / 600.0f), isPulseWave);
drawSwitchCell(3, syncState, "SYNC");
// === BOTTOM ROW: DETN / RING / FM / VOL ===
drawDetuneKnob(0, detValue);
{
int cx = 1 * 32 + 16, yOffset = 44;
u8g2.setFont(u8g2_font_04b_03b_tr);
u8g2.drawStr(cx - u8g2.getStrWidth("RING") / 2, yOffset - 1, "RING");
int swW = 22, swH = 10, swX = cx - swW / 2, swY = yOffset + 3;
if (ringState) {
u8g2.drawBox(swX, swY, swW, swH);
u8g2.setDrawColor(0);
u8g2.setFont(u8g2_font_micro_tr);
u8g2.drawStr(swX + 7, swY + 8, "ON");
u8g2.setDrawColor(1);
} else {
u8g2.drawFrame(swX, swY, swW, swH);
u8g2.setFont(u8g2_font_micro_tr);
u8g2.drawStr(swX + 5, swY + 8, "OFF");
}
}
drawFMCell(2, fmSource, fmAmountPct);
drawVolumeCell(3, volEnabled, volPct);
// === GRID ===
for (int col = 0; col < 4; col++) {
int cellX = col * 32;
drawDottedRect(cellX, 12, 32, 23, 2);
drawDottedRect(cellX, 36, 32, 28, 2);
}
}