From a437abdac25b11e139e900983b42e184245772d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Aug 2022 09:08:54 +0100 Subject: [PATCH 01/10] build(deps): bump terser from 4.8.0 to 4.8.1 (#750) Bumps [terser](https://github.com/terser/terser) from 4.8.0 to 4.8.1. - [Release notes](https://github.com/terser/terser/releases) - [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md) - [Commits](https://github.com/terser/terser/commits) --- updated-dependencies: - dependency-name: terser dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index a47cebe5e..9957c3013 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11816,9 +11816,9 @@ source-map-resolve@^0.5.0: urix "^0.1.0" source-map-support@^0.5.19, source-map-support@~0.5.12: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -12444,9 +12444,9 @@ terser-webpack-plugin@^3.0.3: webpack-sources "^1.4.3" terser@^4.1.2, terser@^4.8.0: - version "4.8.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" - integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== + version "4.8.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.1.tgz#a00e5634562de2239fd404c649051bf6fc21144f" + integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw== dependencies: commander "^2.20.0" source-map "~0.6.1" From b3df0f627f8529bf3838c7f35be7fef4e862ee02 Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Sun, 28 Aug 2022 19:50:38 +0100 Subject: [PATCH 02/10] fix(output-window): sets size on creation of window and removes overflow (#756) --- public/output-window.html | 25 +++++++++++++++++++++---- src/application/window-handler.js | 18 ++++++++++++------ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/public/output-window.html b/public/output-window.html index 331293e60..6e0066925 100644 --- a/public/output-window.html +++ b/public/output-window.html @@ -1,9 +1,26 @@ - - - - + + + + + + + diff --git a/src/application/window-handler.js b/src/application/window-handler.js index 11063e971..ffea8834e 100644 --- a/src/application/window-handler.js +++ b/src/application/window-handler.js @@ -44,6 +44,15 @@ export default function windowHandler() { }; } + function setSize(win) { + const { innerWidth: width, innerHeight: height } = win; + + this.store.dispatch("size/setSize", { + width, + height + }); + } + this._store.subscribe(async ({ type, payload }) => { if (type === "windows/ADD_WINDOW") { const { width, height, backgroundColor, title, id } = payload; @@ -108,14 +117,11 @@ export default function windowHandler() { // Setup the new requestAnimationFrame() timer = requestAnimationFrame(() => { - const { innerWidth: width, innerHeight: height } = win; - - this.store.dispatch("size/setSize", { - width, - height - }); + setSize.call(this, win); }); }); + + setSize.call(this, win); } }); From b232b0b58e235b12699b41cba7807a58a78c0a9c Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Tue, 6 Sep 2022 20:26:21 +0100 Subject: [PATCH 03/10] feat(group): add draw to output toggle (#762) * feat(group): add draw to output toggle * fix loading old enable format from preset --- src/application/constants.js | 4 + src/application/index.js | 3 +- src/application/worker/loop.js | 8 +- .../worker/store/modules/groups.js | 10 +-- src/components/GalleryItem.vue | 6 +- src/components/Group.vue | 7 +- src/components/inputs/Checkbox.vue | 80 ++++++++++++++----- 7 files changed, 86 insertions(+), 32 deletions(-) diff --git a/src/application/constants.js b/src/application/constants.js index c397f5612..70bc9eb68 100644 --- a/src/application/constants.js +++ b/src/application/constants.js @@ -15,3 +15,7 @@ export default { return 512; } }; + +export const GROUP_DISABLED = 0; +export const GROUP_ENABLED = 1; +export const GROUP_DRAW_TO_OUTPUT = 2; diff --git a/src/application/index.js b/src/application/index.js index f76f85f15..02ac2b24b 100644 --- a/src/application/index.js +++ b/src/application/index.js @@ -15,6 +15,7 @@ import PromiseWorker from "promise-worker-transferable"; import Vue from "vue"; import { ipcRenderer } from "electron"; import { app } from "@electron/remote"; +import { GROUP_ENABLED } from "./constants"; let imageBitmap; const imageBitmapQueue = []; @@ -132,7 +133,7 @@ export default class ModV { }; // Make the default group - this.store.dispatch("groups/createGroup", { enabled: true }); + this.store.dispatch("groups/createGroup", { enabled: GROUP_ENABLED }); window.addEventListener("beforeunload", () => true); } diff --git a/src/application/worker/loop.js b/src/application/worker/loop.js index 2d8f7b07e..f6aa8d444 100644 --- a/src/application/worker/loop.js +++ b/src/application/worker/loop.js @@ -2,7 +2,7 @@ import get from "lodash.get"; import store from "./store"; import map from "../utils/map"; -import constants from "../constants"; +import constants, { GROUP_DISABLED, GROUP_ENABLED } from "../constants"; import { applyWindow } from "meyda/src/utilities"; const meyda = { windowing: applyWindow }; @@ -142,7 +142,7 @@ function loop(delta, features, fftOutput) { const isGalleryGroup = group.name === constants.GALLERY_GROUP_NAME; const groupModulesLength = group.modules.length; - if (!group.enabled || group.alpha < 0.001) { + if (group.enabled === GROUP_DISABLED || group.alpha < 0.001) { continue; } @@ -286,7 +286,7 @@ function loop(delta, features, fftOutput) { } } - if (!group.hidden) { + if (!group.hidden && group.enabled === GROUP_ENABLED) { lastCanvas = drawTo.canvas; } } @@ -307,7 +307,7 @@ function loop(delta, features, fftOutput) { modules } = group; const groupModulesLength = modules.length; - if (!enabled || groupModulesLength < 1 || !(alpha > 0)) { + if (enabled !== GROUP_ENABLED || groupModulesLength < 1 || !(alpha > 0)) { continue; } diff --git a/src/application/worker/store/modules/groups.js b/src/application/worker/store/modules/groups.js index 396c3ffe7..5238acbb0 100644 --- a/src/application/worker/store/modules/groups.js +++ b/src/application/worker/store/modules/groups.js @@ -1,6 +1,6 @@ import SWAP from "./common/swap"; import store from "../"; -import constants from "../../../constants"; +import constants, { GROUP_DISABLED } from "../../../constants"; import uuidv4 from "uuid/v4"; import { applyExpression } from "../../../utils/apply-expression"; @@ -15,7 +15,8 @@ import { applyExpression } from "../../../utils/apply-expression"; * * @property {Array} modules The draw order of the Modules contained within the Group * - * @property {Boolean} enabled Indicates whether the Group should be drawn + * @property {Number} enabled Indicates whether the Group should be drawn. 0: not drawn, + * 1: drawn, 2: not drawn to output canvas * * @property {Number} alpha The level of opacity, between 0 and 1, the Group should * be drawn at @@ -34,9 +35,6 @@ import { applyExpression } from "../../../utils/apply-expression"; * * @property {String} compositeOperation The {@link Blendmode} the Group muxes with * - * @property {Boolean} drawToOutput Indicates whether the Group should draw to the output - * canvas - * * @property {String} drawToCanvasId The ID of the auxillary Canvas to draw the Group to, * null indicates the Group should draw to the main output * @@ -133,7 +131,7 @@ const actions = { name, id, clearing: args.clearing || false, - enabled: args.enabled || false, + enabled: Number(args.enabled) || GROUP_DISABLED, hidden: args.hidden || false, modules: args.modules || [], inherit, diff --git a/src/components/GalleryItem.vue b/src/components/GalleryItem.vue index 40d67dcf0..a2ef17ca1 100644 --- a/src/components/GalleryItem.vue +++ b/src/components/GalleryItem.vue @@ -12,6 +12,8 @@ @@ -34,7 +79,7 @@ label.light { background: #363636; } -input:checked + span::before { +span.on::before { content: ""; width: 10px; height: 4px; @@ -47,16 +92,15 @@ input:checked + span::before { transform: translateX(2px) translateY(3px) rotate(-45deg); } -/* input:checked + span::after { +span.partial::before { content: ""; - width: 15px; - height: 15px; + width: 10px; + height: 4px; display: inline-block; position: absolute; - top: 2px; - right: 3px; - border-top: 2px solid white; - transform: rotate(-45deg); - transform-origin: right top; -} */ + top: 0; + left: 0; + border-bottom: 2px solid white; + transform: translateX(3px) translateY(3px); +} From 00be42f140dde54c40916c30f1a3583199319e7e Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Tue, 6 Sep 2022 21:47:06 +0100 Subject: [PATCH 04/10] fix(setup-media): splits audio and video mediastreams and cleans up UI (#757) --- src/application/index.js | 18 +- src/application/setup-media.js | 173 ++++++++++-------- .../InputDeviceConfig/AudioVideo.vue | 24 ++- 3 files changed, 129 insertions(+), 86 deletions(-) diff --git a/src/application/index.js b/src/application/index.js index 02ac2b24b..4cf6331d1 100644 --- a/src/application/index.js +++ b/src/application/index.js @@ -142,7 +142,7 @@ export default class ModV { this.windowHandler(); try { - await this.setupMedia({}); + await this.setupMedia({ useDefaultDevices: true }); } catch (e) { console.error(e); } @@ -260,13 +260,15 @@ export default class ModV { } = this.store.state; const features = this.meyda.get(featuresToGet); - this.updateBeatDetektor(delta, features); - features.byteFrequencyData = Array.from(getByteFrequencyData() || []); - this.$worker.postMessage({ type: "meyda", payload: features }); - - for (let i = 0; i < featuresToGet.length; i += 1) { - const feature = featuresToGet[i]; - this.features[feature] = features[feature]; + if (features) { + this.updateBeatDetektor(delta, features); + features.byteFrequencyData = Array.from(getByteFrequencyData() || []); + this.$worker.postMessage({ type: "meyda", payload: features }); + + for (let i = 0; i < featuresToGet.length; i += 1) { + const feature = featuresToGet[i]; + this.features[feature] = features[feature]; + } } } diff --git a/src/application/setup-media.js b/src/application/setup-media.js index 116080c4f..dfffe3091 100644 --- a/src/application/setup-media.js +++ b/src/application/setup-media.js @@ -41,17 +41,18 @@ async function enumerateDevices() { } async function getMediaStream({ audioSourceId, videoSourceId }) { - const constraints = {}; + const audioConstraints = {}; + const videoConstraints = {}; if (audioSourceId) { - constraints.audio = { + audioConstraints.audio = { echoCancellation: { exact: false }, deviceId: audioSourceId }; } if (videoSourceId) { - constraints.video = { + videoConstraints.video = { deviceId: videoSourceId, frameRate: { ideal: 60 @@ -60,10 +61,13 @@ async function getMediaStream({ audioSourceId, videoSourceId }) { } /* Ask for user media access */ - return navigator.mediaDevices.getUserMedia(constraints); + return [ + audioSourceId && navigator.mediaDevices.getUserMedia(audioConstraints), + videoSourceId && navigator.mediaDevices.getUserMedia(videoConstraints) + ]; } -async function setupMedia({ audioId, videoId }) { +async function setupMedia({ audioId, videoId, useDefaultDevices = false }) { const { _store: store } = this; const mediaStreamDevices = await enumerateDevices.bind(this)(); @@ -71,104 +75,127 @@ async function setupMedia({ audioId, videoId }) { let audioSourceId = audioId; let videoSourceId = videoId; - if (!audioSourceId && store.state["mediaStream"].currentAudioSource) { - audioSourceId = store.state["mediaStream"].currentAudioSource; - } else if (!audioSourceId && mediaStreamDevices.audio.length > 0) { + if (!audioId && useDefaultDevices && mediaStreamDevices.audio.length > 0) { audioSourceId = mediaStreamDevices.audio[0].deviceId; } - if (!videoSourceId && store.state["mediaStream"].currentVideoSource) { - videoSourceId = store.state["mediaStream"].currentVideoSource; - } else if (!videoSourceId && mediaStreamDevices.video.length > 0) { + if (!videoId && useDefaultDevices && mediaStreamDevices.video.length > 0) { videoSourceId = mediaStreamDevices.video[0].deviceId; } - if (this._mediaStream) { - const tracks = this._mediaStream.getTracks(); - for (let i = 0, len = tracks.length; i < len; i++) { - const track = tracks[i]; - track.stop(); + const streams = []; + + if (audioId) { + streams.push(this._audioMediaStream); + } + + if (videoId) { + streams.push(this._videoMediaStream); + } + + for (let i = 0, len = streams.length; i < len; i++) { + const stream = streams[i]; + + if (stream) { + const tracks = stream.getTracks(); + for (let j = 0, jLen = tracks.length; j < jLen; j++) { + const track = tracks[j]; + track.stop(); + } } } - const mediaStream = await getMediaStream({ audioSourceId, videoSourceId }); + const [audioMediaStream, videoMediaStream] = await Promise.all( + await getMediaStream({ + audioSourceId, + videoSourceId + }) + ); // This video element is required to keep the camera alive for the ImageCapture API // (this._imageCapture, ./index.js) - if (this.videoStream) { - this.videoStream.pause(); - delete this.videoStream; - } + if (videoMediaStream) { + if (this.videoStream) { + this.videoStream.pause(); + delete this.videoStream; + } - this.videoStream = document.createElement("video"); - this.videoStream.autoplay = true; - this.videoStream.muted = true; + this.videoStream = document.createElement("video"); + this.videoStream.autoplay = true; + this.videoStream.muted = true; - this.videoStream.srcObject = mediaStream; - this.videoStream.onloadedmetadata = () => { - this.videoStream.play(); - }; + this.videoStream.srcObject = videoMediaStream; + this.videoStream.onloadedmetadata = () => { + this.videoStream.play(); + }; + + const [track] = videoMediaStream.getVideoTracks(); + if (track) { + this._imageCapture = new ImageCapture(track); + } - if (this.audioContext) { - this.audioContext.close(); + store.commit("mediaStream/SET_CURRENT_VIDEO_SOURCE", { + videoId: videoSourceId + }); } - // Create new Audio Context - this.audioContext = new window.AudioContext({ - latencyHint: "playback" - }); + if (audioMediaStream) { + if (this.audioContext) { + this.audioContext.close(); + } + + // Create new Audio Context + this.audioContext = new window.AudioContext({ + latencyHint: "playback" + }); - // Create new Audio Analyser - analyserNode = this.audioContext.createAnalyser(); - analyserNode.smoothingTimeConstant = 0; + // Create new Audio Analyser + analyserNode = this.audioContext.createAnalyser(); + analyserNode.smoothingTimeConstant = 0; - // Set up arrays for analyser - floatFrequencyDataArray = new Float32Array(analyserNode.frequencyBinCount); - byteFrequencyDataArray = new Uint8Array(analyserNode.frequencyBinCount); + // Set up arrays for analyser + floatFrequencyDataArray = new Float32Array(analyserNode.frequencyBinCount); + byteFrequencyDataArray = new Uint8Array(analyserNode.frequencyBinCount); - // Create a gain node - this.gainNode = this.audioContext.createGain(); + // Create a gain node + this.gainNode = this.audioContext.createGain(); - // Mute the node - this.gainNode.gain.value = 0; + // Mute the node + this.gainNode.gain.value = 0; - // Create the audio input stream (audio) - this.audioStream = this.audioContext.createMediaStreamSource(mediaStream); + // Create the audio input stream (audio) + this.audioStream = this.audioContext.createMediaStreamSource( + audioMediaStream + ); - // Connect the audio stream to the analyser (this is a passthru) (audio->(analyser)) - this.audioStream.connect(analyserNode); + // Connect the audio stream to the analyser (this is a passthru) (audio->(analyser)) + this.audioStream.connect(analyserNode); - // Connect the audio stream to the gain node (audio->(analyser)->gain) - this.audioStream.connect(this.gainNode); + // Connect the audio stream to the gain node (audio->(analyser)->gain) + this.audioStream.connect(this.gainNode); - // Connect the gain node to the output (audio->(analyser)->gain->destination) - this.gainNode.connect(this.audioContext.destination); + // Connect the gain node to the output (audio->(analyser)->gain->destination) + this.gainNode.connect(this.audioContext.destination); - // Set up Meyda - // eslint-disable-next-line new-cap - this.meyda = new Meyda.createMeydaAnalyzer({ - audioContext: this.audioContext, - source: this.audioStream, - bufferSize: constants.AUDIO_BUFFER_SIZE, - windowingFunction: "rect", - featureExtractors: ["complexSpectrum"] - }); + // Set up Meyda + // eslint-disable-next-line new-cap + this.meyda = new Meyda.createMeydaAnalyzer({ + audioContext: this.audioContext, + source: this.audioStream, + bufferSize: constants.AUDIO_BUFFER_SIZE, + windowingFunction: "rect", + featureExtractors: ["complexSpectrum"] + }); - const [track] = mediaStream.getVideoTracks(); - if (track) { - this._imageCapture = new ImageCapture(track); + store.commit("mediaStream/SET_CURRENT_AUDIO_SOURCE", { + audioId: audioSourceId + }); } - store.commit("mediaStream/SET_CURRENT_AUDIO_SOURCE", { - audioId: audioSourceId - }); - store.commit("mediaStream/SET_CURRENT_VIDEO_SOURCE", { - videoId: videoSourceId - }); - - this._mediaStream = mediaStream; + this._audioMediaStream = audioMediaStream || this._audioMediaStream; + this._videoMediaStream = videoMediaStream || this._videoMediaStream; - return mediaStream; + return [audioMediaStream, videoMediaStream]; } function getFloatFrequencyData() { diff --git a/src/components/InputDeviceConfig/AudioVideo.vue b/src/components/InputDeviceConfig/AudioVideo.vue index aefa251f4..9a0f51249 100644 --- a/src/components/InputDeviceConfig/AudioVideo.vue +++ b/src/components/InputDeviceConfig/AudioVideo.vue @@ -16,7 +16,11 @@ Audio Input -