antigravity one shot

This commit is contained in:
2025-12-21 12:20:40 +01:00
parent 5fd7786ba3
commit f510de47d1
23 changed files with 4462 additions and 0 deletions

51
src/music/NoteUtils.ts Normal file
View File

@@ -0,0 +1,51 @@
export const NOTE_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
export const A4 = 440;
export interface NoteDetail {
midi: number;
name: string;
octave: number;
scientific: string; // e.g., "C4"
frequency: number;
}
export function frequencyToMidi(frequency: number): number {
if (frequency <= 0) return 0;
return Math.round(69 + 12 * Math.log2(frequency / A4));
}
export function midiToFrequency(midi: number): number {
return A4 * Math.pow(2, (midi - 69) / 12);
}
export function getNoteDetails(midi: number): NoteDetail {
const semitone = midi % 12;
const name = NOTE_NAMES[semitone];
const octave = Math.floor(midi / 12) - 1; // MIDI 60 is C4
return {
midi,
name,
octave,
scientific: `${name}${octave}`,
frequency: midiToFrequency(midi)
};
}
export function getCentDifference(frequency: number, targetMidi: number): number {
const targetFreq = midiToFrequency(targetMidi);
if (frequency <= 0 || targetFreq <= 0) return 0;
return 1200 * Math.log2(frequency / targetFreq);
}
/**
* Returns a random MIDI note between min and max (inclusive)
*/
export function getRandomNote(min: number, max: number, validNotes?: number[]): number {
if (validNotes && validNotes.length > 0) {
const filtered = validNotes.filter(n => n >= min && n <= max);
if (filtered.length === 0) return min; // Fallback
return filtered[Math.floor(Math.random() * filtered.length)];
}
return Math.floor(Math.random() * (max - min + 1)) + min;
}

64
src/music/Tunings.ts Normal file
View File

@@ -0,0 +1,64 @@
export interface Tuning {
name: string;
strings: number[]; // MIDI numbers for strings, usually low to high (6th to 1st)
}
// Low E2 (40), A2 (45), D3 (50), G3 (55), B3 (59), E4 (64)
export const STANDARD_TUNING: Tuning = {
name: "Standard",
strings: [40, 45, 50, 55, 59, 64]
};
// Low D2 (38), A2 (45), D3 (50), G3 (55), B3 (59), E4 (64)
export const DROP_D_TUNING: Tuning = {
name: "Drop D",
strings: [38, 45, 50, 55, 59, 64]
};
export const TUNINGS: Record<string, Tuning> = {
"standard": STANDARD_TUNING,
"drop_d": DROP_D_TUNING,
};
export interface FretPosition {
stringIndex: number; // 0-based index of the string in the strings array (0 = lowest string)
fret: number;
}
/**
* Finds all places on the fretboard where a specific note can be played.
* Assumes 20 frets by default.
*/
export function getFretboardPositions(midiNote: number, tuning: Tuning = STANDARD_TUNING, maxFrets: number = 20): FretPosition[] {
const positions: FretPosition[] = [];
tuning.strings.forEach((openNote, stringIndex) => {
const fret = midiNote - openNote;
if (fret >= 0 && fret <= maxFrets) {
positions.push({ stringIndex, fret });
}
});
return positions;
}
/**
* Helpers to get useful ranges for beginners
*/
export function getOpenStringNotes(tuning: Tuning = STANDARD_TUNING): number[] {
return tuning.strings;
}
export function getFirstPositionNotes(tuning: Tuning = STANDARD_TUNING): number[] {
const notes = new Set<number>();
tuning.strings.forEach(openNote => {
// First position usually means frets 0-4
for (let i = 0; i <= 4; i++) {
notes.add(openNote + i);
}
});
return Array.from(notes).sort((a, b) => a - b);
}