Remaster: cel-shaded skin, a crowd that tracks trade, and a funk band
Two skins over one game. 1979 is the machine as it was; the remaster is the same rules in cel-shaded vector art with a modern palette. The simulation is untouched - only the paint differs - and the toggle sits under the set. The street is now driven by the game. Before prices are in, the number of people walking on comes from the forecast; afterwards it comes from the glasses that actually sold, so the report shows the crowd you earned. A heat wave has them fanning themselves; a downpour leaves one figure hurrying past under an umbrella. Selling no longer snaps straight to the books. The stand trades for a few seconds first, tally climbing and till filling, which is where that crowd animation pays off. Skippable. The synth grew a kit (pitched-sine kick, snare with body, hats), a sweeping resonant lowpass for the bass, chords and a shuffle - enough for a funk band. The chiptunes stay for the 1979 skin. Boot is now a cassette load, which doubles as the gesture browsers require before audio will play. Fixes found on the way: the picture scaled about its centre while flex auto-margins collapsed to zero on overflow, which pushed the report's buttons off the bottom; and the validation line moved the button when it appeared, so it is now reserved and faded. Sibling tabs in one browser also keep their score tables in step.
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import type { Track, Tune } from './synth'
|
||||
|
||||
/**
|
||||
* The remaster's band. D dorian, light shuffle, everything syncopated:
|
||||
* a slap-ish filtered bass, clavinet stabs off the beat, and a kit that
|
||||
* leaves the downbeat alone more often than it hits it.
|
||||
*
|
||||
* Patterns are written in sixteenths, sixteen to a bar.
|
||||
*/
|
||||
const bar = (...steps: string[]) => steps
|
||||
|
||||
const KIT_GAIN = { kick: 0.5, snare: 0.32, hat: 0.12 }
|
||||
|
||||
const kick = (notes: string[]): Track => ({ wave: 'noise', gain: KIT_GAIN.kick, notes })
|
||||
const snare = (notes: string[]): Track => ({ wave: 'noise', gain: KIT_GAIN.snare, notes })
|
||||
const hats = (notes: string[]): Track => ({ wave: 'noise', gain: KIT_GAIN.hat, notes })
|
||||
|
||||
/** Sixteenth hats with the off-beats ducked, so the groove breathes. */
|
||||
const HAT_BAR = bar('H', 'h', 'H', 'h', 'H', 'h', 'H', 'h', 'H', 'h', 'H', 'h', 'H', 'h', 'O', 'h')
|
||||
const hatPattern = HAT_BAR.map((n) => (n === 'h' ? 'H' : n))
|
||||
|
||||
export const FUNK_DAY: Tune = {
|
||||
bpm: 102,
|
||||
stepsPerBeat: 4,
|
||||
swing: 0.16,
|
||||
tracks: [
|
||||
// Bass: the engine. Short, filtered, and never on every beat.
|
||||
{
|
||||
wave: 'saw',
|
||||
gain: 0.22,
|
||||
gate: 0.55,
|
||||
filter: { from: 1600, to: 260, q: 7 },
|
||||
notes: [
|
||||
...bar('D2', '.', '.', 'D2', '.', '.', 'D3', '.', 'A2', '.', '.', 'C3', '.', 'D2', '.', '.'),
|
||||
...bar('D2', '.', '.', 'D2', '.', 'F2', '.', '.', 'G2', '.', 'A2', '.', '.', 'C3', '.', '.'),
|
||||
...bar('G2', '.', '.', 'G2', '.', '.', 'G3', '.', 'D3', '.', '.', 'F3', '.', 'G2', '.', '.'),
|
||||
...bar('A2', '.', '.', 'A2', '.', 'C3', '.', '.', 'E3', '.', 'G3', '.', 'A3', '.', 'G3', '.'),
|
||||
],
|
||||
},
|
||||
// Clavinet: stabs in the gaps the bass leaves.
|
||||
{
|
||||
wave: 'pulse25',
|
||||
gain: 0.09,
|
||||
gate: 0.3,
|
||||
filter: { from: 3200, to: 900, q: 9 },
|
||||
notes: [
|
||||
...bar('.', '.', 'F4/A4/C5', '.', '.', 'F4/A4/C5', '.', '.', '.', '.', 'E4/A4/C5', '.', '.', '.', '.', '.'),
|
||||
...bar('.', '.', 'F4/A4/C5', '.', '.', '.', '.', 'F4/A4/D5', '.', '.', '.', '.', 'E4/G4/C5', '.', '.', '.'),
|
||||
...bar('.', '.', 'G4/B4/D5', '.', '.', 'G4/B4/D5', '.', '.', '.', '.', 'F4/B4/D5', '.', '.', '.', '.', '.'),
|
||||
...bar('.', '.', 'E4/A4/C5', '.', '.', '.', 'E4/G4/C5', '.', '.', 'E4/A4/C5', '.', '.', '.', '.', '.', '.'),
|
||||
],
|
||||
},
|
||||
// A thin lead line that comes and goes.
|
||||
{
|
||||
wave: 'pulse12',
|
||||
gain: 0.07,
|
||||
gate: 0.7,
|
||||
notes: [
|
||||
...bar('.', '.', '.', '.', '.', '.', '.', '.', 'D5', '.', 'F5', '.', 'E5', '=', '.', '.'),
|
||||
...bar('.', '.', '.', '.', 'C5', '.', 'D5', '=', '=', '.', '.', '.', '.', '.', '.', '.'),
|
||||
...bar('.', '.', '.', '.', '.', '.', '.', '.', 'G5', '.', 'F5', '.', 'D5', '=', '.', '.'),
|
||||
...bar('C5', '.', 'D5', '.', 'E5', '=', '=', '.', '.', '.', '.', '.', 'A4', '.', 'C5', '.'),
|
||||
],
|
||||
},
|
||||
kick([
|
||||
...bar('K', '.', '.', '.', '.', '.', 'K', '.', '.', '.', 'K', '.', '.', '.', '.', '.'),
|
||||
...bar('K', '.', '.', '.', '.', '.', 'K', '.', '.', '.', 'K', '.', '.', 'K', '.', '.'),
|
||||
...bar('K', '.', '.', '.', '.', '.', 'K', '.', '.', '.', 'K', '.', '.', '.', '.', '.'),
|
||||
...bar('K', '.', 'K', '.', '.', '.', 'K', '.', '.', '.', 'K', '.', '.', '.', 'K', '.'),
|
||||
]),
|
||||
snare([
|
||||
...bar('.', '.', '.', '.', 'S', '.', '.', '.', '.', '.', '.', '.', 'S', '.', '.', '.'),
|
||||
...bar('.', '.', '.', '.', 'S', '.', '.', '.', '.', '.', '.', 'C', 'S', '.', '.', '.'),
|
||||
...bar('.', '.', '.', '.', 'S', '.', '.', '.', '.', '.', '.', '.', 'S', '.', '.', '.'),
|
||||
...bar('.', '.', '.', '.', 'S', '.', '.', 'C', '.', '.', '.', '.', 'S', '.', 'S', 'S'),
|
||||
]),
|
||||
hats([...hatPattern, ...hatPattern, ...hatPattern, ...hatPattern]),
|
||||
],
|
||||
}
|
||||
|
||||
/** Title theme: the same band, louder and showing off. */
|
||||
export const FUNK_TITLE: Tune = {
|
||||
bpm: 108,
|
||||
stepsPerBeat: 4,
|
||||
swing: 0.15,
|
||||
tracks: [
|
||||
{
|
||||
wave: 'saw',
|
||||
gain: 0.24,
|
||||
gate: 0.5,
|
||||
filter: { from: 2200, to: 300, q: 8 },
|
||||
notes: [
|
||||
...bar('D2', '.', 'D3', '.', '.', 'D2', '.', '.', 'A2', '.', 'C3', '.', 'D3', '.', 'A2', '.'),
|
||||
...bar('D2', '.', 'D3', '.', '.', 'D2', '.', 'F2', '.', 'G2', '.', 'A2', '.', 'C3', '.', '.'),
|
||||
...bar('B2', '.', 'B3', '.', '.', 'B2', '.', '.', 'F3', '.', 'A3', '.', 'B3', '.', 'F3', '.'),
|
||||
...bar('G2', '.', 'G3', '.', '.', 'G2', '.', 'A2', '.', 'B2', '.', 'C3', '.', 'C3', 'C3', '.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
wave: 'pulse25',
|
||||
gain: 0.11,
|
||||
gate: 0.26,
|
||||
filter: { from: 4200, to: 1100, q: 10 },
|
||||
notes: [
|
||||
...bar('.', 'F4/A4/C5', '.', '.', 'F4/A4/C5', '.', '.', '.', '.', 'E4/A4/C5', '.', '.', '.', 'F4/A4/C5', '.', '.'),
|
||||
...bar('.', 'F4/A4/C5', '.', '.', '.', '.', 'F4/A4/D5', '.', '.', '.', 'E4/G4/C5', '.', '.', '.', '.', '.'),
|
||||
...bar('.', 'D4/F4/B4', '.', '.', 'D4/F4/B4', '.', '.', '.', '.', 'D4/F4/A4', '.', '.', '.', 'D4/F4/B4', '.', '.'),
|
||||
...bar('.', 'D4/G4/B4', '.', '.', '.', 'D4/G4/B4', '.', '.', '.', 'E4/G4/C5', '.', '.', '.', '.', '.', '.'),
|
||||
],
|
||||
},
|
||||
// Horn-ish hook.
|
||||
{
|
||||
wave: 'pulse50',
|
||||
gain: 0.1,
|
||||
gate: 0.85,
|
||||
filter: { from: 2600, to: 1400, q: 3 },
|
||||
notes: [
|
||||
...bar('D5', '.', 'F5', '.', 'G5', '=', '=', '.', 'F5', '.', 'D5', '.', 'C5', '=', '.', '.'),
|
||||
...bar('.', '.', 'A4', '.', 'C5', '.', 'D5', '=', '=', '=', '.', '.', '.', '.', '.', '.'),
|
||||
...bar('B4', '.', 'D5', '.', 'F5', '=', '=', '.', 'A5', '.', 'F5', '.', 'D5', '=', '.', '.'),
|
||||
...bar('G5', '.', 'F5', '.', 'D5', '.', 'C5', '.', 'D5', '=', '=', '=', '=', '.', '.', '.'),
|
||||
],
|
||||
},
|
||||
kick([
|
||||
...bar('K', '.', '.', '.', '.', '.', 'K', '.', '.', '.', 'K', '.', '.', '.', '.', '.'),
|
||||
...bar('K', '.', '.', '.', '.', '.', 'K', '.', '.', '.', 'K', '.', '.', 'K', '.', '.'),
|
||||
...bar('K', '.', '.', '.', '.', '.', 'K', '.', '.', '.', 'K', '.', '.', '.', '.', '.'),
|
||||
...bar('K', '.', 'K', '.', '.', '.', 'K', '.', '.', 'K', '.', '.', 'K', '.', 'K', '.'),
|
||||
]),
|
||||
snare([
|
||||
...bar('.', '.', '.', '.', 'S', '.', '.', '.', '.', 'C', '.', '.', 'S', '.', '.', '.'),
|
||||
...bar('.', '.', '.', '.', 'S', '.', '.', '.', '.', '.', '.', 'C', 'S', '.', '.', 'S'),
|
||||
...bar('.', '.', '.', '.', 'S', '.', '.', '.', '.', 'C', '.', '.', 'S', '.', '.', '.'),
|
||||
...bar('.', '.', '.', '.', 'S', '.', 'S', '.', '.', '.', 'S', '.', 'S', 'S', '.', 'S'),
|
||||
]),
|
||||
hats([...hatPattern, ...hatPattern, ...hatPattern, ...hatPattern]),
|
||||
],
|
||||
}
|
||||
|
||||
/** Closing theme: slower, fatter, a little smug. */
|
||||
export const FUNK_END: Tune = {
|
||||
bpm: 92,
|
||||
stepsPerBeat: 4,
|
||||
swing: 0.18,
|
||||
tracks: [
|
||||
{
|
||||
wave: 'saw',
|
||||
gain: 0.24,
|
||||
gate: 0.6,
|
||||
filter: { from: 1800, to: 240, q: 7 },
|
||||
notes: [
|
||||
...bar('D2', '.', '.', 'D2', '.', '.', 'A2', '.', 'D3', '.', '.', 'C3', '.', 'A2', '.', '.'),
|
||||
...bar('G2', '.', '.', 'G2', '.', '.', 'D3', '.', 'G3', '.', '.', 'F3', '.', 'D3', '.', '.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
wave: 'pulse25',
|
||||
gain: 0.12,
|
||||
gate: 0.9,
|
||||
filter: { from: 3000, to: 1200, q: 5 },
|
||||
notes: [
|
||||
...bar('D5/F5/A5', '=', '=', '=', '.', '.', 'C5/E5/A5', '=', '=', '.', '.', '.', 'A4/D5/F5', '=', '=', '.'),
|
||||
...bar('D5/G5/B5', '=', '=', '=', '.', '.', 'D5/F5/A5', '=', '=', '.', '.', '.', 'C5/E5/G5', '=', '=', '.'),
|
||||
],
|
||||
},
|
||||
kick([
|
||||
...bar('K', '.', '.', '.', '.', '.', 'K', '.', '.', '.', 'K', '.', '.', '.', '.', '.'),
|
||||
...bar('K', '.', '.', '.', '.', '.', 'K', '.', '.', '.', 'K', '.', '.', 'K', 'K', '.'),
|
||||
]),
|
||||
snare([
|
||||
...bar('.', '.', '.', '.', 'S', '.', '.', '.', '.', '.', '.', '.', 'S', '.', '.', '.'),
|
||||
...bar('.', '.', '.', '.', 'S', '.', '.', '.', '.', '.', '.', '.', 'S', '.', 'S', 'S'),
|
||||
]),
|
||||
hats([...hatPattern, ...hatPattern]),
|
||||
],
|
||||
}
|
||||
+142
-23
@@ -22,14 +22,26 @@ export type Wave = 'pulse12' | 'pulse25' | 'pulse50' | 'triangle' | 'saw' | 'noi
|
||||
export interface Track {
|
||||
wave: Wave
|
||||
gain: number
|
||||
/** One entry per step. '.' rest, '=' sustain previous, otherwise a note. */
|
||||
/**
|
||||
* One entry per step. '.' rest, '=' sustain previous, otherwise a note.
|
||||
* Slashes stack notes into a chord: "F4/A4/C5". On a noise track the
|
||||
* letter picks the drum: K kick, S snare, C clap, H closed hat, O open hat.
|
||||
*/
|
||||
notes: string[]
|
||||
/** Sweeping lowpass, the whole point of a funk bass. */
|
||||
filter?: { from: number; to: number; q?: number }
|
||||
/** Fraction of the note's length actually sounded; low values are stabs. */
|
||||
gate?: number
|
||||
/** Cents, for a fatter unison. */
|
||||
detune?: number
|
||||
}
|
||||
|
||||
export interface Tune {
|
||||
bpm: number
|
||||
stepsPerBeat: number
|
||||
tracks: Track[]
|
||||
/** 0 is straight, ~0.15 is a light funk shuffle. Delays every other step. */
|
||||
swing?: number
|
||||
}
|
||||
|
||||
/** Fourier series for a pulse wave of the given duty cycle. */
|
||||
@@ -112,6 +124,7 @@ export class Synth {
|
||||
at: number,
|
||||
dur: number,
|
||||
gain: number,
|
||||
opts: { filter?: Track['filter']; detune?: number } = {},
|
||||
) {
|
||||
const ctx = this.ensure()
|
||||
const osc = ctx.createOscillator()
|
||||
@@ -119,6 +132,7 @@ export class Synth {
|
||||
else if (wave === 'saw') osc.type = 'sawtooth'
|
||||
else osc.setPeriodicWave(this.waves[wave] ?? this.waves.pulse50!)
|
||||
osc.frequency.setValueAtTime(freq, at)
|
||||
if (opts.detune) osc.detune.setValueAtTime(opts.detune, at)
|
||||
|
||||
const env = ctx.createGain()
|
||||
const peak = Math.max(0.0001, gain)
|
||||
@@ -127,38 +141,78 @@ export class Synth {
|
||||
env.gain.setValueAtTime(peak, at + Math.max(0.02, dur * 0.6))
|
||||
env.gain.exponentialRampToValueAtTime(0.0001, at + dur)
|
||||
|
||||
osc.connect(env).connect(dest)
|
||||
let node: AudioNode = osc
|
||||
if (opts.filter) {
|
||||
const lp = ctx.createBiquadFilter()
|
||||
lp.type = 'lowpass'
|
||||
lp.Q.value = opts.filter.q ?? 6
|
||||
lp.frequency.setValueAtTime(opts.filter.from, at)
|
||||
lp.frequency.exponentialRampToValueAtTime(
|
||||
Math.max(60, opts.filter.to),
|
||||
at + Math.max(0.05, dur),
|
||||
)
|
||||
osc.connect(lp)
|
||||
node = lp
|
||||
}
|
||||
|
||||
node.connect(env).connect(dest)
|
||||
osc.start(at)
|
||||
osc.stop(at + dur + 0.02)
|
||||
}
|
||||
|
||||
private percussion(dest: AudioNode, kind: string, at: number, gain: number) {
|
||||
/** A small kit: pitched-sine kick, noise-and-tone snare, clap, two hats. */
|
||||
private drum(dest: AudioNode, kind: string, at: number, gain: number) {
|
||||
const ctx = this.ensure()
|
||||
const src = ctx.createBufferSource()
|
||||
src.buffer = this.noiseBuffer
|
||||
|
||||
if (kind === 'K') {
|
||||
const osc = ctx.createOscillator()
|
||||
osc.type = 'sine'
|
||||
osc.frequency.setValueAtTime(130, at)
|
||||
osc.frequency.exponentialRampToValueAtTime(42, at + 0.11)
|
||||
const env = ctx.createGain()
|
||||
env.gain.setValueAtTime(gain * 1.5, at)
|
||||
env.gain.exponentialRampToValueAtTime(0.0001, at + 0.24)
|
||||
osc.connect(env).connect(dest)
|
||||
osc.start(at)
|
||||
osc.stop(at + 0.26)
|
||||
return
|
||||
}
|
||||
|
||||
const noise = ctx.createBufferSource()
|
||||
noise.buffer = this.noiseBuffer
|
||||
const filter = ctx.createBiquadFilter()
|
||||
const env = ctx.createGain()
|
||||
let dur = 0.05
|
||||
|
||||
let dur = 0.06
|
||||
if (kind === 'K') {
|
||||
filter.type = 'lowpass'
|
||||
filter.frequency.value = 220
|
||||
dur = 0.12
|
||||
} else if (kind === 'S') {
|
||||
if (kind === 'S' || kind === 'C') {
|
||||
filter.type = 'bandpass'
|
||||
filter.frequency.value = 1800
|
||||
dur = 0.12
|
||||
filter.frequency.value = kind === 'S' ? 1900 : 1300
|
||||
filter.Q.value = kind === 'S' ? 0.9 : 2.4
|
||||
dur = kind === 'S' ? 0.16 : 0.1
|
||||
if (kind === 'S') {
|
||||
// A little body under the crack.
|
||||
const tone = ctx.createOscillator()
|
||||
tone.type = 'triangle'
|
||||
tone.frequency.setValueAtTime(210, at)
|
||||
tone.frequency.exponentialRampToValueAtTime(150, at + 0.09)
|
||||
const tenv = ctx.createGain()
|
||||
tenv.gain.setValueAtTime(gain * 0.6, at)
|
||||
tenv.gain.exponentialRampToValueAtTime(0.0001, at + 0.1)
|
||||
tone.connect(tenv).connect(dest)
|
||||
tone.start(at)
|
||||
tone.stop(at + 0.12)
|
||||
}
|
||||
} else {
|
||||
filter.type = 'highpass'
|
||||
filter.frequency.value = 6000
|
||||
dur = 0.04
|
||||
filter.frequency.value = 7200
|
||||
dur = kind === 'O' ? 0.22 : 0.035
|
||||
}
|
||||
|
||||
env.gain.setValueAtTime(gain, at)
|
||||
env.gain.exponentialRampToValueAtTime(0.0001, at + dur)
|
||||
src.connect(filter).connect(env).connect(dest)
|
||||
src.start(at)
|
||||
src.stop(at + dur + 0.02)
|
||||
noise.connect(filter).connect(env).connect(dest)
|
||||
noise.start(at)
|
||||
noise.stop(at + dur + 0.02)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------- sequencer
|
||||
@@ -189,8 +243,12 @@ export class Synth {
|
||||
if (!ctx || !tune) return
|
||||
const stepDur = 60 / tune.bpm / tune.stepsPerBeat
|
||||
const length = Math.max(...tune.tracks.map((t) => t.notes.length))
|
||||
const swing = tune.swing ?? 0
|
||||
|
||||
while (this.nextStepTime < ctx.currentTime + 0.2) {
|
||||
// A shuffle pushes every other step late without moving the downbeats.
|
||||
const at = this.nextStepTime + (this.step % 2 === 1 ? swing * stepDur : 0)
|
||||
|
||||
for (const track of tune.tracks) {
|
||||
const note = track.notes[this.step % track.notes.length]
|
||||
if (!note || note === '.' || note === '=') continue
|
||||
@@ -201,13 +259,20 @@ export class Synth {
|
||||
if (track.notes[(this.step + i) % track.notes.length] === '=') held++
|
||||
else break
|
||||
}
|
||||
const dur = held * stepDur * 0.95
|
||||
const dur = held * stepDur * (track.gate ?? 0.95)
|
||||
|
||||
if (track.wave === 'noise') {
|
||||
this.percussion(this.musicBus, note, this.nextStepTime, track.gain)
|
||||
} else {
|
||||
const f = noteToFreq(note)
|
||||
if (f) this.voice(this.musicBus, track.wave, f, this.nextStepTime, dur, track.gain)
|
||||
this.drum(this.musicBus, note, at, track.gain)
|
||||
continue
|
||||
}
|
||||
|
||||
for (const part of note.split('/')) {
|
||||
const f = noteToFreq(part)
|
||||
if (!f) continue
|
||||
this.voice(this.musicBus, track.wave, f, at, dur, track.gain, {
|
||||
filter: track.filter,
|
||||
detune: track.detune,
|
||||
})
|
||||
}
|
||||
}
|
||||
this.nextStepTime += stepDur
|
||||
@@ -230,6 +295,11 @@ export class Synth {
|
||||
this.seq([['E5', 0.05]], 'pulse12', 0.15)
|
||||
}
|
||||
|
||||
/** A quiet register blip, played once per few glasses as the day runs. */
|
||||
tick() {
|
||||
this.seq([['B5', 0.035]], 'pulse12', 0.09)
|
||||
}
|
||||
|
||||
select() {
|
||||
this.seq([['C5', 0.05], ['G5', 0.09]], 'pulse25', 0.18)
|
||||
}
|
||||
@@ -285,6 +355,55 @@ export class Synth {
|
||||
osc.stop(at + 0.7)
|
||||
}
|
||||
|
||||
/** The Atari's key-click beep. */
|
||||
beep() {
|
||||
this.seq([['A5', 0.06]], 'pulse50', 0.14)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cassette load: bandpassed hiss under a square wave flipping between two
|
||||
* tones, which is roughly what audio FSK sounded like coming off tape.
|
||||
*/
|
||||
tape(seconds: number): () => void {
|
||||
const ctx = this.ensure()
|
||||
const at = ctx.currentTime + 0.02
|
||||
const until = at + seconds
|
||||
|
||||
const osc = ctx.createOscillator()
|
||||
osc.type = 'square'
|
||||
for (let t = at, flip = 0; t < until; t += 0.018, flip++) {
|
||||
osc.frequency.setValueAtTime(flip % 2 === 0 ? 1300 : 2400, t)
|
||||
}
|
||||
const oscGain = ctx.createGain()
|
||||
oscGain.gain.value = 0.045
|
||||
|
||||
const hiss = ctx.createBufferSource()
|
||||
hiss.buffer = this.noiseBuffer
|
||||
hiss.loop = true
|
||||
const band = ctx.createBiquadFilter()
|
||||
band.type = 'bandpass'
|
||||
band.frequency.value = 1800
|
||||
band.Q.value = 0.7
|
||||
const hissGain = ctx.createGain()
|
||||
hissGain.gain.value = 0.05
|
||||
|
||||
osc.connect(oscGain).connect(this.sfxBus)
|
||||
hiss.connect(band).connect(hissGain).connect(this.sfxBus)
|
||||
osc.start(at)
|
||||
hiss.start(at)
|
||||
osc.stop(until)
|
||||
hiss.stop(until)
|
||||
|
||||
return () => {
|
||||
try {
|
||||
osc.stop()
|
||||
hiss.stop()
|
||||
} catch {
|
||||
// Already stopped; nothing to do.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fanfare() {
|
||||
this.seq(
|
||||
[['C5', 0.11], ['E5', 0.11], ['G5', 0.11], ['C6', 0.11], ['G5', 0.11], ['C6', 0.4]],
|
||||
|
||||
Reference in New Issue
Block a user