Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,31 @@ <h1>Partial Player Plus</h1>
</div>

<div class="control">
<label for="note">OR as Note (C1 = lowest C on piano)</label>

<input
id="note"
type="text"
value="C4"
spellcheck="false"
autocomplete="off">
<div class="dual-control-row">
<div class="dual-control">
<label for="note">OR as Note</label>
<input
id="note"
type="text"
value="C4"
spellcheck="false"
autocomplete="off">
</div>

<div class="dual-control">
<label for="transpose">Transpose</label>
<div class="transpose-controls">
<button id="transposeToggle" type="button">On</button>
<input
id="transpose"
type="text"
value="C"
spellcheck="false"
autocomplete="off">

</div>
</div>
</div>
</div>

<div class="control">
Expand Down
83 changes: 56 additions & 27 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -383,6 +412,6 @@ setupSpinner(multPlus, multiplierInput, 1);
setupSpinner(aMinus, referenceAInput, -1, updateFromNote);
setupSpinner(aPlus, referenceAInput, 1, updateFromNote);


updateTransposeToggle();
updateDisplay();

42 changes: 42 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
Expand Down
Loading