From acf632517a3659ff5c23ba4e65f54a805e2ccebf Mon Sep 17 00:00:00 2001 From: gautamsamal Date: Thu, 10 Jan 2019 23:06:24 +0530 Subject: [PATCH] Added key event - space key to start/stop Fixed issues with delay + starts after Fixed scrolling issue in number --- .../circular-range.js => common/directives.js | 13 +++ index.html | 2 +- synthesizerV2/audioLibrary.js | 16 +-- synthesizerV2/main.html | 34 +++--- synthesizerV2/main.js | 33 +++++- synthesizerV2/synthPlayer.js | 101 +++++++++++++++--- 6 files changed, 158 insertions(+), 41 deletions(-) rename knob/js/circular-range.js => common/directives.js (74%) diff --git a/knob/js/circular-range.js b/common/directives.js similarity index 74% rename from knob/js/circular-range.js rename to common/directives.js index 157bd0e..5f34299 100644 --- a/knob/js/circular-range.js +++ b/common/directives.js @@ -1,3 +1,16 @@ +angular.module('mainApp').directive('restrictScroll', [function () { + return { + restrict: 'A', + link: function (scope, elem, attrs) { + // disable mousewheel on a input number field when in focus + // (to prevent Cromium browsers change the value when scrolling) + $(elem).on("wheel", function (e) { + $(this).blur(); + }); + } + } +}]); + angular.module('mainApp').directive('circularRange', [function () { return { restrict: 'A', diff --git a/index.html b/index.html index 7fe6439..b94bf85 100644 --- a/index.html +++ b/index.html @@ -34,7 +34,7 @@ - + diff --git a/synthesizerV2/audioLibrary.js b/synthesizerV2/audioLibrary.js index 6b50063..ea984c9 100644 --- a/synthesizerV2/audioLibrary.js +++ b/synthesizerV2/audioLibrary.js @@ -150,10 +150,11 @@ class BufferPlayer extends AudioBaseNode { } class ModulatingOscillator extends AudioBaseNode { - constructor(context, modulationType, frequency, detune) { + constructor(context, modulationType, frequency, detune, frequencyDelay) { super(context); - this._modulationOsc = new Oscillator(context, modulationType, frequency, detune, 0.05); + // Same Oscillator with a frequency delay + this._modulationOsc = new Oscillator(context, modulationType, frequency, detune, frequencyDelay); this._mainGain = new Gain(context); // Add a slight distortion @@ -169,25 +170,26 @@ class ModulatingOscillator extends AudioBaseNode { } class ADSREnv extends Gain { - constructor(context, gainADSR) { + constructor(context, gainADSR, offset = 0) { super(context); this.gainADSR = gainADSR; + this.offset = offset; this.setup(); } setup() { const { a = 0, d = 0, s = 0, r = 0 } = this.gainADSR; - this.setValueAtTime(0.00001, 0); + this.setValueAtTime(0.00001, this.context.currentTime + this.offset); if (a && a > 0) { - this.rampValueAtTime(1, (this.context.currentTime + a)); + this.rampValueAtTime(1, (this.context.currentTime + this.offset + a)); } if (d && d > 0 && s && s > 0) { - this.rampValueAtTime(s, (this.context.currentTime + a + d)); + this.rampValueAtTime(s, (this.context.currentTime + this.offset + a + d)); } if (r && r > 0) { - this.rampValueAtTime(0.00001, (this.context.currentTime + a + d + r)); + this.rampValueAtTime(0.00001, (this.context.currentTime + this.offset + a + d + r)); } } } diff --git a/synthesizerV2/main.html b/synthesizerV2/main.html index 6ed7aaf..0114390 100644 --- a/synthesizerV2/main.html +++ b/synthesizerV2/main.html @@ -45,12 +45,14 @@ } .player-control > span > i{ cursor: pointer; + color: #a7a4a4; +} +.player-control > span > i.active{ + color: #fff; }
@@ -58,10 +60,12 @@ Synthesizer 2.0
- +   - +
@@ -101,13 +105,13 @@
- +
- +
@@ -131,19 +135,19 @@
- +
- +
- +
@@ -163,10 +167,10 @@
- + sec(s)  or after  - + times
@@ -311,13 +315,13 @@
- +
- +
@@ -360,9 +364,9 @@

Load pre-saved configuration


Make it a track? (Will update the current track with extracted audio)

- + to - +
diff --git a/synthesizerV2/main.js b/synthesizerV2/main.js index 24ff181..a255b3e 100644 --- a/synthesizerV2/main.js +++ b/synthesizerV2/main.js @@ -45,10 +45,6 @@ angular.module('mainApp').controller('SynthV2Ctrl', ($rootScope, $scope, $http, SynthV2Factory.playChannels($scope.currentProject.channels, $scope.recording); }; - $scope.$on('$destroy', () => { - SynthV2Factory.stop(); - }); - // Update on record time $scope.$on('Record:Timer:Update', () => { $scope.$digest(); @@ -182,6 +178,35 @@ angular.module('mainApp').controller('SynthV2Ctrl', ($rootScope, $scope, $http, document.querySelector("audio").src = `data:audio/ogg;base64,${base64Src}`; } + // Register event handler + function _playOnSpcaeBar(e) { + // Do nothing for text box + const element = $(e.target); + if (element.is("input[type=text]")) { + return; + } + if (e.key === ' ' || e.key === 'Spacebar') { + if ($scope.SynthV2Factory.currentContext && $scope.SynthV2Factory.currentContext.state === 'running') { + console.log('Stopping with key-press'); + $scope.stop(); + } else { + console.log('Playing with key-press'); + $scope.play(); + } + e.preventDefault(); + } + } + $(window).on('keypress', _playOnSpcaeBar); + + $scope.$on('Player:Event', (e, eventType) => { + $scope.$digest(); + }); + + $scope.$on('$destroy', () => { + SynthV2Factory.stop(); + $(window).off('keypress', _playOnSpcaeBar); + }); + $scope.addNewChannel(); _loadSavedConfigs(); }); \ No newline at end of file diff --git a/synthesizerV2/synthPlayer.js b/synthesizerV2/synthPlayer.js index 928a209..b8592b2 100644 --- a/synthesizerV2/synthPlayer.js +++ b/synthesizerV2/synthPlayer.js @@ -69,7 +69,10 @@ class AudioChannel { let startAfter = isNaN(this.startAfter) ? 0 : parseFloat(this.startAfter); let duration = this.duration ? parseFloat(parseFloat(this.duration).toFixed(2)) : null; - offSet += startAfter; + //for first loop we need starts after. + if (!this.loopCount) { + offSet += startAfter; + } return { offSet, duration }; } @@ -197,7 +200,8 @@ class AudioChannel { console.log('ADSR Connected'); - const adsrEnv = new ADSREnv(context, this.gain); + const { offSet } = this.fixDurationAndDelay(); + const adsrEnv = new ADSREnv(context, this.gain, offSet); this.output.connect(adsrEnv); this.output = adsrEnv; } @@ -266,7 +270,9 @@ class AudioChannel { console.log('Mosulator Connected'); - const modulationOsc = new ModulatingOscillator(context, this.modulation.wave, this.modulation.frequency, this.modulation.detune); + const { offSet } = this.fixDurationAndDelay(); + // With a frequency delay of 0.05 + const modulationOsc = new ModulatingOscillator(context, this.modulation.wave, this.modulation.frequency, this.modulation.detune, (offSet + 0.05)); this.output.connect(modulationOsc); this.output = modulationOsc; // Store modulation oscillator @@ -305,11 +311,7 @@ class AudioChannel { // Oscillator console.log('OSC config', this); const { offSet, duration } = this.fixDurationAndDelay(); - // For loop count - if (!this.loopCount) { - this.loopCount = 0; - } - this.loopCount++; + console.log(offSet, duration); const osc = new Oscillator(context, this.wave, this.frequency, this.detune, this.frequencyDelay); if (this.output) { @@ -324,6 +326,12 @@ class AudioChannel { osc.start(offSet); osc.stop(offSet, duration); this._playModulatingOsc(); + + // For loop count + if (!this.loopCount) { + this.loopCount = 0; + } + this.loopCount++; }; /** @@ -341,11 +349,6 @@ class AudioChannel { // Noise console.log('Buffer config', this); const { offSet, duration } = this.fixDurationAndDelay(); - // For loop count - if (!this.loopCount) { - this.loopCount = 0; - } - this.loopCount++; const buffPlayer = new BufferPlayer(context, buffer || this.buffer); @@ -362,12 +365,19 @@ class AudioChannel { buffPlayer.start(offSet); buffPlayer.stop(offSet, duration); this._playModulatingOsc(); + + // For loop count + if (!this.loopCount) { + this.loopCount = 0; + } + this.loopCount++; }; } angular.module('mainApp').factory('SynthV2Factory', ($rootScope) => { const service = { - currentContext: null + currentContext: null, + playduration: null }; let timerWorkers = []; @@ -381,6 +391,8 @@ angular.module('mainApp').factory('SynthV2Factory', ($rootScope) => { // Stop all players first service.stop(); const channelInstances = []; + _broadcastPlayerEvent('START'); + promiseArray = channels.map(channel => { const parsedChannel = AudioChannel.parseJSON(channel); channelInstances.push(parsedChannel); @@ -399,10 +411,17 @@ angular.module('mainApp').factory('SynthV2Factory', ($rootScope) => { return; } const channelInstance = channel.configure(context, destination); + _calculateDuration(channelInstance); if (channelInstance.timerWorker) { timerWorkers.push(channelInstance.timerWorker); } }); + + // Auto stop context + console.log('Will auto stop at ', service.playduration); + if (service.playduration !== null && service.playduration !== -1) { + _scheduleAutoStop(context); + } }).catch(err => { console.log(err); alert('Error while processing channels'); @@ -413,15 +432,22 @@ angular.module('mainApp').factory('SynthV2Factory', ($rootScope) => { if (!service.currentContext) return; service.currentContext.suspend(); + _broadcastPlayerEvent('PAUSE'); }; service.resume = function () { if (!service.currentContext) return; service.currentContext.resume(); + _broadcastPlayerEvent('RESUME'); }; service.stop = function () { + service.playduration = null; + if (service.autoStopTimer) { + clearTimeout(service.autoStopTimer); + service.autoStopTimer = null; + } if (!service.currentContext) return; service.currentContext.close(); @@ -430,8 +456,55 @@ angular.module('mainApp').factory('SynthV2Factory', ($rootScope) => { w.postMessage('stop'); }); timerWorkers = []; + _broadcastPlayerEvent('STOP'); }; + function _broadcastPlayerEvent(...values) { + setTimeout(function () { + $rootScope.$broadcast('Player:Event', ...values); + }, 0); + } + + function _calculateDuration(channel) { + // it's a infinite loop + if (service.playduration === -1) { + return; + } + let playduration = service.playduration || 0; + if (!channel.loop) { + service.playduration = Math.max(service.playduration, parseFloat(channel.totalDuration + channel.startAfter || 0)); + return; + } + if (!(channel.limitRepeatCount && channel.limitRepeatCount > 0) + && !(channel.limitRepeatSec && channel.limitRepeatSec > 0)) { + // Loop without an end + service.playduration = -1; + return; + } + if (channel.limitRepeatCount && channel.limitRepeatCount > 0) { + service.playduration = + Math.max(service.playduration, parseFloat((channel.totalDuration * channel.limitRepeatCount) + channel.startAfter || 0)); + } + + if (channel.limitRepeatSec && channel.limitRepeatSec > 0) { + service.playduration = + Math.max(service.playduration, parseFloat(channel.limitRepeatSec + channel.startAfter || 0)); + } + + } + + function _scheduleAutoStop(context) { + const timeWorker = Utils.setUpScheduler(() => { + if (context.currentTime > service.playduration) { + console.log('Auto-Stopping at ', context.currentTime); + // Stop player + service.stop(); + } + }); + timeWorker.postMessage("start"); + timerWorkers.push(timeWorker); + } + function _setUpRecorder(context, recordTime) { let destination; const chunks = [];