From 79e7274ee7b0e574be618c63919b3e0ce3aeefc6 Mon Sep 17 00:00:00 2001
From: Pierre Ranwez <pierre.ranwez.etu@univ-lille.fr>
Date: Tue, 15 Feb 2022 12:27:44 +0100
Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20loop?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 background.js |  29 ++++----
 content.js    | 184 +++++++++++++++++++-------------------------------
 main.js       |  21 ++++--
 popup.html    |   2 +-
 4 files changed, 101 insertions(+), 135 deletions(-)

diff --git a/background.js b/background.js
index 6da0cbd..eabb704 100644
--- a/background.js
+++ b/background.js
@@ -7,32 +7,44 @@ h1, h2 {
 	font-size: [chiffre2 20:100]px;
 	color: rgb([chiffre 0:255],255,255);
 }`;
-let parameters = `[[\"       \",\"typo\",\"sans-serif;serif;cursive;fantasy\",\"r\",\"note:10\",\"onset:E\"],[\"       \",\"chiffre\",\"0:1000\",\"i:100\",\"cc:60\",\"onset:D\"],[\"       \",\"chiffre2\",\"0:200\",\"i:10\",\"cc:60\",\"onset:E\"],[\"       \",\"couleur\",\"#000:#fff\",\"d\",\"cc:60\",\"loud\"],[\"       \",\"opacité\",\"0:1\",\"d\",\"cc:60\",\"hi\"]]`;
+let parameters = `[[\"       \",\"typo\",\"sans-serif;serif;cursive;fantasy\",\"r\",\"note:10\",\"onset:E\"],[\"       \",\"chiffre\",\"0:1000\",\"i:100\",\"cc:60\",\"onset:D\"],[\"       \",\"chiffre2\",\"0:200\",\"i:10\",\"cc:60\",\"onset:E\"],[\"       \",\"couleur\",\"#000:#fff\",\"d\",\"cc:60\",\"loud\"],[\"       \",\"opacité\",\"0:1\",\"d\",\"cc:1\",\"hi\"]]`;
 let activate = false;
-let activateFav = "ressources/icon256.png";
 let audioB = false;
 let midiB = false;
 let audioI = false;
 let midiI = false;
 let popup = false;
 let loopPlay = false;
+let records = [[
+	{
+		time: 1,
+		data: { value: 0, controller: { number: 1 } },
+		type: 'controlchange'
+	},
+	{
+		time: 200,
+		data: { value: 1, controller: { number: 1 } },
+		type: 'controlchange'
+	}]
+];
 let all = "";
 
 //Initialize the CSS storage on startup
 chrome.runtime.onInstalled.addListener(() => {
 	chrome.storage.sync.set({ css });
 	chrome.storage.sync.set({ activate });
-	chrome.storage.sync.set({ activateFav });
 	chrome.storage.sync.set({ audioB });
 	chrome.storage.sync.set({ midiB });
 	chrome.storage.sync.set({ audioI });
 	chrome.storage.sync.set({ midiI });
 	chrome.storage.sync.set({ popup });
 	chrome.storage.sync.set({ loopPlay });
+	chrome.storage.sync.set({ records });
 	chrome.storage.sync.set({ parameters });
 	chrome.storage.sync.set({ all });
 });
 
+
 /**
  * @description This function is called when popup is opened and add a listener when user close it.
  */
@@ -42,15 +54,4 @@ chrome.runtime.onConnect.addListener(function (port) {
 			chrome.storage.sync.set({ popup: false });
 		});
 	}
-});
-
-function loopPlay2() {
-	console.log('looooop');
-}
-
-chrome.runtime.onMessage.addListener((message, callback) => {
-	// const tabId = getForegroundTabId();
-	// if (message.data === "loop") {
-	// 	chrome.scripting.executeScript({ func: loopPlay2, tabId });
-	// }
 });
\ No newline at end of file
diff --git a/content.js b/content.js
index adcc2e9..6e8d2a5 100644
--- a/content.js
+++ b/content.js
@@ -1,5 +1,3 @@
-var audioEvents = [];
-var midiEvents = [];
 var audioContext = null;
 var meter = null;
 var analyser = null;
@@ -9,17 +7,6 @@ var buf = new Float32Array(buflen);
 var audio = false;
 var midi = false;
 var audioSample = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
-var record = false;
-var records = []; // array of recorded values
-var recording = {
-    time: new Date().getTime(),
-    events: [
-        [1.0, 'controlchange', '1', 0],
-        [2.0, 'controlchange', '1', 1],
-    ]
-}; // dict with current record
-var time;
-var playLoop = false;
 
 var parameterSave = {};
 function range(template, value) {
@@ -177,81 +164,13 @@ function midiEvent(type, data) {
     });
 
 
-    var newTime = new Date().getTime();
-    bpm = ((1 / ((newTime - time) / 1000)) * 60);
-    time = newTime;
-    if (record && bpm == 120) {
-        switch (type) {
-            case 'controlchange':
-                recording['events'].push([(newTime - time) / 1000, type, data.controller.name, data.value]);
-                break;
-            case 'noteon':
-                recording['events'].push([(newTime - time) / 1000, type, data.note.numer, data.value]);
-                break;
-            case 'pitchbend':
-                recording['events'].push([(newTime - time) / 1000, type, type, data.value]);
-                break;
-        }
+    if (isRecording) {
+        const time = Math.floor(performance.now() - recordingTime);
+        RECORDED[recordCount].push({ type, data, time })
     }
 
 }
 
-// Function that play recorded midi loop
-function playMidiLoop(loopId) {
-    chrome.runtime.sendMessage({ data: 'loop' });
-    // if (records[loopId]) {
-    //     playLoopInfo = true;
-    //     while (playLoopInfo && playLoop && records[loopId]['events'].length > 0) {
-    //         chrome.storage.sync.get(['loopPlay'], function (result) {
-    //             playLoopInfo = result.loopPlay;
-    //         });
-    //         console.log(playLoopInfo);
-    //         for (let index = 0; index < records[loopId]['events'].length; index++) {
-    //             const event = records[loopId]['events'][index];
-    //             const eventNext = records[loopId]['events'][(index + 1) % records[loopId]['events'].length];
-    //             // !!! TODO: add a way to play midi events
-    //             audioEvents.forEach(event => {
-    //                 midiValue = event[3];
-    //                 console.log(event[3]);
-    //                 templates = event['templates'];
-    //                 templates.forEach(template => {
-    //                     midiValue = (template['min'] + ((template['max'] - template['min']) * midiValue));
-    //                     if (event[1] == 'noteon' && template['eventType'] == 'note' && template['eventName'] == event[2]) {
-    //                         value = event['value'].replace('[' + template['templateFull'] + ']', midiValue);
-    //                         let els = document.querySelectorAll(event['selector']);
-    //                         els.forEach(e => {
-    //                             e.style[event['property']] = value; // Note velocity
-    //                         });
-    //                     }
-    //                     if (event[1] == 'controlchange' && template['eventType'] == 'cc' && template['eventName'] == event[2]) {
-    //                         value = event['value'].replace('[' + template['templateFull'] + ']', midiValue);
-    //                         let els = document.querySelectorAll(event['selector']);
-    //                         els.forEach(e => {
-    //                             e.style[event['property']] = value; // Control value
-    //                         });
-    //                     }
-    //                     if (event[1] == 'pitchbend' && template['eventType'] == 'pb') {
-    //                         value = event['value'].replace('[' + template['templateFull'] + ']', midiValue);
-    //                         let els = document.querySelectorAll(event['selector']);
-    //                         els.forEach(e => {
-    //                             e.style[event['property']] = value; // Pitchbend value
-    //                         });
-    //                     }
-    //                     if (event[1] == 'loud' && template['eventType'] == 'loud') {
-    //                         value = event['value'].replace('[' + template['templateFull'] + ']', midiValue);
-    //                         let els = document.querySelectorAll(event['selector']);
-    //                         els.forEach(e => {
-    //                             e.style[event['property']] = value; // Pitchbend value
-    //                         });
-    //                     }
-    //                 });
-    //             });
-    //             sleepFor((event[0] - eventNext[0]) * 1000);
-    //         }
-    //     }
-    // }
-}
-
 function createAudioMeter(audioContext, clipLevel, averaging, clipLag) {
     var processor = audioContext.createScriptProcessor(512);
     processor.onaudioprocess = volumeAudioProcess;
@@ -338,11 +257,6 @@ function audioApi() {
     });
 };
 
-function sleepFor(sleepDuration) {
-    var now = new Date().getTime();
-    while (new Date().getTime() < now + sleepDuration) { /* Do nothing */ }
-}
-
 function didntGetStream() {
     chrome.storage.sync.set({ audioI: false });
     audio = false;
@@ -441,7 +355,59 @@ function freqToBin(freq, rounding = 'round') {
     return bin < max ? bin : max;
 }
 
+// Allow to store the current note in an index, for duration computation
+const CURRENT = {};
+// const RECORDED = [];
+chrome.storage.sync.get(['records'], function (result) {
+    RECORDED = result.records;
+});
+
+let recordCount = 0;
+let isRecording = false;
+let isLoop = false;
+let recordingTime = 0;
+let theLoop;
+const record = (status) => {
+    isRecording = status;
+    recordingTime = performance.now();
+    recordCount = RECORDED.length;
+};
+
+
+// Start loop
+const loop = () => {
+    isLoop = !isLoop;
+    isRecording = false;
+    console.log('Loop started', RECORDED);
+    if (RECORDED[0].length) {
+        const loopLength = RECORDED[0][RECORDED.length - 1].time;
+        if (isLoop) {
+            loopNotes();
+            theLoop = setInterval(() => loopNotes(), loopLength);
+        } else {
+            clearInterval(theLoop)
+        }
+    }
+};
+
+const loopNotes = () => {
+    console.log('Looping notes');
+    RECORDED[0].forEach(note => {
+        setTimeout(() => {
+            // Prevent to keep playing also after stop
+            if (!isLoop) return;
+            console.log('Looping :p', note.type);
+            midiEvent(note.type, note.data)
+            // setTimeout(() => play(note.note, 0), 200)
+        }, note.time);
+    })
+}
 
+const reset = () => {
+    RECORDED[0].length = 0;
+    isRecording = false;
+    isLoop = false;
+  }
 
 function audioEvent() {
     analyser.getFloatTimeDomainData(buf);
@@ -485,7 +451,7 @@ function audioEvent() {
     });
 
     const sum = audioSample.reduce((a, b) => a + b, 0);
-    const avg = (sum / audioSample.length) |rgba(16 18 27 / 40%)| 0;
+    const avg = (sum / audioSample.length) || 0;
     var attack = false;
     if (avg + 0.05 < meter.volume) {
         attack = true;
@@ -531,13 +497,6 @@ function audioEvent() {
         }
     });
 
-    var newTime = new Date().getTime();
-    bpm = ((1 / ((newTime - time) / 1000)) * 60);
-    time = newTime;
-    if (record && bpm == 120) {
-        recording['events'].push([(newTime - time) / 1000, 'loud', 'data.controller.name', meter.volume * 1.4]);
-    }
-    // sleepFor(20);
     if (audio) {
         rafID = window.requestAnimationFrame(audioEvent);
     }
@@ -592,30 +551,25 @@ function onMessage({ type, data }) {
             break;
         }
         case 'playLoop': {
-            playLoop = !playLoop;
-            playMidiLoop(data);
+            loop();
             break;
         }
         case 'getLoop': {
-            chrome.runtime.sendMessage({ type: 'midiRecords', data: records });
+            chrome.storage.sync.get(['records'], function (result) {
+                chrome.runtime.sendMessage({ type: 'midiRecords', data: result.records });
+            });
             break;
         }
         case 'record': {
-            record = data;
-            // If record in stopped add it to the list of records.
-            if (!record) {
-                recording['time'] = (new Date().getTime() - recording['time']) / 1000;
-                records.push(recording);
-                recording = {
-                    time: new Date().getTime(),
-                    events: []
-                };
-                chrome.runtime.sendMessage({ type: 'midiRecords', data: records });
-            }
-            else {
-                time = new Date().getTime();
-                recording['time'] = new Date().getTime();
-            }
+            record(data);
+            break;
+        }
+        case 'resetLoop': {
+            reset();
+            chrome.storage.sync.set({ 'records': RECORDED });
+            chrome.storage.sync.get(['records'], function (result) {
+                chrome.runtime.sendMessage({ type: 'midiRecords', data: result.records });
+            });
             break;
         }
     }
diff --git a/main.js b/main.js
index 6d25ebb..328cff7 100644
--- a/main.js
+++ b/main.js
@@ -325,7 +325,7 @@ function onMessage({ type, data }) {
 		case 'midiRecords': {
 			$('#loopList').text('');
 			for (let i = 0; i < data.length; i++) {
-				$('#loopList').append('<tr><td>Loop ' + i + '</td><td>' + data[i]['time'] + 'ms</td><td>' + data[i]['events'].length + '</td><td><button id="loopPlay" value="' + i + '">▶️ Play</button></td></tr>');
+				$('#loopList').append('<tr><td>Loop ' + i + '</td><td>' + data[i][data[i].length - 1].time + 'ms</td><td>' + data[i].length + '</td><td><button id="loopPlay" value="' + i + '">Play</button><button id="loopReset" value="' + i + '">Reset</button></td></tr>');
 			}
 			break;
 		}
@@ -402,12 +402,13 @@ $('#record').on('click', function () {
 		});
 
 });
+
 $(document).on('click', '#loopPlay', function () {
 	if ($(this).text().includes('Play')) {
-		$(this).text('⏸️ Pause');
+		$(this).text('Pause');
 		chrome.storage.sync.set({ loopPlay: true });
 	} else {
-		$(this).text('▶️ Play');
+		$(this).text('Play');
 		chrome.storage.sync.set({ loopPlay: false });
 
 	}
@@ -415,7 +416,16 @@ $(document).on('click', '#loopPlay', function () {
 	chrome.tabs.query({ active: true, currentWindow: true },
 		function (tabs) {
 			chrome.tabs.sendMessage(tabs[0].id, { type: 'playLoop', data: id });
-		});
+		}
+	);
+});
+$(document).on('click', '#loopReset', function () {
+	id = $(this).val();
+	chrome.tabs.query({ active: true, currentWindow: true },
+		function (tabs) {
+			chrome.tabs.sendMessage(tabs[0].id, { type: 'resetLoop', data: id });
+		}
+	);
 });
 
 $(document).on('click', '.icon-tabler-plus', function () {
@@ -438,7 +448,8 @@ $(document).on('click', '.icon-tabler-minus', function () {
 // Wait messages from content script
 chrome.runtime.onMessage.addListener(onMessage);
 // Indicate to Background that the popup is ready
-chrome.runtime.connect({ name: "popup" });
+var port = chrome.runtime.connect({ name: "popup" });
+
 // Load the editor
 loadEditor();
 // Initialize the settings UI
diff --git a/popup.html b/popup.html
index 3ffc1fb..29f3fc8 100644
--- a/popup.html
+++ b/popup.html
@@ -100,7 +100,7 @@
 				<path d="M20 12v3a3 3 0 0 1 -3 3h-13m3 3l-3 -3l3 -3"></path>
 			</svg> LOOPS
 		</summary>
-		<button id="record">Record</button> BPM : <input id="bpm" type="number" value="120" disabled>
+		<button id="record">Record</button>
 		<table width="100%">
 			<thead>
 				<tr>
-- 
GitLab