From 4ea6d8cc4e591815142738b3dabc7a67dfb33f7b Mon Sep 17 00:00:00 2001 From: "Affe Zwei (AZ)" <62151108+AffeZwei@users.noreply.github.com> Date: Sun, 5 Jul 2026 17:04:57 +0100 Subject: [PATCH] Beta transposer --- index.html | 33 ++++++++++++++++------ script.js | 83 ++++++++++++++++++++++++++++++++++++------------------ style.css | 42 +++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 35 deletions(-) diff --git a/index.html b/index.html index d88f361..aa4cb5e 100644 --- a/index.html +++ b/index.html @@ -35,14 +35,31 @@

Partial Player Plus

- - - +
+
+ + +
+ +
+ +
+ + + +
+
+
diff --git a/script.js b/script.js index c4b423e..803a003 100644 --- a/script.js +++ b/script.js @@ -3,6 +3,8 @@ const multiplierInput = document.getElementById("multiplier"); const volumeSlider = document.getElementById("volume"); const noteInput = document.getElementById("note"); +const transposeNote = document.getElementById("transpose"); +const transposeToggleButton = document.getElementById("transposeToggle"); const referenceAInput = document.getElementById("referenceA"); const outputDisplay = document.getElementById("outputFrequency"); @@ -69,6 +71,7 @@ let oscillator = null; let gainNode = null; let holdTimeout; let holdInterval; +let transposeEnabled = true; function getVolume() { return Math.pow(Number(volumeSlider.value) / 100, 2); @@ -153,45 +156,65 @@ function frequencyToMidi(freq, referenceA) { } -function frequencyToNote(freq, referenceA) { +function updateTransposeToggle() { + const label = transposeEnabled ? "On" : "Off"; + transposeToggleButton.textContent = `Transpose: ${label}`; + transposeToggleButton.classList.toggle("active", transposeEnabled); +} - const midiFloat = frequencyToMidi(freq, referenceA); +function toggleTranspose() { + transposeEnabled = !transposeEnabled; + updateTransposeToggle(); + updateDisplay(); +} +function frequencyToNote(freq, referenceA) { + const midiFloat = frequencyToMidi(freq, referenceA); const midi = Math.round(midiFloat); - const cents = (midiFloat - midi) * 100; + //const octave = Math.floor(midi / 12) - 1; + let octave = Math.floor(midi / 12) - 1; + let noteIndex = midi % 12; + let note = NOTE_NAMES[noteIndex]; - const octave = Math.floor(midi / 12) - 1; +if (transposeEnabled) { + const transposeName = getNoteName(transposeNote.value); - const note = NOTE_NAMES[midi % 12]; + if (transposeName) { + const transposeIndex = NOTE_INDEX[transposeName]; - return { + if (transposeIndex !== undefined) { - note: note + octave, + // interval from C to the selected transposition + const delta = transposeIndex - NOTE_INDEX.C; - cents: cents + const displayMidi = midi - delta; - }; + noteIndex = ((displayMidi % 12) + 12) % 12; + note = NOTE_NAMES[noteIndex]; + octave = Math.floor(displayMidi / 12) - 1; + } + } +} + return { + note: note + octave, + cents: cents + }; } function updateDisplay() { - const freq = getFrequency(); const refA = Number(referenceAInput.value); - const nearest = frequencyToNote(freq, refA) + const nearest = frequencyToNote(freq, refA); const sign = nearest.cents >= 0 ? "+" : ""; - nearestNote.textContent = - `${nearest.note} (${sign}${nearest.cents.toFixed(2)}¢)`; + nearestNote.textContent = `${nearest.note} (${sign}${nearest.cents.toFixed(2)}¢)`; - outputDisplay.textContent = freq.toFixed(2) + " Hz" ; + outputDisplay.textContent = freq.toFixed(2) + " Hz"; if (oscillator) { - oscillator.frequency.setValueAtTime( - freq, - audioContext.currentTime - ); + oscillator.frequency.setValueAtTime(freq, audioContext.currentTime); } } @@ -209,22 +232,25 @@ function updateFromFrequency() { } function parseNote(text) { + const match = String(text).trim().match(/^([A-Ga-g])([#b]?)(-?\d+)?$/i); - const match = text.trim().match(/^([A-Ga-g])([#b]?)(-?\d+)$/); + if (!match) return null; - if (!match) - return null; + const letter = match[1].toUpperCase(); + const accidental = (match[2] || "").toLowerCase(); return { - - note: (match[1].toUpperCase() + match[2]), - - octave: Number(match[3]) - + note: letter + accidental, + octave: match[3] === undefined ? null : Number(match[3]) }; +} +function getNoteName(text) { + const parsed = parseNote(text); + return parsed ? parsed.note : null; } + function noteToMidi(noteString) { const parsed = parseNote(noteString); @@ -364,6 +390,9 @@ fundamentalInput.addEventListener("input", updateFromFrequency); noteInput.addEventListener("input", updateFromNote); +transposeNote.addEventListener("input", updateDisplay); +transposeToggleButton.addEventListener("click", toggleTranspose); + referenceAInput.addEventListener("input", updateFromNote); multiplierInput.addEventListener("input", updateDisplay); @@ -383,6 +412,6 @@ setupSpinner(multPlus, multiplierInput, 1); setupSpinner(aMinus, referenceAInput, -1, updateFromNote); setupSpinner(aPlus, referenceAInput, 1, updateFromNote); - +updateTransposeToggle(); updateDisplay(); diff --git a/style.css b/style.css index 2e73d7b..999cf63 100644 --- a/style.css +++ b/style.css @@ -78,6 +78,48 @@ input[type=text] { } +.dual-control-row { + display: flex; + gap: 16px; +} + +.dual-control { + flex: 1; +} + +.dual-control input { + width: 100%; +} + +.transpose-controls { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + align-items: center; +} + +.transpose-controls button { + width: 100%; + padding: 8px 4px; + border: none; + border-radius: 8px; + background: #4fd2ff; + color: #111; + font-weight: bold; + font-size: 9px; +} + +.transpose-controls button.active { + background: #a2211d; + color: white; +} + +.transpose-controls input { + width: 100%; +} + + + input[type=range] { width: 100%;