diff --git a/README.md b/README.md
index 8093bda43976ff8bb8756ac318142111f204a046..e85e12485db2993b6a5b7898d167db671d2bd383 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ To ***save*** your template, click on `Save` button or use keybind `CTRL`+`S`. T
 |-------------|-----------|------------------------------------------|
 | note        | id        | Note on, use case : [note:21]            |
 | cc          | id, range | Control change, use case : [cc:10,0,100] |
-| pb          |           | Pitchbend, use case : [pb,0,100]         |
+| pb          | range     | Pitchbend, use case : [pb,0,100]         |
 
 ### Audio Variables
 
@@ -58,6 +58,16 @@ a {
 }
 ```
 
+## Looping
+
+***- - WIP FEATURE - -***
+
+Click record button, play on MIDI device then stop recording.
+
+On click, all tick defined buy a BPM, save midi envent in a disctionnary with a timestamp.
+
+On loop play, execute CSS style in relation with loop.
+
 ## Ressources
 
 - Chrome extensions : https://developer.chrome.com/docs/extensions/mv3/getstarted/
diff --git a/background.js b/background.js
index 97f1689271ce082480a907e19c8830f309771be8..602f013f827d2e25132f4e6d510c514bfd31cace 100644
--- a/background.js
+++ b/background.js
@@ -6,6 +6,7 @@ let midiB = false;
 let audioI = false;
 let midiI = false;
 let popup = false;
+let loopPlay = false;
 
 //Initialize the CSS storage on startup
 chrome.runtime.onInstalled.addListener(() => {
@@ -17,6 +18,7 @@ chrome.runtime.onInstalled.addListener(() => {
 	chrome.storage.sync.set({ audioI });
 	chrome.storage.sync.set({ midiI });
 	chrome.storage.sync.set({ popup });
+	chrome.storage.sync.set({ loopPlay });
 });
 
 /**
diff --git a/content.js b/content.js
index cdb0159c392aff70d618e35965af99679138396c..e336df91eb08a3c83b5593db2d9bbab388bc5bf3 100644
--- a/content.js
+++ b/content.js
@@ -9,6 +9,17 @@ 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;
 
 /**
  * Parse CSS with CSSParser and after add events to midi or audio.
@@ -179,7 +190,7 @@ function midiEvent(type, data) {
     });
     midiEvents.forEach(event => {
         midiValue = data.value;
-        templates = templateToDict(event['value']);
+        templates = event['templates'];
         templates.forEach(template => {
             midiValue = (template['min'] + ((template['max'] - template['min']) * midiValue));
             if (type == 'noteon' && template['eventType'] == 'note' && template['eventName'] == data.note.number) {
@@ -205,8 +216,80 @@ 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;
+        }
+    }
+
 }
 
+// Function that play recorded midi loop
+function playMidiLoop(loopId) {
+    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);
@@ -283,7 +366,7 @@ function audioApi() {
             }
         }
         else {
-            console.warn('Audio suspend');
+            console.info('Audio suspend');
             audioContext.suspend();
             chrome.storage.sync.set({ audioI: false });
             audio = false;
@@ -320,7 +403,7 @@ function gotStream(stream) {
     meter = createAudioMeter(audioContext);
     mediaStreamSource.connect(meter);
     audioContext.resume();
-    console.warn('Audio start');
+    console.info('Audio start');
     chrome.storage.sync.set({ audioI: true });
     chrome.runtime.sendMessage({ type: 'updateUi', data: true });
     // kick off the visual updating
@@ -481,6 +564,14 @@ 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);
@@ -524,6 +615,33 @@ function onMessage({ type, data }) {
             midiApi();
             break;
         }
+        case 'playLoop': {
+            playLoop = !playLoop;
+            playMidiLoop(data);
+            break;
+        }
+        case 'getLoop': {
+            chrome.runtime.sendMessage({ type: 'midiRecords', data: 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();
+            }
+            break;
+        }
     }
 }
 
diff --git a/main.js b/main.js
index 10a475bcf731719ca9a54847b19254309c68b088..895bd8d527fe76e8132e9c0f4ca31eac896d2a6c 100644
--- a/main.js
+++ b/main.js
@@ -96,6 +96,11 @@ function initUi() {
 			$('.midiI').removeClass('active');
 		}
 	});
+	chrome.tabs.query({ active: true, currentWindow: true },
+		function (tabs) {
+			chrome.tabs.sendMessage(tabs[0].id, { type: 'getLoop', data: false });
+		}
+	);
 }
 
 
@@ -142,6 +147,13 @@ function onMessage({ type, data }) {
 			$('#midiDevices').text(data);
 			break;
 		}
+		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>');
+			}
+			break;
+		}
 	}
 }
 
@@ -245,6 +257,40 @@ $('.midiI').on('click', function () {
 	initUi();
 });
 
+/**
+ * Record loop button
+ */
+var record = false;
+$('#record').on('click', function () {
+	record = !record;
+	if (record) {
+		$('#record').text('⏹️ Stop');
+	}
+	else {
+		$('#record').text('⏺️ Record');
+	}
+	chrome.tabs.query({ active: true, currentWindow: true },
+		function (tabs) {
+			chrome.tabs.sendMessage(tabs[0].id, { type: 'record', data: record });
+		});
+
+});
+$(document).on('click', '#loopPlay', function () {
+	if ($(this).text().includes('Play')) {
+		$(this).text('⏸️ Pause');
+		chrome.storage.sync.set({ loopPlay: true });
+	} else {
+		$(this).text('▶️ Play');
+		chrome.storage.sync.set({ loopPlay: false });
+		
+	}
+	id = $(this).val();
+	chrome.tabs.query({ active: true, currentWindow: true },
+		function (tabs) {
+			chrome.tabs.sendMessage(tabs[0].id, { type: 'playLoop', data: id });
+		});
+});
+
 
 // Wait messages from content script
 chrome.runtime.onMessage.addListener(onMessage);
diff --git a/popup.html b/popup.html
index c36207d9ae612ff576b5b6c1da3c1631ae415778..c0a42df8b58937450ee273719c713934e5d6ecbd 100644
--- a/popup.html
+++ b/popup.html
@@ -22,6 +22,24 @@
 	</div>
 	<div id="editor"></div>
 
+	<div>
+		<h3>Loop Settings</h3>
+		<button id="record">Record</button> BPM : <input id="bpm" type="number" value="120" disabled>
+		<table width="100%">
+			<thead>
+				<tr>
+					<th>Nom</th>
+					<th>Temps</th>
+					<th>Nombre d'evenements</th>
+					<th>Actions</th>
+				</tr>
+			</thead>
+			<tbody id="loopList">
+
+			</tbody>
+		</table>
+	</div>
+
 	<script src="jquery-3.6.0.min.js" type="text/javascript" charset="utf-8"></script>
 	<script src="ace/ace.js" type="text/javascript" charset="utf-8"></script>
 	<script src="ace/ext-language_tools.js" type="text/javascript" charset="utf-8"></script>