demo/index.html
<html>
<head>
<title>Sequencer Demo</title>
<link href="demo.css" rel="stylesheet">
</head>
<body>
<h1>intermix Test App</h1>
<fieldset>
<legend>Step Sequencer</legend>
<div id="sequencer"></div>
<div class="transport">
<button id="start" class="demo-button">Start</button>
<button id="stop" class="demo-button">Stop</button>
<button id="reset" class="demo-button">Reset</button>
<input id="bpm" type="number" value="120">
</div>
</fieldset>
<div class="synth-and-routing">
<fieldset>
<legend>Synth Parameter</legend>
<div>
<input type="range" id="synth-volume" name="synth-volume" min="0" max="127" value="127">
<label for="synth-volume">Volume</label>
</div>
<div>
<input type="range" id="synth-attack" name="synth-attack" min="0" max="1" step="0.01" value="0.1">
<label for="synth-attack">Filter Attack</label>
</div>
<div>
<input type="range" id="synth-decay" name="synth-decay" min="0" max="1" step="0.01" value="0.1">
<label for="synth-decay">Filter Decay</label>
</div>
</fieldset>
<fieldset>
<legend>Routing</legend>
<div class="routing">
<label for="snare-out">Snare goes to</label>
<select name="snare-out" id="snare-out">
<option value="destination">Soundcard</option>
<option value="delay">Delay FX</option>
</select>
</div>
<div class="routing">
<label for="hihat-out">Hihat goes to</label>
<select name="hihat-out" id="hihat-out">
<option value="destination">Soundcard</option>
<option value="delay">Delay FX</option>
</select>
</div>
<div class="routing">
<label for="bass-out">Bass goes to</label>
<select name="bass-out" id="bass-out">
<option value="destination">Soundcard</option>
<option value="delay">Delay FX</option>
</select>
</div>
</fieldset>
</div>
<fieldset>
<legend>Debugging</legend>
<button id="resumeAudioContext">resume audio context</button>
<button id="getstate">get current state</button>
</fieldset>
<footer>
Drumsounds sampled from a <a target="_blank" href="http://www.vintagesynth.com/casio/sk1.php">Casio SK-1</a>.
</footer>
</body>
<script>
// redux is reading the 'process' global var
// that doesn't exists in the browser
const process = {
env: {
NODE_ENV: 'debug',
}
};
</script>
<script type="module">
import * as intermix from "/dist/esm/intermix.esm.js";
import StepSequencer from "./StepSequencer.js";
const stepmodel = [
[1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0],
];
const synthNotes = [41, 41, 41, 29, 41, 41, 41, 42, 41, 30, 42, 44, 41, 42, 30, 41];
const rownames = ["Bassdrum", "Snare", "Hihat Closed", "Hihat Open", "Bass"];
const stepSequencer = new StepSequencer({
parent: "sequencer",
model: stepmodel,
synthNotes,
rownames,
});
const samples = [
"/demo/assets/Casio SK-1/skkick.wav",
"/demo/assets/Casio SK-1/sksnare.wav",
"/demo/assets/Casio SK-1/skclhat.wav",
"/demo/assets/Casio SK-1/skophat.wav",
]
const instrumentUID = [];
const seqPartUID = [];
const sequencerUID = intermix.addPlugin('Sequencer');
const delayUID = intermix.addPlugin('Delay');
const synthUID = intermix.addPlugin('Synth');
const seqActionCreators = initSequencer();
initSamplers();
const synthActionCreators = initSynth();
initSequencerParts();
/**
* The callback that should be passed to the sequencer animation
*/
function nextStep(step) {
if (step % 4 === 0) {
stepSequencer.next();
}
}
/**
* create the sequencer and initialize the UI animation
*/
function initSequencer() {
const actionCreators = intermix.getActionCreators(sequencerUID);
actionCreators.animate(nextStep);
setLoop(actionCreators);
return actionCreators;
}
function setLoop(actionCreators) {
actionCreators.loopEnd(63);
actionCreators.loopActivate();
}
/**
* create sequencer parts and add them to the score.
*/
function initSequencerParts() {
createSeqParts();
addPartsToScore();
populateSeqParts();
}
function createSeqParts() {
const partCount = stepmodel.length;
for (let i = 0; i < partCount; i++) {
seqPartUID.push(intermix.addSeqPart());
}
}
function addPartsToScore() {
seqPartUID.forEach((seqPartId, index) => {
seqActionCreators.addToScore([seqPartId, instrumentUID[index]])
})
}
/**
* map step-model to sequencer parts
*/
function populateSeqParts() {
stepmodel.forEach((seqRow, rowIndex) => {
const seqPartId = seqPartUID[rowIndex];
const seqPartActionCreators = intermix.getActionCreators(seqPartId);
seqRow.forEach((seqStep, stepIndex) => {
if (seqStep !== 0) {
let stepNoteNumber = 49;
if (rowIndex === stepmodel.length - 1) {
stepNoteNumber = synthNotes[stepIndex];
}
seqPartActionCreators.activeStep(stepIndex);
seqPartActionCreators.addNote(["note", stepNoteNumber, 1, 0.125, 0.0]);
}
})
})
}
/**
* create sampler plugins and a sequencer part for each sound
*/
function initSamplers() {
createSamplers();
mapSamplesToSamplers();
}
function createSamplers() {
for (let i = 0; i < 4; i++) {
instrumentUID.push(intermix.addPlugin('Sampler'))
}
}
/**
* add a sample to each sampler
*/
function mapSamplesToSamplers() {
samples.forEach((sample, index) => {
addAudioDataToSampler(instrumentUID[index], sample);
})
}
/**
* Load audio files, decode it and send it to the sampler instances
*/
function addAudioDataToSampler(samplerUID, rawData) {
intermix.loadFileFromServer(rawData).then((response) => {
const ac = intermix.getAudioContext();
return ac.decodeAudioData(response);
}).then((decoded) => {
const actionCreators = intermix.getActionCreators(samplerUID)
actionCreators.audioData(decoded)
})
}
function initSynth() {
instrumentUID.push(synthUID);
const actionCreators = intermix.getActionCreators(synthUID);
const volume = 30;
const attack = 0.2;
const volumeSlider = document.getElementById("synth-volume");
const attackSlider = document.getElementById("synth-attack");
const decaySlider = document.getElementById("synth-decay");
actionCreators.volume(volume);
actionCreators.envAttack(["Envelope Attack", attack, 0]);
volumeSlider.valueAsNumber = volume;
attackSlider.valueAsNumber = attack;
return actionCreators;
}
/**
* This function will be called when a cell gets clicked.
* It is used to modify sequencer parts
*/
stepSequencer.onStepChange = (rowNumber, cellNumber, cellActive) => {
const seqPartId = seqPartUID[rowNumber];
const actionCreators = intermix.getActionCreators(seqPartId);
actionCreators.activeStep(cellNumber);
let noteNumber = 49;
if (parseInt(rowNumber) === stepmodel.length - 1) { // synth has to be treated different
const input = document.getElementById("note-input_" + cellNumber);
noteNumber = input.valueAsNumber;
}
if (cellActive) {
stepmodel[rowNumber][cellNumber] = noteNumber;
actionCreators.addNote(["note", noteNumber, 1, 0.125, 0.0]);
} else {
noteNumber = stepmodel[rowNumber][cellNumber];
actionCreators.removeNote(["note", noteNumber, 1, 0.125, 0.0]);
}
}
/**
* This function will be called when a note number gets changed.
* Modifies the synthNotes array and the corresponding sequencer part.
*/
stepSequencer.onNoteChange = (stepNumber, noteNumber) => {
console.log("stepNumber: " + stepNumber);
console.log("noteNumber: " + noteNumber);
const oldNoteNumber = synthNotes[stepNumber];
synthNotes[stepNumber] = noteNumber;
const actionCreators = intermix.getActionCreators(seqPartUID[seqPartUID.length - 1]);
actionCreators.activeStep(stepNumber);
actionCreators.removeNote(["note", oldNoteNumber, 1, 1, 0]);
actionCreators.addNote(["note", noteNumber, 1, 0.125, 0.0]);
}
//===============
// event handler
//===============
document.getElementById("start").addEventListener("click", () => {
seqActionCreators.start();
})
document.getElementById("stop").addEventListener("click", () => {
seqActionCreators.stop();
})
document.getElementById("reset").addEventListener("click", () => {
// seqActionCreators.stop();
seqActionCreators.reset();
stepSequencer.reset();
})
document.getElementById("bpm").addEventListener("change", (event) => {
seqActionCreators.BPM(event.target.valueAsNumber);
})
document.getElementById("synth-volume").addEventListener("change", (event) => {
synthActionCreators.volume(event.target.valueAsNumber);
})
document.getElementById("synth-attack").addEventListener("change", (event) => {
const attack = event.target.valueAsNumber;
const value = ["Envelope Attack", attack, 0];
synthActionCreators.envAttack(value);
})
document.getElementById("synth-decay").addEventListener("change", (event) => {
const decay = event.target.valueAsNumber;
const value = ["Envelope Decay", decay, 0];
synthActionCreators.envDecay(value);
})
document.getElementById("snare-out").addEventListener("change", (event) => {
if (event.target.value === "delay") {
wireInstrument(instrumentUID[1], delayUID);
} else {
wireInstrument(instrumentUID[1], "destination");
}
});
document.getElementById("hihat-out").addEventListener("change", (event) => {
if (event.target.value === "delay") {
wireInstrument(instrumentUID[2], delayUID);
} else {
wireInstrument(instrumentUID[2], "destination");
}
});
document.getElementById("bass-out").addEventListener("change", (event) => {
if (event.target.value === "delay") {
wireInstrument(instrumentUID[4], delayUID);
} else {
wireInstrument(instrumentUID[4], "destination");
}
});
document.getElementById('resumeAudioContext').addEventListener('click', () => {
intermix.resumeAudioContext();
console.log("Audio Context Resumed");
});
document.getElementById("getstate").addEventListener('click', () => {
console.log(intermix.getState());
})
function wireInstrument(sourceID, targetID) {
const outNode = [sourceID, 0];
const inNode = [targetID, 0];
intermix.connectPlugins(outNode, inNode);
}
</script>
</html>