/** * Notenmuster der Hintergrundmusik: ein pompöser Marsch (eigene Komposition, CC0), * der die Propaganda-Stimmung überzeichnet. Ohne Web-Audio-Abhängigkeit, damit sich * Rhythmus und Harmonie testen lassen; gespielt wird er von `systems/Music.js`. * * Raster: 16 Schritte (Sechzehntel) pro Takt, 8 Takte, Viervierteltakt. * @module logic/marchPattern */ export const TEMPO_BPM = 112; export const STEPS_PER_BAR = 16; export const BARS = 8; export const TOTAL_STEPS = STEPS_PER_BAR * BARS; /** Dauer eines Sechzehntels in Sekunden. */ export const STEP_SECONDS = 60 / TEMPO_BPM / 4; /** Akkordgrundtöne (MIDI) je Takt: I I IV V I IV V I in C-Dur. */ export const CHORD_ROOTS = Object.freeze([48, 48, 53, 55, 48, 53, 55, 48]); /** Dur-Dreiklang über dem Grundton. */ const MAJOR_TRIAD = Object.freeze([0, 4, 7]); /** * Fanfarenmelodie: [Takt, Schritt, MIDI-Ton, Länge in Schritten]. * @type {readonly (readonly [number, number, number, number])[]} */ const MELODY = Object.freeze([ [0, 0, 67, 3], [0, 3, 67, 1], [0, 4, 72, 4], [0, 8, 76, 6], [0, 14, 72, 2], [1, 0, 74, 4], [1, 4, 72, 4], [1, 8, 71, 4], [1, 12, 67, 4], [2, 0, 69, 3], [2, 3, 69, 1], [2, 4, 72, 4], [2, 8, 77, 6], [2, 14, 76, 2], [3, 0, 74, 4], [3, 4, 71, 4], [3, 8, 67, 8], [4, 0, 67, 3], [4, 3, 67, 1], [4, 4, 72, 4], [4, 8, 76, 4], [4, 12, 79, 4], [5, 0, 81, 6], [5, 6, 77, 2], [5, 8, 76, 4], [5, 12, 72, 4], [6, 0, 74, 3], [6, 3, 76, 1], [6, 4, 74, 4], [6, 8, 71, 4], [6, 12, 67, 4], [7, 0, 72, 8], ]); /** * @typedef {Object} MusicEvent * @property {'kick'|'snare'|'bass'|'stab'|'lead'} instrument * @property {number[]} notes MIDI-Töne (leer bei Schlagzeug). * @property {number} lengthSteps * @property {number} velocity 0..1 */ /** * Alle Ereignisse eines Schritts. * @param {number} step 0..TOTAL_STEPS-1 (größere Werte werden umgebrochen). * @returns {MusicEvent[]} */ export function eventsAtStep(step) { const s = ((step % TOTAL_STEPS) + TOTAL_STEPS) % TOTAL_STEPS; const bar = Math.floor(s / STEPS_PER_BAR); const pos = s % STEPS_PER_BAR; const root = CHORD_ROOTS[bar]; const events = []; // „Umm“ auf 1 und 3 (Bass + große Trommel), „ta“ auf 2 und 4 (Akkord + kleine Trommel). if (pos === 0 || pos === 8) { events.push({ instrument: 'kick', notes: [], lengthSteps: 2, velocity: 1 }); events.push({ instrument: 'bass', notes: [pos === 0 ? root - 12 : root - 5], lengthSteps: 4, velocity: 0.9 }); } if (pos === 4 || pos === 12) { events.push({ instrument: 'snare', notes: [], lengthSteps: 1, velocity: 0.8 }); events.push({ instrument: 'stab', notes: MAJOR_TRIAD.map((i) => root + 12 + i), lengthSteps: 2, velocity: 0.8 }); } // Trommelwirbel in der zweiten Hälfte des letzten Takts leitet die Wiederholung ein. if (bar === BARS - 1 && pos >= 8 && pos !== 12) { events.push({ instrument: 'snare', notes: [], lengthSteps: 1, velocity: 0.35 + (pos - 8) * 0.07 }); } for (const [mBar, mStep, note, length] of MELODY) { if (mBar === bar && mStep === pos) { events.push({ instrument: 'lead', notes: [note], lengthSteps: length, velocity: 0.8 }); } } return events; } /** * Frequenz eines MIDI-Tons (A4 = 69 = 440 Hz). * @param {number} midi * @returns {number} */ export function midiToFrequency(midi) { return 440 * Math.pow(2, (midi - 69) / 12); }