move css and js files into folders

This commit is contained in:
2026-07-26 15:53:03 +05:30
parent a8ae3d0e80
commit 2cf1ceeef9
2 changed files with 343 additions and 343 deletions
+150 -150
View File
@@ -1,150 +1,150 @@
body { body {
font-family: sans-serif; font-family: sans-serif;
text-align: center; text-align: center;
background: #2a2a2a; background: #2a2a2a;
padding: 20px; padding: 20px;
} }
h1 { h1 {
color: #fff; color: #fff;
} }
.controls { .controls {
margin-bottom: 30px; margin-bottom: 30px;
} }
.controls label { .controls label {
color: #fff; color: #fff;
margin-right: 10px; margin-right: 10px;
font-size: 16px; font-size: 16px;
} }
.controls select { .controls select {
padding: 8px 12px; padding: 8px 12px;
font-size: 14px; font-size: 14px;
border-radius: 5px; border-radius: 5px;
border: none; border: none;
background: #fff; background: #fff;
color: #333; color: #333;
cursor: pointer; cursor: pointer;
} }
.piano { .piano {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: flex-end; align-items: flex-end;
margin-top: 50px; margin-top: 50px;
background: #111; background: #111;
padding: 20px; padding: 20px;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.8); box-shadow: 0 10px 40px rgba(0, 0, 0, 0.8);
} }
.key { .key {
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
justify-content: center; justify-content: center;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
border: 1px solid #222; border: 1px solid #222;
border-radius: 5px; border-radius: 5px;
user-select: none; user-select: none;
transition: all 0.05s; transition: all 0.05s;
position: relative; position: relative;
min-height: 1em; min-height: 1em;
} }
/* Sharp keys may show Carnatic swara labels if they correspond to the chosen scale */ /* Sharp keys may show Carnatic swara labels if they correspond to the chosen scale */
.key.sharp.sa-root { .key.sharp.sa-root {
color: #fff; color: #fff;
font-size: 14px; font-size: 14px;
} }
.key.has-label .key-label { .key.has-label .key-label {
position: relative; position: relative;
display: inline-flex; display: inline-flex;
} }
.key.has-label .key-label sub { .key.has-label .key-label sub {
font-size: 0.65em; font-size: 0.65em;
display: inline-block; display: inline-block;
vertical-align: text-bottom; vertical-align: text-bottom;
line-height: 1; line-height: 1;
position: relative; position: relative;
top: 0.18em; top: 0.18em;
} }
.key.lower.has-label .key-label::after, .key.lower.has-label .key-label::after,
.key.upper.has-label .key-label::after { .key.upper.has-label .key-label::after {
position: absolute; position: absolute;
content: '•'; content: '•';
font-size: 12px; font-size: 12px;
color: currentColor; color: currentColor;
pointer-events: none; pointer-events: none;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
} }
.key.black.has-label .key-label::after { .key.black.has-label .key-label::after {
color: #777; color: #777;
} }
.key.lower.has-label .key-label::after { .key.lower.has-label .key-label::after {
top: 100%; top: 100%;
margin-top: -2px; margin-top: -2px;
} }
.key.upper.has-label .key-label::after { .key.upper.has-label .key-label::after {
bottom: 100%; bottom: 100%;
margin-bottom: -5px; margin-bottom: -5px;
} }
/* White keys */ /* White keys */
.key.white { .key.white {
width: 60px; width: 60px;
height: 250px; height: 250px;
background: linear-gradient(135deg, #fff 0%, #f5f5f5 100%); background: linear-gradient(135deg, #fff 0%, #f5f5f5 100%);
color: #333; color: #333;
margin: 0 -1px; margin: 0 -1px;
padding-bottom: 15px; padding-bottom: 15px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4), inset 0 -1px 3px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4), inset 0 -1px 3px rgba(0, 0, 0, 0.1);
} }
.key.white:active { .key.white:active {
background: linear-gradient(135deg, #e0e0e0 0%, #d5d5d5 100%); background: linear-gradient(135deg, #e0e0e0 0%, #d5d5d5 100%);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), inset 0 1px 3px rgba(0, 0, 0, 0.2); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), inset 0 1px 3px rgba(0, 0, 0, 0.2);
transform: translateY(2px); transform: translateY(2px);
} }
/* Black keys */ /* Black keys */
.key.black { .key.black {
width: 40px; width: 40px;
height: 160px; height: 160px;
background: linear-gradient(135deg, #222 0%, #111 100%); background: linear-gradient(135deg, #222 0%, #111 100%);
color: #fff; color: #fff;
position: relative; position: relative;
z-index: 10; z-index: 10;
margin: 0 -20px; margin: 0 -20px;
padding-top: 10px; padding-top: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.8), inset 0 1px 2px rgba(255, 255, 255, 0.1); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.8), inset 0 1px 2px rgba(255, 255, 255, 0.1);
align-self: flex-start; align-self: flex-start;
} }
.key.black:active { .key.black:active {
background: linear-gradient(135deg, #111 0%, #000 100%); background: linear-gradient(135deg, #111 0%, #000 100%);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.9), inset 0 1px 2px rgba(255, 255, 255, 0.05); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.9), inset 0 1px 2px rgba(255, 255, 255, 0.05);
transform: translateY(2px); transform: translateY(2px);
} }
.key.sa-root.white { .key.sa-root.white {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4), inset 0 -1px 3px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4), inset 0 -1px 3px rgba(0, 0, 0, 0.1);
} }
.key.sa-root.black { .key.sa-root.black {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.8), inset 0 1px 2px rgba(255, 255, 255, 0.1); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.8), inset 0 1px 2px rgba(255, 255, 255, 0.1);
} }
.key.sa-root:not(.sharp) { .key.sa-root:not(.sharp) {
color: #d84315; color: #d84315;
font-weight: 700; font-weight: 700;
} }
+193 -193
View File
@@ -1,193 +1,193 @@
// Initialize audio context lazily to avoid autoplay restrictions // Initialize audio context lazily to avoid autoplay restrictions
let audioCtx; let audioCtx;
const NOTE_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; const NOTE_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
const SVARA_NAMES = { const SVARA_NAMES = {
full: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"], full: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"],
short: ["S", "R", "G", "M", "P", "D", "N"], short: ["S", "R", "G", "M", "P", "D", "N"],
malayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"] malayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"]
}; };
const semitoneMap = { const semitoneMap = {
C: 0, C: 0,
"C#": 1, "C#": 1,
D: 2, D: 2,
"D#": 3, "D#": 3,
E: 4, E: 4,
F: 5, F: 5,
"F#": 6, "F#": 6,
G: 7, G: 7,
"G#": 8, "G#": 8,
A: 9, A: 9,
"A#": 10, "A#": 10,
B: 11, B: 11,
Ab: 8, Ab: 8,
Bb: 10 Bb: 10
}; };
const ragams = { const ragams = {
Bhair: { Bhair: {
intervals: [0, 2, 3, 6, 7, 9, 11], intervals: [0, 2, 3, 6, 7, 9, 11],
namesFull: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"], namesFull: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"],
namesShort: ["S", "R", "G", "M", "P", "D", "N"], namesShort: ["S", "R", "G", "M", "P", "D", "N"],
namesMalayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"], namesMalayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"],
variantNumbers: ["", "2", "2", "1", "", "2", "2"] variantNumbers: ["", "2", "2", "1", "", "2", "2"]
}, },
Kalyani: { Kalyani: {
intervals: [0, 2, 4, 6, 7, 9, 11], intervals: [0, 2, 4, 6, 7, 9, 11],
namesFull: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"], namesFull: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"],
namesShort: ["S", "R", "G", "M", "P", "D", "N"], namesShort: ["S", "R", "G", "M", "P", "D", "N"],
namesMalayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"], namesMalayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"],
variantNumbers: ["", "2", "3", "2", "", "2", "3"] variantNumbers: ["", "2", "3", "2", "", "2", "3"]
}, },
Mayamalavagowla: { Mayamalavagowla: {
intervals: [0, 1, 4, 5, 7, 8, 11], intervals: [0, 1, 4, 5, 7, 8, 11],
namesFull: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"], namesFull: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"],
namesShort: ["S", "R", "G", "M", "P", "D", "N"], namesShort: ["S", "R", "G", "M", "P", "D", "N"],
namesMalayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"], namesMalayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"],
variantNumbers: ["", "1", "3", "1", "", "1", "3"] variantNumbers: ["", "1", "3", "1", "", "1", "3"]
}, },
Mohanam: { Mohanam: {
intervals: [0, 2, 4, 7, 9], intervals: [0, 2, 4, 7, 9],
namesFull: ["Sa", "Ri", "Ga", "Pa", "Dha"], namesFull: ["Sa", "Ri", "Ga", "Pa", "Dha"],
namesShort: ["S", "R", "G", "P", "D"], namesShort: ["S", "R", "G", "P", "D"],
namesMalayalam: ["സ", "രി", "ഗ", "പ", "ധ"], namesMalayalam: ["സ", "രി", "ഗ", "പ", "ധ"],
variantNumbers: ["", "2", "3", "", "2"] variantNumbers: ["", "2", "3", "", "2"]
}, },
Shankarabharanam: { Shankarabharanam: {
intervals: [0, 2, 4, 5, 7, 9, 11], intervals: [0, 2, 4, 5, 7, 9, 11],
namesFull: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"], namesFull: ["Sa", "Ri", "Ga", "Ma", "Pa", "Dha", "Ni"],
namesShort: ["S", "R", "G", "M", "P", "D", "N"], namesShort: ["S", "R", "G", "M", "P", "D", "N"],
namesMalayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"], namesMalayalam: ["സ", "രി", "ഗ", "മ", "പ", "ധ", "നി"],
variantNumbers: ["", "2", "3", "1", "", "2", "3"] variantNumbers: ["", "2", "3", "1", "", "2", "3"]
} }
}; };
const ROOT_OCTAVE = 4; const ROOT_OCTAVE = 4;
const SOUND_OCTAVE_OFFSET = -12; const SOUND_OCTAVE_OFFSET = -12;
const svaraNamesSelect = document.getElementById("svaraNames"); const svaraNamesSelect = document.getElementById("svaraNames");
const svaraStyleSelect = document.getElementById("svaraStyle"); const svaraStyleSelect = document.getElementById("svaraStyle");
const startingNoteSelect = document.getElementById("startingNote"); const startingNoteSelect = document.getElementById("startingNote");
const ragamSelect = document.getElementById("ragamSelect"); const ragamSelect = document.getElementById("ragamSelect");
const pianoElement = document.getElementById("piano"); const pianoElement = document.getElementById("piano");
function normalizeSemitone(value) { function normalizeSemitone(value) {
return ((value % 12) + 12) % 12; return ((value % 12) + 12) % 12;
} }
function absFrequency(absSemitone) { function absFrequency(absSemitone) {
const a4Abs = 4 * 12 + semitoneMap.A; // fixed A4 reference for real pitch const a4Abs = 4 * 12 + semitoneMap.A; // fixed A4 reference for real pitch
const soundAbsSemitone = absSemitone + SOUND_OCTAVE_OFFSET; const soundAbsSemitone = absSemitone + SOUND_OCTAVE_OFFSET;
return 440 * Math.pow(2, (soundAbsSemitone - a4Abs) / 12); return 440 * Math.pow(2, (soundAbsSemitone - a4Abs) / 12);
} }
function noteNameToAbs(noteName) { function noteNameToAbs(noteName) {
return ROOT_OCTAVE * 12 + semitoneMap[noteName]; return ROOT_OCTAVE * 12 + semitoneMap[noteName];
} }
function getSwaraLabel(absSemitone, startAbs, svaraNames, ragam, svaraStyle) { function getSwaraLabel(absSemitone, startAbs, svaraNames, ragam, svaraStyle) {
const diff = normalizeSemitone(absSemitone - startAbs); const diff = normalizeSemitone(absSemitone - startAbs);
const intervalIndex = ragam.intervals.indexOf(diff); const intervalIndex = ragam.intervals.indexOf(diff);
if (intervalIndex === -1) { if (intervalIndex === -1) {
return ""; return "";
} }
const baseLabel = svaraNames[intervalIndex]; const baseLabel = svaraNames[intervalIndex];
if (svaraStyle === "variant" && ragam.variantNumbers) { if (svaraStyle === "variant" && ragam.variantNumbers) {
const variant = ragam.variantNumbers[intervalIndex]; const variant = ragam.variantNumbers[intervalIndex];
if (variant) { if (variant) {
return `${baseLabel}<sup>${variant}</sup>`; return `${baseLabel}<sup>${variant}</sup>`;
} }
} }
return baseLabel; return baseLabel;
} }
function buildKeyHtml(absSemitone, startAbs, svaraNames, ragam, svaraStyle) { function buildKeyHtml(absSemitone, startAbs, svaraNames, ragam, svaraStyle) {
const noteName = NOTE_NAMES[normalizeSemitone(absSemitone)]; const noteName = NOTE_NAMES[normalizeSemitone(absSemitone)];
const isSharp = noteName.includes("#"); const isSharp = noteName.includes("#");
const label = getSwaraLabel(absSemitone, startAbs, svaraNames, ragam, svaraStyle); const label = getSwaraLabel(absSemitone, startAbs, svaraNames, ragam, svaraStyle);
const classes = ["key", isSharp ? "black" : "white"]; const classes = ["key", isSharp ? "black" : "white"];
if (absSemitone < startAbs) { if (absSemitone < startAbs) {
classes.push("lower"); classes.push("lower");
} else if (absSemitone > startAbs + 11) { } else if (absSemitone > startAbs + 11) {
classes.push("upper"); classes.push("upper");
} }
if (normalizeSemitone(absSemitone - startAbs) === 0) { if (normalizeSemitone(absSemitone - startAbs) === 0) {
classes.push("sa-root"); classes.push("sa-root");
} }
if (label) { if (label) {
classes.push("has-label"); classes.push("has-label");
} }
const content = label ? `<span class="key-label">${label}</span>` : ""; const content = label ? `<span class="key-label">${label}</span>` : "";
return `<div class="${classes.join(" ")}" data-abs="${absSemitone}">${content}</div>`; return `<div class="${classes.join(" ")}" data-abs="${absSemitone}">${content}</div>`;
} }
function renderKeyboard() { function renderKeyboard() {
const startNote = startingNoteSelect.value; const startNote = startingNoteSelect.value;
const svaraNames = SVARA_NAMES[svaraNamesSelect.value] || SVARA_NAMES.full; const svaraNames = SVARA_NAMES[svaraNamesSelect.value] || SVARA_NAMES.full;
const svaraStyle = svaraStyleSelect.value; const svaraStyle = svaraStyleSelect.value;
const ragam = ragams[ragamSelect.value]; const ragam = ragams[ragamSelect.value];
const startAbs = noteNameToAbs(startNote); const startAbs = noteNameToAbs(startNote);
const lowerPaAbs = startAbs - 5; const lowerPaAbs = startAbs - 5;
const upperPaAbs = startAbs + 19; const upperPaAbs = startAbs + 19;
let html = ""; let html = "";
for (let abs = lowerPaAbs; abs <= upperPaAbs; abs += 1) { for (let abs = lowerPaAbs; abs <= upperPaAbs; abs += 1) {
html += buildKeyHtml(abs, startAbs, svaraNames, ragam, svaraStyle); html += buildKeyHtml(abs, startAbs, svaraNames, ragam, svaraStyle);
} }
pianoElement.innerHTML = html; pianoElement.innerHTML = html;
attachKeyListeners(); attachKeyListeners();
} }
function attachKeyListeners() { function attachKeyListeners() {
const keys = pianoElement.querySelectorAll(".key"); const keys = pianoElement.querySelectorAll(".key");
keys.forEach((key) => { keys.forEach((key) => {
key.addEventListener("click", () => { key.addEventListener("click", () => {
playNote(parseInt(key.dataset.abs, 10)); playNote(parseInt(key.dataset.abs, 10));
}); });
}); });
} }
function ensureAudioContext() { function ensureAudioContext() {
if (!audioCtx) { if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)(); audioCtx = new (window.AudioContext || window.webkitAudioContext)();
} }
return audioCtx; return audioCtx;
} }
async function playNote(absSemitone) { async function playNote(absSemitone) {
const ctx = ensureAudioContext(); const ctx = ensureAudioContext();
if (ctx.state === "suspended") { if (ctx.state === "suspended") {
await ctx.resume(); await ctx.resume();
} }
const freq = absFrequency(absSemitone); const freq = absFrequency(absSemitone);
const oscillator = ctx.createOscillator(); const oscillator = ctx.createOscillator();
const gainNode = ctx.createGain(); const gainNode = ctx.createGain();
oscillator.type = "sine"; oscillator.type = "sine";
oscillator.frequency.setValueAtTime(freq, ctx.currentTime); oscillator.frequency.setValueAtTime(freq, ctx.currentTime);
oscillator.connect(gainNode); oscillator.connect(gainNode);
gainNode.connect(ctx.destination); gainNode.connect(ctx.destination);
gainNode.gain.setValueAtTime(0.2, ctx.currentTime); gainNode.gain.setValueAtTime(0.2, ctx.currentTime);
oscillator.start(); oscillator.start();
oscillator.stop(ctx.currentTime + 0.5); oscillator.stop(ctx.currentTime + 0.5);
} }
renderKeyboard(); renderKeyboard();
svaraNamesSelect.addEventListener("change", renderKeyboard); svaraNamesSelect.addEventListener("change", renderKeyboard);
svaraStyleSelect.addEventListener("change", renderKeyboard); svaraStyleSelect.addEventListener("change", renderKeyboard);
startingNoteSelect.addEventListener("change", renderKeyboard); startingNoteSelect.addEventListener("change", renderKeyboard);
ragamSelect.addEventListener("change", renderKeyboard); ragamSelect.addEventListener("change", renderKeyboard);