Skip to content

Commit

Permalink
Added an effect by voice
Browse files Browse the repository at this point in the history
Resolves hundredrabbits#39

To change effect for a voice: <voice number><effect name><value>
If the effect is different than the current one, it's replaced (current
one is disconnected and replaced by the new effect)
  • Loading branch information
giann committed May 29, 2019
1 parent 512cccc commit 36c08d9
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 58 deletions.
2 changes: 1 addition & 1 deletion desktop/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ app.on('ready', () => {
require('electron').protocol.registerBufferProtocol('js', protocolHandler)

app.win = new BrowserWindow({
width: 445,
width: 607,
height: 210,
minWidth: 200,
minHeight: 190,
Expand Down
90 changes: 53 additions & 37 deletions desktop/sources/links/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,65 @@ body { background:black; padding: 30px; font-family: 'input_mono_regular'; font-

/* Mixer */

#mixer { line-height: 15px; color:#666; position: relative; text-transform: uppercase; min-width: 420px}
#mixer { line-height: 15px; color:#666; position: relative; text-transform: uppercase; min-width: 582px}
#mixer > div { position: absolute; width: 95px}
#mixer > div.channel { position: absolute; }
#mixer > div.effect { width:65px; }
#mixer > div .cid { font-family: 'input_mono_medium'; font-weight: normal; }
#mixer > div canvas { position: absolute; right: -10px;top: 0.5px;}
#mixer span { padding: 0px 2.55px; }

#mixer #ch0 { top:calc(0 * 15px); left:0px; }
#mixer #ch1 { top:calc(1 * 15px); left:0px; }
#mixer #ch2 { top:calc(2 * 15px); left:0px; }
#mixer #ch3 { top:calc(3 * 15px); left:0px; }
#mixer #ch4 { top:calc(4 * 15px); left:0px; }
#mixer #ch5 { top:calc(5 * 15px); left:0px; }
#mixer #ch6 { top:calc(6 * 15px); left:0px; }
#mixer #ch7 { top:calc(7 * 15px); left:0px; }

#mixer #ch8 { top:calc(0 * 15px); left:calc(1 * 110px); }
#mixer #ch9 { top:calc(1 * 15px); left:calc(1 * 110px); }
#mixer #cha { top:calc(2 * 15px); left:calc(1 * 110px); }
#mixer #chb { top:calc(3 * 15px); left:calc(1 * 110px); }
#mixer #chc { top:calc(4 * 15px); left:calc(1 * 110px); }
#mixer #chd { top:calc(5 * 15px); left:calc(1 * 110px); }
#mixer #che { top:calc(6 * 15px); left:calc(1 * 110px); }
#mixer #chf { top:calc(7 * 15px); left:calc(1 * 110px); }

#mixer #chbit { top:calc(0 * 15px); left:calc(2 * 110px); }
#mixer #chdis { top:calc(1 * 15px); left:calc(2 * 110px); }
#mixer #chwah { top:calc(2 * 15px); left:calc(2 * 110px); }
#mixer #chche { top:calc(3 * 15px); left:calc(2 * 110px); }
#mixer #chfee { top:calc(4 * 15px); left:calc(2 * 110px); }
#mixer #chdel { top:calc(5 * 15px); left:calc(2 * 110px); }
#mixer #chtre { top:calc(6 * 15px); left:calc(2 * 110px); }
#mixer #chrev { top:calc(7 * 15px); left:calc(2 * 110px); }

#mixer #chpha { top:calc(0 * 15px); left:calc(2 * 150px); }
#mixer #chvib { top:calc(1 * 15px); left:calc(2 * 150px); }
#mixer #chcho { top:calc(2 * 15px); left:calc(2 * 150px); }
#mixer #chste { top:calc(3 * 15px); left:calc(2 * 150px); }
#mixer #chequ { top:calc(4 * 15px); left:calc(2 * 150px); }
#mixer #chcom { top:calc(5 * 15px); left:calc(2 * 150px); }
#mixer #chvol { top:calc(6 * 15px); left:calc(2 * 150px); }
#mixer #chlim { top:calc(7 * 15px); left:calc(2 * 150px); }
#mixer #ch0 { top:calc(0 * 15px); left:0px; }
#mixer #ch0-fx { top:calc(0 * 15px); left:110px; }
#mixer #ch1 { top:calc(1 * 15px); left:0px; }
#mixer #ch1-fx { top:calc(1 * 15px); left:110px; }
#mixer #ch2 { top:calc(2 * 15px); left:0px; }
#mixer #ch2-fx { top:calc(2 * 15px); left:110px; }
#mixer #ch3 { top:calc(3 * 15px); left:0px; }
#mixer #ch3-fx { top:calc(3 * 15px); left:110px; }
#mixer #ch4 { top:calc(4 * 15px); left:0px; }
#mixer #ch4-fx { top:calc(4 * 15px); left:110px; }
#mixer #ch5 { top:calc(5 * 15px); left:0px; }
#mixer #ch5-fx { top:calc(5 * 15px); left:110px; }
#mixer #ch6 { top:calc(6 * 15px); left:0px; }
#mixer #ch6-fx { top:calc(6 * 15px); left:110px; }
#mixer #ch7 { top:calc(7 * 15px); left:0px; }
#mixer #ch7-fx { top:calc(7 * 15px); left:110px; }

#mixer #ch8 { top:calc(0 * 15px); left:calc(1 * 110px + 1 * 81px); }
#mixer #ch8-fx { top:calc(0 * 15px); left:calc(2 * 110px + 1 * 81px); }
#mixer #ch9 { top:calc(1 * 15px); left:calc(1 * 110px + 1 * 81px); }
#mixer #ch9-fx { top:calc(1 * 15px); left:calc(2 * 110px + 1 * 81px); }
#mixer #cha { top:calc(2 * 15px); left:calc(1 * 110px + 1 * 81px); }
#mixer #cha-fx { top:calc(2 * 15px); left:calc(2 * 110px + 1 * 81px); }
#mixer #chb { top:calc(3 * 15px); left:calc(1 * 110px + 1 * 81px); }
#mixer #chb-fx { top:calc(3 * 15px); left:calc(2 * 110px + 1 * 81px); }
#mixer #chc { top:calc(4 * 15px); left:calc(1 * 110px + 1 * 81px); }
#mixer #chc-fx { top:calc(4 * 15px); left:calc(2 * 110px + 1 * 81px); }
#mixer #chd { top:calc(5 * 15px); left:calc(1 * 110px + 1 * 81px); }
#mixer #chd-fx { top:calc(5 * 15px); left:calc(2 * 110px + 1 * 81px); }
#mixer #che { top:calc(6 * 15px); left:calc(1 * 110px + 1 * 81px); }
#mixer #che-fx { top:calc(6 * 15px); left:calc(2 * 110px + 1 * 81px); }
#mixer #chf { top:calc(7 * 15px); left:calc(1 * 110px + 1 * 81px); }
#mixer #chf-fx { top:calc(7 * 15px); left:calc(2 * 110px + 1 * 81px); }

#mixer #chbit { top:calc(0 * 15px); left:calc(2 * 110px + 2 * 81px); }
#mixer #chdis { top:calc(1 * 15px); left:calc(2 * 110px + 2 * 81px); }
#mixer #chwah { top:calc(2 * 15px); left:calc(2 * 110px + 2 * 81px); }
#mixer #chche { top:calc(3 * 15px); left:calc(2 * 110px + 2 * 81px); }
#mixer #chfee { top:calc(4 * 15px); left:calc(2 * 110px + 2 * 81px); }
#mixer #chdel { top:calc(5 * 15px); left:calc(2 * 110px + 2 * 81px); }
#mixer #chtre { top:calc(6 * 15px); left:calc(2 * 110px + 2 * 81px); }
#mixer #chrev { top:calc(7 * 15px); left:calc(2 * 110px + 2 * 81px); }

#mixer #chpha { top:calc(0 * 15px); left:calc(2 * 110px + 3 * 81px); }
#mixer #chvib { top:calc(1 * 15px); left:calc(2 * 110px + 3 * 81px); }
#mixer #chcho { top:calc(2 * 15px); left:calc(2 * 110px + 3 * 81px); }
#mixer #chste { top:calc(3 * 15px); left:calc(2 * 110px + 3 * 81px); }
#mixer #chequ { top:calc(4 * 15px); left:calc(2 * 110px + 3 * 81px); }
#mixer #chcom { top:calc(5 * 15px); left:calc(2 * 110px + 3 * 81px); }
#mixer #chvol { top:calc(6 * 15px); left:calc(2 * 110px + 3 * 81px); }
#mixer #chlim { top:calc(7 * 15px); left:calc(2 * 110px + 3 * 81px); }

/* Commander */

Expand All @@ -60,4 +76,4 @@ body { background:black; padding: 30px; font-family: 'input_mono_regular'; font-
@media (max-width: 380px) {
#pilot #mixer { columns:1 !important; min-width: 0px }
#pilot #mixer > div { max-width: 100%}
}
}
17 changes: 17 additions & 0 deletions desktop/sources/scripts/interface.channel.effect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import EffectInterface from './interface.effect.js'

export default function ChannelEffectInterface (pilot, channelId, id, node) {
EffectInterface.call(this, pilot, id, node)

this.el.id = `ch${parseInt(channelId).toString(16)}-fx`;

this.run = function (msg) {
if (!msg || msg.substr(0, 4).toLowerCase() !== `${parseInt(channelId).toString(16)}${id}`) {
return
}

if (msg.substr(0, 4).toLowerCase() === `${parseInt(channelId).toString(16)}${id}`) {
this.operate(`${msg.substr(1)}`.substr(3))
}
}
}
21 changes: 18 additions & 3 deletions desktop/sources/scripts/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export default function Interface (pilot, id, node) {
this.meter = new Tone.Meter(0.95)
this.waveform = new Tone.Waveform(256)

this.effectId = id
this.el = document.createElement('div')
this.el.id = `ch${id}`
this.canvas = document.createElement('canvas')
Expand Down Expand Up @@ -35,15 +36,29 @@ export default function Interface (pilot, id, node) {
host.appendChild(this.el)
}

this.uninstall = function () {
this.el.remove()
}

this.start = function () {
this.updateAll({}, true)
loop()
this.loop()
}

this.stop = function () {
cancelAnimationFrame(this.loopId)
}

this.connect = function (node) {
this.node.connect(node)
}

this.disconnect = function (node) {
this.node.disconnect()
this.node.dispose()
}


function draw (level) {
if (pilot.animate !== true) { return }
if (lastUpdate && performance.now() - lastUpdate < 30) { return }
Expand Down Expand Up @@ -93,8 +108,8 @@ export default function Interface (pilot, id, node) {
context.closePath()
}

function loop () {
requestAnimationFrame(loop)
this.loop = function() {
this.loopId = requestAnimationFrame(this.loop.bind(this))
draw()
}

Expand Down
116 changes: 99 additions & 17 deletions desktop/sources/scripts/mixer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,53 @@ const Tone = require('tone')

import ChannelInterface from './interface.channel.js'
import EffectInterface from './interface.effect.js'
import ChannelEffectInterface from './interface.channel.effect.js'

const getEffect = function(type) {
switch(type) {
case 'bit':
return new Tone.BitCrusher(4)
case 'dis':
return new Tone.Distortion(0.05)
case 'wah':
return new Tone.AutoWah(100, 6, 0)
case 'che':
return new Tone.Chebyshev(50)
case 'fee':
return new Tone.FeedbackDelay(0)
case 'del':
return new Tone.PingPongDelay('4n', 0.2)
case 'tre':
return new Tone.Tremolo()
case 'rev':
return new Tone.JCReverb(0)
case 'pha':
return new Tone.Phaser(0.5, 3, 350)
case 'vib':
return new Tone.Vibrato()
case 'cho':
return new Tone.Chorus(4, 2.5, 0.5)
case 'ste':
return new Tone.StereoWidener(0.5, 3, 350)
case 'equ':
return new Tone.EQ3(5, 0, 5)
case 'com':
return new Tone.Compressor(-6, 4)
case 'vol':
return new Tone.Volume(6)
case 'lim':
return new Tone.Limiter(-2)
}

return null
}

export default function Mixer (pilot) {
this.el = document.createElement('div')
this.el.id = 'mixer'

this.channels = []
this.channelsEffects = []
this.effects = {}

this.install = function (host) {
Expand Down Expand Up @@ -37,30 +78,37 @@ export default function Mixer (pilot) {
this.channels[14] = new ChannelInterface(pilot, 14, new Tone.MembraneSynth({ 'octaves': 15, 'oscillator': { 'type': 'triangle' } }))
this.channels[15] = new ChannelInterface(pilot, 15, new Tone.MembraneSynth({ 'octaves': 20, 'oscillator': { 'type': 'square' } }))

// One FX slot (default rev) for each channel
for (const id in this.channels) {
this.channelsEffects[id] = new ChannelEffectInterface(pilot, id, 'rev', getEffect('rev'))
}

// I
this.effects.bitcrusher = new EffectInterface(pilot, 'bit', new Tone.BitCrusher(4))
this.effects.distortion = new EffectInterface(pilot, 'dis', new Tone.Distortion(0.05))
this.effects.autowah = new EffectInterface(pilot, 'wah', new Tone.AutoWah(100, 6, 0))
this.effects.chebyshev = new EffectInterface(pilot, 'che', new Tone.Chebyshev(50))
this.effects.bitcrusher = new EffectInterface(pilot, 'bit', getEffect('bit'))
this.effects.distortion = new EffectInterface(pilot, 'dis', getEffect('dis'))
this.effects.autowah = new EffectInterface(pilot, 'wah', getEffect('wah'))
this.effects.chebyshev = new EffectInterface(pilot, 'che', getEffect('che'))
// II
this.effects.feedback = new EffectInterface(pilot, 'fee', new Tone.FeedbackDelay(0))
this.effects.delay = new EffectInterface(pilot, 'del', new Tone.PingPongDelay('4n', 0.2))
this.effects.tremolo = new EffectInterface(pilot, 'tre', new Tone.Tremolo())
this.effects.reverb = new EffectInterface(pilot, 'rev', new Tone.JCReverb(0))
this.effects.feedback = new EffectInterface(pilot, 'fee', getEffect('fee'))
this.effects.delay = new EffectInterface(pilot, 'del', getEffect('del'))
this.effects.tremolo = new EffectInterface(pilot, 'tre', getEffect('tre'))
this.effects.reverb = new EffectInterface(pilot, 'rev', getEffect('rev'))
// III
this.effects.phaser = new EffectInterface(pilot, 'pha', new Tone.Phaser(0.5, 3, 350))
this.effects.vibrato = new EffectInterface(pilot, 'vib', new Tone.Vibrato())
this.effects.chorus = new EffectInterface(pilot, 'cho', new Tone.Chorus(4, 2.5, 0.5))
this.effects.widener = new EffectInterface(pilot, 'ste', new Tone.StereoWidener(0.5, 3, 350))
this.effects.phaser = new EffectInterface(pilot, 'pha', getEffect('pha'))
this.effects.vibrato = new EffectInterface(pilot, 'vib', getEffect('vib'))
this.effects.chorus = new EffectInterface(pilot, 'cho', getEffect('cho'))
this.effects.widener = new EffectInterface(pilot, 'ste', getEffect('ste'))
// Mastering
this.effects.equalizer = new EffectInterface(pilot, 'equ', new Tone.EQ3(5, 0, 5))
this.effects.compressor = new EffectInterface(pilot, 'com', new Tone.Compressor(-6, 4))
this.effects.volume = new EffectInterface(pilot, 'vol', new Tone.Volume(6))
this.effects.limiter = new EffectInterface(pilot, 'lim', new Tone.Limiter(-2))
this.effects.equalizer = new EffectInterface(pilot, 'equ', getEffect('equ'))
this.effects.compressor = new EffectInterface(pilot, 'com', getEffect('com'))
this.effects.volume = new EffectInterface(pilot, 'vol', getEffect('vol'))
this.effects.limiter = new EffectInterface(pilot, 'lim', getEffect('lim'))

// Connect
for (const id in this.channels) {
this.channels[id].connect(this.effects.bitcrusher.node)
this.channels[id].connect(this.channelsEffects[id].node)

this.channelsEffects[id].node.connect(this.effects.bitcrusher.node)
}

this.effects.bitcrusher.connect(this.effects.distortion.node)
Expand All @@ -85,6 +133,8 @@ export default function Mixer (pilot) {
// Add all instruments to dom
for (const id in this.channels) {
this.channels[id].install(this.el)

this.channelsEffects[id].install(this.el)
}

// Add all effects to dom
Expand All @@ -99,6 +149,7 @@ export default function Mixer (pilot) {
console.log('Synthetiser', 'Starting..')
for (const id in this.channels) {
this.channels[id].start()
this.channelsEffects[id].start()
}
for (const id in this.effects) {
this.effects[id].start()
Expand Down Expand Up @@ -131,6 +182,37 @@ export default function Mixer (pilot) {
// Single
for (const id in this.channels) {
this.channels[id].run(msg)

// Channel effect might have changed
if (msg
&& `${msg}`.substr(0, 1).toLowerCase() === id.toString(16)
&& `${msg}`.substr(1, 3).toLowerCase() !== this.channelsEffects[id].effectId) {
let effect = `${msg}`.substr(1, 3).toLowerCase()
let channel = this.channels[id]
let channelEffect = this.channelsEffects[id]

if (channel && effect !== channelEffect.effectId) {
let newEffect = getEffect(effect)

if (newEffect) {
// Remove current effect
this.channelsEffects[id].stop()
this.channelsEffects[id].uninstall()
this.channelsEffects[id].disconnect()

// Install new one
this.channelsEffects[id] = new ChannelEffectInterface(pilot, id, effect, newEffect)
this.channelsEffects[id].install(this.el)
this.channelsEffects[id].start()

// Put back in circuit
this.channels[id].connect(this.channelsEffects[id].node)
this.channelsEffects[id].node.connect(this.effects.bitcrusher.node)
}
}
}

this.channelsEffects[id].run(msg)
}
for (const id in this.effects) {
this.effects[id].run(msg)
Expand Down

0 comments on commit 36c08d9

Please sign in to comment.