Created
June 14, 2024 17:36
-
-
Save sandwaves/bc3ed71a8f4dbc115725cf59bd187220 to your computer and use it in GitHub Desktop.
Ableton Note Chords Viewer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title>Chord Grid</title> | |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css"> | |
<link rel="stylesheet" href="style.css"> | |
</head> | |
<body> | |
<div class="container"> | |
<div class="controls"> | |
<label for="root-note">Root Note:</label> | |
<select id="root-note" aria-label="Select Root Note"> | |
<option value="C2">C</option> | |
<option value="C#2">C#</option> | |
<option value="D2">D</option> | |
<option value="D#2">D#</option> | |
<option value="E2">E</option> | |
<option value="F2">F</option> | |
<option value="F#2">F#</option> | |
<option value="G2">G</option> | |
<option value="G#2">G#</option> | |
<option value="A2">A</option> | |
<option value="A#2">A#</option> | |
<option value="B2">B</option> | |
</select> | |
<div class="radio-group"> | |
<input type="radio" id="major" name="chord-type" value="major" checked> | |
<label for="major">Major</label> | |
</div> | |
<div class="radio-group"> | |
<input type="radio" id="minor" name="chord-type" value="minor"> | |
<label for="minor">Minor</label> | |
</div> | |
<button id="generate-grid" aria-label="Generate Chord Grid">Generate Grid</button> | |
<button id="play-chord" aria-label="Play Chord">Play Chord</button> | |
<button id="save-image" aria-label="Save Chord Grid Image">Save Image</button> | |
<label for="volume-control">Volume:</label> | |
<input type="range" id="volume-control" min="0" max="1" step="0.01" value="1"> | |
<button id="mute-button" aria-label="Mute/Unmute">Mute</button> | |
</div> | |
<div id="grid-container" aria-live="polite"></div> | |
</div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.24/Tone.min.js"></script> | |
<script src="script.js"></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
document.addEventListener("DOMContentLoaded", () => { | |
// Getting references to DOM elements | |
const rootNoteSelect = document.getElementById("root-note"); | |
const chordTypeRadios = document.getElementsByName("chord-type"); | |
const generateGridButton = document.getElementById("generate-grid"); | |
const saveImageButton = document.getElementById("save-image"); | |
const playChordButton = document.getElementById("play-chord"); | |
const volumeControl = document.getElementById("volume-control"); | |
const muteButton = document.getElementById("mute-button"); | |
const gridContainer = document.getElementById("grid-container"); | |
// Arrays for musical notes and MIDI grid notes, shifted one octave higher | |
const notes = [ | |
"C", | |
"C#", | |
"D", | |
"D#", | |
"E", | |
"F", | |
"F#", | |
"G", | |
"G#", | |
"A", | |
"A#", | |
"B" | |
]; | |
const gridNotes = [ | |
"C2", | |
"C#2", | |
"D2", | |
"D#2", | |
"E2", | |
"F2", | |
"F#2", | |
"G2", | |
"G#2", | |
"A2", | |
"A#2", | |
"B2", | |
"C3", | |
"C#3", | |
"D3", | |
"D#3", | |
"E3", | |
"F3", | |
"F#3", | |
"G3", | |
"G#3", | |
"A3", | |
"A#3", | |
"B3", | |
"C4" | |
]; | |
// 5x5 chromatic grid for MIDI notes, shifted one octave higher | |
const chromaticGrid = [ | |
["G#3", "A3", "A#3", "B3", "C4"], // Row 1 | |
["D#3", "E3", "F3", "F#3", "G3"], // Row 2 | |
["A#2", "B2", "C3", "C#3", "D3"], // Row 3 | |
["F2", "F#2", "G2", "G#2", "A2"], // Row 4 | |
["C2", "C#2", "D2", "D#2", "E2"] // Row 5 | |
]; | |
// Initialize the Tone.js PolySynth | |
const synth = new Tone.PolySynth(Tone.Synth).toDestination(); | |
let isMuted = false; | |
// Function to play a note | |
const playNote = (note) => { | |
if (!isMuted) { | |
synth.triggerAttackRelease(note, "8n"); | |
} | |
}; | |
// Function to generate the chord grid | |
const generateGrid = () => { | |
gridContainer.innerHTML = ""; | |
chromaticGrid.forEach((row) => { | |
row.forEach((note) => { | |
const pad = document.createElement("div"); | |
pad.classList.add("pad"); | |
if (note.includes("#")) { | |
pad.classList.add("sharp"); | |
} | |
pad.textContent = note.replace("#", "♯"); | |
pad.addEventListener("click", () => playNote(note)); // Play note on click | |
gridContainer.appendChild(pad); | |
}); | |
}); | |
highlightChord(); | |
}; | |
// Function to highlight the notes of the selected chord | |
const highlightChord = () => { | |
const rootNote = rootNoteSelect.value; | |
const chordType = Array.from(chordTypeRadios).find((radio) => radio.checked) | |
.value; | |
const rootIndex = gridNotes.indexOf(rootNote); | |
const majorChord = [0, 4, 7]; | |
const minorChord = [0, 3, 7]; | |
const chordPattern = chordType === "major" ? majorChord : minorChord; | |
const pads = gridContainer.querySelectorAll(".pad"); | |
chordPattern.forEach((interval) => { | |
const noteIndex = (rootIndex + interval) % gridNotes.length; | |
const note = gridNotes[noteIndex].replace("#", "♯"); | |
const pad = Array.from(pads).find((p) => p.textContent === note); | |
if (pad) { | |
pad.classList.add("highlight"); | |
} | |
}); | |
}; | |
// Function to play the highlighted chord | |
const playChord = () => { | |
const highlightedPads = gridContainer.querySelectorAll(".highlight"); | |
const notes = Array.from(highlightedPads).map((pad) => | |
pad.textContent.replace("♯", "#") | |
); | |
if (!isMuted) { | |
synth.triggerAttackRelease(notes, "8n"); | |
} | |
}; | |
// Event listeners | |
generateGridButton.addEventListener("click", generateGrid); | |
playChordButton.addEventListener("click", playChord); | |
// Save grid as image functionality | |
saveImageButton.addEventListener("click", () => { | |
html2canvas(gridContainer).then((canvas) => { | |
const link = document.createElement("a"); | |
link.href = canvas.toDataURL(); | |
link.download = "chord-grid.png"; | |
link.click(); | |
}); | |
}); | |
// Volume control | |
volumeControl.addEventListener("input", (event) => { | |
const volume = event.target.value; | |
synth.volume.value = Tone.gainToDb(volume); | |
}); | |
// Mute button | |
muteButton.addEventListener("click", () => { | |
isMuted = !isMuted; | |
muteButton.textContent = isMuted ? "Unmute" : "Mute"; | |
}); | |
// Initial grid generation | |
generateGrid(); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Define color variables for easy customization and consistent theming */ | |
:root { | |
--primary-color: #1db954; /* Primary green color for interactive elements */ | |
--secondary-color: #1ed760; /* Lighter green for hover states and secondary elements */ | |
--accent-color: #ff4081; /* Contrasting accent color for highlights */ | |
--bg-color: #121212; /* Dark background color */ | |
--text-color: #ffffff; /* White text color for high contrast */ | |
--control-bg-color: #212121; /* Slightly lighter dark color for control panel background */ | |
} | |
/* Global body styling */ | |
body { | |
background-color: var(--bg-color); /* Set the background color */ | |
color: var(--text-color); /* Set the text color */ | |
font-family: "Roboto", sans-serif; /* Use the Roboto font family */ | |
display: flex; /* Use flexbox for layout */ | |
justify-content: center; /* Center content horizontally */ | |
align-items: center; /* Center content vertically */ | |
height: 100vh; /* Set height to full viewport height */ | |
margin: 0; /* Remove default margin */ | |
} | |
/* Container styling for overall layout */ | |
.container { | |
display: flex; /* Use flexbox for layout */ | |
justify-content: center; /* Center content horizontally */ | |
align-items: center; /* Center content vertically */ | |
gap: 20px; /* Add space between elements */ | |
} | |
/* Control panel styling */ | |
.controls { | |
background-color: var(--control-bg-color); /* Set background color */ | |
padding: 20px; /* Add padding */ | |
border-radius: 10px; /* Round the corners */ | |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Add a subtle shadow */ | |
display: flex; | |
flex-direction: column; | |
} | |
/* Styling for labels in the control panel */ | |
.controls label { | |
color: var(--primary-color); /* Set label color */ | |
font-size: 16px; /* Set font size */ | |
margin-bottom: 5px; /* Add space below the label */ | |
} | |
/* Styling for select and radio input elements */ | |
.controls select, | |
.controls input[type="radio"] { | |
margin-bottom: 15px; /* Add space below the elements */ | |
} | |
/* Button styling */ | |
.controls button, | |
.controls input[type="range"] { | |
margin-bottom: 15px; /* Add space below the elements */ | |
background-color: var(--primary-color); /* Set background color */ | |
color: var(--text-color); /* Set text color */ | |
border: none; /* Remove border */ | |
padding: 12px; /* Add padding */ | |
border-radius: 5px; /* Round the corners */ | |
cursor: pointer; /* Change cursor to pointer on hover */ | |
transition: background-color 0.3s ease, transform 0.2s ease; /* Add transition effects */ | |
} | |
/* Button hover state styling */ | |
.controls button:hover { | |
background-color: var( | |
--secondary-color | |
); /* Change background color on hover */ | |
transform: scale(1.05); /* Slightly enlarge button on hover */ | |
} | |
/* Button focus state styling */ | |
.controls button:focus { | |
outline: 2px solid var(--accent-color); /* Add an outline for focus state */ | |
outline-offset: 2px; /* Offset the outline */ | |
} | |
/* Grid container styling */ | |
#grid-container { | |
display: grid; /* Use CSS Grid layout */ | |
grid-template-columns: repeat(5, 60px); /* Define 5 columns */ | |
grid-template-rows: repeat(5, 60px); /* Define 5 rows */ | |
gap: 1px; /* Add space between grid cells */ | |
padding: 20px; /* Add padding */ | |
background-color: var(--control-bg-color); /* Set background color */ | |
border-radius: 10px; /* Round the corners */ | |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Add a subtle shadow */ | |
} | |
/* Pad (grid cell) styling */ | |
.pad { | |
width: 60px; /* Set width */ | |
height: 60px; /* Set height */ | |
display: flex; /* Use flexbox for layout */ | |
align-items: center; /* Center content vertically */ | |
justify-content: center; /* Center content horizontally */ | |
background-color: #303030; /* Set background color */ | |
border: 1px solid var(--bg-color); /* Set border color */ | |
font-size: 14px; /* Set font size */ | |
color: #4d4d4d; /* Set text color */ | |
transition: background-color 0.3s ease, transform 0.2s ease; /* Add transition effects */ | |
} | |
/* Pad hover state styling */ | |
.pad:hover { | |
transform: scale(1.1); /* Slightly enlarge pad on hover */ | |
} | |
/* Pad focus state styling */ | |
.pad:focus { | |
outline: 2px solid var(--accent-color); /* Add an outline for focus state */ | |
outline-offset: 2px; /* Offset the outline */ | |
} | |
/* Styling for sharp notes */ | |
.pad.sharp { | |
background-color: #262626; /* Set background color for sharp notes */ | |
} | |
/* Styling for highlighted pads */ | |
.pad.highlight { | |
background-color: var( | |
--primary-color | |
); /* Set background color for highlighted pads */ | |
color: var(--bg-color); /* Set text color */ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment