Skip to content

Commit

Permalink
feat(time): Made setValueAtTime() work by tweaking .currentTime, so t…
Browse files Browse the repository at this point in the history
…hat it is more in sync with the
  • Loading branch information
meszaros-lajos-gyorgy committed Dec 2, 2018
1 parent 611be7f commit bd5e6df
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 25 deletions.
12 changes: 6 additions & 6 deletions poc/common/virtual-webaudio.js

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions poc/delayed-monosynth/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<title>Delayed monosynth</title>
</head>

<body>
Press the following keys to play the monosynth:
<pre>
W E T Y U
A S D F G H J K
</pre>
<b>Notice that the notes are delayed half a second!</b>
<script src="../common/virtual-webaudio.js"></script>
<script src="./script.js"></script>
<script src="/reload/reload.js"></script>
</body>

</html>
121 changes: 121 additions & 0 deletions poc/delayed-monosynth/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/* global virtualWebaudio, AudioContext */

const { VirtualAudioContext, patch, render, diff } = virtualWebaudio

const sound = (frequency, volume, delayInSeconds) => {
const ctx = new VirtualAudioContext()

const osc = ctx.createOscillator()
const gain = ctx.createGain()

osc.frequency.setValueAtTime(frequency, ctx.currentTime + delayInSeconds)
osc.type = 'triangle'

gain.gain.setValueAtTime(volume, ctx.currentTime + delayInSeconds)

osc.connect(gain)
gain.connect(ctx.destination)

osc.start()

return ctx
}

const frequencies = [
261.6,
277.1,
293.6,
311.1,
329.6,
349.2,
369.9,
391.9,
415.3,
440,
466.1,
493.8,
523.2
]
const keyMap = {
A: 0,
W: 1,
S: 2,
E: 3,
D: 4,
F: 5,
T: 6,
G: 7,
Z: 8,
Y: 8,
H: 9,
U: 10,
J: 11,
K: 12
}

const ctx = new AudioContext()

let old = null

const change = (virtualCtx, ctx) => {
if (old === null) {
render(virtualCtx, ctx)
} else {
patch(diff(old, virtualCtx), ctx)
}

old = virtualCtx
}

let lastPressedNoteIndex = null
let isNotePlaying = false
const keyPressed = {
A: false,
W: false,
S: false,
E: false,
D: false,
F: false,
T: false,
G: false,
Z: false,
Y: false,
H: false,
U: false,
J: false,
K: false
}

document.body.addEventListener('keydown', e => {
const pressedChar = String.fromCharCode(e.keyCode)
if (!keyPressed[pressedChar]) {
keyPressed[pressedChar] = true

if (keyMap.hasOwnProperty(pressedChar)) {
if (lastPressedNoteIndex !== keyMap[pressedChar] || !isNotePlaying) {
lastPressedNoteIndex = keyMap[pressedChar]
isNotePlaying = true

change(sound(frequencies[lastPressedNoteIndex], 1, 0.5), ctx)
}
}
}
})

document.body.addEventListener('keyup', e => {
const pressedChar = String.fromCharCode(e.keyCode)
if (keyPressed[pressedChar]) {
keyPressed[pressedChar] = false

if (keyMap.hasOwnProperty(pressedChar)) {
if (lastPressedNoteIndex === keyMap[pressedChar] && isNotePlaying) {
lastPressedNoteIndex = keyMap[pressedChar]
isNotePlaying = false
change(sound(frequencies[lastPressedNoteIndex], 0, 0.5), ctx)
}
}
}
})

// setting initial volume to 0
change(sound(0, 0, 0), ctx)
4 changes: 3 additions & 1 deletion poc/envelope/index.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<title>Envelope</title>
</head>

<body>
<script src="//cdn.jsdelivr.net/npm/ramda@latest/dist/ramda.min.js"></script>
<script src="../common/virtual-webaudio.js"></script>
<script src="./script.js"></script>
<script src="/reload/reload.js"></script>
</body>

</html>
2 changes: 1 addition & 1 deletion poc/envelope/script.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* global virtualWebaudio, AudioContext */

const { VirtualAudioContext, patch, render, diff } = virtualWebaudio
const { VirtualAudioContext, render } = virtualWebaudio

const attackAndDecay = () => {
const ctx = new VirtualAudioContext()
Expand Down
4 changes: 3 additions & 1 deletion poc/manual-lfo/index.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<title>Manual LFO</title>
</head>

<body>
<script src="//cdn.jsdelivr.net/npm/ramda@latest/dist/ramda.min.js"></script>
<script src="../common/virtual-webaudio.js"></script>
<script src="./script.js"></script>
<script src="/reload/reload.js"></script>
</body>

</html>
6 changes: 4 additions & 2 deletions poc/monosynth/index.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<title>Monosynth</title>
</head>

<body>
Press the following keys to play the monosynth:
<pre>
W E T Y U I
W E T Y U
A S D F G H J K
</pre>
<script src="//cdn.jsdelivr.net/npm/ramda@latest/dist/ramda.min.js"></script>
<script src="../common/virtual-webaudio.js"></script>
<script src="./script.js"></script>
<script src="/reload/reload.js"></script>
</body>

</html>
5 changes: 0 additions & 5 deletions poc/server.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
const express = require('express')
const http = require('http')
const path = require('path')
const reload = require('reload')

const app = express()

app.use(express.static('poc'))

app.get('/', (req, res) => {
res.sendFile(path.resolve('./poc/index.html'))
})

const server = http.createServer(app)

reload(app)
Expand Down
8 changes: 6 additions & 2 deletions src/VirtualAudioContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ class VirtualAudioContext {
this._ = {
uniqueIdGenerator: new UniqueIdGenerator(0),
events: new Events(),
initialTime: Date.now()
initialTime: Date.now(),
unitOfTimeInSeconds: 0.02
}
}
get currentTime () {
return Math.floor((Date.now() - this._.initialTime) / 10) * 10 // manual 10ms throttle
const { initialTime, unitOfTimeInSeconds } = this._
const realTimeInSeconds = (Date.now() - initialTime) / 1000
const throttle = unitOfTimeInSeconds * 1000
return Math.round(realTimeInSeconds * throttle) / throttle
}
get destination () {
return CTX_DESTINATION
Expand Down
4 changes: 3 additions & 1 deletion src/VirtualAudioParam.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { EVENTS } from './constants'
import { markTimeArg } from './helpers'
import { adjust } from 'ramda'

class VirtualAudioParam {
constructor (nodeId, ctx, name, defaultValue) {
Expand All @@ -17,7 +19,7 @@ class VirtualAudioParam {
}

setValueAtTime (...args) {
this._.ctx._.events.add(EVENTS.CALL, [this._.name, 'setValueAtTime'], this._.nodeId, this._.ctx.currentTime, args)
this._.ctx._.events.add(EVENTS.CALL, [this._.name, 'setValueAtTime'], this._.nodeId, this._.ctx.currentTime, adjust(markTimeArg, 1, args))
}
linearRampToValueAtTime (...args) {
this._.ctx._.events.add(EVENTS.CALL, [this._.name, 'linearRampToValueAtTime'], this._.nodeId, this._.ctx.currentTime, args)
Expand Down
36 changes: 32 additions & 4 deletions src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ import {
evolve,
update,
contains,
__
__,
startsWith,
is,
both,
add,
replace,
when,
map,
takeLast
} from 'ramda'

import { CTX_DESTINATION, EVENTS } from './constants'
Expand All @@ -28,16 +36,21 @@ const invertEvent = cond([
[propEq('eventName', EVENTS.CALL), cond([
[compose(equals('start'), last, prop('param')), evolve({ param: update(-1, 'stop') })],
[compose(equals('stop'), last, prop('param')), evolve({ param: update(-1, 'start') })],
[compose(equals('setValueAtTime'), last, prop('param')), evolve({
param: update(-1, 'cancelScheduledValues'),
args: takeLast(1)
})],
[compose(contains(__, [
'setPeriodicWave',
'setValueAtTime',
'linearRampToValueAtTime',
'exponentialRampToValueAtTime',
'setTargetAtTime',
'setValueCurveAtTime',
'cancelScheduledValues',
'cancelAndHoldAtTime'
]), last, prop('param')), assoc('eventName', EVENTS.NOP)],
]), last, prop('param')), ({ param }) => {
console.warn(`inverting: inversion has not yet been implemented for ${param}`)
}],
[T, ({ param }) => {
console.error(`inverting: unknown command ${param}`)
}]
Expand All @@ -62,6 +75,19 @@ const removeNodeById = (id, ctx) => {
delete ctx._nodes[id]
}

const TIME_MARK = '{{currentTime}}'

const markTimeArg = arg => TIME_MARK + ' + ' + arg

const parseTimeArg = curry((time, arg) => when(
both(is(String), startsWith(TIME_MARK + ' + ')),
compose(
add(time),
parseFloat,
replace(TIME_MARK + ' + ', '')
)
)(arg))

const applyEventToContext = curry(({ targetId, eventName, param, time, args }, ctx) => {
// TODO: how to deal with time?
switch (eventName) {
Expand Down Expand Up @@ -152,7 +178,7 @@ const applyEventToContext = curry(({ targetId, eventName, param, time, args }, c
case 'setValueCurveAtTime':
case 'cancelScheduledValues':
case 'cancelAndHoldAtTime':
apply(node[paramName][command].bind(node[paramName]), args)
apply(node[paramName][command].bind(node[paramName]), map(parseTimeArg(time), args))
break
default: {
console.error('unknown command', command)
Expand Down Expand Up @@ -197,5 +223,7 @@ export {
getNodeById,
setNodeById,
removeNodeById,
markTimeArg,
parseTimeArg,
applyEventToContext
}
3 changes: 1 addition & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@ const diff = (virtualCtxA, virtualCtxB) => {
}

const patch = (eventsData, ctx) => {
const now = Date.now()
const now = ctx.currentTime

compose(
forEach(applyEventToContext(__, ctx)),
// TODO: SORT BY targetId, time DESC
map(evolve({
time: add(now)
}))
Expand Down

0 comments on commit bd5e6df

Please sign in to comment.