Skip to content

Instantly share code, notes, and snippets.

@sandwaves
Created June 14, 2024 17:36
Show Gist options
  • Save sandwaves/bc3ed71a8f4dbc115725cf59bd187220 to your computer and use it in GitHub Desktop.
Save sandwaves/bc3ed71a8f4dbc115725cf59bd187220 to your computer and use it in GitHub Desktop.
Ableton Note Chords Viewer
<!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>
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();
});
/* 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