src/pipeline/step3notes.js
- import { Step, Solvable } from "./step.js";
- import { modelNotes } from "../model/constraints3.js";
- import { INSTRUMENTS } from "../model/instruments.js";
- import { noteNames, timeGrid } from "../model/theory.js";
- export class Step3Notes extends Solvable(Step) {
-
- schema() {
- return SCHEMA;
- }
-
- async solve(parameters, solver) {
- const { voice, voices, numerator, chords } = parameters;
- const bars = chords.length > 0 ? chords.length : parameters.bars;
- const time = timeGrid(voice, voices, numerator, bars);
- const solution = await solver(modelNotes(parameters, time));
- if (!solution) {
- return { textOutput: "No solution found for music grid." };
- }
- return {
- textOutput: "Music grid solution found.",
- solution,
- numSubBeats: time.numSubBeats,
- subdivision: time.subdivision,
- numerator: parameters.numerator,
- denominator: parameters.denominator,
- key: parameters.key,
- mode: parameters.mode,
- };
- }
- }
- const SCHEMA = [
- {
- name: "voices",
- type: "stepper",
- label: "Number of Voices",
- tooltip: "Number of simultaneous melodic lines",
- default: 2,
- min: 1,
- max: 4,
- },
- {
- name: "voice",
- type: "nested",
- label: "Voice",
- tooltip: "Configure pitch range, instrument & options per voice",
- firstColumn: { type: "color", key: "color" },
- counter: "voices",
- defaults: [
- {
- color: "#0000ff",
- min_pitch: 60,
- max_pitch: 84,
- instrument: 0,
- chord_tones: "scale",
- duration: "eighth",
- allow_repetitions: true,
- },
- {
- color: "#00ff00",
- min_pitch: 52,
- max_pitch: 76,
- instrument: 0,
- chord_tones: "chord",
- duration: "quarter",
- allow_repetitions: true,
- },
- {
- color: "#ff0000",
- min_pitch: 45,
- max_pitch: 69,
- instrument: 0,
- chord_tones: "chord",
- duration: "half",
- allow_repetitions: true,
- },
- {
- color: "#888888",
- min_pitch: 36,
- max_pitch: 60,
- instrument: 0,
- chord_tones: "chord",
- duration: "whole",
- allow_repetitions: false,
- },
- ],
- template: [
- {
- name: "duration",
- type: "select",
- label: "Duration",
- tooltip: "How many notes per beat",
- default: "quarter",
- options: ["whole", "half", "quarter", "eighth", "triplet", "sixteenth"],
- labels: ["Whole", "Half", "Quarter", "Eighth", "Triplet", "Sixteenth"],
- },
- {
- name: "min_pitch",
- type: "int",
- label: "Min Pitch",
- tooltip: "Lowest allowed note for this voice",
- default: 48,
- min: 24,
- max: 96,
- step: 1,
- format: (v) =>
- `${noteNames()[v % 12]}<sub>${Math.floor(v / 12) - 1}</sub>`,
- },
- {
- name: "max_pitch",
- type: "int",
- label: "Max Pitch",
- tooltip: "Highest allowed note for this voice",
- default: 72,
- min: 24,
- max: 96,
- step: 1,
- format: (v) =>
- `${noteNames()[v % 12]}<sub>${Math.floor(v / 12) - 1}</sub>`,
- },
- {
- name: "instrument",
- type: "select",
- label: "Instrument",
- tooltip: "MIDI instrument (General MIDI)",
- default: 0,
- groups: Object.entries(INSTRUMENTS).map(([label, names], g) => ({
- label,
- options: names.map((name, i) => ({
- value: Object.values(INSTRUMENTS).slice(0, g).flat().length + i,
- label: name,
- })),
- })),
- },
- {
- name: "voice_options",
- type: "checkboxes",
- label: "Options",
- tooltip: "Per voice",
- items: [
- {
- name: "chord_tones",
- label: "Chord/Scale Tones",
- tooltip: "[ ]: atonal, [-]: scale tones, [v]: chord tones",
- default: "chord",
- tristate: true,
- },
- {
- name: "allow_repetitions",
- label: "Allow Repetitions",
- tooltip:
- "Allow consecutive notes of the same pitch (disables M9 for this voice)",
- default: true,
- ariaInvalid: "false",
- },
- ],
- },
- ],
- },
- {
- name: "seed_pct",
- type: "int",
- label: "Variety",
- tooltip: "% of notes randomly pre-assigned for melodic diversity",
- default: 25,
- min: 0,
- max: 50,
- step: 5,
- format: (v) => `${v}%`,
- },
- {
- name: "dissonance",
- type: "float",
- label: "Dissonance",
- tooltip: "0 = prefer consonance, 0.5 = neutral, 1 = prefer dissonance",
- default: 0.5,
- min: 0,
- max: 1,
- step: 0.1,
- },
- {
- name: "max_interval",
- type: "int",
- label: "Max Interval",
- tooltip: "Max jump between consecutive notes (in semitones)",
- default: 6,
- min: 0,
- max: 12,
- step: 1,
- format: (v) => `${v} <abbr title="Semitones">st</abbr>`,
- },
- {
- name: "rest_pct",
- type: "int",
- label: "Max Holds/Rests",
- tooltip:
- "Max % of beats that can be holds or rests per voice (0 = disable holds/rests entirely)",
- default: 25,
- min: 0,
- max: 50,
- step: 5,
- format: (v) => `${v}%`,
- },
- {
- name: "constraints",
- type: "checkboxes",
- label: "Counterpoint",
- tooltip: "Voice interdependency",
- ariaInvalid: "true",
- items: [
- {
- name: "forbid_unisons",
- label: "Forbid Unisons",
- tooltip: "Prevent two voices from playing the same pitch",
- default: true,
- },
- {
- name: "forbid_parallels",
- label: "Forbid Parallels",
- tooltip: "Prevent parallel 5ths and octaves between voices",
- default: true,
- },
- ],
- },
- ];