Typed pitch to rise per 12
64-bitReads 6:12, 6/12, 4:10, a bare rise, or an angle in degrees.
rise = tan(a·π ÷ 180) × 12; rise = rise ÷ run × 12
An angle a becomes tan(a·π ÷ 180) × 12; a ratio becomes rise ÷ run × 12. The value is returned unrounded. A decimal comma is read as a point.
/**
* Read a pitch: "6:12", "6/12", "6 in 12", "6" (rise per 12), "4:10" (any run), "26.57°" or "26.57 deg" (an angle),
* "0" or "" (flat). → { ok, rise } (rise per 12) | { ok: false, error: key }
*/
export function parsePitch(text) {
const s = String(text ?? '').trim().toLowerCase().replace(/,/g, '.');
if (!s) return { ok: true, rise: 0 };
let m = s.match(/^(\d+(?:\.\d+)?|\.\d+)\s*(?:°|deg|degrees?)$/);
if (m) {
const a = parseFloat(m[1]);
if (!(a >= 0 && a < 85)) return { ok: false, error: 'pitch.err.angle' };
return { ok: true, rise: Math.tan(a * Math.PI / 180) * 12 };
}
m = s.match(/^(\d+(?:\.\d+)?|\.\d+)(?:\s*(?::|\/|in)\s*(\d+(?:\.\d+)?))?$/);
if (!m) return { ok: false, error: 'pitch.err.read' };
const rise = parseFloat(m[1]), run = m[2] === undefined ? 12 : parseFloat(m[2]);
if (!(run > 0)) return { ok: false, error: 'pitch.err.read' };
const r12 = rise / run * 12;
if (!(r12 <= MAX_PITCH)) return { ok: false, error: 'pitch.err.steep' };
return { ok: true, rise: r12 };
}