Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export default defineConfig({
title: 'PolyBoard',
description: 'Custom firmware for Launchpad Pro',
customCss: ['./src/styles/custom.css'],
components: {
SiteTitle: './src/components/SiteTitle.astro',
},
social: [
{ icon: 'github', label: 'GitHub', href: 'https://github.com/b4nst/PolyBoard' },
],
Expand All @@ -37,16 +40,16 @@ export default defineConfig({
{
label: 'Reference',
collapsed: true,
autogenerate: { directory: 'reference' }
items: [
{ label: 'Releases', link: '/reference/releases' },
'reference/button-layout',
'reference/faq',
]
},
{
label: 'Development',
collapsed: true,
autogenerate: { directory: 'development' }
},
{
label: 'Releases',
link: '/releases',
}
],
})],
Expand Down
30 changes: 24 additions & 6 deletions docs/src/components/Btn.astro
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,35 @@ const { name, showLabel = true } = Astro.props;
const buttonIcons: Record<string, string> = {
'oct-up': 'up-caret',
'oct-down': 'down-caret',
'transpose-up': 'right-caret',
'transpose-down': 'left-caret',
'page-1': '',
'page-2': '',
'page-3': '',
'page-4': '',
'interval-1': '',
'interval-2': '',
'interval-3': '',
'interval-4': '',
'circle': 'approve-check-circle',
'double': 'list-format',
'quantise': 'random',
'quantise': 'bars',
'shift': 'rocket',
};

const buttonLabels: Record<string, string> = {
'oct-up': '',
'oct-down': '',
'transpose-up': '',
'transpose-down': '',
'page-1': '1',
'page-2': '2',
'page-3': '3',
'page-4': '4',
'interval-1': '1',
'interval-2': '2',
'interval-3': '3',
'interval-4': '4',
'circle': '',
'double': '',
'quantise': '',
Expand All @@ -38,25 +50,31 @@ const buttonLabels: Record<string, string> = {
const buttonColors: Record<string, string> = {
'oct-up': '#3c3c3c',
'oct-down': '#3c3c3c',
'page-1': '#ffffff',
'transpose-up': '#3c3c3c',
'transpose-down': '#3c3c3c',
'page-1': '#ff7f00',
'page-2': '#3c3c3c',
'page-3': '#3c3c3c',
'page-4': '#3c3c3c',
'interval-1': '#3c3c3c',
'interval-2': '#3c3c3c',
'interval-3': '#ff00ff',
'interval-4': '#3c3c3c',
'circle': '#3c3c3c',
'double': '#3c3c3c',
'quantise': '#ffffff',
'quantise': '#3c3c3c',
'shift': '#3c3c3c',
};

const isRound = ['oct-up', 'oct-down', 'circle', 'double', 'quantise', 'shift'].includes(name);
const isRound = ['oct-up', 'oct-down', 'transpose-up', 'transpose-down', 'page-1', 'page-2', 'page-3', 'page-4', 'interval-1', 'interval-2', 'interval-3', 'interval-4', 'circle', 'double', 'quantise', 'shift'].includes(name);
const icon = buttonIcons[name];
const label = buttonLabels[name];
const bgColor = buttonColors[name] || '#3c3c3c';
---

<span class:list={['btn', { round: isRound }]} style={`background-color: ${bgColor};`}>
{icon && showLabel && <Icon name={icon as any} size="0.9em" />}
{label && showLabel && <span class="label">{label}</span>}
{icon && <Icon name={icon as any} size="0.9em" />}
{label && (showLabel || !icon) && <span class="label">{label}</span>}
</span>

<style>
Expand Down
187 changes: 173 additions & 14 deletions docs/src/components/LaunchpadGrid.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { Icon } from '@astrojs/starlight/components';

interface Props {
variant?: 'full' | 'circle-of-fifths' | 'scales';
variant?: 'full' | 'circle-of-fifths' | 'scales' | 'setup' | 'indices' | 'logo';
}

const { variant = 'full' } = Astro.props;
Expand All @@ -14,7 +14,7 @@ const COLOR_CHROMATIC = '#3c3c3c';
const COLOR_BLACK = '#1a1a1a';
const COLOR_SELECTED = '#ff00ff';
const COLOR_OPTION = '#003c3c';
const COLOR_PAGE_ACTIVE = '#ffffff';
const COLOR_PAGE_ACTIVE = '#ff7f00'; // Orange for active page
const COLOR_PAGE_INACTIVE = '#3c3c3c';
const COLOR_CTRL_HINT = '#3c3c3c';
const COLOR_WHITE = '#ffffff';
Expand Down Expand Up @@ -74,14 +74,15 @@ function isRoot(semitone: number): boolean {
return (semitone % 12) === 0;
}

type CellType = 'grid' | 'control' | 'empty';
type IconName = 'up-caret' | 'down-caret' | 'approve-check-circle' | 'list-format' | 'random' | 'rocket';
type CellType = 'grid' | 'control' | 'empty' | 'setup';
type IconName = 'up-caret' | 'down-caret' | 'left-caret' | 'right-caret' | 'approve-check-circle' | 'list-format' | 'bars' | 'rocket';

interface Cell {
color: string;
label?: string;
icon?: IconName;
type: CellType;
darkIcon?: boolean; // Use dark icon color for bright backgrounds
}

function getGridData(): Cell[][] {
Expand All @@ -96,12 +97,21 @@ function getGridData(): Cell[][] {
const isBottomRow = row === 0;
const isLeftCol = col === 0;
const isRightCol = col === 9;
const isControl = isTopRow || isBottomRow || isLeftCol || isRightCol;
const isCorner = (isTopRow || isBottomRow) && (isLeftCol || isRightCol);
const isControl = (isTopRow || isBottomRow || isLeftCol || isRightCol) && !isCorner;

grid[row][col] = {
color: COLOR_BLACK,
type: isControl ? 'control' : 'grid'
};
// Top-left corner is the setup button (small dark circle)
if (row === 9 && col === 0) {
grid[row][col] = { color: COLOR_BLACK, type: 'setup' };
} else if (isCorner) {
// Other corners are empty space
grid[row][col] = { color: 'transparent', type: 'empty' };
} else {
grid[row][col] = {
color: COLOR_BLACK,
type: isControl ? 'control' : 'grid'
};
}
}
}

Expand All @@ -128,6 +138,110 @@ function getGridData(): Cell[][] {
}
// Double button lit
grid[2][0] = { color: COLOR_SELECTED, icon: 'list-format', type: 'control' };
} else if (variant === 'logo') {
// Boot logo: P (cyan) and B (magenta)
const COLOR_CYAN = '#00ffff';
const COLOR_MAGENTA = '#ff00ff';
const COLOR_DIM_WHITE = '#3c3c3c';

// Logo indices from layout.h
const LOGO_B = [25, 26, 35, 37, 46, 47, 57]; // Magenta
const LOGO_P = [42, 52, 53, 62, 64, 73, 74]; // Cyan

for (let row = 0; row < 10; row++) {
for (let col = 0; col < 10; col++) {
const index = row * 10 + col;
const isCorner = (row === 0 || row === 9) && (col === 0 || col === 9);
const isControl = row === 0 || row === 9 || col === 0 || col === 9;

let color = COLOR_DIM_WHITE;
if (LOGO_B.includes(index)) {
color = COLOR_MAGENTA;
} else if (LOGO_P.includes(index)) {
color = COLOR_CYAN;
}

if (row === 9 && col === 0) {
grid[row][col] = { color: COLOR_DIM_WHITE, type: 'setup' };
} else if (isCorner) {
grid[row][col] = { color: 'transparent', type: 'empty' };
} else if (isControl) {
grid[row][col] = { color: COLOR_DIM_WHITE, type: 'control' };
} else {
grid[row][col] = { color, type: 'grid' };
}
}
}
} else if (variant === 'indices') {
// Button indices reference grid
const COLOR_PAD = '#3c3c3c';
const COLOR_CTRL = '#2a4a5a';
const COLOR_VIRTUAL = '#1a1a1a';

for (let row = 0; row < 10; row++) {
for (let col = 0; col < 10; col++) {
const index = row * 10 + col;
const isCorner = (row === 0 || row === 9) && (col === 0 || col === 9);
const isControl = row === 0 || row === 9 || col === 0 || col === 9;

if (row === 9 && col === 0) {
// Setup button
grid[row][col] = { color: COLOR_CTRL, label: '90', type: 'setup' };
} else if (isCorner) {
// Virtual indices (no physical button)
grid[row][col] = { color: COLOR_VIRTUAL, label: String(index), type: 'empty' };
} else if (isControl) {
grid[row][col] = { color: COLOR_CTRL, label: String(index), type: 'control' };
} else {
grid[row][col] = { color: COLOR_PAD, label: String(index), type: 'grid' };
}
}
}
} else if (variant === 'setup') {
// Setup mode grid
const COLOR_CYAN = '#00ffff';
const COLOR_DIM = '#003c3c';

// Setup button lit white
grid[9][0] = { color: COLOR_WHITE, type: 'setup' };

// Row 1 (pads 11-18): MIDI Channels 1-8
for (let col = 1; col <= 8; col++) {
// Channel 1 selected (cyan), others dim
const color = col === 1 ? COLOR_CYAN : COLOR_DIM;
grid[1][col] = { color, label: String(col), type: 'grid' };
}

// Row 2 (pads 21-28): MIDI Channels 9-16
for (let col = 1; col <= 8; col++) {
grid[2][col] = { color: COLOR_DIM, label: String(col + 8), type: 'grid' };
}

// Row 3 (pads 31-34): Velocity curves
const velocityLabels = ['Li', 'So', 'Ha', 'Fi'];
for (let col = 1; col <= 4; col++) {
// Linear (first) selected
const color = col === 1 ? COLOR_CYAN : COLOR_DIM;
grid[3][col] = { color, label: velocityLabels[col - 1], type: 'grid' };
}

// Row 4 (pads 41-42): Aftertouch modes
const atLabels = ['Ch', 'Po'];
for (let col = 1; col <= 2; col++) {
// Channel (first) selected
const color = col === 1 ? COLOR_CYAN : COLOR_DIM;
grid[4][col] = { color, label: atLabels[col - 1], type: 'grid' };
}

// Top row controls - pages still work
grid[9][5] = { color: COLOR_PAGE_ACTIVE, label: '1', type: 'control' };
grid[9][6] = { color: COLOR_PAGE_INACTIVE, label: '2', type: 'control' };
grid[9][7] = { color: COLOR_PAGE_INACTIVE, label: '3', type: 'control' };
grid[9][8] = { color: COLOR_PAGE_INACTIVE, label: '4', type: 'control' };

// Left column - Circle and Double still work
grid[1][0] = { color: COLOR_CTRL_HINT, icon: 'approve-check-circle', type: 'control' };
grid[2][0] = { color: COLOR_CTRL_HINT, icon: 'list-format', type: 'control' };
} else {
// Full play mode grid
const interval = 5; // Perfect 4th default
Expand All @@ -149,6 +263,8 @@ function getGridData(): Cell[][] {
// Top row controls
grid[9][1] = { color: COLOR_CTRL_HINT, icon: 'down-caret', type: 'control' }; // Oct down
grid[9][2] = { color: COLOR_CTRL_HINT, icon: 'up-caret', type: 'control' }; // Oct up
grid[9][3] = { color: COLOR_CTRL_HINT, icon: 'left-caret', type: 'control' }; // Transpose down
grid[9][4] = { color: COLOR_CTRL_HINT, icon: 'right-caret', type: 'control' }; // Transpose up
grid[9][5] = { color: COLOR_PAGE_ACTIVE, label: '1', type: 'control' }; // Page 1
grid[9][6] = { color: COLOR_PAGE_INACTIVE, label: '2', type: 'control' }; // Page 2
grid[9][7] = { color: COLOR_PAGE_INACTIVE, label: '3', type: 'control' }; // Page 3
Expand All @@ -157,15 +273,16 @@ function getGridData(): Cell[][] {
// Left column controls
grid[1][0] = { color: COLOR_CTRL_HINT, icon: 'approve-check-circle', type: 'control' }; // Circle
grid[2][0] = { color: COLOR_CTRL_HINT, icon: 'list-format', type: 'control' }; // Double
grid[4][0] = { color: COLOR_WHITE, icon: 'random', type: 'control' }; // Quantise
grid[4][0] = { color: COLOR_CTRL_HINT, icon: 'bars', type: 'control' }; // Quantise
grid[8][0] = { color: COLOR_CTRL_HINT, icon: 'rocket', type: 'control' }; // Shift

// Right column - velocity curve indicator
grid[2][9] = { color: COLOR_CTRL_HINT, type: 'control' }; // Scene 2 (Linear active)

// Bottom row - transpose indicator (neutral)
grid[0][4] = { color: COLOR_CTRL_HINT, type: 'control' };
grid[0][5] = { color: COLOR_CTRL_HINT, type: 'control' };
// Bottom row controls - all lit with dim hint
for (let col = 1; col <= 8; col++) {
grid[0][col] = { color: COLOR_CTRL_HINT, type: 'control' };
}
}

return grid;
Expand All @@ -187,7 +304,13 @@ const gridData = getGridData();
<div class="row">
{row.map((cell) => (
<div
class:list={['pad', { control: cell.type === 'control' }]}
class:list={['pad', {
control: cell.type === 'control',
setup: cell.type === 'setup',
empty: cell.type === 'empty',
'has-label': cell.type === 'empty' && cell.label,
'dark-icon': cell.darkIcon
}]}
style={`background-color: ${cell.color};`}
>
{cell.icon && <Icon name={cell.icon} size="16px" />}
Expand Down Expand Up @@ -245,6 +368,33 @@ const gridData = getGridData();
border-radius: 50%;
}

.pad.setup {
border-radius: 50%;
width: 20px;
height: 20px;
min-width: 20px;
min-height: 20px;
max-width: 20px;
max-height: 20px;
margin: 8px;
}

.pad.empty:not(.has-label) {
background: transparent !important;
}

.pad.empty.has-label {
opacity: 0.5;
}

.pad.dark-icon {
color: #1a1a1a;
}

.pad.dark-icon :global(svg) {
color: #1a1a1a;
}

.pad :global(svg) {
margin: 0;
padding: 0;
Expand All @@ -268,6 +418,15 @@ const gridData = getGridData();
max-width: 28px;
max-height: 28px;
}
.pad.setup {
width: 16px;
height: 16px;
min-width: 16px;
min-height: 16px;
max-width: 16px;
max-height: 16px;
margin: 6px;
}
.label {
font-size: 8px;
}
Expand Down
Loading