Music Editor

by Ben Gillbanks

HTML

<h2>Music Editor</h2>
	<label>Tempo (ms per note):</label>
	<input id="tempo" type="number" value="100">
	<label>Note Length:</label>
	<input id="noteLength" type="number" value="80">
	<br><br>
	<label>Music Length (notes):</label>
<input id="channelLength" type="number" value="16" step="4">
<div id="tabs"></div>
<button id="addChannel">Add Channel</button>
<div id="channel-content"></div>
	<button id="generate">Generate Song Text</button>
	<pre id="output"></pre>

CSS

body {
			font-family: sans-serif;
		}
		table {
			border-collapse: collapse;
			margin-top: 5px;
			font-size: 10px;
		}
		td, th {
			border: 1px solid #ddd;
			text-align: center;
			padding: 0;
			line-height: 0;
		}
		input[type=radio] {
			width: 10px;
			height: 10px;
			margin:0;
			padding: 0;
		}
		.channel {
			margin-bottom: 20px;
		}

JavaScript

// Define note characters.
const noteChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const numRows = noteChars.length;
let numColumns = Number(document.getElementById('channelLength').value) || 16;
let channelCount = 0;

const tabsContainer = document.getElementById('tabs');
const channelContentContainer = document.getElementById('channel-content');

// Create a channel grid.
function createChannel(channelIndex) {
	const channelContainer = document.createElement('div');
	channelContainer.className = 'channel';
	channelContainer.dataset.channel = channelIndex;
	// Only show the first channel.
	if (channelIndex !== 0) {
		channelContainer.style.display = 'none';
	}

	// Instrument selector.
	const instrumentLabel = document.createElement('span');
	instrumentLabel.textContent = 'Channel ' + (channelIndex + 1) + ' Instrument: ';
	const instrumentSelect = document.createElement('select');
	instrumentSelect.id = 'instrument-' + channelIndex;
	["0", "1", "2", "3", "4", "5", "6", "7"].forEach(inst => {
		const option = document.createElement('option');
		option.value = inst;
		option.textContent = inst;
		instrumentSelect.appendChild(option);
	});
	channelContainer.appendChild(instrumentLabel);
	channelContainer.appendChild(instrumentSelect);
	channelContainer.appendChild(document.createElement('br'));

	// Create table grid with radio buttons.
	const table = document.createElement('table');
	for (let r = 0; r < numRows; r++) {
		const tr = document.createElement('tr');
		// Note label cell.
		const labelTd = document.createElement('td');
		labelTd.textContent = noteChars[r];
		tr.appendChild(labelTd);
		// Create cells for each time column.
		for (let c = 0; c < numColumns; c++) {
			const td = document.createElement('td');
			const radio = document.createElement('input');
			radio.type = 'radio';
			// Group by channel and column.
			radio.name = `radio-ch-${channelIndex}-col-${c}`;
			radio.dataset.row =...