SM-26 synthesizer: initial implementation

This commit is contained in:
SM-26 2026-09-11 16:38:28 +02:00
commit 8734ac25f1
18 changed files with 2619 additions and 0 deletions

155
Source/LcdDisplay.cpp Normal file
View file

@ -0,0 +1,155 @@
#include "LcdDisplay.h"
LcdDisplay::LcdDisplay()
{
clear();
startTimerHz (30);
}
juce::Font LcdDisplay::getMonoFont() const
{
return juce::Font (juce::FontOptions { juce::Font::getDefaultMonospacedFontName(),
(float) kCharH - 1.0f, juce::Font::plain });
}
void LcdDisplay::rasterize (juce::uint32 c)
{
if (c >= 256 || glyphCache[c].cached) return;
juce::Image img (juce::Image::ARGB, kCharW * 2, kCharH * 2, true);
{
juce::Graphics g (img);
g.setColour (juce::Colours::white);
g.setFont (getMonoFont());
juce::String s;
s << (juce_wchar) c;
g.drawText (s, img.getBounds().removeFromBottom (kCharH * 2 - 2),
juce::Justification::centredBottom, false);
}
const float sx = (float) img.getWidth() / (float) kCharW;
const float sy = (float) img.getHeight() / (float) kCharH;
for (int row = 0; row < 8; ++row)
glyphCache[c].rows[row] = 0;
for (int row = 0; row < 8; ++row)
{
uint8_t bits = 0;
for (int col = 0; col < 5; ++col)
{
int count = 0, total = 0;
int px0 = (int) (col * sx);
int py0 = (int) (row * sy);
int px1 = juce::jmin ((int) ((col + 1) * sx), img.getWidth() - 1);
int py1 = juce::jmin ((int) ((row + 1) * sy), img.getHeight() - 1);
for (int y = py0; y <= py1; ++y)
for (int x = px0; x <= px1; ++x)
{
total++;
if (img.getPixelAt (x, y).getAlpha() > 127)
count++;
}
if (total > 0 && count > total / 3)
bits |= (uint8_t) (1u << (4 - col));
}
glyphCache[c].rows[row] = bits;
}
glyphCache[c].cached = true;
}
uint8_t* LcdDisplay::getGlyph (juce::uint32 c)
{
if (c >= 256) c = '?';
if (! glyphCache[c].cached)
rasterize (c);
return glyphCache[c].rows;
}
void LcdDisplay::setLine (int row, const juce::String& s)
{
if (row >= 0 && row < kRows)
lines[row] = s;
}
void LcdDisplay::showCursor (bool show) { cursorVisible = show; }
void LcdDisplay::clear()
{
for (int r = 0; r < kRows; ++r)
lines[r] = "";
cursorCol = 0;
cursorRow = 0;
cursorVisible = false;
}
void LcdDisplay::paint (juce::Graphics& g)
{
const float cw = (float) kCharW;
const float ch = (float) kCharH;
const float w = (float) getWidth();
const float h = (float) getHeight();
const juce::Colour bgCol (0xFF0D1A05);
const juce::Colour gridCol (0xFF15260A);
const juce::Colour pixOn (0xFF4AE038);
const juce::Colour pixOnDim (0xFF1E3A12);
const juce::Colour pixOff (0xFF0F1C07);
g.fillAll (bgCol);
g.setColour (gridCol);
float totalW = (float) kCols * cw;
float totalH = (float) kRows * ch;
float ox = (w - totalW) / 2.0f;
float oy = (h - totalH) / 2.0f;
g.fillRect (ox - 1.0f, oy - 1.0f, totalW + 2.0f, totalH + 2.0f);
bool blinkOn = cursorVisible &&
((int) (juce::Time::getMillisecondCounterHiRes() / 400.0) % 2 == 0);
for (int row = 0; row < kRows; ++row)
{
const juce::String& line = lines[row];
for (int col = 0; col < kCols; ++col)
{
juce_wchar ch2 = col < line.length() ? line [(size_t) col] : (juce_wchar) ' ';
uint8_t* glyph = getGlyph ((juce::uint32) ch2);
float cx = ox + (float) col * cw;
float cy = oy + (float) row * ch;
g.setColour (pixOff);
g.fillRect (cx + 0.5f, cy + 0.5f, cw - 1.0f, ch - 1.0f);
for (int gRow = 0; gRow < 8; ++gRow)
{
uint8_t bits = glyph[gRow];
if (bits == 0) continue;
for (int gCol = 0; gCol < 5; ++gCol)
{
if (bits & (1u << (4 - gCol)))
{
float px = cx + 1.0f + (float) gCol * (cw - 2.0f) / 5.0f;
float py = cy + 1.0f + (float) gRow * (ch - 2.0f) / 9.0f;
float pw = (cw - 2.0f) / 5.0f - 0.5f;
float ph = (ch - 2.0f) / 9.0f - 0.3f;
g.setColour (pixOnDim);
g.fillRect (px - 0.4f, py - 0.3f, pw + 0.8f, ph + 0.6f);
g.setColour (pixOn);
g.fillRect (px, py, pw, ph);
}
}
}
if (blinkOn && col == cursorCol && row == cursorRow)
{
g.setColour (pixOn);
g.fillRect (cx + 1.0f, cy + ch - 3.0f, cw - 2.0f, 2.0f);
}
}
}
}
void LcdDisplay::timerCallback() { repaint(); }