Add a soprano recorder tab converter as a Linux AppImage.
The app turns a lead line into Baroque fingering tabs with rhythm kept, ships Twinkle and Ode to Joy, and reads photos locally with oemer when the bundled Python is present. Co-authored-by: catpersonisme <catpersonisme@gmail.com>
This commit is contained in:
55 files changed
+17536
No files matched your search
@@ -0,0 +1,135 @@
|
||||
import {
|
||||
fingeringMarks,
|
||||
fingeringProse,
|
||||
type Fingering,
|
||||
} from "@/lib/fingering";
|
||||
|
||||
type HoleState = Fingering["holes"][number];
|
||||
|
||||
function Hole({
|
||||
cx,
|
||||
cy,
|
||||
r,
|
||||
state,
|
||||
}: {
|
||||
cx: number;
|
||||
cy: number;
|
||||
r: number;
|
||||
state: HoleState;
|
||||
}) {
|
||||
return (
|
||||
<g>
|
||||
<circle cx={cx} cy={cy} r={r} fill="#fbf6ec" />
|
||||
{state === "closed" ? <circle cx={cx} cy={cy} r={r - 1.1} fill="#1c3830" /> : null}
|
||||
{state === "half" ? (
|
||||
<path
|
||||
d={`M ${cx} ${cy - r + 1} A ${r - 1} ${r - 1} 0 0 0 ${cx} ${cy + r - 1} Z`}
|
||||
fill="#1c3830"
|
||||
/>
|
||||
) : null}
|
||||
<circle cx={cx} cy={cy} r={r} fill="none" stroke="#5c3d28" strokeWidth="1.6" />
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
export function FingeringDiagram({
|
||||
fingering,
|
||||
pitch,
|
||||
size = "lg",
|
||||
}: {
|
||||
fingering: Fingering | null;
|
||||
pitch: string;
|
||||
size?: "lg" | "sm";
|
||||
}) {
|
||||
const width = size === "lg" ? 112 : 88;
|
||||
if (!fingering) {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-center rounded-2xl border border-dashed border-destructive/50 bg-destructive/5 px-2 text-center text-xs leading-snug text-destructive"
|
||||
style={{ width, height: size === "lg" ? 220 : 172 }}
|
||||
role="img"
|
||||
aria-label={`${pitch} is outside the C5 to D7 chart. No fingering is shown.`}
|
||||
>
|
||||
Outside C5–D7. No fingering.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const [h1, h2, h3, h4, h5, h6a, h6b, h7a, h7b] = fingering.holes;
|
||||
const label = `${pitch}. ${fingeringProse(fingering)}. ${fingeringMarks(fingering).join(", ")}`;
|
||||
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 104 228"
|
||||
width={width}
|
||||
role="img"
|
||||
aria-label={label}
|
||||
className="h-auto shrink-0"
|
||||
>
|
||||
<rect x="34" y="8" width="46" height="208" rx="20" fill="#e4cfae" stroke="#6a4328" strokeWidth="2" />
|
||||
<path d="M46 28c8-16 22-16 30 0" fill="none" stroke="#6a4328" strokeWidth="2" />
|
||||
<Hole cx={18} cy={40} r={8} state={fingering.thumb} />
|
||||
<text x={14} y={62} textAnchor="middle" fontSize="8" fill="#6a4328">
|
||||
T
|
||||
</text>
|
||||
<Hole cx={57} cy={58} r={8} state={h1} />
|
||||
<Hole cx={57} cy={80} r={8} state={h2} />
|
||||
<Hole cx={57} cy={102} r={8} state={h3} />
|
||||
<Hole cx={57} cy={132} r={8} state={h4} />
|
||||
<Hole cx={57} cy={154} r={8} state={h5} />
|
||||
<Hole cx={48} cy={178} r={6.5} state={h6a} />
|
||||
<Hole cx={66} cy={178} r={6.5} state={h6b} />
|
||||
<Hole cx={48} cy={198} r={6.5} state={h7a} />
|
||||
<Hole cx={66} cy={198} r={6.5} state={h7b} />
|
||||
<text x={84} y={61} fontSize="8" fill="#7a5a3c">1</text>
|
||||
<text x={84} y={83} fontSize="8" fill="#7a5a3c">2</text>
|
||||
<text x={84} y={105} fontSize="8" fill="#7a5a3c">3</text>
|
||||
<text x={84} y={135} fontSize="8" fill="#7a5a3c">4</text>
|
||||
<text x={84} y={157} fontSize="8" fill="#7a5a3c">5</text>
|
||||
<text x={84} y={181} fontSize="8" fill="#7a5a3c">6</text>
|
||||
<text x={84} y={201} fontSize="8" fill="#7a5a3c">7</text>
|
||||
{fingering.bell ? (
|
||||
<text x={57} y={222} textAnchor="middle" fontSize="8" fill="#6a4328">
|
||||
bell
|
||||
</text>
|
||||
) : null}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function RestMark({ label }: { label: string }) {
|
||||
const kind = label.includes("whole")
|
||||
? "whole"
|
||||
: label.includes("half")
|
||||
? "half"
|
||||
: label.includes("sixteenth")
|
||||
? "sixteenth"
|
||||
: label.includes("eighth")
|
||||
? "eighth"
|
||||
: "quarter";
|
||||
return (
|
||||
<svg viewBox="0 0 64 120" width={72} className="h-auto" role="img" aria-label={`${label} rest`}>
|
||||
<line x1="8" y1="60" x2="56" y2="60" stroke="#c4b39a" strokeWidth="2" />
|
||||
{kind === "whole" ? <rect x="20" y="36" width="24" height="14" rx="1" fill="#2a2118" /> : null}
|
||||
{kind === "half" ? <rect x="20" y="60" width="24" height="14" rx="1" fill="#2a2118" /> : null}
|
||||
{kind === "quarter" ? (
|
||||
<path
|
||||
d="M34 28c8 8 8 14 0 20s-8 12 2 20 10 10 4 18"
|
||||
fill="none"
|
||||
stroke="#2a2118"
|
||||
strokeWidth="4"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
) : null}
|
||||
{kind === "eighth" || kind === "sixteenth" ? (
|
||||
<g fill="#2a2118">
|
||||
<circle cx="36" cy="78" r="6" />
|
||||
<path d="M36 72c10-14 16-20 8-36" fill="none" stroke="#2a2118" strokeWidth="3" />
|
||||
{kind === "sixteenth" ? (
|
||||
<path d="M32 64c10-10 14-14 6-26" fill="none" stroke="#2a2118" strokeWidth="3" />
|
||||
) : null}
|
||||
</g>
|
||||
) : null}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user