move css and js files into folders
This commit is contained in:
+150
-150
@@ -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
@@ -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);
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user