slop game oder so
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
Reference in New Issue
Block a user