diff --git a/audio/input.js b/audio/input.js index 8cd6c672..ccf74635 100644 --- a/audio/input.js +++ b/audio/input.js @@ -13,7 +13,7 @@ class AudioInput extends EventEmitter { constructor(options) { super(); // this._sampleRate = options._sampleRate || 48000; - const deviceIndex = options.deviceIndex || null; + const deviceIndex = options.deviceIndex ?? null; const profile = options.profile || null; this._args = [ '-u' ]; if (!profile) { @@ -22,7 +22,7 @@ class AudioInput extends EventEmitter { const profilePath = options.profilePath || 'audioprofile.pstats'; this._args.push('-m', 'cProfile', '-o', profilePath, mainScript); } - if (deviceIndex) { + if(deviceIndex !== undefined ){ this._args.push('-d', deviceIndex.toString()); } this._subprocess = null; diff --git a/server/setups/program-presets/default.json b/server/setups/program-presets/default.json index c1bcae5d..2ec794d2 100644 --- a/server/setups/program-presets/default.json +++ b/server/setups/program-presets/default.json @@ -740,6 +740,74 @@ } ], "multiply": true + }, + "ShootingArcCelebration": { + "programs": [ + { + "programName": "congaShooting2", + "shape": "fullArc", + "config": { + "fireThreshold": 0.31, + "soundMetricP1": "mic2_rms", + "soundMetricP2": "mic2_rms" + } + }, + { + "programName": "congaScore", + "shape": "highestSide", + "config": { + "playerNumber": 1, + "globalBrightness": 0.47, + "reverse": true, + "colorHue": 0.7, + "colorSat": 0.7 + } + }, + { + "programName": "congaScore", + "shape": "lowestSide", + "config": { + "globalBrightness": 1, + "reverse": true, + "playerNumber": "0", + "colorHue": 0.71, + "colorSat": 0.84 + } + }, + { + "programName": "congaCelebration", + "shape": "head", + "config": { + "programs": [ + { + "programName": "radial3d", + "config": { + "colorMap": "a_pink_r" + } + } + ], + "animateOnlyOnCelebration": true + } + } + ], + "fps": 60 + }, + "Rope w/ Celebration": { + "programs": [ + { + "programName": "congaCelebration", + "shape": "man" + }, + { + "programName": "congaRope", + "shape": "fullArc", + "config": { + "soundMetricP1": "peakDecay", + "soundMetricP2": "mic2_peakDecay" + } + } + ], + "multiply": false } }, "sound-waves": { diff --git a/server/src/LightController.js b/server/src/LightController.js index 9feb8319..8650a43b 100644 --- a/server/src/LightController.js +++ b/server/src/LightController.js @@ -18,6 +18,7 @@ const programNames = [ "mix", "congaShooting2", "congaScore", + "congaCelebration", "congaShooting", "congaRope", "PROGRAM_Main_fuego2019", diff --git a/server/src/index.js b/server/src/index.js index 5815ac94..22e91fbc 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -19,10 +19,10 @@ controller.start(); console.log('Available audio devices:\n', listDevices()); -const audioInput = new AudioInput({deviceIndex: 1,}); +const audioInput = new AudioInput({deviceIndex: 0,}); // audioInput.on('audioframe', audioEmitter.updateFrame.bind(audioEmitter)); -const audioInput2 = new AudioInput({deviceIndex: 3,}); +const audioInput2 = new AudioInput({deviceIndex: 2,}); audioInput2.on('audioframe', (frame) => { audioEmitter.frame2 = frame; }); diff --git a/server/src/light-programs/conga-utils/GlobalGame.js b/server/src/light-programs/conga-utils/GlobalGame.js index 208f8b63..5dcefe1c 100644 --- a/server/src/light-programs/conga-utils/GlobalGame.js +++ b/server/src/light-programs/conga-utils/GlobalGame.js @@ -1,6 +1,10 @@ +const ColorUtils = require("./../utils/ColorUtils"); + class Game { constructor() { this.score = [0,0]; + this.player1Color = ColorUtils.HSVtoRGB(400, 1, 1); + this.player2Color = ColorUtils.HSVtoRGB(400+0.33, 1, 1); } addPoint(playerIndex, points = 1) { @@ -14,6 +18,20 @@ class Game { restart() { this.score = [0,0]; } + + winner(){ + let winner = false; + if (this.score[0] == this.max()){ + winner = 1; + } + else if(this.score[1] == this.max()){ + winner = 2; + } + if(winner){ + this.restart(); + } + return winner; + } } module.exports = { diff --git a/server/src/light-programs/programs/congaCelebration.js b/server/src/light-programs/programs/congaCelebration.js new file mode 100644 index 00000000..74877500 --- /dev/null +++ b/server/src/light-programs/programs/congaCelebration.js @@ -0,0 +1,135 @@ +const LightProgram = require("./../base-programs/LightProgram"); +const ColorUtils = require("./../utils/ColorUtils"); +const GlobalGame = require("./../conga-utils/GlobalGame"); +const _ = require("lodash"); +const programsByShape = require("./../base-programs/ProgramsByShape"); + +module.exports = class CongaCelebratiton extends LightProgram { + init() { + this.past = null; + this.celebrate = 0; + this.winner = false; + this.colors = new Array(this.numberOfLeds).fill(ColorUtils.HSVtoRGB(0, 0, this.config.brillo)); + // Shallow copy of schedule + this.programs = _.map(this.config.programs, config => this.getProgramInstanceFromParam(config)); + } + + getProgramInstanceFromParam({programName, config, shape}) { + let p = null; + // For performance, only use programsByShape if there is a shape + if(shape) { + const programClass = this.lightController.programs[programName].generator; + const byShapeClass = programsByShape({[shape]: [programClass, config || {}]}); + p = new byShapeClass(this.config, this.geometry, this.shapeMapping, this.lightController); + } else { + p = this.lightController.instanciateProgram(programName); + p.updateConfig({...p.config, ...config}) + } + + p.init(); + return p; + } + + getCelebrationBaseColors() { + + + + } + + drawFrame(draw, audio) { + this.colors = new Array(this.numberOfLeds).fill(ColorUtils.HSVtoRGB(0, 0, this.config.brillo/100)); + let winner = GlobalGame.game.winner(); + let combinedColors = this.colors; + if (winner && (this.celebrate == 0 || winner != this.winner)){ + this.winner = winner; + this.celebrate = this.config.celebrationDurationInFrames; + } + if (this.celebrate > 0){ + let winnerColor = this.winner == 1 ? GlobalGame.game.player1Color : GlobalGame.game.player2Color; + this.colors = new Array(this.numberOfLeds).fill(winnerColor); + this.celebrate--; + } + if(!this.config.animateOnlyOnCelebration || this.celebrate > 0){ + combinedColors = this.colors; + + this.extraTime = (this.extraTime || 0) + Math.random() * 10; + + for (const prog of this.programs) { + // Done by ProgramScheduler, has to be replicated here + prog.timeInMs = this.timeInMs; + let globalBrightness = prog.config.globalBrightness || 0; + prog.drawFrame((colors) => { + for (let i = 0; i < this.numberOfLeds; i++) { + let [r, g, b, a] = combinedColors[i] + const [r2, g2, b2, a2] = colors[i]; + if (this.config.multiply) { + // globalBrightness of 0 means "the layer does not darken the other layer" + r = r * ((r2+(255-r2)*(1-globalBrightness)) || 0) / 255; + g = g * ((g2+(255-g2)*(1-globalBrightness)) || 0) / 255; + b = b * ((b2+(255-b2)*(1-globalBrightness)) || 0) / 255; + a = a + (a2 || 0) + } else { + r += (r2 || 0) * globalBrightness; + g += (g2 || 0) * globalBrightness; + b += (b2 || 0) * globalBrightness; + a += a2 || 0; + } + combinedColors[i] = [r, g, b, a]; + } + }, audio) + } + } + draw(combinedColors); + } + + updateConfig(newConfig) { + // TODO: backwards compatibility with previous version of mix + if(newConfig.a && newConfig.b) { + let {a, b, ... other} = newConfig; + newConfig = {... other, programs: [a, b]} + } + + // Override LightProgram version to decide when a program init needs to be called + if (this.programs) { + let updated = newConfig.programs; + let oldConfigs = this.config.programs; + + this.programs = _.map(updated, (newProgDef, i) => { + let oldProgDef = oldConfigs[i]; + + let subprogram = null; + + // Detect if the selected program type is the same or it changed + if (oldProgDef && oldProgDef.programName === newProgDef.programName && oldProgDef.shape === newProgDef.shape) { + subprogram = this.programs[i] + subprogram.updateConfig({ ... subprogram.config, ... newProgDef.config }) + } else { + subprogram = this.getProgramInstanceFromParam(newProgDef) + } + + // Detect if a different preset was selected and apply the default+preset program config + if(oldProgDef && oldProgDef.presetName !== newProgDef.presetName && newProgDef.presetName) { + const presets = this.lightController.getProgramPresets(newProgDef.programName); + const defaults = this.lightController.getProgramDefaultParams(newProgDef.programName); + newProgDef.config = presets[newProgDef.presetName]; + subprogram.updateConfig({ ... defaults, ... presets[newProgDef.presetName] }) + } + + return subprogram + }); + } + + super.updateConfig(newConfig) + } + + // Override and extend config Schema + static configSchema() { + let res = super.configSchema(); + res.brillo = { type: Number, min: 0, max: 1, step: 0.01, default: 0.5 }; + res.celebrationDurationInFrames = { type: Number, min: 0, max: 200, step: 5, default: 500 }; + res.animateOnlyOnCelebration = {type: Boolean, default: false}; + res.programs = {type: 'programs', default: [{programName: 'circles'}]}; + res.multiply = {type: Boolean, default: false}; + return res; + } +}; diff --git a/server/src/light-programs/programs/congaRope.js b/server/src/light-programs/programs/congaRope.js index 142e038b..63226218 100644 --- a/server/src/light-programs/programs/congaRope.js +++ b/server/src/light-programs/programs/congaRope.js @@ -1,6 +1,7 @@ const LightProgram = require("./../base-programs/LightProgram"); const ColorUtils = require("./../utils/ColorUtils"); const _ = require("lodash"); +const GlobalGame = require("./../conga-utils/GlobalGame"); module.exports = class CongaShooting extends LightProgram { @@ -11,8 +12,8 @@ module.exports = class CongaShooting extends LightProgram { this.rope = {pos: this.center - this.segmentSize, length: 2 * this.segmentSize}; this.explosionLevel = 0; this.time = 0; - this.audioWindow1 = new Array(1000).fill(0); - this.audioWindow2 = new Array(1000).fill(0); + this.audioWindow1 = new Array(600).fill(0); + this.audioWindow2 = new Array(600).fill(0); this.maxVolume = 0; } @@ -21,17 +22,16 @@ module.exports = class CongaShooting extends LightProgram { let volP2 = (audio[this.config.soundMetricP2] || 0) * this.config.multiplier; this.audioWindow1.unshift(); - this.audioWindow1.push(volP1 > this.config.fireThreshold ? 1 : 0); + this.audioWindow1.push(volP1 > this.config.fireThreshold ? volP1 : 0); this.audioWindow2.unshift(); - this.audioWindow2.push(volP2 > this.config.fireThreshold ? 1 : 0); + this.audioWindow2.push(volP2 > this.config.fireThreshold ? volP2 : 0); } detectBursts(audio){ this.updateAudioWindow(audio); let burstSizeP1 = _.sum(this.audioWindow1); let burstSizeP2 = _.sum(this.audioWindow2); - console.log(burstSizeP1, burstSizeP2); if(burstSizeP1 < this.config.burstThreshold){ burstSizeP1 = 0; @@ -82,7 +82,7 @@ module.exports = class CongaShooting extends LightProgram { } gameOver(winner){ this.rope = {pos: this.center - this.segmentSize, length: 2 * this.segmentSize}; - this.paintAll(); + GlobalGame.game.score[winner] = GlobalGame.game.max(); } drawFrame(draw, audio) { @@ -98,10 +98,10 @@ module.exports = class CongaShooting extends LightProgram { } if (this.rope.pos == 0){ - this.gameOver('P1'); + this.gameOver(0); } if(this.rope.pos + this.rope.length == this.numberOfLeds){ - this.gameOver('P2'); + this.gameOver(1); } let baseColor = ColorUtils.HSVtoRGB(0, 0, this.explosionLevel/20); for (let i = 0; i < this.numberOfLeds; i++) { diff --git a/server/src/light-programs/programs/congaShooting2.js b/server/src/light-programs/programs/congaShooting2.js index 03d562c2..169fcad5 100644 --- a/server/src/light-programs/programs/congaShooting2.js +++ b/server/src/light-programs/programs/congaShooting2.js @@ -41,10 +41,6 @@ module.exports = class CongaShooting extends LightProgram { if(b.pos < 0 || b.pos > this.numberOfLeds) { this.bulletsA = _.without(this.bulletsA, b); GlobalGame.game.addPoint(0); - - if(GlobalGame.game.score[0] === 10) { - GlobalGame.game.restart() - } } } @@ -54,10 +50,6 @@ module.exports = class CongaShooting extends LightProgram { if(b.pos < 0 || b.pos > this.numberOfLeds) { this.bulletsB = _.without(this.bulletsB, b); GlobalGame.game.addPoint(1); - - if(GlobalGame.game.score[1] === 10) { - GlobalGame.game.restart() - } } } @@ -97,12 +89,12 @@ module.exports = class CongaShooting extends LightProgram { this.colors[i] = baseColor; for(const b of this.bulletsA){ if (Math.abs(b.pos - i) < this.config.speed){ - this.colors[i] = ColorUtils.HSVtoRGB(b.size/400, 1, 1); + this.colors[i] = GlobalGame.game.player1Color; } } for(const b of this.bulletsB){ if (Math.abs(b.pos - i) < this.config.speed){ - this.colors[i] = ColorUtils.HSVtoRGB(b.size/400+0.33, 1, 1); + this.colors[i] = GlobalGame.game.player2Color; } } }