move css and js files into folders
This commit is contained in:
+193
@@ -0,0 +1,193 @@
|
||||
// Initialize audio context lazily to avoid autoplay restrictions
|
||||
let audioCtx;
|
||||
|
||||
const NOTE_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
||||
|
||||
const SVARA_NAMES = {
|
||||
full: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"],
|
||||
short: ["S", "R", "G", "M", "P", "D", "N"],
|
||||
malayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"]
|
||||
};
|
||||
|
||||
const semitoneMap = {
|
||||
C: 0,
|
||||
"C#": 1,
|
||||
D: 2,
|
||||
"D#": 3,
|
||||
E: 4,
|
||||
F: 5,
|
||||
"F#": 6,
|
||||
G: 7,
|
||||
"G#": 8,
|
||||
A: 9,
|
||||
"A#": 10,
|
||||
B: 11,
|
||||
Ab: 8,
|
||||
Bb: 10
|
||||
};
|
||||
const ragams = {
|
||||
Bhair: {
|
||||
intervals: [0, 2, 3, 6, 7, 9, 11],
|
||||
namesFull: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"],
|
||||
namesShort: ["S", "R", "G", "M", "P", "D", "N"],
|
||||
namesMalayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"],
|
||||
variantNumbers: ["", "2", "2", "1", "", "2", "2"]
|
||||
},
|
||||
Kalyani: {
|
||||
intervals: [0, 2, 4, 6, 7, 9, 11],
|
||||
namesFull: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"],
|
||||
namesShort: ["S", "R", "G", "M", "P", "D", "N"],
|
||||
namesMalayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"],
|
||||
variantNumbers: ["", "2", "3", "2", "", "2", "3"]
|
||||
},
|
||||
Mayamalavagowla: {
|
||||
intervals: [0, 1, 4, 5, 7, 8, 11],
|
||||
namesFull: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"],
|
||||
namesShort: ["S", "R", "G", "M", "P", "D", "N"],
|
||||
namesMalayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"],
|
||||
variantNumbers: ["", "1", "3", "1", "", "1", "3"]
|
||||
},
|
||||
Mohanam: {
|
||||
intervals: [0, 2, 4, 7, 9],
|
||||
namesFull: ["Sa", "Ri", "Ga", "Pa", "Dha"],
|
||||
namesShort: ["S", "R", "G", "P", "D"],
|
||||
namesMalayalam: ["സ", "രി", "ഗ", "പ", "ധ"],
|
||||
variantNumbers: ["", "2", "3", "", "2"]
|
||||
},
|
||||
Shankarabharanam: {
|
||||
intervals: [0, 2, 4, 5, 7, 9, 11],
|
||||
namesFull: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"],
|
||||
namesShort: ["S", "R", "G", "M", "P", "D", "N"],
|
||||
namesMalayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"],
|
||||
variantNumbers: ["", "2", "3", "1", "", "2", "3"]
|
||||
}
|
||||
};
|
||||
const ROOT_OCTAVE = 4;
|
||||
const SOUND_OCTAVE_OFFSET = -12;
|
||||
|
||||
const svaraNamesSelect = document.getElementById("svaraNames");
|
||||
const svaraStyleSelect = document.getElementById("svaraStyle");
|
||||
const startingNoteSelect = document.getElementById("startingNote");
|
||||
const ragamSelect = document.getElementById("ragamSelect");
|
||||
const pianoElement = document.getElementById("piano");
|
||||
|
||||
function normalizeSemitone(value) {
|
||||
return ((value % 12) + 12) % 12;
|
||||
}
|
||||
|
||||
function absFrequency(absSemitone) {
|
||||
const a4Abs = 4 * 12 + semitoneMap.A; // fixed A4 reference for real pitch
|
||||
const soundAbsSemitone = absSemitone + SOUND_OCTAVE_OFFSET;
|
||||
return 440 * Math.pow(2, (soundAbsSemitone - a4Abs) / 12);
|
||||
}
|
||||
|
||||
function noteNameToAbs(noteName) {
|
||||
return ROOT_OCTAVE * 12 + semitoneMap[noteName];
|
||||
}
|
||||
|
||||
function getSwaraLabel(absSemitone, startAbs, svaraNames, ragam, svaraStyle) {
|
||||
const diff = normalizeSemitone(absSemitone - startAbs);
|
||||
const intervalIndex = ragam.intervals.indexOf(diff);
|
||||
|
||||
if (intervalIndex === -1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const baseLabel = svaraNames[intervalIndex];
|
||||
if (svaraStyle === "variant" && ragam.variantNumbers) {
|
||||
const variant = ragam.variantNumbers[intervalIndex];
|
||||
if (variant) {
|
||||
return `${baseLabel}<sup>${variant}</sup>`;
|
||||
}
|
||||
}
|
||||
|
||||
return baseLabel;
|
||||
}
|
||||
|
||||
function buildKeyHtml(absSemitone, startAbs, svaraNames, ragam, svaraStyle) {
|
||||
const noteName = NOTE_NAMES[normalizeSemitone(absSemitone)];
|
||||
const isSharp = noteName.includes("#");
|
||||
const label = getSwaraLabel(absSemitone, startAbs, svaraNames, ragam, svaraStyle);
|
||||
const classes = ["key", isSharp ? "black" : "white"];
|
||||
|
||||
if (absSemitone < startAbs) {
|
||||
classes.push("lower");
|
||||
} else if (absSemitone > startAbs + 11) {
|
||||
classes.push("upper");
|
||||
}
|
||||
|
||||
if (normalizeSemitone(absSemitone - startAbs) === 0) {
|
||||
classes.push("sa-root");
|
||||
}
|
||||
|
||||
if (label) {
|
||||
classes.push("has-label");
|
||||
}
|
||||
|
||||
const content = label ? `<span class="key-label">${label}</span>` : "";
|
||||
return `<div class="${classes.join(" ")}" data-abs="${absSemitone}">${content}</div>`;
|
||||
}
|
||||
|
||||
function renderKeyboard() {
|
||||
const startNote = startingNoteSelect.value;
|
||||
const svaraNames = SVARA_NAMES[svaraNamesSelect.value] || SVARA_NAMES.full;
|
||||
const svaraStyle = svaraStyleSelect.value;
|
||||
const ragam = ragams[ragamSelect.value];
|
||||
const startAbs = noteNameToAbs(startNote);
|
||||
const lowerPaAbs = startAbs - 5;
|
||||
const upperPaAbs = startAbs + 19;
|
||||
let html = "";
|
||||
|
||||
for (let abs = lowerPaAbs; abs <= upperPaAbs; abs += 1) {
|
||||
html += buildKeyHtml(abs, startAbs, svaraNames, ragam, svaraStyle);
|
||||
}
|
||||
|
||||
pianoElement.innerHTML = html;
|
||||
attachKeyListeners();
|
||||
}
|
||||
|
||||
function attachKeyListeners() {
|
||||
const keys = pianoElement.querySelectorAll(".key");
|
||||
keys.forEach((key) => {
|
||||
key.addEventListener("click", () => {
|
||||
playNote(parseInt(key.dataset.abs, 10));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function ensureAudioContext() {
|
||||
if (!audioCtx) {
|
||||
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
}
|
||||
return audioCtx;
|
||||
}
|
||||
|
||||
async function playNote(absSemitone) {
|
||||
const ctx = ensureAudioContext();
|
||||
if (ctx.state === "suspended") {
|
||||
await ctx.resume();
|
||||
}
|
||||
|
||||
const freq = absFrequency(absSemitone);
|
||||
|
||||
const oscillator = ctx.createOscillator();
|
||||
const gainNode = ctx.createGain();
|
||||
|
||||
oscillator.type = "sine";
|
||||
oscillator.frequency.setValueAtTime(freq, ctx.currentTime);
|
||||
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(ctx.destination);
|
||||
|
||||
gainNode.gain.setValueAtTime(0.2, ctx.currentTime);
|
||||
oscillator.start();
|
||||
oscillator.stop(ctx.currentTime + 0.5);
|
||||
}
|
||||
|
||||
renderKeyboard();
|
||||
svaraNamesSelect.addEventListener("change", renderKeyboard);
|
||||
svaraStyleSelect.addEventListener("change", renderKeyboard);
|
||||
startingNoteSelect.addEventListener("change", renderKeyboard);
|
||||
ragamSelect.addEventListener("change", renderKeyboard);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user