SlideShare a Scribd company logo
1 of 47
Download to read offline
Sequencing Audio Using
React and the Web Audio API
Roland TR-808
@vincentriemer
Roland TR-808
Lets Build a Simple Drum
Machine
function trigger(context, deadline) {
const oscillator = context.createOscillator();
const amplifier = context.createGain();
oscillator.frequency.setValueAtTime(200, deadline);
amplifier.gain.setValueAtTime(0, deadline)
oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15);
amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02);
amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2);
oscillator.connect(amplifier);
amplifier.connect(context.destination);
oscillator.start(deadline);
setTimeout(() => {
amplifier.disconnect();
}, 3000);
}
componentDidMount() {
this.context = new AudioContext();
this.clock = new waaclock(this.context);
}
handleTick({ deadline }) {
const { currentStep, steps } = this.state;
const newCurrentStep = currentStep + 1;
if (steps[newCurrentStep % steps.length]) {
trigger(this.context, deadline);
}
this.setState({ currentStep: newCurrentStep });
}
handlePlayPress() {
if (this.state.playing) {
// stop
this.setState(
{ playing: false },
() => {
this.clock.stop();
this.tickEvent = null;
}
);
} else {
//play
this.setState({
currentStep: -1,
playing: true
}, () => {
this.clock.start();
this.tickEvent = this.clock.callbackAtTime(
this.handleTick.bind(this),
this.context.currentTime
).repeat(0.47);
});
}
}
render() {
const { currentStep, playing, steps } = this.state;
return (
<div className="sequencer-wrapper">
<div className="step-display">
{`Current Step: ${currentStep % steps.length}`}
</div>
<button
className="play-button"
onClick={() => this.handlePlayPress()}
>
{playing ? 'Stop' : 'Play'}
</button>
</div>
);
}
}
ReactDOM.render(
<DrumMachine />,
document.getElementById(‘root')
);
class DrumMachine extends React.Component {
Lets Start With State
• Pattern
• Current step in the pattern
• Is the drum machine currently
playing
function trigger(context, deadline) {
const oscillator = context.createOscillator();
const amplifier = context.createGain();
oscillator.frequency.setValueAtTime(200, deadline);
amplifier.gain.setValueAtTime(0, deadline)
oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15);
amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02);
amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2);
oscillator.connect(amplifier);
amplifier.connect(context.destination);
oscillator.start(deadline);
setTimeout(() => {
amplifier.disconnect();
}, 3000);
}
componentDidMount() {
this.context = new AudioContext();
this.clock = new waaclock(this.context);
}
handleTick({ deadline }) {
const { currentStep, steps } = this.state;
const newCurrentStep = currentStep + 1;
if (steps[newCurrentStep % steps.length]) {
trigger(this.context, deadline);
}
this.setState({ currentStep: newCurrentStep });
}
handlePlayPress() {
if (this.state.playing) {
// stop
this.setState(
{ playing: false },
() => {
this.clock.stop();
this.tickEvent = null;
}
);
} else {
//play
this.setState({
currentStep: -1,
playing: true
}, () => {
this.clock.start();
this.tickEvent = this.clock.callbackAtTime(
this.handleTick.bind(this),
this.context.currentTime
).repeat(0.47);
});
}
}
render() {
const { currentStep, playing, steps } = this.state;
return (
<div className="sequencer-wrapper">
<div className="step-display">
{`Current Step: ${currentStep % steps.length}`}
</div>
<button
className="play-button"
onClick={() => this.handlePlayPress()}
>
{playing ? 'Stop' : 'Play'}
</button>
</div>
);
}
class DrumMachine extends React.Component {
}
ReactDOM.render(
<DrumMachine />,
document.getElementById(‘root')
);
constructor(props) {
super(props);
this.state = {
steps: [0, 0, 0, 0],
currentStep: 0,
playing: false
};
}
Lets Start With State
• Pattern
• Current step in the pattern
• Is the drum machine currently
playing
}
ReactDOM.render(
currentStep: 0,
playing: false
};
} Rendering the State
render() {
const { currentStep, playing, steps } = this.state;
return (
<div className="sequencer-wrapper">
<div className="step-display">
{`Current Step: ${currentStep % steps.length}`}
</div>
<button
className="play-button"
onClick={() => this.handlePlayPress()}
>
{playing ? 'Stop' : 'Play'}
</button>
</div>
);
}
function trigger(context, deadline) {
const oscillator = context.createOscillator();
const amplifier = context.createGain();
oscillator.frequency.setValueAtTime(200, deadline);
amplifier.gain.setValueAtTime(0, deadline)
oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15);
amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02);
amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2);
oscillator.connect(amplifier);
amplifier.connect(context.destination);
oscillator.start(deadline);
setTimeout(() => {
amplifier.disconnect();
}, 3000);
}
componentDidMount() {
this.context = new AudioContext();
this.clock = new waaclock(this.context);
}
handleTick({ deadline }) {
const { currentStep, steps } = this.state;
const newCurrentStep = currentStep + 1;
if (steps[newCurrentStep % steps.length]) {
trigger(this.context, deadline);
}
this.setState({ currentStep: newCurrentStep });
}
handlePlayPress() {
if (this.state.playing) {
// stop
this.setState(
{ playing: false },
() => {
this.clock.stop();
this.tickEvent = null;
}
);
} else {
//play
this.setState({
currentStep: -1,
playing: true
}, () => {
this.clock.start();
this.tickEvent = this.clock.callbackAtTime(
this.handleTick.bind(this),
this.context.currentTime
).repeat(0.47);
});
}
}
}
ReactDOM.render(
currentStep: 0,
playing: false
};
}
render() {
const { currentStep, playing, steps } = this.state;
return (
<div className="sequencer-wrapper">
<div className="step-display">
{`Current Step: ${currentStep % steps.length}`}
</div>
<button
className="play-button"
onClick={() => this.handlePlayPress()}
>
{playing ? 'Stop' : 'Play'}
</button>
</div>
);
}
Rendering the State
function trigger(context, deadline) {
const oscillator = context.createOscillator();
const amplifier = context.createGain();
oscillator.frequency.setValueAtTime(200, deadline);
amplifier.gain.setValueAtTime(0, deadline)
oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15);
amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02);
amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2);
oscillator.connect(amplifier);
amplifier.connect(context.destination);
oscillator.start(deadline);
setTimeout(() => {
amplifier.disconnect();
}, 3000);
}
componentDidMount() {
this.context = new AudioContext();
this.clock = new waaclock(this.context);
}
handleTick({ deadline }) {
const { currentStep, steps } = this.state;
const newCurrentStep = currentStep + 1;
if (steps[newCurrentStep % steps.length]) {
trigger(this.context, deadline);
}
this.setState({ currentStep: newCurrentStep });
}
handlePlayPress() {
if (this.state.playing) {
// stop
this.setState(
{ playing: false },
() => {
this.clock.stop();
this.tickEvent = null;
}
);
} else {
//play
this.setState({
currentStep: -1,
playing: true
}, () => {
this.clock.start();
this.tickEvent = this.clock.callbackAtTime(
this.handleTick.bind(this),
this.context.currentTime
).repeat(0.47);
});
}
}
}
ReactDOM.render(
currentStep: 0,
playing: false
};
}
render() {
const { currentStep, playing, steps } = this.state;
return (
<div className="sequencer-wrapper">
<div className="step-display">
{`Current Step: ${currentStep % steps.length}`}
</div>
<button
className="play-button"
onClick={() => this.handlePlayPress()}
>
{playing ? 'Stop' : 'Play'}
</button>
</div>
);
}
Rendering the State
Audio Timing in the Web
…or why setInterval isn’t enough
setInterval AudioContext
• Inaccurate
• Gets locked up by other JS
on the main thread
• Extremely accurate
• Runs on dedicated audio
thread
• Can only schedule audio-
related events ahead of
time
Timing Options
Audio Events
Audio Events
Scheduling
Audio Events
Scheduling
Audio Events
Scheduling
WAAClock
https://github.com/sebpiq/WAAClock
Tale of Two Clocks
https://www.html5rocks.com/en/tutorials/audio/scheduling/
render() {
const { currentStep, playing, steps } = this.state;
return (
<div className="sequencer-wrapper">
<div className="step-display">
{`Current Step: ${currentStep % steps.length}`}
</div>
<button
className="play-button"
class DrumMachine extends React.Component {
constructor(props) {
super(props);
this.state = {
steps: [0, 0, 0, 0, 0, 0, 0, 0],
currentStep: 0,
playing: false
};
}
componentDidMount() {
this.context = new window.AudioContext();
this.clock = new WAAClock(this.context);
}
Web Audio Initialization
constructor(props) {
super(props);
this.state = {
steps: [0, 0, 0, 0, 0, 0, 0, 0],
currentStep: 0,
playing: false
};
}
Starting/Stopping the
Clock
} else {
}
}
handlePlayPress() {
if (!this.state.playing) {
render() {
const { currentStep, playing, steps } = this.state;
componentDidMount() {
this.context = new window.AudioContext();
this.clock = new WAAClock(this.context);
}
Starting the Clock
this.setState({
currentStep: -1,
playing: true
}, () => {
this.clock.start();
this.tickEvent = this.clock.callbackAtTime(
this.handleTick.bind(this),
this.context.currentTime
).repeat(0.47);
});
} else {
}
}
handlePlayPress() {
if (!this.state.playing) {
steps: [0, 0, 0, 0, 0, 0, 0, 0],
currentStep: 0,
playing: false
};
}
componentDidMount() {
this.context = new window.AudioContext();
function trigger(context, deadline) {
const oscillator = context.createOscillator();
const amplifier = context.createGain();
oscillator.frequency.setValueAtTime(200, deadline);
amplifier.gain.setValueAtTime(0, deadline)
oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15);
amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02);
amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2);
oscillator.connect(amplifier);
amplifier.connect(context.destination);
oscillator.start(deadline);
setTimeout(() => {
amplifier.disconnect();
}, 3000);
}
handleTick({ deadline }) {
const { currentStep, steps } = this.state;
const newCurrentStep = currentStep + 1;
if (steps[newCurrentStep % steps.length]) {
trigger(this.context, deadline);
}
this.setState({ currentStep: newCurrentStep });
}
Starting the Clock
} else {
}
}
this.setState(
{ playing: false },
() => {
this.clock.stop();
this.tickEvent = null;
}
);
handlePlayPress() {
if (!this.state.playing) {
steps: [0, 0, 0, 0, 0, 0, 0, 0],
currentStep: 0,
playing: false
};
}
componentDidMount() {
this.context = new window.AudioContext();
Start the clock
this.setState({
currentStep: -1,
playing: true
}, () => {
this.clock.start();
this.tickEvent = this.clock.callbackAtTime(
this.handleTick.bind(this),
this.context.currentTime
).repeat(0.47);
});
function trigger(context, deadline) {
const oscillator = context.createOscillator();
const amplifier = context.createGain();
oscillator.frequency.setValueAtTime(200, deadline);
amplifier.gain.setValueAtTime(0, deadline)
oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15);
amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02);
amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2);
oscillator.connect(amplifier);
amplifier.connect(context.destination);
oscillator.start(deadline);
setTimeout(() => {
amplifier.disconnect();
}, 3000);
}
handleTick({ deadline }) {
const { currentStep, steps } = this.state;
const newCurrentStep = currentStep + 1;
if (steps[newCurrentStep % steps.length]) {
trigger(this.context, deadline);
}
this.setState({ currentStep: newCurrentStep });
}
Starting the Clock
} else {
}
}
this.setState(
{ playing: false },
() => {
this.clock.stop();
this.tickEvent = null;
}
);
handlePlayPress() {
if (!this.state.playing) {
steps: [0, 0, 0, 0, 0, 0, 0, 0],
currentStep: 0,
playing: false
};
}
componentDidMount() {
this.context = new window.AudioContext();
Execute handleTick
callback immediately
this.setState({
currentStep: -1,
playing: true
}, () => {
this.clock.start();
this.tickEvent = this.clock.callbackAtTime(
this.handleTick.bind(this),
this.context.currentTime
).repeat(0.47);
});
function trigger(context, deadline) {
const oscillator = context.createOscillator();
const amplifier = context.createGain();
oscillator.frequency.setValueAtTime(200, deadline);
amplifier.gain.setValueAtTime(0, deadline)
oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15);
amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02);
amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2);
oscillator.connect(amplifier);
amplifier.connect(context.destination);
oscillator.start(deadline);
setTimeout(() => {
amplifier.disconnect();
}, 3000);
}
handleTick({ deadline }) {
const { currentStep, steps } = this.state;
const newCurrentStep = currentStep + 1;
if (steps[newCurrentStep % steps.length]) {
trigger(this.context, deadline);
}
this.setState({ currentStep: newCurrentStep });
}
Starting the Clock
} else {
}
}
this.setState(
{ playing: false },
() => {
this.clock.stop();
this.tickEvent = null;
}
);
handlePlayPress() {
if (!this.state.playing) {
steps: [0, 0, 0, 0, 0, 0, 0, 0],
currentStep: 0,
playing: false
};
}
componentDidMount() {
this.context = new window.AudioContext();
Repeat callback at a
given interval
(~128bpm)
this.setState({
currentStep: -1,
playing: true
}, () => {
this.clock.start();
this.tickEvent = this.clock.callbackAtTime(
this.handleTick.bind(this),
this.context.currentTime
).repeat(0.47);
});
function trigger(context, deadline) {
const oscillator = context.createOscillator();
const amplifier = context.createGain();
oscillator.frequency.setValueAtTime(200, deadline);
amplifier.gain.setValueAtTime(0, deadline)
oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15);
amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02);
amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2);
oscillator.connect(amplifier);
amplifier.connect(context.destination);
oscillator.start(deadline);
setTimeout(() => {
amplifier.disconnect();
}, 3000);
}
handleTick({ deadline }) {
const { currentStep, steps } = this.state;
const newCurrentStep = currentStep + 1;
if (steps[newCurrentStep % steps.length]) {
trigger(this.context, deadline);
}
this.setState({ currentStep: newCurrentStep });
}
Stopping the Clock
}, () => {
this.clock.start();
this.tickEvent = this.clock.callbackAtTime(
this.handleTick.bind(this),
this.context.currentTime
).repeat(0.47);
});
} else {
}
}
componentDidMount() {
this.context = new window.AudioContext();
this.clock = new WAAClock(this.context);
}
this.setState(
{ playing: false },
() => {
this.clock.stop();
this.tickEvent.clear();
this.tickEvent = null;
});
function trigger(context, deadline) {
const oscillator = context.createOscillator();
const amplifier = context.createGain();
oscillator.frequency.setValueAtTime(200, deadline);
amplifier.gain.setValueAtTime(0, deadline)
oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15);
amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02);
amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2);
oscillator.connect(amplifier);
amplifier.connect(context.destination);
oscillator.start(deadline);
setTimeout(() => {
amplifier.disconnect();
}, 3000);
}
handleTick({ deadline }) {
const { currentStep, steps } = this.state;
const newCurrentStep = currentStep + 1;
if (steps[newCurrentStep % steps.length]) {
this.triggerSound(this.context, deadline);
}
this.setState({ currentStep: newCurrentStep });
}
Clock Tick Handler
this.clock.stop();
this.tickEvent.clear();
this.tickEvent = null;
});
}
}
render() {
const { currentStep, playing, steps } = this.state;
componentDidMount() {
this.context = new window.AudioContext();
this.clock = new WAAClock(this.context);
}
function trigger(context, deadline) {
const oscillator = context.createOscillator();
const amplifier = context.createGain();
oscillator.frequency.setValueAtTime(200, deadline);
amplifier.gain.setValueAtTime(0, deadline)
oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15);
amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02);
amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2);
oscillator.connect(amplifier);
amplifier.connect(context.destination);
oscillator.start(deadline);
setTimeout(() => {
amplifier.disconnect();
}, 3000);
}
handleTick({ deadline }) {
const { currentStep, steps } = this.state;
const newCurrentStep = currentStep + 1;
if (steps[newCurrentStep % steps.length]) {
this.triggerSound(this.context, deadline);
}
this.setState({ currentStep: newCurrentStep });
}
Clock Tick Handler
this.clock.stop();
this.tickEvent.clear();
this.tickEvent = null;
});
}
}
render() {
const { currentStep, playing, steps } = this.state;
componentDidMount() {
this.context = new window.AudioContext();
this.clock = new WAAClock(this.context);
}
function trigger(context, deadline) {
const oscillator = context.createOscillator();
const amplifier = context.createGain();
oscillator.frequency.setValueAtTime(200, deadline);
amplifier.gain.setValueAtTime(0, deadline)
oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15);
amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02);
amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2);
oscillator.connect(amplifier);
amplifier.connect(context.destination);
oscillator.start(deadline);
setTimeout(() => {
amplifier.disconnect();
}, 3000);
}
handleTick({ deadline }) {
const { currentStep, steps } = this.state;
const newCurrentStep = currentStep + 1;
if (steps[newCurrentStep % steps.length]) {
this.triggerSound(this.context, deadline);
}
this.setState({ currentStep: newCurrentStep });
}
Clock Tick Handler
this.clock.stop();
this.tickEvent.clear();
this.tickEvent = null;
});
}
}
render() {
const { currentStep, playing, steps } = this.state;
componentDidMount() {
this.context = new window.AudioContext();
this.clock = new WAAClock(this.context);
}
Basic Bass Drum Synthesis
Bass Drum Structure
Oscillator
Ampli!er
Pitch
Modulation
Gain
Modulation
Oscillator
Oscillator
const oscillator = context.createOscillator();
oscillator.frequency.setValueAtTime(200, deadline);
oscillator.start(deadline);
Pitch Modulation
Pitch Modulation
oscillator.frequency.linearRampToValueAtTime(
50, // target frequency (50Hz)
deadline + 0.15 // time of ramp (150ms)
);
Ampli!er
Ampli!er
const amplifier = context.createGain();
oscillator.connect(amplifier);
amplifier.gain.setValueAtTime(0, deadline);
Gain Modulation
Gain Modulation
amplifier.gain.linearRampToValueAtTime(
1.0, // target gain
deadline + 0.02 // time of ramp (20ms)
);
amplifier.gain.linearRampToValueAtTime(
0.0,
deadline + 0.20
);
Sending to Speakers
amplifier.connect(context.destination);
Cleanup
setTimeout(() => {
amplifier.disconnect();
}, 3000);
Demo
• Move React state to Redux
• Use seamless-immutable for Redux store
• Make heavy use of reselect for computed/derived state
• 16 steps per pattern
• 16 patterns
• 2 Parts of each pattern
• 2 Variations of each part
• 12 instruments/sounds
• 12,288 total steps!
Complex Problems
Simple Problems
Twitter: @vincentriemer
Site: vincentriemer.com
Demo: codepen.io/vincentriemer/pen/BpbXMo
iO-808: io808.com

More Related Content

What's hot

Dive into kotlins coroutines
Dive into kotlins coroutinesDive into kotlins coroutines
Dive into kotlins coroutinesFreddie Wang
 
Games, AI, and Research - Part 2 Training (FightingICE AI Programming)
Games, AI, and Research - Part 2 Training (FightingICE AI Programming)Games, AI, and Research - Part 2 Training (FightingICE AI Programming)
Games, AI, and Research - Part 2 Training (FightingICE AI Programming)Pujana Paliyawan
 
The Ring programming language version 1.7 book - Part 64 of 196
The Ring programming language version 1.7 book - Part 64 of 196The Ring programming language version 1.7 book - Part 64 of 196
The Ring programming language version 1.7 book - Part 64 of 196Mahmoud Samir Fayed
 
The Ring programming language version 1.5.2 book - Part 48 of 181
The Ring programming language version 1.5.2 book - Part 48 of 181The Ring programming language version 1.5.2 book - Part 48 of 181
The Ring programming language version 1.5.2 book - Part 48 of 181Mahmoud Samir Fayed
 
Semantics-Aware Trace Analysis [PLDI 2009]
Semantics-Aware Trace Analysis [PLDI 2009]Semantics-Aware Trace Analysis [PLDI 2009]
Semantics-Aware Trace Analysis [PLDI 2009]Kevin Hoffman
 
생산적인 개발을 위한 지속적인 테스트
생산적인 개발을 위한 지속적인 테스트생산적인 개발을 위한 지속적인 테스트
생산적인 개발을 위한 지속적인 테스트기룡 남
 
Kotlin - Coroutine
Kotlin - CoroutineKotlin - Coroutine
Kotlin - CoroutineSean Tsai
 
TDC2018SP | Trilha Kotlin - Programacao assincrona utilizando Coroutines
TDC2018SP | Trilha Kotlin - Programacao assincrona utilizando CoroutinesTDC2018SP | Trilha Kotlin - Programacao assincrona utilizando Coroutines
TDC2018SP | Trilha Kotlin - Programacao assincrona utilizando Coroutinestdc-globalcode
 
Programação assíncrona utilizando Coroutines
Programação assíncrona utilizando CoroutinesProgramação assíncrona utilizando Coroutines
Programação assíncrona utilizando CoroutinesDiego Gonçalves Santos
 
Pokemon battle simulator (Java Program written on Blue J Editor)
Pokemon battle simulator (Java Program written on Blue J Editor)Pokemon battle simulator (Java Program written on Blue J Editor)
Pokemon battle simulator (Java Program written on Blue J Editor)Mahir Bathija
 
The Ring programming language version 1.6 book - Part 51 of 189
The Ring programming language version 1.6 book - Part 51 of 189The Ring programming language version 1.6 book - Part 51 of 189
The Ring programming language version 1.6 book - Part 51 of 189Mahmoud Samir Fayed
 
The Ring programming language version 1.10 book - Part 61 of 212
The Ring programming language version 1.10 book - Part 61 of 212The Ring programming language version 1.10 book - Part 61 of 212
The Ring programming language version 1.10 book - Part 61 of 212Mahmoud Samir Fayed
 
The Ring programming language version 1.5.1 book - Part 65 of 180
The Ring programming language version 1.5.1 book - Part 65 of 180The Ring programming language version 1.5.1 book - Part 65 of 180
The Ring programming language version 1.5.1 book - Part 65 of 180Mahmoud Samir Fayed
 
Unittesting JavaScript with Evidence
Unittesting JavaScript with EvidenceUnittesting JavaScript with Evidence
Unittesting JavaScript with EvidenceTobie Langel
 
The Ring programming language version 1.10 book - Part 71 of 212
The Ring programming language version 1.10 book - Part 71 of 212The Ring programming language version 1.10 book - Part 71 of 212
The Ring programming language version 1.10 book - Part 71 of 212Mahmoud Samir Fayed
 

What's hot (20)

Dive into kotlins coroutines
Dive into kotlins coroutinesDive into kotlins coroutines
Dive into kotlins coroutines
 
Advanced SQL Queries
Advanced SQL QueriesAdvanced SQL Queries
Advanced SQL Queries
 
Games, AI, and Research - Part 2 Training (FightingICE AI Programming)
Games, AI, and Research - Part 2 Training (FightingICE AI Programming)Games, AI, and Research - Part 2 Training (FightingICE AI Programming)
Games, AI, and Research - Part 2 Training (FightingICE AI Programming)
 
The Ring programming language version 1.7 book - Part 64 of 196
The Ring programming language version 1.7 book - Part 64 of 196The Ring programming language version 1.7 book - Part 64 of 196
The Ring programming language version 1.7 book - Part 64 of 196
 
The Ring programming language version 1.5.2 book - Part 48 of 181
The Ring programming language version 1.5.2 book - Part 48 of 181The Ring programming language version 1.5.2 book - Part 48 of 181
The Ring programming language version 1.5.2 book - Part 48 of 181
 
Semantics-Aware Trace Analysis [PLDI 2009]
Semantics-Aware Trace Analysis [PLDI 2009]Semantics-Aware Trace Analysis [PLDI 2009]
Semantics-Aware Trace Analysis [PLDI 2009]
 
생산적인 개발을 위한 지속적인 테스트
생산적인 개발을 위한 지속적인 테스트생산적인 개발을 위한 지속적인 테스트
생산적인 개발을 위한 지속적인 테스트
 
Ak13 pam
Ak13 pamAk13 pam
Ak13 pam
 
Pdxpugday2010 pg90
Pdxpugday2010 pg90Pdxpugday2010 pg90
Pdxpugday2010 pg90
 
Kotlin - Coroutine
Kotlin - CoroutineKotlin - Coroutine
Kotlin - Coroutine
 
TDC2018SP | Trilha Kotlin - Programacao assincrona utilizando Coroutines
TDC2018SP | Trilha Kotlin - Programacao assincrona utilizando CoroutinesTDC2018SP | Trilha Kotlin - Programacao assincrona utilizando Coroutines
TDC2018SP | Trilha Kotlin - Programacao assincrona utilizando Coroutines
 
Hadoop on aws amazon
Hadoop on aws amazonHadoop on aws amazon
Hadoop on aws amazon
 
Dmxedit
DmxeditDmxedit
Dmxedit
 
Programação assíncrona utilizando Coroutines
Programação assíncrona utilizando CoroutinesProgramação assíncrona utilizando Coroutines
Programação assíncrona utilizando Coroutines
 
Pokemon battle simulator (Java Program written on Blue J Editor)
Pokemon battle simulator (Java Program written on Blue J Editor)Pokemon battle simulator (Java Program written on Blue J Editor)
Pokemon battle simulator (Java Program written on Blue J Editor)
 
The Ring programming language version 1.6 book - Part 51 of 189
The Ring programming language version 1.6 book - Part 51 of 189The Ring programming language version 1.6 book - Part 51 of 189
The Ring programming language version 1.6 book - Part 51 of 189
 
The Ring programming language version 1.10 book - Part 61 of 212
The Ring programming language version 1.10 book - Part 61 of 212The Ring programming language version 1.10 book - Part 61 of 212
The Ring programming language version 1.10 book - Part 61 of 212
 
The Ring programming language version 1.5.1 book - Part 65 of 180
The Ring programming language version 1.5.1 book - Part 65 of 180The Ring programming language version 1.5.1 book - Part 65 of 180
The Ring programming language version 1.5.1 book - Part 65 of 180
 
Unittesting JavaScript with Evidence
Unittesting JavaScript with EvidenceUnittesting JavaScript with Evidence
Unittesting JavaScript with Evidence
 
The Ring programming language version 1.10 book - Part 71 of 212
The Ring programming language version 1.10 book - Part 71 of 212The Ring programming language version 1.10 book - Part 71 of 212
The Ring programming language version 1.10 book - Part 71 of 212
 

Similar to Sequencing Audio Using React and the Web Audio API

Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...
Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...
Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...Codemotion
 
Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019
Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019
Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019Codemotion
 
ES6 patterns in the wild
ES6 patterns in the wildES6 patterns in the wild
ES6 patterns in the wildJoe Morgan
 
code for quiz in my sql
code for quiz  in my sql code for quiz  in my sql
code for quiz in my sql JOYITAKUNDU1
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSAdam L Barrett
 
Powering code reuse with context and render props
Powering code reuse with context and render propsPowering code reuse with context and render props
Powering code reuse with context and render propsForbes Lindesay
 
Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016Codemotion
 
Job Queue in Golang
Job Queue in GolangJob Queue in Golang
Job Queue in GolangBo-Yi Wu
 
Greach, GroovyFx Workshop
Greach, GroovyFx WorkshopGreach, GroovyFx Workshop
Greach, GroovyFx WorkshopDierk König
 
Advanced Akka For Architects
Advanced Akka For ArchitectsAdvanced Akka For Architects
Advanced Akka For ArchitectsLightbend
 
Throttle and Debounce Patterns in Web Apps
Throttle and Debounce Patterns in Web AppsThrottle and Debounce Patterns in Web Apps
Throttle and Debounce Patterns in Web AppsAlmir Filho
 
React new features and intro to Hooks
React new features and intro to HooksReact new features and intro to Hooks
React new features and intro to HooksSoluto
 
JavaFX 2.0 With Alternative Languages - Groovy, Clojure, Scala, Fantom, and V...
JavaFX 2.0 With Alternative Languages - Groovy, Clojure, Scala, Fantom, and V...JavaFX 2.0 With Alternative Languages - Groovy, Clojure, Scala, Fantom, and V...
JavaFX 2.0 With Alternative Languages - Groovy, Clojure, Scala, Fantom, and V...Stephen Chin
 
The Ring programming language version 1.10 book - Part 70 of 212
The Ring programming language version 1.10 book - Part 70 of 212The Ring programming language version 1.10 book - Part 70 of 212
The Ring programming language version 1.10 book - Part 70 of 212Mahmoud Samir Fayed
 
Better react/redux apps using redux-saga
Better react/redux apps using redux-sagaBetter react/redux apps using redux-saga
Better react/redux apps using redux-sagaYounes (omar) Meliani
 
Process control nodejs
Process control nodejsProcess control nodejs
Process control nodejsLearningTech
 
JAVA - please help with the error regarding the bold line below- I am.docx
JAVA - please help with the error regarding the bold line below- I am.docxJAVA - please help with the error regarding the bold line below- I am.docx
JAVA - please help with the error regarding the bold line below- I am.docxBenjaminIjsDaviesq
 

Similar to Sequencing Audio Using React and the Web Audio API (20)

Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...
Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...
Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...
 
Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019
Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019
Matteo Antony Mistretta - Refactoring into React hooks - Codemotion Rome 2019
 
ES6 patterns in the wild
ES6 patterns in the wildES6 patterns in the wild
ES6 patterns in the wild
 
code for quiz in my sql
code for quiz  in my sql code for quiz  in my sql
code for quiz in my sql
 
Angular2 rxjs
Angular2 rxjsAngular2 rxjs
Angular2 rxjs
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJS
 
Powering code reuse with context and render props
Powering code reuse with context and render propsPowering code reuse with context and render props
Powering code reuse with context and render props
 
Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016
 
ReactJS
ReactJSReactJS
ReactJS
 
Job Queue in Golang
Job Queue in GolangJob Queue in Golang
Job Queue in Golang
 
Greach, GroovyFx Workshop
Greach, GroovyFx WorkshopGreach, GroovyFx Workshop
Greach, GroovyFx Workshop
 
Advanced Akka For Architects
Advanced Akka For ArchitectsAdvanced Akka For Architects
Advanced Akka For Architects
 
Throttle and Debounce Patterns in Web Apps
Throttle and Debounce Patterns in Web AppsThrottle and Debounce Patterns in Web Apps
Throttle and Debounce Patterns in Web Apps
 
React new features and intro to Hooks
React new features and intro to HooksReact new features and intro to Hooks
React new features and intro to Hooks
 
Road to react hooks
Road to react hooksRoad to react hooks
Road to react hooks
 
JavaFX 2.0 With Alternative Languages - Groovy, Clojure, Scala, Fantom, and V...
JavaFX 2.0 With Alternative Languages - Groovy, Clojure, Scala, Fantom, and V...JavaFX 2.0 With Alternative Languages - Groovy, Clojure, Scala, Fantom, and V...
JavaFX 2.0 With Alternative Languages - Groovy, Clojure, Scala, Fantom, and V...
 
The Ring programming language version 1.10 book - Part 70 of 212
The Ring programming language version 1.10 book - Part 70 of 212The Ring programming language version 1.10 book - Part 70 of 212
The Ring programming language version 1.10 book - Part 70 of 212
 
Better react/redux apps using redux-saga
Better react/redux apps using redux-sagaBetter react/redux apps using redux-saga
Better react/redux apps using redux-saga
 
Process control nodejs
Process control nodejsProcess control nodejs
Process control nodejs
 
JAVA - please help with the error regarding the bold line below- I am.docx
JAVA - please help with the error regarding the bold line below- I am.docxJAVA - please help with the error regarding the bold line below- I am.docx
JAVA - please help with the error regarding the bold line below- I am.docx
 

Recently uploaded

A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)OPEN KNOWLEDGE GmbH
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationkaushalgiri8080
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...soniya singh
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number SystemsJheuzeDellosa
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
Engage Usergroup 2024 - The Good The Bad_The Ugly
Engage Usergroup 2024 - The Good The Bad_The UglyEngage Usergroup 2024 - The Good The Bad_The Ugly
Engage Usergroup 2024 - The Good The Bad_The UglyFrank van der Linden
 
Introduction to Decentralized Applications (dApps)
Introduction to Decentralized Applications (dApps)Introduction to Decentralized Applications (dApps)
Introduction to Decentralized Applications (dApps)Intelisync
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataBradBedford3
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 
Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - InfographicHr365.us smith
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio, Inc.
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...Christina Lin
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 

Recently uploaded (20)

A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanation
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number Systems
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
Engage Usergroup 2024 - The Good The Bad_The Ugly
Engage Usergroup 2024 - The Good The Bad_The UglyEngage Usergroup 2024 - The Good The Bad_The Ugly
Engage Usergroup 2024 - The Good The Bad_The Ugly
 
Introduction to Decentralized Applications (dApps)
Introduction to Decentralized Applications (dApps)Introduction to Decentralized Applications (dApps)
Introduction to Decentralized Applications (dApps)
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 
Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - Infographic
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 

Sequencing Audio Using React and the Web Audio API

  • 1. Sequencing Audio Using React and the Web Audio API Roland TR-808 @vincentriemer
  • 3.
  • 4.
  • 5. Lets Build a Simple Drum Machine
  • 6.
  • 7. function trigger(context, deadline) { const oscillator = context.createOscillator(); const amplifier = context.createGain(); oscillator.frequency.setValueAtTime(200, deadline); amplifier.gain.setValueAtTime(0, deadline) oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15); amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02); amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2); oscillator.connect(amplifier); amplifier.connect(context.destination); oscillator.start(deadline); setTimeout(() => { amplifier.disconnect(); }, 3000); } componentDidMount() { this.context = new AudioContext(); this.clock = new waaclock(this.context); } handleTick({ deadline }) { const { currentStep, steps } = this.state; const newCurrentStep = currentStep + 1; if (steps[newCurrentStep % steps.length]) { trigger(this.context, deadline); } this.setState({ currentStep: newCurrentStep }); } handlePlayPress() { if (this.state.playing) { // stop this.setState( { playing: false }, () => { this.clock.stop(); this.tickEvent = null; } ); } else { //play this.setState({ currentStep: -1, playing: true }, () => { this.clock.start(); this.tickEvent = this.clock.callbackAtTime( this.handleTick.bind(this), this.context.currentTime ).repeat(0.47); }); } } render() { const { currentStep, playing, steps } = this.state; return ( <div className="sequencer-wrapper"> <div className="step-display"> {`Current Step: ${currentStep % steps.length}`} </div> <button className="play-button" onClick={() => this.handlePlayPress()} > {playing ? 'Stop' : 'Play'} </button> </div> ); } } ReactDOM.render( <DrumMachine />, document.getElementById(‘root') ); class DrumMachine extends React.Component { Lets Start With State • Pattern • Current step in the pattern • Is the drum machine currently playing
  • 8. function trigger(context, deadline) { const oscillator = context.createOscillator(); const amplifier = context.createGain(); oscillator.frequency.setValueAtTime(200, deadline); amplifier.gain.setValueAtTime(0, deadline) oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15); amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02); amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2); oscillator.connect(amplifier); amplifier.connect(context.destination); oscillator.start(deadline); setTimeout(() => { amplifier.disconnect(); }, 3000); } componentDidMount() { this.context = new AudioContext(); this.clock = new waaclock(this.context); } handleTick({ deadline }) { const { currentStep, steps } = this.state; const newCurrentStep = currentStep + 1; if (steps[newCurrentStep % steps.length]) { trigger(this.context, deadline); } this.setState({ currentStep: newCurrentStep }); } handlePlayPress() { if (this.state.playing) { // stop this.setState( { playing: false }, () => { this.clock.stop(); this.tickEvent = null; } ); } else { //play this.setState({ currentStep: -1, playing: true }, () => { this.clock.start(); this.tickEvent = this.clock.callbackAtTime( this.handleTick.bind(this), this.context.currentTime ).repeat(0.47); }); } } render() { const { currentStep, playing, steps } = this.state; return ( <div className="sequencer-wrapper"> <div className="step-display"> {`Current Step: ${currentStep % steps.length}`} </div> <button className="play-button" onClick={() => this.handlePlayPress()} > {playing ? 'Stop' : 'Play'} </button> </div> ); } class DrumMachine extends React.Component { } ReactDOM.render( <DrumMachine />, document.getElementById(‘root') ); constructor(props) { super(props); this.state = { steps: [0, 0, 0, 0], currentStep: 0, playing: false }; } Lets Start With State • Pattern • Current step in the pattern • Is the drum machine currently playing
  • 9. } ReactDOM.render( currentStep: 0, playing: false }; } Rendering the State render() { const { currentStep, playing, steps } = this.state; return ( <div className="sequencer-wrapper"> <div className="step-display"> {`Current Step: ${currentStep % steps.length}`} </div> <button className="play-button" onClick={() => this.handlePlayPress()} > {playing ? 'Stop' : 'Play'} </button> </div> ); }
  • 10. function trigger(context, deadline) { const oscillator = context.createOscillator(); const amplifier = context.createGain(); oscillator.frequency.setValueAtTime(200, deadline); amplifier.gain.setValueAtTime(0, deadline) oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15); amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02); amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2); oscillator.connect(amplifier); amplifier.connect(context.destination); oscillator.start(deadline); setTimeout(() => { amplifier.disconnect(); }, 3000); } componentDidMount() { this.context = new AudioContext(); this.clock = new waaclock(this.context); } handleTick({ deadline }) { const { currentStep, steps } = this.state; const newCurrentStep = currentStep + 1; if (steps[newCurrentStep % steps.length]) { trigger(this.context, deadline); } this.setState({ currentStep: newCurrentStep }); } handlePlayPress() { if (this.state.playing) { // stop this.setState( { playing: false }, () => { this.clock.stop(); this.tickEvent = null; } ); } else { //play this.setState({ currentStep: -1, playing: true }, () => { this.clock.start(); this.tickEvent = this.clock.callbackAtTime( this.handleTick.bind(this), this.context.currentTime ).repeat(0.47); }); } } } ReactDOM.render( currentStep: 0, playing: false }; } render() { const { currentStep, playing, steps } = this.state; return ( <div className="sequencer-wrapper"> <div className="step-display"> {`Current Step: ${currentStep % steps.length}`} </div> <button className="play-button" onClick={() => this.handlePlayPress()} > {playing ? 'Stop' : 'Play'} </button> </div> ); } Rendering the State
  • 11. function trigger(context, deadline) { const oscillator = context.createOscillator(); const amplifier = context.createGain(); oscillator.frequency.setValueAtTime(200, deadline); amplifier.gain.setValueAtTime(0, deadline) oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15); amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02); amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2); oscillator.connect(amplifier); amplifier.connect(context.destination); oscillator.start(deadline); setTimeout(() => { amplifier.disconnect(); }, 3000); } componentDidMount() { this.context = new AudioContext(); this.clock = new waaclock(this.context); } handleTick({ deadline }) { const { currentStep, steps } = this.state; const newCurrentStep = currentStep + 1; if (steps[newCurrentStep % steps.length]) { trigger(this.context, deadline); } this.setState({ currentStep: newCurrentStep }); } handlePlayPress() { if (this.state.playing) { // stop this.setState( { playing: false }, () => { this.clock.stop(); this.tickEvent = null; } ); } else { //play this.setState({ currentStep: -1, playing: true }, () => { this.clock.start(); this.tickEvent = this.clock.callbackAtTime( this.handleTick.bind(this), this.context.currentTime ).repeat(0.47); }); } } } ReactDOM.render( currentStep: 0, playing: false }; } render() { const { currentStep, playing, steps } = this.state; return ( <div className="sequencer-wrapper"> <div className="step-display"> {`Current Step: ${currentStep % steps.length}`} </div> <button className="play-button" onClick={() => this.handlePlayPress()} > {playing ? 'Stop' : 'Play'} </button> </div> ); } Rendering the State
  • 12. Audio Timing in the Web …or why setInterval isn’t enough
  • 13. setInterval AudioContext • Inaccurate • Gets locked up by other JS on the main thread • Extremely accurate • Runs on dedicated audio thread • Can only schedule audio- related events ahead of time Timing Options
  • 17. Audio Events Scheduling WAAClock https://github.com/sebpiq/WAAClock Tale of Two Clocks https://www.html5rocks.com/en/tutorials/audio/scheduling/
  • 18. render() { const { currentStep, playing, steps } = this.state; return ( <div className="sequencer-wrapper"> <div className="step-display"> {`Current Step: ${currentStep % steps.length}`} </div> <button className="play-button" class DrumMachine extends React.Component { constructor(props) { super(props); this.state = { steps: [0, 0, 0, 0, 0, 0, 0, 0], currentStep: 0, playing: false }; } componentDidMount() { this.context = new window.AudioContext(); this.clock = new WAAClock(this.context); } Web Audio Initialization
  • 19. constructor(props) { super(props); this.state = { steps: [0, 0, 0, 0, 0, 0, 0, 0], currentStep: 0, playing: false }; } Starting/Stopping the Clock } else { } } handlePlayPress() { if (!this.state.playing) { render() { const { currentStep, playing, steps } = this.state; componentDidMount() { this.context = new window.AudioContext(); this.clock = new WAAClock(this.context); }
  • 20. Starting the Clock this.setState({ currentStep: -1, playing: true }, () => { this.clock.start(); this.tickEvent = this.clock.callbackAtTime( this.handleTick.bind(this), this.context.currentTime ).repeat(0.47); }); } else { } } handlePlayPress() { if (!this.state.playing) { steps: [0, 0, 0, 0, 0, 0, 0, 0], currentStep: 0, playing: false }; } componentDidMount() { this.context = new window.AudioContext();
  • 21. function trigger(context, deadline) { const oscillator = context.createOscillator(); const amplifier = context.createGain(); oscillator.frequency.setValueAtTime(200, deadline); amplifier.gain.setValueAtTime(0, deadline) oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15); amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02); amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2); oscillator.connect(amplifier); amplifier.connect(context.destination); oscillator.start(deadline); setTimeout(() => { amplifier.disconnect(); }, 3000); } handleTick({ deadline }) { const { currentStep, steps } = this.state; const newCurrentStep = currentStep + 1; if (steps[newCurrentStep % steps.length]) { trigger(this.context, deadline); } this.setState({ currentStep: newCurrentStep }); } Starting the Clock } else { } } this.setState( { playing: false }, () => { this.clock.stop(); this.tickEvent = null; } ); handlePlayPress() { if (!this.state.playing) { steps: [0, 0, 0, 0, 0, 0, 0, 0], currentStep: 0, playing: false }; } componentDidMount() { this.context = new window.AudioContext(); Start the clock this.setState({ currentStep: -1, playing: true }, () => { this.clock.start(); this.tickEvent = this.clock.callbackAtTime( this.handleTick.bind(this), this.context.currentTime ).repeat(0.47); });
  • 22. function trigger(context, deadline) { const oscillator = context.createOscillator(); const amplifier = context.createGain(); oscillator.frequency.setValueAtTime(200, deadline); amplifier.gain.setValueAtTime(0, deadline) oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15); amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02); amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2); oscillator.connect(amplifier); amplifier.connect(context.destination); oscillator.start(deadline); setTimeout(() => { amplifier.disconnect(); }, 3000); } handleTick({ deadline }) { const { currentStep, steps } = this.state; const newCurrentStep = currentStep + 1; if (steps[newCurrentStep % steps.length]) { trigger(this.context, deadline); } this.setState({ currentStep: newCurrentStep }); } Starting the Clock } else { } } this.setState( { playing: false }, () => { this.clock.stop(); this.tickEvent = null; } ); handlePlayPress() { if (!this.state.playing) { steps: [0, 0, 0, 0, 0, 0, 0, 0], currentStep: 0, playing: false }; } componentDidMount() { this.context = new window.AudioContext(); Execute handleTick callback immediately this.setState({ currentStep: -1, playing: true }, () => { this.clock.start(); this.tickEvent = this.clock.callbackAtTime( this.handleTick.bind(this), this.context.currentTime ).repeat(0.47); });
  • 23. function trigger(context, deadline) { const oscillator = context.createOscillator(); const amplifier = context.createGain(); oscillator.frequency.setValueAtTime(200, deadline); amplifier.gain.setValueAtTime(0, deadline) oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15); amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02); amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2); oscillator.connect(amplifier); amplifier.connect(context.destination); oscillator.start(deadline); setTimeout(() => { amplifier.disconnect(); }, 3000); } handleTick({ deadline }) { const { currentStep, steps } = this.state; const newCurrentStep = currentStep + 1; if (steps[newCurrentStep % steps.length]) { trigger(this.context, deadline); } this.setState({ currentStep: newCurrentStep }); } Starting the Clock } else { } } this.setState( { playing: false }, () => { this.clock.stop(); this.tickEvent = null; } ); handlePlayPress() { if (!this.state.playing) { steps: [0, 0, 0, 0, 0, 0, 0, 0], currentStep: 0, playing: false }; } componentDidMount() { this.context = new window.AudioContext(); Repeat callback at a given interval (~128bpm) this.setState({ currentStep: -1, playing: true }, () => { this.clock.start(); this.tickEvent = this.clock.callbackAtTime( this.handleTick.bind(this), this.context.currentTime ).repeat(0.47); });
  • 24. function trigger(context, deadline) { const oscillator = context.createOscillator(); const amplifier = context.createGain(); oscillator.frequency.setValueAtTime(200, deadline); amplifier.gain.setValueAtTime(0, deadline) oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15); amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02); amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2); oscillator.connect(amplifier); amplifier.connect(context.destination); oscillator.start(deadline); setTimeout(() => { amplifier.disconnect(); }, 3000); } handleTick({ deadline }) { const { currentStep, steps } = this.state; const newCurrentStep = currentStep + 1; if (steps[newCurrentStep % steps.length]) { trigger(this.context, deadline); } this.setState({ currentStep: newCurrentStep }); } Stopping the Clock }, () => { this.clock.start(); this.tickEvent = this.clock.callbackAtTime( this.handleTick.bind(this), this.context.currentTime ).repeat(0.47); }); } else { } } componentDidMount() { this.context = new window.AudioContext(); this.clock = new WAAClock(this.context); } this.setState( { playing: false }, () => { this.clock.stop(); this.tickEvent.clear(); this.tickEvent = null; });
  • 25. function trigger(context, deadline) { const oscillator = context.createOscillator(); const amplifier = context.createGain(); oscillator.frequency.setValueAtTime(200, deadline); amplifier.gain.setValueAtTime(0, deadline) oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15); amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02); amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2); oscillator.connect(amplifier); amplifier.connect(context.destination); oscillator.start(deadline); setTimeout(() => { amplifier.disconnect(); }, 3000); } handleTick({ deadline }) { const { currentStep, steps } = this.state; const newCurrentStep = currentStep + 1; if (steps[newCurrentStep % steps.length]) { this.triggerSound(this.context, deadline); } this.setState({ currentStep: newCurrentStep }); } Clock Tick Handler this.clock.stop(); this.tickEvent.clear(); this.tickEvent = null; }); } } render() { const { currentStep, playing, steps } = this.state; componentDidMount() { this.context = new window.AudioContext(); this.clock = new WAAClock(this.context); }
  • 26. function trigger(context, deadline) { const oscillator = context.createOscillator(); const amplifier = context.createGain(); oscillator.frequency.setValueAtTime(200, deadline); amplifier.gain.setValueAtTime(0, deadline) oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15); amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02); amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2); oscillator.connect(amplifier); amplifier.connect(context.destination); oscillator.start(deadline); setTimeout(() => { amplifier.disconnect(); }, 3000); } handleTick({ deadline }) { const { currentStep, steps } = this.state; const newCurrentStep = currentStep + 1; if (steps[newCurrentStep % steps.length]) { this.triggerSound(this.context, deadline); } this.setState({ currentStep: newCurrentStep }); } Clock Tick Handler this.clock.stop(); this.tickEvent.clear(); this.tickEvent = null; }); } } render() { const { currentStep, playing, steps } = this.state; componentDidMount() { this.context = new window.AudioContext(); this.clock = new WAAClock(this.context); }
  • 27. function trigger(context, deadline) { const oscillator = context.createOscillator(); const amplifier = context.createGain(); oscillator.frequency.setValueAtTime(200, deadline); amplifier.gain.setValueAtTime(0, deadline) oscillator.frequency.linearRampToValueAtTime( 50, deadline + 0.15); amplifier.gain.linearRampToValueAtTime(1, deadline + 0.02); amplifier.gain.linearRampToValueAtTime(0, deadline + 0.2); oscillator.connect(amplifier); amplifier.connect(context.destination); oscillator.start(deadline); setTimeout(() => { amplifier.disconnect(); }, 3000); } handleTick({ deadline }) { const { currentStep, steps } = this.state; const newCurrentStep = currentStep + 1; if (steps[newCurrentStep % steps.length]) { this.triggerSound(this.context, deadline); } this.setState({ currentStep: newCurrentStep }); } Clock Tick Handler this.clock.stop(); this.tickEvent.clear(); this.tickEvent = null; }); } } render() { const { currentStep, playing, steps } = this.state; componentDidMount() { this.context = new window.AudioContext(); this.clock = new WAAClock(this.context); }
  • 28. Basic Bass Drum Synthesis
  • 31. Oscillator const oscillator = context.createOscillator(); oscillator.frequency.setValueAtTime(200, deadline); oscillator.start(deadline);
  • 33. Pitch Modulation oscillator.frequency.linearRampToValueAtTime( 50, // target frequency (50Hz) deadline + 0.15 // time of ramp (150ms) );
  • 35. Ampli!er const amplifier = context.createGain(); oscillator.connect(amplifier); amplifier.gain.setValueAtTime(0, deadline);
  • 37. Gain Modulation amplifier.gain.linearRampToValueAtTime( 1.0, // target gain deadline + 0.02 // time of ramp (20ms) ); amplifier.gain.linearRampToValueAtTime( 0.0, deadline + 0.20 );
  • 40. Demo
  • 41.
  • 42. • Move React state to Redux • Use seamless-immutable for Redux store • Make heavy use of reselect for computed/derived state
  • 43. • 16 steps per pattern • 16 patterns • 2 Parts of each pattern • 2 Variations of each part • 12 instruments/sounds • 12,288 total steps!
  • 46.
  • 47. Twitter: @vincentriemer Site: vincentriemer.com Demo: codepen.io/vincentriemer/pen/BpbXMo iO-808: io808.com