@libraz/libcantus 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/analyze/index.cjs +39 -1947
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +197 -37
- package/dist/analyze/index.d.ts +197 -37
- package/dist/analyze/index.js +19 -8
- package/dist/chunk-36N73KZ3.cjs +246 -0
- package/dist/chunk-36N73KZ3.cjs.map +1 -0
- package/dist/chunk-4HLLZPXT.cjs +3077 -0
- package/dist/chunk-4HLLZPXT.cjs.map +1 -0
- package/dist/chunk-7MGZ7D62.cjs +498 -0
- package/dist/chunk-7MGZ7D62.cjs.map +1 -0
- package/dist/{chunk-4MHA432G.js → chunk-7NKDPF74.js} +403 -180
- package/dist/chunk-7NKDPF74.js.map +1 -0
- package/dist/chunk-CAN76NCU.cjs +679 -0
- package/dist/chunk-CAN76NCU.cjs.map +1 -0
- package/dist/{chunk-MIWO425M.js → chunk-D2FAJBYL.js} +101 -34
- package/dist/chunk-D2FAJBYL.js.map +1 -0
- package/dist/{chunk-SGU7SPLK.js → chunk-EHVEBWM5.js} +1056 -531
- package/dist/chunk-EHVEBWM5.js.map +1 -0
- package/dist/chunk-FCANXBAT.cjs +30 -0
- package/dist/chunk-FCANXBAT.cjs.map +1 -0
- package/dist/chunk-FNDWXRRI.cjs +479 -0
- package/dist/chunk-FNDWXRRI.cjs.map +1 -0
- package/dist/{chunk-IT2O4XFE.js → chunk-GQSGS2N4.js} +43 -24
- package/dist/chunk-GQSGS2N4.js.map +1 -0
- package/dist/{chunk-62QZKCJQ.js → chunk-HKGYCQ34.js} +10 -6
- package/dist/chunk-HKGYCQ34.js.map +1 -0
- package/dist/{chunk-AKLYPWIN.js → chunk-I24MV65U.js} +29 -6
- package/dist/chunk-I24MV65U.js.map +1 -0
- package/dist/{chunk-CHMXELDS.js → chunk-KMHOICIY.js} +311 -181
- package/dist/chunk-KMHOICIY.js.map +1 -0
- package/dist/chunk-LW53AHQU.cjs +1117 -0
- package/dist/chunk-LW53AHQU.cjs.map +1 -0
- package/dist/{chunk-2ZWMSJH6.js → chunk-NKIBQ6PT.js} +2 -2
- package/dist/chunk-NKIBQ6PT.js.map +1 -0
- package/dist/chunk-NP4NA2FB.cjs +106 -0
- package/dist/chunk-NP4NA2FB.cjs.map +1 -0
- package/dist/chunk-NVE34YGZ.cjs +1449 -0
- package/dist/chunk-NVE34YGZ.cjs.map +1 -0
- package/dist/{chunk-EH6EM37U.js → chunk-NYYWMV2C.js} +61 -49
- package/dist/chunk-NYYWMV2C.js.map +1 -0
- package/dist/{chunk-GOAW4VYL.js → chunk-Q2XUP227.js} +243 -117
- package/dist/chunk-Q2XUP227.js.map +1 -0
- package/dist/chunk-Q6OP4U43.cjs +327 -0
- package/dist/chunk-Q6OP4U43.cjs.map +1 -0
- package/dist/chunk-RC6GBMCD.cjs +845 -0
- package/dist/chunk-RC6GBMCD.cjs.map +1 -0
- package/dist/{chunk-C5XYKUVG.js → chunk-RK35RIN7.js} +370 -157
- package/dist/chunk-RK35RIN7.js.map +1 -0
- package/dist/chunk-RUMU3UA6.cjs +53 -0
- package/dist/chunk-RUMU3UA6.cjs.map +1 -0
- package/dist/chunk-T64C7ZD7.cjs +1 -0
- package/dist/chunk-T64C7ZD7.cjs.map +1 -0
- package/dist/chunk-XEBG3G53.js +498 -0
- package/dist/chunk-XEBG3G53.js.map +1 -0
- package/dist/{chunk-I5R7ZBJS.js → chunk-YLEMGEFH.js} +103 -59
- package/dist/chunk-YLEMGEFH.js.map +1 -0
- package/dist/chunk-ZEJR4TKL.cjs +173 -0
- package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
- package/dist/core/index.cjs +130 -647
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +301 -39
- package/dist/core/index.d.ts +301 -39
- package/dist/core/index.js +54 -16
- package/dist/{function-DYt4iKsn.d.cts → function-BtiZb9i9.d.ts} +28 -7
- package/dist/{function-DVH4Eusr.d.ts → function-DNugAOrt.d.cts} +28 -7
- package/dist/generate/index.cjs +28 -4085
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +370 -62
- package/dist/generate/index.d.ts +370 -62
- package/dist/generate/index.js +23 -9
- package/dist/{index-CxqBQtH1.d.cts → index-6VR8-kit.d.ts} +19 -4
- package/dist/index-70p168VM.d.ts +192 -0
- package/dist/{index-CpOsP1y9.d.cts → index-Be74mcon.d.cts} +18 -17
- package/dist/index-BtXT6UQ0.d.cts +192 -0
- package/dist/{index-DxvARIP2.d.ts → index-C5UBbQ4G.d.ts} +18 -17
- package/dist/{index-Bt4tOUD9.d.ts → index-C83mZI1P.d.ts} +93 -8
- package/dist/{index-Bg5dYMmZ.d.ts → index-D6b6na69.d.cts} +14 -11
- package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
- package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
- package/dist/index-OG65ocOr.d.cts +272 -0
- package/dist/index-OG65ocOr.d.ts +272 -0
- package/dist/{index-Jc0Ryi79.d.ts → index-tJhkmQgE.d.cts} +19 -4
- package/dist/{index-Dikst44v.d.cts → index-tSQh-xwY.d.ts} +14 -11
- package/dist/{index-BNlGP86A.d.cts → index-y_ak9_Hi.d.cts} +93 -8
- package/dist/index.cjs +419 -7383
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +111 -27
- package/dist/model/index.cjs +8 -2562
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +433 -75
- package/dist/model/index.d.ts +433 -75
- package/dist/model/index.js +6 -7
- package/dist/styled-DIRkk9Uz.d.ts +506 -0
- package/dist/styled-DJbkJQfj.d.cts +506 -0
- package/dist/theory/index.cjs +172 -1822
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +151 -233
- package/dist/theory/index.d.ts +151 -233
- package/dist/theory/index.js +30 -7
- package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
- package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
- package/package.json +24 -6
- package/dist/chunk-2ZWMSJH6.js.map +0 -1
- package/dist/chunk-4MHA432G.js.map +0 -1
- package/dist/chunk-62QZKCJQ.js.map +0 -1
- package/dist/chunk-AKLYPWIN.js.map +0 -1
- package/dist/chunk-C5XYKUVG.js.map +0 -1
- package/dist/chunk-CHMXELDS.js.map +0 -1
- package/dist/chunk-DYWFNC4A.js +0 -152
- package/dist/chunk-DYWFNC4A.js.map +0 -1
- package/dist/chunk-EH6EM37U.js.map +0 -1
- package/dist/chunk-FBQK3ZQN.js +0 -131
- package/dist/chunk-FBQK3ZQN.js.map +0 -1
- package/dist/chunk-GOAW4VYL.js.map +0 -1
- package/dist/chunk-I5R7ZBJS.js.map +0 -1
- package/dist/chunk-IT2O4XFE.js.map +0 -1
- package/dist/chunk-MIWO425M.js.map +0 -1
- package/dist/chunk-SGU7SPLK.js.map +0 -1
- package/dist/index-CGMZNCx6.d.cts +0 -140
- package/dist/index-CGMZNCx6.d.ts +0 -140
- package/dist/index-CnZCo29u.d.cts +0 -108
- package/dist/index-JnF4v9bx.d.ts +0 -108
- package/dist/styled-DZ7GjShb.d.ts +0 -282
- package/dist/styled-ZTbdaVFM.d.cts +0 -282
- package/src/analyze/arrange/index.ts +0 -21
- package/src/analyze/arrange/internal.ts +0 -127
- package/src/analyze/arrange/tension.ts +0 -199
- package/src/analyze/arrange/tracks.ts +0 -312
- package/src/analyze/detect/index.ts +0 -272
- package/src/analyze/functional/borrowed.ts +0 -84
- package/src/analyze/functional/cadence.ts +0 -66
- package/src/analyze/functional/function.ts +0 -184
- package/src/analyze/functional/index.ts +0 -23
- package/src/analyze/functional/internal.ts +0 -29
- package/src/analyze/functional/roman.ts +0 -402
- package/src/analyze/index.ts +0 -49
- package/src/analyze/timeline/index.ts +0 -451
- package/src/analyze/voice/index.ts +0 -339
- package/src/core/event-index/index.ts +0 -104
- package/src/core/index.ts +0 -61
- package/src/core/interval/index.ts +0 -81
- package/src/core/meter/index.ts +0 -302
- package/src/core/pitch/index.ts +0 -311
- package/src/core/random/index.ts +0 -76
- package/src/core/tuning/index.ts +0 -186
- package/src/core/types.ts +0 -29
- package/src/core/validation/index.ts +0 -119
- package/src/generate/bass/index.ts +0 -373
- package/src/generate/countermelody/index.ts +0 -470
- package/src/generate/drums/beat.ts +0 -266
- package/src/generate/drums/euclid.ts +0 -65
- package/src/generate/drums/fills.ts +0 -356
- package/src/generate/drums/ghost.ts +0 -119
- package/src/generate/drums/hihat.ts +0 -285
- package/src/generate/drums/hit.ts +0 -31
- package/src/generate/drums/index.ts +0 -361
- package/src/generate/drums/internal.ts +0 -257
- package/src/generate/drums/kick.ts +0 -160
- package/src/generate/drums/percussion.ts +0 -121
- package/src/generate/drums/rng.ts +0 -7
- package/src/generate/drums/swing.ts +0 -71
- package/src/generate/groove/index.ts +0 -351
- package/src/generate/harmonize/index.ts +0 -451
- package/src/generate/index.ts +0 -58
- package/src/generate/motif/index.ts +0 -422
- package/src/generate/progression/index.ts +0 -362
- package/src/generate/reharmony/index.ts +0 -323
- package/src/generate/rhythm/index.ts +0 -175
- package/src/index.ts +0 -13
- package/src/model/chord.ts +0 -442
- package/src/model/index.ts +0 -15
- package/src/model/interval.ts +0 -82
- package/src/model/key.ts +0 -266
- package/src/model/note.ts +0 -176
- package/src/model/progression.ts +0 -161
- package/src/model/shared.ts +0 -12
- package/src/theory/chord/index.ts +0 -375
- package/src/theory/chordscale/index.ts +0 -374
- package/src/theory/counterpoint/index.ts +0 -230
- package/src/theory/harmony/index.ts +0 -77
- package/src/theory/index.ts +0 -112
- package/src/theory/safety/index.ts +0 -488
- package/src/theory/scale/degrees.ts +0 -123
- package/src/theory/scale/index.ts +0 -28
- package/src/theory/scale/key.ts +0 -56
- package/src/theory/scale/masks.ts +0 -153
- package/src/theory/spelling/index.ts +0 -249
- package/src/theory/symbol/index.ts +0 -268
- package/src/theory/voicing/index.ts +0 -5
- package/src/theory/voicing/internal.ts +0 -168
- package/src/theory/voicing/leading.ts +0 -112
- package/src/theory/voicing/satb.ts +0 -216
- package/src/theory/voicing/styled.ts +0 -199
- package/tsconfig.json +0 -23
package/dist/core/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/index.ts","../../src/core/validation/index.ts","../../src/core/event-index/index.ts","../../src/core/interval/index.ts","../../src/core/meter/index.ts","../../src/core/pitch/index.ts","../../src/core/random/index.ts","../../src/core/tuning/index.ts"],"sourcesContent":["/**\n * Public API of the core layer. Re-exports every core-layer module;\n * also available from the package root.\n */\n\nexport type { IndexedNoteEvent, NoteEventIndex } from './event-index/index.js';\nexport { createNoteEventIndex } from './event-index/index.js';\nexport {\n classifyInterval,\n IntervalQuality,\n isConsonantInterval,\n isPerfectInterval,\n} from './interval/index.js';\nexport type { BarPosition, TimeSignature } from './meter/index.js';\nexport {\n barPositionToBeat,\n beatsPerBar,\n beatToBarPosition,\n formatTimeSignature,\n isCompound,\n isStrongBeat,\n metricWeight,\n parseTimeSignature,\n pulsesPerBar,\n tuplet,\n} from './meter/index.js';\nexport type { Note as NoteData, SpelledInterval } from './pitch/index.js';\nexport {\n formatNote,\n midiToNote,\n noteToMidi,\n noteToPitchClass,\n parseNote,\n spelledInterval,\n} from './pitch/index.js';\nexport type { Rng } from './random/index.js';\nexport { createRng } from './random/index.js';\nexport type { Tuning } from './tuning/index.js';\nexport {\n centsBetweenFreq,\n centsOfSteps,\n edo,\n frequencyOf,\n JUST_RATIOS,\n justDeviationCents,\n nearestStep,\n ratioToCents,\n TWELVE_TET,\n} from './tuning/index.js';\nexport type { KeyScale, NoteEvent } from './types.js';\nexport {\n assertFiniteNumber,\n assertGenerationBudget,\n assertInteger,\n assertNoteEvent,\n assertNoteEvents,\n assertPositiveInt,\n assertRange,\n assertTimeSignature,\n DEFAULT_GENERATION_BUDGET,\n} from './validation/index.js';\n","import type { TimeSignature } from '../meter/index.js';\nimport type { NoteEvent } from '../types.js';\n\n/** Default upper bound for synchronous event/window/candidate generation. */\nexport const DEFAULT_GENERATION_BUDGET = 1_000_000;\n\n/** Require a finite JavaScript number and return it unchanged. */\nexport function assertFiniteNumber(value: number, name: string): number {\n if (!Number.isFinite(value)) {\n throw new RangeError(`${name} must be finite; received ${value}`);\n }\n return value;\n}\n\n/** Require an integer in the inclusive range `[min, max]`. */\nexport function assertInteger(\n value: number,\n name: string,\n min = Number.MIN_SAFE_INTEGER,\n max = Number.MAX_SAFE_INTEGER,\n): number {\n assertFiniteNumber(value, name);\n if (!Number.isSafeInteger(value) || value < min || value > max) {\n throw new RangeError(`${name} must be an integer in [${min}, ${max}]; received ${value}`);\n }\n return value;\n}\n\n/** Require a positive safe integer, optionally capped by `max`. */\nexport function assertPositiveInt(\n value: number,\n name: string,\n max = DEFAULT_GENERATION_BUDGET,\n): number {\n return assertInteger(value, name, 1, max);\n}\n\n/** Require a finite number in the inclusive range `[min, max]`. */\nexport function assertRange(value: number, min: number, max: number, name: string): number {\n assertFiniteNumber(value, name);\n if (value < min || value > max) {\n throw new RangeError(`${name} must be in [${min}, ${max}]; received ${value}`);\n }\n return value;\n}\n\n/** Reject work estimates that would exceed a synchronous generation budget. */\nexport function assertGenerationBudget(\n estimated: number,\n name: string,\n limit = DEFAULT_GENERATION_BUDGET,\n): number {\n assertFiniteNumber(estimated, name);\n assertPositiveInt(limit, `${name} limit`, Number.MAX_SAFE_INTEGER);\n if (estimated < 0 || estimated > limit) {\n throw new RangeError(`${name} exceeds the generation budget ${limit}; received ${estimated}`);\n }\n return estimated;\n}\n\n/** Validate a time signature, including additive grouping, before any early return. */\nexport function assertTimeSignature(ts: TimeSignature, name = 'time signature'): TimeSignature {\n assertPositiveInt(ts.numerator, `${name}.numerator`);\n assertPositiveInt(ts.denominator, `${name}.denominator`);\n if (ts.grouping !== undefined) {\n if (ts.grouping.length === 0) {\n throw new RangeError(`${name}.grouping must not be empty`);\n }\n let sum = 0;\n for (let index = 0; index < ts.grouping.length; index += 1) {\n sum += assertPositiveInt(ts.grouping[index] ?? Number.NaN, `${name}.grouping[${index}]`);\n }\n const compound = ts.numerator % 3 === 0 && ts.numerator > 3;\n const pulses = compound ? ts.numerator / 3 : ts.numerator;\n if (sum !== pulses) {\n throw new RangeError(`${name}.grouping must sum to ${pulses}; received ${sum}`);\n }\n }\n return ts;\n}\n\n/** Validate the finite fields of one timeline note event. */\nexport function assertNoteEvent(\n event: NoteEvent,\n name = 'note event',\n options: { allowNonPositiveDuration?: boolean } = {},\n): NoteEvent {\n assertFiniteNumber(event.pitch, `${name}.pitch`);\n assertRange(event.startBeat, 0, Number.MAX_SAFE_INTEGER, `${name}.startBeat`);\n assertRange(\n event.durationBeat,\n options.allowNonPositiveDuration ? -Number.MAX_SAFE_INTEGER : Number.MIN_VALUE,\n Number.MAX_SAFE_INTEGER,\n `${name}.durationBeat`,\n );\n if (!options.allowNonPositiveDuration && event.durationBeat <= 0) {\n throw new RangeError(`${name}.durationBeat must be positive; received ${event.durationBeat}`);\n }\n if (event.velocity !== undefined) {\n assertRange(event.velocity, 0, 127, `${name}.velocity`);\n }\n return event;\n}\n\n/** Validate an event array and its allocation budget without copying it. */\nexport function assertNoteEvents(\n events: NoteEvent[],\n name = 'note events',\n options: { allowNonPositiveDuration?: boolean; budget?: number } = {},\n): NoteEvent[] {\n assertGenerationBudget(events.length, `${name} count`, options.budget);\n for (let index = 0; index < events.length; index += 1) {\n const event = events[index];\n if (event !== undefined) {\n assertNoteEvent(event, `${name}[${index}]`, options);\n }\n }\n return events;\n}\n","import type { NoteEvent } from '../types.js';\nimport { assertNoteEvents } from '../validation/index.js';\n\n/** A validated note retaining its position in the caller's original array. */\nexport type IndexedNoteEvent = {\n note: NoteEvent;\n originalIndex: number;\n endBeat: number;\n};\n\n/** Binary-searchable, stable-sorted index over note onsets and active spans. */\nexport type NoteEventIndex = {\n notes: IndexedNoteEvent[];\n /** Latest-onset note sounding at `beat`, with later input order winning ties. */\n at: (beat: number) => IndexedNoteEvent | undefined;\n /** Whether one or more notes attack at `beat`. */\n attacksAt: (beat: number) => boolean;\n /** Unique attack beats strictly inside `(startBeat, endBeat)`. */\n onsetsBetween: (startBeat: number, endBeat: number) => number[];\n};\n\nconst EPS = 1e-9;\n\nfunction upperBound(values: IndexedNoteEvent[], beat: number): number {\n let low = 0;\n let high = values.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((values[middle]?.note.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n return low;\n}\n\n/**\n * Validate and stable-sort note events once, then expose logarithmic onset and\n * active-note lookups. Non-positive-duration notes may be retained for callers\n * that intentionally filter them later, but never count as sounding.\n */\nexport function createNoteEventIndex(\n events: NoteEvent[],\n options: { allowNonPositiveDuration?: boolean } = {},\n): NoteEventIndex {\n assertNoteEvents(events, 'note events', options);\n const notes = events\n .map((note, originalIndex) => ({\n note,\n originalIndex,\n endBeat: note.startBeat + note.durationBeat,\n }))\n .sort((a, b) => a.note.startBeat - b.note.startBeat || a.originalIndex - b.originalIndex);\n const prefixMaxEnd: number[] = [];\n let maxEnd = Number.NEGATIVE_INFINITY;\n for (const indexed of notes) {\n maxEnd = Math.max(maxEnd, indexed.endBeat);\n prefixMaxEnd.push(maxEnd);\n }\n\n return {\n notes,\n at(beat) {\n let index = upperBound(notes, beat + EPS) - 1;\n while (index >= 0) {\n if ((prefixMaxEnd[index] ?? Number.NEGATIVE_INFINITY) <= beat + EPS) {\n return undefined;\n }\n const indexed = notes[index];\n if (\n indexed &&\n indexed.note.durationBeat > 0 &&\n indexed.note.startBeat - EPS <= beat &&\n beat < indexed.endBeat - EPS\n ) {\n return indexed;\n }\n index -= 1;\n }\n return undefined;\n },\n attacksAt(beat) {\n const firstAfter = upperBound(notes, beat + EPS);\n const candidate = notes[firstAfter - 1];\n return candidate !== undefined && Math.abs(candidate.note.startBeat - beat) < EPS;\n },\n onsetsBetween(startBeat, endBeat) {\n const result: number[] = [];\n let index = upperBound(notes, startBeat + EPS);\n while (index < notes.length) {\n const onset = notes[index]?.note.startBeat;\n if (onset === undefined || onset >= endBeat - EPS) {\n break;\n }\n if (result[result.length - 1] !== onset) {\n result.push(onset);\n }\n index += 1;\n }\n return result;\n },\n };\n}\n","/**\n * Counterpoint classification of a harmonic interval.\n *\n * @category Pitch & Intervals\n */\nexport enum IntervalQuality {\n PerfectConsonance = 0,\n ImperfectConsonance = 1,\n Dissonance = 2,\n}\n\n/** Reduce an interval to a simple interval class in the range [0, 11]. */\nfunction simpleInterval(semitones: number): number {\n return Math.abs(semitones) % 12;\n}\n\n/**\n * Classify an interval for counterpoint evaluation.\n *\n * The interval is reduced modulo 12, so compound intervals classify as their\n * simple equivalents. The perfect fourth (5) is context-dependent: with\n * `twoVoice` (the default) it classifies as a dissonance, matching\n * {@link isConsonantInterval}; otherwise it is an imperfect consonance.\n *\n * @param semitones Interval size in semitones (may be negative or compound).\n * @param twoVoice When true, the perfect fourth is treated as dissonant.\n * @returns The counterpoint quality of the interval.\n * @example\n * ```ts\n * import { classifyInterval, IntervalQuality } from '@libraz/libcantus';\n * classifyInterval(7); // IntervalQuality.PerfectConsonance\n * classifyInterval(5); // IntervalQuality.Dissonance (two-voice)\n * classifyInterval(5, false); // IntervalQuality.ImperfectConsonance\n * ```\n * @category Pitch & Intervals\n */\nexport function classifyInterval(semitones: number, twoVoice = true): IntervalQuality {\n const pc = simpleInterval(semitones);\n if (pc === 0 || pc === 7) {\n return IntervalQuality.PerfectConsonance;\n }\n if (pc === 3 || pc === 4 || pc === 8 || pc === 9) {\n return IntervalQuality.ImperfectConsonance;\n }\n if (pc === 5) {\n return twoVoice ? IntervalQuality.Dissonance : IntervalQuality.ImperfectConsonance;\n }\n return IntervalQuality.Dissonance;\n}\n\n/**\n * Test whether an interval is a perfect interval (unison/octave or fifth).\n *\n * @param semitones Interval size in semitones (may be negative or compound).\n * @returns True for the unison/octave (0) and the perfect fifth (7) mod 12.\n * @category Pitch & Intervals\n */\nexport function isPerfectInterval(semitones: number): boolean {\n const pc = simpleInterval(semitones);\n return pc === 0 || pc === 7;\n}\n\n/**\n * Test whether an interval is consonant.\n *\n * The consonant set is {0, 3, 4, 5, 7, 8, 9} mod 12. In two-voice counterpoint\n * the perfect fourth (5) is treated as a dissonance, so `twoVoice` set to true\n * excludes it.\n *\n * @param semitones Interval size in semitones (may be negative or compound).\n * @param twoVoice When true, the perfect fourth is treated as dissonant.\n * @returns True if the interval is consonant in the given context.\n * @category Pitch & Intervals\n */\nexport function isConsonantInterval(semitones: number, twoVoice = true): boolean {\n const pc = simpleInterval(semitones);\n if (pc === 5) {\n return !twoVoice;\n }\n return pc === 0 || pc === 3 || pc === 4 || pc === 7 || pc === 8 || pc === 9;\n}\n","/**\n * Meter and time signatures: bar/beat arithmetic, simple vs. compound meters,\n * metric-accent hierarchy, and tuplet subdivision.\n *\n * Positions and durations are measured in quarter-note beats, matching the rest\n * of the library's beat convention (four quarter-note beats per bar in 4/4).\n */\n\nimport {\n assertFiniteNumber,\n assertInteger,\n assertPositiveInt,\n assertRange,\n assertTimeSignature,\n} from '../validation/index.js';\n\n/**\n * A time signature as a numerator over a note-value denominator.\n *\n * @category Rhythm & Meter\n */\nexport type TimeSignature = {\n numerator: number;\n denominator: number;\n /**\n * Optional additive grouping of the bar's main pulses, as the felt-beat\n * lengths in pulses — e.g. `[2, 2, 3]` for a 2+2+3 reading of 7/8, or\n * `[3, 2]` for 5/8. The entries must be positive integers summing to\n * {@link pulsesPerBar}. When present, the head pulse of each group (other\n * than the downbeat) is treated as a secondary strong pulse by\n * {@link metricWeight}/{@link isStrongBeat}; when absent, all main pulses\n * are weighted equally as flat, evenly divided pulses.\n */\n grouping?: number[];\n};\n\n/**\n * A position expressed as a bar index and a quarter-note offset within the bar.\n *\n * @category Rhythm & Meter\n */\nexport type BarPosition = {\n bar: number;\n /** Quarter-note offset from the start of the bar. */\n beat: number;\n};\n\nconst EPS = 1e-9;\n\n/** Whether `value` is an integer multiple of `unit` (within a float tolerance). */\nfunction isMultiple(value: number, unit: number): boolean {\n if (unit === 0) {\n return false;\n }\n const ratio = value / unit;\n return Math.abs(ratio - Math.round(ratio)) < EPS;\n}\n\n/**\n * Parse a time signature such as `\"4/4\"` or `\"6/8\"`.\n *\n * @param text The signature text.\n * @returns The parsed time signature.\n * @throws If the text is not `n/d` with positive integers.\n * @example\n * ```ts\n * import { parseTimeSignature } from '@libraz/libcantus';\n * parseTimeSignature('6/8'); // { numerator: 6, denominator: 8 }\n * ```\n * @category Rhythm & Meter\n */\nexport function parseTimeSignature(text: string): TimeSignature {\n const match = /^\\s*(\\d+)\\s*\\/\\s*(\\d+)\\s*$/.exec(text);\n if (!match) {\n throw new Error(`Invalid time signature: ${text}`);\n }\n const numerator = Number.parseInt(match[1] ?? '', 10);\n const denominator = Number.parseInt(match[2] ?? '', 10);\n if (\n !Number.isSafeInteger(numerator) ||\n !Number.isSafeInteger(denominator) ||\n numerator <= 0 ||\n denominator <= 0\n ) {\n throw new Error(`Invalid time signature: ${text}`);\n }\n return { numerator, denominator };\n}\n\n/**\n * Render a time signature as `\"n/d\"`.\n *\n * @category Rhythm & Meter\n */\nexport function formatTimeSignature(ts: TimeSignature): string {\n assertTimeSignature(ts);\n return `${ts.numerator}/${ts.denominator}`;\n}\n\n/**\n * Whether a meter is compound: its main pulses each divide into three, as in\n * 6/8, 9/8, 12/8, or 6/4. Compound meters are those whose numerator is a\n * multiple of three greater than three, independent of the denominator. Meters\n * like 3/8 and 3/4 (simple triples, numerator 3) are not compound.\n *\n * @param ts The time signature.\n * @returns True for compound meters.\n * @category Rhythm & Meter\n */\nexport function isCompound(ts: TimeSignature): boolean {\n assertTimeSignature(ts);\n return ts.numerator % 3 === 0 && ts.numerator > 3;\n}\n\n/** Length of one denominator unit in quarter-note beats. */\nfunction unitBeats(ts: TimeSignature): number {\n return 4 / ts.denominator;\n}\n\n/**\n * Length of a bar in quarter-note beats.\n *\n * @param ts The time signature.\n * @returns The bar length in quarter notes.\n * @category Rhythm & Meter\n */\nexport function beatsPerBar(ts: TimeSignature): number {\n assertTimeSignature(ts);\n return ts.numerator * unitBeats(ts);\n}\n\n/** Length of one main pulse (a dotted value in compound meters) in quarter notes. */\nfunction pulseBeats(ts: TimeSignature): number {\n return isCompound(ts) ? 3 * unitBeats(ts) : unitBeats(ts);\n}\n\n/**\n * Number of main pulses (felt beats) per bar: the numerator for simple meters,\n * a third of it for compound meters.\n *\n * For additive/irregular meters such as 7/8 or 5/8 this counts the raw pulses\n * (7 and 5 respectively), which are otherwise felt as flat, evenly divided and\n * equally accented pulses. To recover a 2+2+3 or 3+2 felt-beat grouping, set\n * {@link TimeSignature.grouping}; {@link metricWeight} then accents each\n * group's head pulse.\n *\n * @param ts The time signature.\n * @returns The pulse count per bar.\n * @category Rhythm & Meter\n */\nexport function pulsesPerBar(ts: TimeSignature): number {\n return beatsPerBar(ts) / pulseBeats(ts);\n}\n\n/**\n * Whether `grouping` is a valid additive grouping for a bar of `pulses` main\n * pulses: a non-empty list of positive integers summing to `pulses`.\n */\nfunction isValidGrouping(grouping: number[], pulses: number): boolean {\n if (grouping.length === 0) {\n return false;\n }\n let sum = 0;\n for (const g of grouping) {\n if (!Number.isInteger(g) || g <= 0) {\n return false;\n }\n sum += g;\n }\n return sum === pulses;\n}\n\n/** Whether `pulseIndex` is the head pulse of one of the additive groups. */\nfunction isGroupHead(grouping: number[], pulseIndex: number): boolean {\n let acc = 0;\n for (const g of grouping) {\n if (acc === pulseIndex) {\n return true;\n }\n acc += g;\n }\n return false;\n}\n\n/**\n * Convert an absolute quarter-note position to a bar index and in-bar offset.\n *\n * @param beatInQuarters Absolute position in quarter-note beats.\n * @param ts The time signature.\n * @returns The bar and in-bar quarter-note offset.\n * @example\n * ```ts\n * import { parseTimeSignature, beatToBarPosition } from '@libraz/libcantus';\n * const ts = parseTimeSignature('4/4');\n * beatToBarPosition(5, ts); // { bar: 1, beat: 1 }\n * ```\n * @category Rhythm & Meter\n */\nexport function beatToBarPosition(beatInQuarters: number, ts: TimeSignature): BarPosition {\n assertFiniteNumber(beatInQuarters, 'beat');\n const barLen = beatsPerBar(ts);\n const bar = Math.floor(beatInQuarters / barLen);\n return { bar, beat: beatInQuarters - bar * barLen };\n}\n\n/**\n * Convert a bar index and in-bar offset back to an absolute quarter-note\n * position.\n *\n * @param pos The bar position.\n * @param ts The time signature.\n * @returns The absolute position in quarter-note beats.\n * @category Rhythm & Meter\n */\nexport function barPositionToBeat(pos: BarPosition, ts: TimeSignature): number {\n assertInteger(pos.bar, 'bar position bar');\n assertFiniteNumber(pos.beat, 'bar position beat');\n return pos.bar * beatsPerBar(ts) + pos.beat;\n}\n\n/**\n * Metric weight of a position within its bar, on a 0–3 scale:\n * 3 the downbeat, 2 a secondary strong pulse, 1 any other main pulse, and 0 an\n * off-pulse subdivision.\n *\n * The secondary strong pulse is the bar's midpoint in even simple/compound\n * meters. For an additive/irregular meter with {@link TimeSignature.grouping}\n * set (e.g. `[2, 2, 3]` for 7/8), each group's head pulse other than the\n * downbeat is the secondary strong pulse instead; without a grouping every\n * non-downbeat main pulse of such a meter weighs 1 (flat, evenly divided\n * pulses).\n *\n * @param beatInQuarters Absolute or in-bar quarter-note position.\n * @param ts The time signature.\n * @returns The metric weight (0–3).\n * @throws If `ts.grouping` is present but is not a positive-integer list\n * summing to {@link pulsesPerBar}.\n * @example\n * ```ts\n * import { parseTimeSignature, metricWeight } from '@libraz/libcantus';\n * const ts = parseTimeSignature('4/4');\n * metricWeight(0, ts); // 3 (the downbeat)\n * ```\n * @category Rhythm & Meter\n */\nexport function metricWeight(beatInQuarters: number, ts: TimeSignature): number {\n assertFiniteNumber(beatInQuarters, 'beat');\n assertTimeSignature(ts);\n const { beat } = beatToBarPosition(beatInQuarters, ts);\n const pulse = pulseBeats(ts);\n if (!isMultiple(beat, pulse)) {\n return 0;\n }\n const pulses = pulsesPerBar(ts);\n const pulseIndex = Math.round(beat / pulse) % pulses;\n if (pulseIndex === 0) {\n return 3;\n }\n const grouping = ts.grouping;\n if (grouping !== undefined) {\n if (!isValidGrouping(grouping, pulses)) {\n throw new Error(\n `Invalid grouping [${grouping.join(', ')}] for ${formatTimeSignature(ts)}: ` +\n `entries must be positive integers summing to ${pulses}`,\n );\n }\n return isGroupHead(grouping, pulseIndex) ? 2 : 1;\n }\n if (pulses % 2 === 0 && pulseIndex === pulses / 2) {\n return 2;\n }\n return 1;\n}\n\n/**\n * Whether a position is metrically accented (weight 2 or more — a downbeat or a\n * secondary strong pulse).\n *\n * @param beatInQuarters Absolute or in-bar quarter-note position.\n * @param ts The time signature.\n * @returns True on strong beats.\n * @category Rhythm & Meter\n */\nexport function isStrongBeat(beatInQuarters: number, ts: TimeSignature): boolean {\n return metricWeight(beatInQuarters, ts) >= 2;\n}\n\n/**\n * Subdivide a span into `count` equal tuplet durations (e.g. an eighth-note\n * triplet is `tuplet(1, 3)`).\n *\n * @param totalBeats Total span in quarter-note beats.\n * @param count Number of equal parts.\n * @returns `count` equal durations summing to `totalBeats`.\n * @throws If `count` is not a positive integer.\n * @category Rhythm & Meter\n */\nexport function tuplet(totalBeats: number, count: number): number[] {\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'tuplet total beats');\n assertPositiveInt(count, 'tuplet count');\n return new Array<number>(count).fill(totalBeats / count);\n}\n","/**\n * Pitch spelling: letter-name notes, enharmonic-aware conversion, and spelled\n * intervals.\n *\n * The rest of the library works in pitch classes (0..11), which cannot tell a\n * G# from an Ab or an augmented fourth from a diminished fifth. This module adds\n * an explicit spelling layer — a note is a diatonic letter plus a chromatic\n * alteration — so interval quality (P/M/m/A/d) and enharmonic identity are\n * preserved.\n */\n\n/** Semitone offset of each natural letter above C: C D E F G A B. */\nconst LETTER_SEMITONES = [0, 2, 4, 5, 7, 9, 11] as const;\n\n/** Letter glyphs indexed by letter number (0 = C .. 6 = B). */\nconst LETTER_NAMES = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] as const;\n\n/**\n * A spelled note: a diatonic letter, a chromatic alteration, and an optional\n * octave.\n *\n * - `letter` is 0..6 for C..B.\n * - `alter` is the number of semitones of alteration: -2 double-flat, -1 flat,\n * 0 natural, +1 sharp, +2 double-sharp.\n * - `octave` follows scientific pitch notation (middle C = C4 = MIDI 60). When\n * absent, the note denotes a bare pitch class.\n *\n * @category Pitch & Intervals\n */\nexport type Note = {\n letter: number;\n alter: number;\n octave?: number;\n};\n\n/**\n * A spelled interval: a diatonic number, a quality label, and its semitone span.\n *\n * @category Pitch & Intervals\n */\nexport type SpelledInterval = {\n /** Diatonic size: 1 = unison, 2 = second, ... 8 = octave, and beyond. */\n number: number;\n /** Quality label: 'P', 'M', 'm', or repeated 'A'/'d' for (multiply) aug/dim. */\n quality: string;\n /** Signed semitone distance from the first note to the second. */\n semitones: number;\n};\n\n/** Reduce any integer to a pitch class in [0, 11]. */\nfunction mod12(n: number): number {\n return ((n % 12) + 12) % 12;\n}\n\n/** Reduce any integer to [0, 7). */\nfunction mod7(n: number): number {\n return ((n % 7) + 7) % 7;\n}\n\n/**\n * Parse scientific pitch notation into a {@link Note}.\n *\n * Accepts a letter (case-insensitive) followed by any number of same-direction\n * accidentals and an optional octave, e.g. `\"C\"`, `\"C#4\"`, `\"Bb\"`, `\"F##3\"`,\n * `\"Ebb2\"`. Both `#` and `x` (double-sharp) are accepted for sharps; `b` for\n * flats. Mixing sharps and flats (e.g. `\"C#b\"`) is rejected.\n *\n * @param text The note text.\n * @returns The parsed note.\n * @throws If the text is not a valid note.\n * @example\n * ```ts\n * import { parseNote, noteToMidi } from '@libraz/libcantus';\n * const n = parseNote('C#4');\n * noteToMidi(n); // 61\n * ```\n * @category Pitch & Intervals\n */\nexport function parseNote(text: string): Note {\n const match = /^([A-Ga-g])([#x]*|b*)(-?\\d+)?$/.exec(text.trim());\n if (!match) {\n throw new Error(`Invalid note: ${text}`);\n }\n const letterGlyph = (match[1] ?? '').toUpperCase();\n const letter = LETTER_NAMES.indexOf(letterGlyph as (typeof LETTER_NAMES)[number]);\n let alter = 0;\n for (const ch of match[2] ?? '') {\n if (ch === '#') {\n alter += 1;\n } else if (ch === 'x') {\n alter += 2;\n } else if (ch === 'b') {\n alter -= 1;\n }\n }\n const note: Note = { letter, alter };\n if (match[3] !== undefined) {\n note.octave = Number.parseInt(match[3], 10);\n }\n return note;\n}\n\n/** Render an alteration as accidental glyphs (`##`, `b`, empty for natural). */\nfunction formatAlter(alter: number): string {\n if (alter > 0) {\n return '#'.repeat(alter);\n }\n if (alter < 0) {\n return 'b'.repeat(-alter);\n }\n return '';\n}\n\n/**\n * Render a {@link Note} as scientific pitch notation.\n *\n * @param note The note to format.\n * @returns The note text, including the octave when present.\n * @example\n * ```ts\n * import { formatNote, parseNote } from '@libraz/libcantus';\n * formatNote(parseNote('C#4')); // 'C#4'\n * ```\n * @category Pitch & Intervals\n */\nexport function formatNote(note: Note): string {\n const glyph = LETTER_NAMES[mod7(note.letter)] ?? 'C';\n const octave = note.octave === undefined ? '' : String(note.octave);\n return `${glyph}${formatAlter(note.alter)}${octave}`;\n}\n\n/**\n * Pitch class (0..11) of a note, ignoring octave.\n *\n * @param note The note.\n * @returns The pitch class.\n * @example\n * ```ts\n * import { noteToPitchClass, parseNote } from '@libraz/libcantus';\n * noteToPitchClass(parseNote('Db')); // 1\n * ```\n * @category Pitch & Intervals\n */\nexport function noteToPitchClass(note: Note): number {\n const natural = LETTER_SEMITONES[mod7(note.letter)] ?? 0;\n return mod12(natural + note.alter);\n}\n\n/**\n * MIDI number of a note (middle C = C4 = 60).\n *\n * @param note The note; must carry an octave.\n * @returns The MIDI number.\n * @throws If the note has no octave.\n * @example\n * ```ts\n * import { noteToMidi, parseNote } from '@libraz/libcantus';\n * noteToMidi(parseNote('A4')); // 69\n * ```\n * @category Pitch & Intervals\n */\nexport function noteToMidi(note: Note): number {\n if (note.octave === undefined) {\n throw new Error('noteToMidi requires an octave');\n }\n const natural = LETTER_SEMITONES[mod7(note.letter)] ?? 0;\n return (note.octave + 1) * 12 + natural + note.alter;\n}\n\n/** Preferred spelling when naming a black key from a bare MIDI number. */\nconst SHARP_SPELLING: readonly [number, number][] = [\n [0, 0],\n [0, 1],\n [1, 0],\n [1, 1],\n [2, 0],\n [3, 0],\n [3, 1],\n [4, 0],\n [4, 1],\n [5, 0],\n [5, 1],\n [6, 0],\n];\n\nconst FLAT_SPELLING: readonly [number, number][] = [\n [0, 0],\n [1, -1],\n [1, 0],\n [2, -1],\n [2, 0],\n [3, 0],\n [4, -1],\n [4, 0],\n [5, -1],\n [5, 0],\n [6, -1],\n [6, 0],\n];\n\n/**\n * Name a MIDI number as a {@link Note}, choosing sharp or flat spelling.\n *\n * The input is rounded to the nearest integer but is not clamped to the MIDI\n * range 0..127: out-of-range values extrapolate linearly (e.g. `-1` -> `B-2`,\n * `128` -> `G#9`) and remain an exact inverse of {@link noteToMidi}.\n *\n * @param midi The MIDI number.\n * @param spelling Whether to prefer sharps or flats for black keys.\n * @returns The spelled note, with octave.\n * @example\n * ```ts\n * import { midiToNote, formatNote } from '@libraz/libcantus';\n * formatNote(midiToNote(61)); // 'C#4'\n * formatNote(midiToNote(61, 'flat')); // 'Db4'\n * ```\n * @category Pitch & Intervals\n */\nexport function midiToNote(midi: number, spelling: 'sharp' | 'flat' = 'sharp'): Note {\n const rounded = Math.round(midi);\n const pc = mod12(rounded);\n const octave = Math.floor(rounded / 12) - 1;\n const table = spelling === 'sharp' ? SHARP_SPELLING : FLAT_SPELLING;\n const entry = table[pc] ?? [0, 0];\n return { letter: entry[0], alter: entry[1], octave };\n}\n\n/** Reference semitone span of a perfect/major simple interval by diatonic number. */\nconst SIMPLE_REFERENCE = [0, 0, 2, 4, 5, 7, 9, 11] as const;\n\n/** Whether a simple diatonic number (1..7 within an octave) is a perfect kind. */\nfunction isPerfectNumber(simpleNumber: number): boolean {\n return simpleNumber === 1 || simpleNumber === 4 || simpleNumber === 5;\n}\n\n/** Quality label from a diatonic number and its actual semitone span. */\nfunction qualityFromSpan(numberValue: number, semitones: number): string {\n const octaves = Math.floor((numberValue - 1) / 7);\n const simple = numberValue - 7 * octaves;\n const reference = (SIMPLE_REFERENCE[simple] ?? 0) + 12 * octaves;\n const delta = semitones - reference;\n if (isPerfectNumber(simple)) {\n if (delta === 0) {\n return 'P';\n }\n return delta > 0 ? 'A'.repeat(delta) : 'd'.repeat(-delta);\n }\n if (delta === 0) {\n return 'M';\n }\n if (delta === -1) {\n return 'm';\n }\n return delta > 0 ? 'A'.repeat(delta) : 'd'.repeat(-delta - 1);\n}\n\n/** Diatonic ladder index of a note (letter + 7 * octave when octave-bearing). */\nfunction diatonicIndex(note: Note): number {\n return mod7(note.letter) + 7 * (note.octave ?? 0);\n}\n\n/**\n * The spelled interval from one note to another.\n *\n * When both notes carry octaves, the interval reflects their full signed\n * distance (so `C4 -> C5` is a perfect octave). When either lacks an octave the\n * interval is measured within a single ascending octave from `a` to `b`.\n *\n * @param a The lower/first note.\n * @param b The upper/second note.\n * @returns The diatonic number, quality, and semitone span.\n * @example\n * ```ts\n * import { spelledInterval, parseNote } from '@libraz/libcantus';\n * spelledInterval(parseNote('C4'), parseNote('G4'));\n * // { number: 5, quality: 'P', semitones: 7 }\n * ```\n * @category Pitch & Intervals\n */\nexport function spelledInterval(a: Note, b: Note): SpelledInterval {\n const octaved = a.octave !== undefined && b.octave !== undefined;\n let letterSteps: number;\n let semitones: number;\n if (octaved) {\n letterSteps = diatonicIndex(b) - diatonicIndex(a);\n // Signed pitch distance from a to b, so a note below a yields a negative span.\n semitones = noteToMidi(b) - noteToMidi(a);\n } else {\n letterSteps = mod7(b.letter - a.letter);\n let rawSemis = mod12(noteToPitchClass(b) - noteToPitchClass(a));\n // Lift the chromatic span into the octave nearest the diatonic reference so\n // wraparound intervals stay consistent with the diatonic number instead of\n // collapsing modulo 12. The correction is bidirectional: an upward wrap\n // (e.g. Ab -> G# = augmented seventh) adds an octave, while a same-letter or\n // downward chromatic step (e.g. E -> Eb, F# -> F, C -> Cb = descending\n // diminished/augmented unison) subtracts one so the sign follows pitch\n // direction instead of returning a garbage stack of augmentations.\n const reference = SIMPLE_REFERENCE[letterSteps + 1] ?? 0;\n if (reference - rawSemis > 6) {\n rawSemis += 12;\n } else if (rawSemis - reference > 6) {\n rawSemis -= 12;\n }\n semitones = rawSemis;\n }\n const absSteps = Math.abs(letterSteps);\n const number = absSteps + 1;\n // Quality comes from the absolute chromatic span; semitones keeps its sign.\n const quality = qualityFromSpan(number, Math.abs(semitones));\n return { number, quality, semitones };\n}\n","/**\n * Deterministic seeded pseudo-random number generation shared across the\n * generative modules (rhythm, drums, bass, groove, counter-melody). A single\n * mulberry32 core exposes the small set of samplers those modules need; given a\n * seed the stream is fully reproducible.\n */\n\nimport { assertFiniteNumber, assertInteger, assertRange } from '../validation/index.js';\n\n/**\n * A deterministic PRNG with the sampling helpers the generators need.\n *\n * @category Utilities\n */\nexport type Rng = {\n /** Next float in [0, 1). */\n next: () => number;\n /** True with probability `p`. */\n prob: (p: number) => boolean;\n /** Integer in the inclusive range [lo, hi]. */\n range: (lo: number, hi: number) => number;\n /** Float in [lo, hi). */\n float: (lo: number, hi: number) => number;\n};\n\n/**\n * Create a seeded PRNG (mulberry32) exposing the shared samplers.\n *\n * @param seed The 32-bit seed; the same seed always yields the same stream.\n * @returns The seeded generator.\n * @example\n * ```ts\n * import { createRng } from '@libraz/libcantus';\n * const rng = createRng(42);\n * rng.next(); // deterministic float in [0, 1)\n * rng.range(1, 6); // deterministic integer in [1, 6]\n * ```\n * @category Utilities\n */\nexport function createRng(seed: number): Rng {\n assertFiniteNumber(seed, 'seed');\n let state = seed >>> 0;\n const next = () => {\n state = (state + 0x6d2b79f5) >>> 0;\n let t = state;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n return {\n next,\n prob: (p) => next() < assertRange(p, 0, 1, 'probability'),\n range: (lo, hi) => {\n assertInteger(lo, 'range lower bound');\n assertInteger(hi, 'range upper bound');\n if (lo > hi) {\n throw new RangeError(\n `range lower bound must not exceed upper bound; received ${lo} > ${hi}`,\n );\n }\n assertInteger(hi - lo, 'range span', 0);\n return lo + Math.floor(next() * (hi - lo + 1));\n },\n float: (lo, hi) => {\n assertFiniteNumber(lo, 'float lower bound');\n assertFiniteNumber(hi, 'float upper bound');\n if (lo > hi) {\n throw new RangeError(\n `float lower bound must not exceed upper bound; received ${lo} > ${hi}`,\n );\n }\n assertFiniteNumber(hi - lo, 'float span');\n return lo + next() * (hi - lo);\n },\n };\n}\n","/**\n * Tuning and microtonality: frequency conversion, cents, arbitrary equal\n * temperaments (EDO), and just-intonation ratios.\n *\n * The rest of the library reasons in twelve-tone pitch classes, which is a\n * deliberate scope choice for tonal theory. This module is the escape hatch for\n * anyone who needs actual frequencies, cents, non-12 equal temperaments, or the\n * acoustic (just) tuning behind the tempered intervals.\n */\n\nimport {\n assertFiniteNumber,\n assertInteger,\n assertPositiveInt,\n assertRange,\n} from '../validation/index.js';\n\n/**\n * An equal-tempered tuning: a reference pitch and a number of equal divisions of\n * the octave. With `divisions === 12` a step index is an ordinary MIDI number.\n *\n * @category Pitch & Intervals\n */\nexport type Tuning = {\n /** Step index (MIDI number when `divisions` is 12) whose frequency is `refFreq`. */\n refStep: number;\n /** Frequency in Hz of `refStep`. */\n refFreq: number;\n /** Equal divisions of the octave (12 standard; e.g. 19 or 31 for microtonal). */\n divisions: number;\n};\n\n/**\n * Standard twelve-tone equal temperament, A4 (MIDI 69) = 440 Hz.\n *\n * @category Pitch & Intervals\n */\nexport const TWELVE_TET: Tuning = { refStep: 69, refFreq: 440, divisions: 12 };\n\nfunction assertTuning(tuning: Tuning): Tuning {\n assertFiniteNumber(tuning.refStep, 'tuning.refStep');\n assertRange(tuning.refFreq, Number.MIN_VALUE, Number.MAX_VALUE, 'tuning.refFreq');\n assertPositiveInt(tuning.divisions, 'tuning.divisions');\n return tuning;\n}\n\n/**\n * Build an equal temperament with `n` divisions of the octave.\n *\n * @param n Divisions of the octave (e.g. 19, 24, 31).\n * @param refFreq Reference frequency in Hz (default 440).\n * @param refStep Step index of the reference (default 69).\n * @returns The tuning.\n * @example\n * ```ts\n * import { edo, frequencyOf } from '@libraz/libcantus';\n * const et19 = edo(19); // { refStep: 69, refFreq: 440, divisions: 19 }\n * frequencyOf(70, et19); // one 19-EDO step above A4\n * ```\n * @category Pitch & Intervals\n */\nexport function edo(n: number, refFreq = 440, refStep = 69): Tuning {\n assertPositiveInt(n, 'EDO divisions');\n assertRange(refFreq, Number.MIN_VALUE, Number.MAX_VALUE, 'reference frequency');\n assertFiniteNumber(refStep, 'reference step');\n return { refStep, refFreq, divisions: n };\n}\n\n/**\n * Frequency in Hz of a step index under a tuning.\n *\n * @param step Step index (a MIDI number under 12-EDO).\n * @param tuning The tuning (default 12-TET).\n * @returns The frequency in Hz.\n * @example\n * ```ts\n * import { frequencyOf } from '@libraz/libcantus';\n * frequencyOf(69); // 440 (A4 in 12-TET)\n * frequencyOf(60); // middle C in Hz\n * ```\n * @category Pitch & Intervals\n */\nexport function frequencyOf(step: number, tuning: Tuning = TWELVE_TET): number {\n assertFiniteNumber(step, 'step');\n assertTuning(tuning);\n const result = tuning.refFreq * 2 ** ((step - tuning.refStep) / tuning.divisions);\n return assertRange(result, Number.MIN_VALUE, Number.MAX_VALUE, 'frequency result');\n}\n\n/**\n * Nearest step index to a frequency under a tuning (the inverse of\n * {@link frequencyOf}, rounded).\n *\n * @param freq Frequency in Hz.\n * @param tuning The tuning (default 12-TET).\n * @returns The nearest step index.\n * @category Pitch & Intervals\n */\nexport function nearestStep(freq: number, tuning: Tuning = TWELVE_TET): number {\n assertRange(freq, Number.MIN_VALUE, Number.MAX_VALUE, 'frequency');\n assertTuning(tuning);\n return Math.round(tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq));\n}\n\n/**\n * Interval in cents between two frequencies.\n *\n * @param a Lower/first frequency in Hz.\n * @param b Upper/second frequency in Hz.\n * @returns Cents from `a` to `b` (negative if `b` is lower).\n * @category Pitch & Intervals\n */\nexport function centsBetweenFreq(a: number, b: number): number {\n assertRange(a, Number.MIN_VALUE, Number.MAX_VALUE, 'first frequency');\n assertRange(b, Number.MIN_VALUE, Number.MAX_VALUE, 'second frequency');\n return 1200 * (Math.log2(b) - Math.log2(a));\n}\n\n/**\n * Cents spanned by a number of equal-temperament steps.\n *\n * @param steps Number of steps.\n * @param tuning The tuning (default 12-TET).\n * @returns The cents.\n * @category Pitch & Intervals\n */\nexport function centsOfSteps(steps: number, tuning: Tuning = TWELVE_TET): number {\n assertFiniteNumber(steps, 'steps');\n assertTuning(tuning);\n return assertFiniteNumber((steps * 1200) / tuning.divisions, 'cents result');\n}\n\n/**\n * Cents of a frequency ratio, e.g. `ratioToCents(3, 2)` ≈ 701.955 for the just\n * perfect fifth.\n *\n * @param numerator Ratio numerator.\n * @param denominator Ratio denominator.\n * @returns The interval in cents.\n * @category Pitch & Intervals\n */\nexport function ratioToCents(numerator: number, denominator: number): number {\n assertRange(numerator, Number.MIN_VALUE, Number.MAX_VALUE, 'ratio numerator');\n assertRange(denominator, Number.MIN_VALUE, Number.MAX_VALUE, 'ratio denominator');\n return 1200 * (Math.log2(numerator) - Math.log2(denominator));\n}\n\n/**\n * Five-limit just-intonation ratios for the twelve interval classes above a\n * unison, indexed by semitone class (0..12).\n *\n * @category Pitch & Intervals\n */\nexport const JUST_RATIOS: Record<number, [number, number]> = {\n 0: [1, 1],\n 1: [16, 15],\n 2: [9, 8],\n 3: [6, 5],\n 4: [5, 4],\n 5: [4, 3],\n 6: [45, 32],\n 7: [3, 2],\n 8: [8, 5],\n 9: [5, 3],\n 10: [9, 5],\n 11: [15, 8],\n 12: [2, 1],\n};\n\n/**\n * Cents by which a five-limit just interval departs from its 12-TET tempering\n * (positive means the just interval is wider).\n *\n * @param semitoneClass Semitone class in [0, 12].\n * @returns The deviation in cents.\n * @throws If `semitoneClass` is not an integer in [0, 12].\n * @category Pitch & Intervals\n */\nexport function justDeviationCents(semitoneClass: number): number {\n assertInteger(semitoneClass, 'semitone class', 0, 12);\n const ratio = JUST_RATIOS[semitoneClass];\n if (!ratio) {\n throw new RangeError(`semitone class has no just ratio: ${semitoneClass}`);\n }\n return ratioToCents(ratio[0], ratio[1]) - semitoneClass * 100;\n}\n"],"mappings":"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","names":["IntervalQuality","EPS"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/core/index.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,q/GAAC","file":"/home/runner/work/libcantus/libcantus/dist/core/index.cjs"}
|
package/dist/core/index.d.cts
CHANGED
|
@@ -1,40 +1,154 @@
|
|
|
1
|
-
import { N as NoteEvent } from '../types-
|
|
2
|
-
export { K as KeyScale } from '../types-
|
|
3
|
-
import { T as TimeSignature } from '../index-
|
|
4
|
-
export { B as BarPosition, b as barPositionToBeat, a as
|
|
5
|
-
export { N as NoteData, S as SpelledInterval, f as formatNote, m as midiToNote, n as
|
|
1
|
+
import { N as NoteEvent } from '../types-BN6_Q9Kl.cjs';
|
|
2
|
+
export { K as KeyScale } from '../types-BN6_Q9Kl.cjs';
|
|
3
|
+
import { T as TimeSignature } from '../index-DXd9JikN.cjs';
|
|
4
|
+
export { B as BarPosition, b as barPositionToBeat, a as barPositionToPulse, c as beatToBarPosition, d as beatsPerBar, f as formatBarPosition, e as formatTimeSignature, i as isCompound, g as isStrongBeat, m as metricWeight, p as parseTimeSignature, h as pulseBeats, j as pulsesPerBar, t as tuplet } from '../index-DXd9JikN.cjs';
|
|
5
|
+
export { I as IntervalQualityLabel, N as NoteData, S as SpelledInterval, d as diatonicLetterOf, f as formatNote, i as intervalSemitones, m as midiToNote, n as naturalPitchClassOf, a as noteToMidi, b as noteToPitchClass, p as parseInterval, c as parseNote, e as pitchClassOf, s as spelledInterval, t as transposeByInterval, g as transposeNote } from '../index-OG65ocOr.cjs';
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* The library's error taxonomy.
|
|
9
|
+
*
|
|
10
|
+
* Every failure this library raises falls into one of three kinds, and the kind
|
|
11
|
+
* decides what a caller can do about it. Batch-voicing a lead sheet, for
|
|
12
|
+
* instance, has to tell "fix the input" apart from "loosen the constraints",
|
|
13
|
+
* and a message string is not a contract.
|
|
14
|
+
*
|
|
15
|
+
* The classes extend the built-in error types a caller would otherwise catch —
|
|
16
|
+
* a rejected argument is still a `RangeError` — so the `code` narrows an
|
|
17
|
+
* existing category rather than replacing it.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* What kind of failure an error reports.
|
|
21
|
+
*
|
|
22
|
+
* - `INVALID_INPUT`: an argument is malformed or out of range. The caller's
|
|
23
|
+
* input must change.
|
|
24
|
+
* - `NO_SOLUTION`: the input is valid but nothing satisfies the constraints.
|
|
25
|
+
* The constraints must change, or the failure must be accepted.
|
|
26
|
+
* - `BUDGET_EXCEEDED`: the requested work is larger than the synchronous
|
|
27
|
+
* generation budget. The request must be smaller, or the budget larger.
|
|
28
|
+
*
|
|
29
|
+
* @category Core
|
|
30
|
+
*/
|
|
31
|
+
type LibcantusErrorCode = 'INVALID_INPUT' | 'NO_SOLUTION' | 'BUDGET_EXCEEDED';
|
|
32
|
+
/**
|
|
33
|
+
* A rejected argument: malformed, out of range, or naming something unknown.
|
|
34
|
+
*
|
|
35
|
+
* @category Core
|
|
36
|
+
*/
|
|
37
|
+
declare class InvalidInputError extends RangeError {
|
|
38
|
+
readonly code: "INVALID_INPUT";
|
|
39
|
+
readonly name = "InvalidInputError";
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A valid request with no answer: no voicing fits the given ranges, no chord
|
|
43
|
+
* matches the pitches, no preset claims the style.
|
|
44
|
+
*
|
|
45
|
+
* @category Core
|
|
46
|
+
*/
|
|
47
|
+
declare class NoSolutionError extends Error {
|
|
48
|
+
readonly code: "NO_SOLUTION";
|
|
49
|
+
readonly name = "NoSolutionError";
|
|
50
|
+
/**
|
|
51
|
+
* Where in a sequence the failure occurred, when the operation was working
|
|
52
|
+
* through one — the chord index of a progression, say.
|
|
53
|
+
*/
|
|
54
|
+
readonly at: number | undefined;
|
|
55
|
+
constructor(message: string, options?: {
|
|
56
|
+
at?: number;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Work that would exceed the synchronous generation budget.
|
|
61
|
+
*
|
|
62
|
+
* @category Core
|
|
63
|
+
*/
|
|
64
|
+
declare class BudgetExceededError extends RangeError {
|
|
65
|
+
readonly code: "BUDGET_EXCEEDED";
|
|
66
|
+
readonly name = "BudgetExceededError";
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Whether a caught value is one of this library's coded errors.
|
|
70
|
+
*
|
|
71
|
+
* @param value The caught value.
|
|
72
|
+
* @returns True when it carries a {@link LibcantusErrorCode}.
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* import { isLibcantusError, voiceChord, parseChordSymbol } from '@libraz/libcantus';
|
|
76
|
+
* try {
|
|
77
|
+
* voiceChord(parseChordSymbol('C'), { ranges: [] });
|
|
78
|
+
* } catch (error) {
|
|
79
|
+
* if (isLibcantusError(error) && error.code === 'NO_SOLUTION') {
|
|
80
|
+
* // loosen the constraints rather than blaming the input
|
|
81
|
+
* }
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
* @category Core
|
|
85
|
+
*/
|
|
86
|
+
declare function isLibcantusError(value: unknown): value is InvalidInputError | NoSolutionError | BudgetExceededError;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A validated note retaining its position in the caller's original array.
|
|
90
|
+
*
|
|
91
|
+
* @category Core
|
|
92
|
+
*/
|
|
8
93
|
type IndexedNoteEvent = {
|
|
9
94
|
note: NoteEvent;
|
|
10
95
|
originalIndex: number;
|
|
11
96
|
endBeat: number;
|
|
12
97
|
};
|
|
13
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Which note wins when several sound from the same onset.
|
|
100
|
+
*
|
|
101
|
+
* `'highest'` and `'lowest'` name a voice, so the answer does not depend on the
|
|
102
|
+
* order the caller happened to store the chord in; `'last'` takes the one
|
|
103
|
+
* latest in the input array.
|
|
104
|
+
*
|
|
105
|
+
* @category Core
|
|
106
|
+
*/
|
|
107
|
+
type OnsetTieBreak = 'highest' | 'lowest' | 'last';
|
|
108
|
+
/**
|
|
109
|
+
* Binary-searchable, stable-sorted index over note onsets and active spans.
|
|
110
|
+
*
|
|
111
|
+
* @category Core
|
|
112
|
+
*/
|
|
14
113
|
type NoteEventIndex = {
|
|
15
114
|
notes: IndexedNoteEvent[];
|
|
16
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Latest-onset note sounding at `beat`. Simultaneous onsets are resolved by
|
|
117
|
+
* the index's {@link OnsetTieBreak}.
|
|
118
|
+
*/
|
|
17
119
|
at: (beat: number) => IndexedNoteEvent | undefined;
|
|
18
|
-
/** Whether one or more notes attack at `beat`. */
|
|
120
|
+
/** Whether one or more sounding notes attack at `beat`. */
|
|
19
121
|
attacksAt: (beat: number) => boolean;
|
|
20
|
-
/** Unique attack beats strictly inside `(startBeat, endBeat)`. */
|
|
122
|
+
/** Unique sounding-note attack beats strictly inside `(startBeat, endBeat)`. */
|
|
21
123
|
onsetsBetween: (startBeat: number, endBeat: number) => number[];
|
|
22
124
|
};
|
|
23
125
|
/**
|
|
24
126
|
* Validate and stable-sort note events once, then expose logarithmic onset and
|
|
25
127
|
* active-note lookups. Non-positive-duration notes may be retained for callers
|
|
26
128
|
* that intentionally filter them later, but never count as sounding.
|
|
129
|
+
*
|
|
130
|
+
* @category Core
|
|
27
131
|
*/
|
|
28
|
-
declare function createNoteEventIndex(events: NoteEvent[], options?: {
|
|
132
|
+
declare function createNoteEventIndex(events: readonly NoteEvent[], options?: {
|
|
29
133
|
allowNonPositiveDuration?: boolean;
|
|
134
|
+
tieBreak?: OnsetTieBreak;
|
|
135
|
+
budget?: number;
|
|
136
|
+
/** What the events are, for the validation error message. */
|
|
137
|
+
name?: string;
|
|
30
138
|
}): NoteEventIndex;
|
|
31
139
|
|
|
32
140
|
/**
|
|
33
|
-
* Counterpoint classification of a harmonic interval.
|
|
141
|
+
* Counterpoint classification of a harmonic interval by consonance.
|
|
142
|
+
*
|
|
143
|
+
* Distinct from a spelled interval's quality label — see
|
|
144
|
+
* {@link IntervalQualityLabel}, which names the `'P'` / `'M'` / `'m'` / `'A'` /
|
|
145
|
+
* `'d'` spelling of an interval rather than how it sounds against another
|
|
146
|
+
* voice.
|
|
34
147
|
*
|
|
148
|
+
* @see {@link IntervalQualityLabel}
|
|
35
149
|
* @category Pitch & Intervals
|
|
36
150
|
*/
|
|
37
|
-
declare enum
|
|
151
|
+
declare enum ConsonanceClass {
|
|
38
152
|
PerfectConsonance = 0,
|
|
39
153
|
ImperfectConsonance = 1,
|
|
40
154
|
Dissonance = 2
|
|
@@ -52,16 +166,22 @@ declare enum IntervalQuality {
|
|
|
52
166
|
* @returns The counterpoint quality of the interval.
|
|
53
167
|
* @example
|
|
54
168
|
* ```ts
|
|
55
|
-
* import { classifyInterval,
|
|
56
|
-
* classifyInterval(7); //
|
|
57
|
-
* classifyInterval(5); //
|
|
58
|
-
* classifyInterval(5, false); //
|
|
169
|
+
* import { classifyInterval, ConsonanceClass } from '@libraz/libcantus';
|
|
170
|
+
* classifyInterval(7); // ConsonanceClass.PerfectConsonance
|
|
171
|
+
* classifyInterval(5); // ConsonanceClass.Dissonance (two-voice)
|
|
172
|
+
* classifyInterval(5, false); // ConsonanceClass.ImperfectConsonance
|
|
59
173
|
* ```
|
|
60
174
|
* @category Pitch & Intervals
|
|
61
175
|
*/
|
|
62
|
-
declare function classifyInterval(semitones: number, twoVoice?: boolean):
|
|
176
|
+
declare function classifyInterval(semitones: number, twoVoice?: boolean): ConsonanceClass;
|
|
63
177
|
/**
|
|
64
|
-
* Test whether an interval is a perfect
|
|
178
|
+
* Test whether an interval is a perfect consonance: the unison/octave or the
|
|
179
|
+
* fifth.
|
|
180
|
+
*
|
|
181
|
+
* The perfect fourth is deliberately excluded. This is the predicate the
|
|
182
|
+
* parallel-motion rules are written against, where the fourth behaves as a
|
|
183
|
+
* dissonance between two voices; {@link classifyInterval} is the general
|
|
184
|
+
* classifier, and it treats the fourth as context-dependent.
|
|
65
185
|
*
|
|
66
186
|
* @param semitones Interval size in semitones (may be negative or compound).
|
|
67
187
|
* @returns True for the unison/octave (0) and the perfect fifth (7) mod 12.
|
|
@@ -136,11 +256,11 @@ declare function createRng(seed: number): Rng;
|
|
|
136
256
|
*/
|
|
137
257
|
type Tuning = {
|
|
138
258
|
/** Step index (MIDI number when `divisions` is 12) whose frequency is `refFreq`. */
|
|
139
|
-
refStep: number;
|
|
259
|
+
readonly refStep: number;
|
|
140
260
|
/** Frequency in Hz of `refStep`. */
|
|
141
|
-
refFreq: number;
|
|
261
|
+
readonly refFreq: number;
|
|
142
262
|
/** Equal divisions of the octave (12 standard; e.g. 19 or 31 for microtonal). */
|
|
143
|
-
divisions: number;
|
|
263
|
+
readonly divisions: number;
|
|
144
264
|
};
|
|
145
265
|
/**
|
|
146
266
|
* Standard twelve-tone equal temperament, A4 (MIDI 69) = 440 Hz.
|
|
@@ -189,6 +309,67 @@ declare function frequencyOf(step: number, tuning?: Tuning): number;
|
|
|
189
309
|
* @category Pitch & Intervals
|
|
190
310
|
*/
|
|
191
311
|
declare function nearestStep(freq: number, tuning?: Tuning): number;
|
|
312
|
+
/**
|
|
313
|
+
* Step index of a frequency under a tuning, unrounded.
|
|
314
|
+
*
|
|
315
|
+
* The exact inverse of {@link frequencyOf}. {@link nearestStep} rounds it,
|
|
316
|
+
* which discards how far off the pitch actually is — the figure a tuner
|
|
317
|
+
* display, a pitch-bend amount, or an intonation check is built on.
|
|
318
|
+
*
|
|
319
|
+
* @param freq Frequency in Hz.
|
|
320
|
+
* @param tuning The tuning (default 12-TET).
|
|
321
|
+
* @returns The fractional step index.
|
|
322
|
+
* @example
|
|
323
|
+
* ```ts
|
|
324
|
+
* import { stepOf } from '@libraz/libcantus';
|
|
325
|
+
* stepOf(442); // slightly above 69, the step for A440
|
|
326
|
+
* ```
|
|
327
|
+
* @category Pitch & Intervals
|
|
328
|
+
*/
|
|
329
|
+
declare function stepOf(freq: number, tuning?: Tuning): number;
|
|
330
|
+
/**
|
|
331
|
+
* How far a frequency sits from its nearest step, in cents.
|
|
332
|
+
*
|
|
333
|
+
* Positive means sharp of the step, negative flat.
|
|
334
|
+
*
|
|
335
|
+
* @param freq Frequency in Hz.
|
|
336
|
+
* @param tuning The tuning (default 12-TET).
|
|
337
|
+
* @returns The signed deviation in cents.
|
|
338
|
+
* @example
|
|
339
|
+
* ```ts
|
|
340
|
+
* import { centsFromNearestStep } from '@libraz/libcantus';
|
|
341
|
+
* Math.round(centsFromNearestStep(442)); // 8
|
|
342
|
+
* ```
|
|
343
|
+
* @category Pitch & Intervals
|
|
344
|
+
*/
|
|
345
|
+
declare function centsFromNearestStep(freq: number, tuning?: Tuning): number;
|
|
346
|
+
/**
|
|
347
|
+
* Steps spanned by an interval in cents, unrounded — the inverse of
|
|
348
|
+
* {@link centsOfSteps}.
|
|
349
|
+
*
|
|
350
|
+
* @param cents The interval in cents.
|
|
351
|
+
* @param tuning The tuning (default 12-TET).
|
|
352
|
+
* @returns The fractional step count.
|
|
353
|
+
* @category Pitch & Intervals
|
|
354
|
+
*/
|
|
355
|
+
declare function stepsOfCents(cents: number, tuning?: Tuning): number;
|
|
356
|
+
/**
|
|
357
|
+
* The frequency ratio an interval in cents spans — the inverse of
|
|
358
|
+
* {@link ratioToCents}.
|
|
359
|
+
*
|
|
360
|
+
* Multiply a frequency by this to move it by that many cents, which is how a
|
|
361
|
+
* cents offset (a pitch-bend spec, a tuning table entry) is applied.
|
|
362
|
+
*
|
|
363
|
+
* @param cents The interval in cents.
|
|
364
|
+
* @returns The frequency ratio.
|
|
365
|
+
* @example
|
|
366
|
+
* ```ts
|
|
367
|
+
* import { centsToRatio } from '@libraz/libcantus';
|
|
368
|
+
* 440 * centsToRatio(14); // A440 raised by 14 cents
|
|
369
|
+
* ```
|
|
370
|
+
* @category Pitch & Intervals
|
|
371
|
+
*/
|
|
372
|
+
declare function centsToRatio(cents: number): number;
|
|
192
373
|
/**
|
|
193
374
|
* Interval in cents between two frequencies.
|
|
194
375
|
*
|
|
@@ -223,7 +404,7 @@ declare function ratioToCents(numerator: number, denominator: number): number;
|
|
|
223
404
|
*
|
|
224
405
|
* @category Pitch & Intervals
|
|
225
406
|
*/
|
|
226
|
-
declare const JUST_RATIOS: Record<number, [number, number]
|
|
407
|
+
declare const JUST_RATIOS: Readonly<Record<number, readonly [number, number]>>;
|
|
227
408
|
/**
|
|
228
409
|
* Cents by which a five-limit just interval departs from its 12-TET tempering
|
|
229
410
|
* (positive means the just interval is wider).
|
|
@@ -235,28 +416,109 @@ declare const JUST_RATIOS: Record<number, [number, number]>;
|
|
|
235
416
|
*/
|
|
236
417
|
declare function justDeviationCents(semitoneClass: number): number;
|
|
237
418
|
|
|
238
|
-
/**
|
|
419
|
+
/**
|
|
420
|
+
* Default upper bound for synchronous event/window/candidate generation.
|
|
421
|
+
*
|
|
422
|
+
* @category Core
|
|
423
|
+
*/
|
|
239
424
|
declare const DEFAULT_GENERATION_BUDGET = 1000000;
|
|
240
|
-
/**
|
|
425
|
+
/**
|
|
426
|
+
* Require a finite JavaScript number and return it unchanged.
|
|
427
|
+
*
|
|
428
|
+
* @category Core
|
|
429
|
+
*/
|
|
241
430
|
declare function assertFiniteNumber(value: number, name: string): number;
|
|
242
|
-
/**
|
|
431
|
+
/**
|
|
432
|
+
* Require an integer in the inclusive range `[min, max]`.
|
|
433
|
+
*
|
|
434
|
+
* @category Core
|
|
435
|
+
*/
|
|
243
436
|
declare function assertInteger(value: number, name: string, min?: number, max?: number): number;
|
|
244
|
-
/**
|
|
437
|
+
/**
|
|
438
|
+
* Drop the notes that never sound.
|
|
439
|
+
*
|
|
440
|
+
* A MIDI or DAW import routinely carries zero-length artefacts. Every entry
|
|
441
|
+
* point in this library ignores them, but a caller holding such an array still
|
|
442
|
+
* has to decide what to do with it before comparing note counts or indices;
|
|
443
|
+
* this is the one filter to apply.
|
|
444
|
+
*
|
|
445
|
+
* @param events The note events to filter.
|
|
446
|
+
* @returns A new array containing only the notes with a positive duration.
|
|
447
|
+
* @example
|
|
448
|
+
* ```ts
|
|
449
|
+
* import { dropSilentNotes } from '@libraz/libcantus';
|
|
450
|
+
* dropSilentNotes([{ pitch: 60, startBeat: 0, durationBeat: 0 }]); // []
|
|
451
|
+
* ```
|
|
452
|
+
* @category Core
|
|
453
|
+
*/
|
|
454
|
+
declare function dropSilentNotes(events: readonly NoteEvent[]): NoteEvent[];
|
|
455
|
+
/**
|
|
456
|
+
* Require a value to be one of a fixed set of names.
|
|
457
|
+
*
|
|
458
|
+
* TypeScript checks string-union options at compile time only, so a value that
|
|
459
|
+
* arrives from JSON, a config file, or a JavaScript caller reaches the engine
|
|
460
|
+
* unchecked and is then read against a table that has no entry for it —
|
|
461
|
+
* producing a silent `undefined`, a NaN velocity, or a crash far from the call.
|
|
462
|
+
*
|
|
463
|
+
* @param value The value to check.
|
|
464
|
+
* @param allowed Every accepted name.
|
|
465
|
+
* @param name What the value is, for the error message.
|
|
466
|
+
* @returns The value, narrowed to the allowed union.
|
|
467
|
+
* @throws If the value is not one of `allowed`.
|
|
468
|
+
*
|
|
469
|
+
* @category Core
|
|
470
|
+
*/
|
|
471
|
+
declare function assertOneOf<const T extends string>(value: unknown, allowed: readonly T[], name: string): T;
|
|
472
|
+
/**
|
|
473
|
+
* Require a positive safe integer, optionally capped by `max`.
|
|
474
|
+
*
|
|
475
|
+
* @category Core
|
|
476
|
+
*/
|
|
245
477
|
declare function assertPositiveInt(value: number, name: string, max?: number): number;
|
|
246
|
-
/**
|
|
478
|
+
/**
|
|
479
|
+
* Require a finite number in the inclusive range `[min, max]`.
|
|
480
|
+
*
|
|
481
|
+
* @category Core
|
|
482
|
+
*/
|
|
247
483
|
declare function assertRange(value: number, min: number, max: number, name: string): number;
|
|
248
|
-
/**
|
|
249
|
-
|
|
250
|
-
|
|
484
|
+
/**
|
|
485
|
+
* Reject work estimates that would exceed a synchronous generation budget.
|
|
486
|
+
*
|
|
487
|
+
* @category Core
|
|
488
|
+
*/
|
|
489
|
+
declare function assertGenerationBudget(estimated: number, name: string, limit?: number | undefined): number;
|
|
490
|
+
/**
|
|
491
|
+
* Validate a time signature, including additive grouping, before any early return.
|
|
492
|
+
*
|
|
493
|
+
* @category Core
|
|
494
|
+
*/
|
|
251
495
|
declare function assertTimeSignature(ts: TimeSignature, name?: string): TimeSignature;
|
|
252
|
-
/**
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
496
|
+
/**
|
|
497
|
+
* How strictly {@link assertNoteEvent} and {@link assertNoteEvents} read an
|
|
498
|
+
* event array.
|
|
499
|
+
*
|
|
500
|
+
* @category Core
|
|
501
|
+
*/
|
|
502
|
+
type NoteEventAssertOptions = {
|
|
503
|
+
/**
|
|
504
|
+
* Accept notes that never sound. The analysis side does, so an array from a
|
|
505
|
+
* MIDI import can be validated before the zero-length artefacts are dropped.
|
|
506
|
+
*/
|
|
258
507
|
allowNonPositiveDuration?: boolean;
|
|
508
|
+
/** Upper bound on the event count; defaults to the generation budget. */
|
|
259
509
|
budget?: number;
|
|
260
|
-
}
|
|
510
|
+
};
|
|
511
|
+
/**
|
|
512
|
+
* Validate the finite fields of one timeline note event.
|
|
513
|
+
*
|
|
514
|
+
* @category Core
|
|
515
|
+
*/
|
|
516
|
+
declare function assertNoteEvent(event: NoteEvent, name?: string, options?: NoteEventAssertOptions): NoteEvent;
|
|
517
|
+
/**
|
|
518
|
+
* Validate an event array and its allocation budget without copying it.
|
|
519
|
+
*
|
|
520
|
+
* @category Core
|
|
521
|
+
*/
|
|
522
|
+
declare function assertNoteEvents(events: readonly NoteEvent[], name?: string, options?: NoteEventAssertOptions): NoteEvent[];
|
|
261
523
|
|
|
262
|
-
export { DEFAULT_GENERATION_BUDGET, type IndexedNoteEvent,
|
|
524
|
+
export { BudgetExceededError, ConsonanceClass, DEFAULT_GENERATION_BUDGET, type IndexedNoteEvent, InvalidInputError, JUST_RATIOS, type LibcantusErrorCode, NoSolutionError, NoteEvent, type NoteEventAssertOptions, type NoteEventIndex, type OnsetTieBreak, type Rng, TWELVE_TET, TimeSignature, type Tuning, assertFiniteNumber, assertGenerationBudget, assertInteger, assertNoteEvent, assertNoteEvents, assertOneOf, assertPositiveInt, assertRange, assertTimeSignature, centsBetweenFreq, centsFromNearestStep, centsOfSteps, centsToRatio, classifyInterval, createNoteEventIndex, createRng, dropSilentNotes, edo, frequencyOf, isConsonantInterval, isLibcantusError, isPerfectInterval, justDeviationCents, nearestStep, ratioToCents, stepOf, stepsOfCents };
|