Initial release
This commit is contained in:
parent
ab3fb26e64
commit
77ae4f9885
52
drivers/linux-rawmidi.js
Normal file
52
drivers/linux-rawmidi.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
const path = require("node:path");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
|
||||||
|
const midiDeviceRegExp = new RegExp("^midi[0-9]+$");
|
||||||
|
|
||||||
|
let openDevice;
|
||||||
|
let callback;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
"list": function () {
|
||||||
|
var deviceNodeList = fs.readdirSync("/dev").filter(d => d.match(midiDeviceRegExp));
|
||||||
|
|
||||||
|
return deviceNodeList.map(d => path.join("/dev", d));
|
||||||
|
},
|
||||||
|
"open": function (device, eventCB) {
|
||||||
|
if (openDevice) {
|
||||||
|
openDevice["read"].close();
|
||||||
|
openDevice["write"].close();
|
||||||
|
}
|
||||||
|
|
||||||
|
var deviceName = path.basename(device);
|
||||||
|
var devicePath = path.join("/dev", deviceName);
|
||||||
|
|
||||||
|
openDevice = {
|
||||||
|
"read": fs.createReadStream(devicePath),
|
||||||
|
"write": fs.createWriteStream(devicePath)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (eventCB) {
|
||||||
|
callback = eventCB;
|
||||||
|
openDevice.read.on("data", function (chunk) {
|
||||||
|
callback(chunk);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"send": function (message) {
|
||||||
|
if (!openDevice) return;
|
||||||
|
|
||||||
|
var data = Buffer.from(new Uint8Array(message));
|
||||||
|
|
||||||
|
openDevice["write"].write(data);
|
||||||
|
},
|
||||||
|
"close": function () {
|
||||||
|
if (!openDevice) return;
|
||||||
|
|
||||||
|
openDevice["read"].close();
|
||||||
|
openDevice["write"].close();
|
||||||
|
|
||||||
|
delete openDevice;
|
||||||
|
delete callback;
|
||||||
|
}
|
||||||
|
};
|
5
index.js
Normal file
5
index.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
if ("electron" in process.versions) {
|
||||||
|
require("./main.js");
|
||||||
|
} else {
|
||||||
|
require("./server.js");
|
||||||
|
}
|
101
main.js
Normal file
101
main.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
const path = require("node:path");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const electron = require("electron");
|
||||||
|
|
||||||
|
let window;
|
||||||
|
|
||||||
|
let driver;
|
||||||
|
let mappings;
|
||||||
|
|
||||||
|
electron.app.on("ready", function () {
|
||||||
|
window = new electron.BrowserWindow({
|
||||||
|
"width": 1280,
|
||||||
|
"height": 480,
|
||||||
|
"backgroundColor": "#080C10FF",
|
||||||
|
"title": "DJ Controller Emulator",
|
||||||
|
"webPreferences": {
|
||||||
|
"preload": path.join(__dirname, "preload.js")
|
||||||
|
},
|
||||||
|
"show": false
|
||||||
|
});
|
||||||
|
|
||||||
|
window.loadFile(path.join(__dirname, "view", "index.html"));
|
||||||
|
|
||||||
|
window.once("ready-to-show", function () {
|
||||||
|
window.show();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.app.on("window-all-closed", function () {
|
||||||
|
electron.app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle("load-driver", function (event, driverName) {
|
||||||
|
if (driver) {
|
||||||
|
driver.close();
|
||||||
|
delete driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
var driverPath = path.join(__dirname, "drivers", path.basename(driverName) + ".js");
|
||||||
|
driver = require(driverPath);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle("load-mappings", function (event, mappingsName) {
|
||||||
|
if (mappings) {
|
||||||
|
delete mappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mappingsPath = path.join(__dirname, "mappings", path.basename(mappingsName) + ".json");
|
||||||
|
mappings = JSON.parse(fs.readFileSync(mappingsPath));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle("list-devices", function (event) {
|
||||||
|
return driver.list();
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle("open-device", function (event, name) {
|
||||||
|
return driver.open(name, function (message) {
|
||||||
|
var byteArray = new Array(...message);
|
||||||
|
var hexStringifiedMessage = byteArray.map(b => b.toString(16)).map(c => ("00".substring(c.length) + c)).join(" ");
|
||||||
|
|
||||||
|
for (var mappingRegExpString in mappings.inbound) {
|
||||||
|
if (mappings.inbound.hasOwnProperty(mappingRegExpString)) {
|
||||||
|
var mappingRegExp = new RegExp(mappingRegExpString);
|
||||||
|
|
||||||
|
var mappingMatches = hexStringifiedMessage.match(mappingRegExp);
|
||||||
|
if (!mappingMatches) continue;
|
||||||
|
|
||||||
|
var mappingName = mappings.inbound[mappingRegExpString];
|
||||||
|
|
||||||
|
mappingMatches.shift();
|
||||||
|
|
||||||
|
var value = parseInt(mappingMatches[0], 16);
|
||||||
|
|
||||||
|
window.webContents.send("signal", mappingName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle("control", function (event, name, value = 0x00) {
|
||||||
|
if (!driver) return false;
|
||||||
|
if (!mappings.outbound[name]) return false;
|
||||||
|
|
||||||
|
var message = mappings.outbound[name].map(function (byte) {
|
||||||
|
if (typeof byte != "number") {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return byte;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
driver.send(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle("close-device", function (event) {
|
||||||
|
return driver.close();
|
||||||
|
});
|
25
mappings/numark-mixtrackplatinum.json
Normal file
25
mappings/numark-mixtrackplatinum.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"inbound": {
|
||||||
|
"bf 44 ([0-9a-f]{2})": "master.volume.left",
|
||||||
|
"bf 44 [0-9a-f]{2} 45 ([0-9a-f]{2})": "master.volume.right"
|
||||||
|
},
|
||||||
|
"outbound": {
|
||||||
|
"deck0.load": [144, 16, 127],
|
||||||
|
"deck1.load": [145, 16, 127],
|
||||||
|
"deck2.load": [146, 16, 127],
|
||||||
|
"deck3.load": [147, 16, 127],
|
||||||
|
"deck0.play_pause": [144, 4, 127],
|
||||||
|
"deck1.play_pause": [145, 4, 127],
|
||||||
|
"deck2.play_pause": [146, 4, 127],
|
||||||
|
"deck3.play_pause": [147, 4, 127],
|
||||||
|
"deck0.volume": [176, 28, ""],
|
||||||
|
"deck1.volume": [177, 28, ""],
|
||||||
|
"deck2.volume": [178, 28, ""],
|
||||||
|
"deck3.volume": [179, 28, ""],
|
||||||
|
"deck0.speed": [176, 9, "", 176, 119, ""],
|
||||||
|
"deck1.speed": [177, 9, "", 177, 119, ""],
|
||||||
|
"deck2.speed": [178, 9, "", 178, 119, ""],
|
||||||
|
"deck3.speed": [179, 9, "", 179, 119, ""],
|
||||||
|
"master.crossfade": [191, 8, ""]
|
||||||
|
}
|
||||||
|
}
|
12
package.json
Normal file
12
package.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "dj-controller-emulator",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "A DJ controller emulator",
|
||||||
|
"main": "index.js",
|
||||||
|
"repository": "https://gitea.x3f200c.net/X3F200C/DJCtrlEmu",
|
||||||
|
"author": "X3F200C",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"dependencies": {
|
||||||
|
"ws": "^8.14.2"
|
||||||
|
}
|
||||||
|
}
|
55
preload.js
Normal file
55
preload.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
const electron = require("electron");
|
||||||
|
|
||||||
|
let volumeMeterLeft;
|
||||||
|
let volumeMeterRight;
|
||||||
|
|
||||||
|
function sendControl(name, value = 0x00) {
|
||||||
|
electron.ipcRenderer.invoke("control", name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.contextBridge.exposeInMainWorld("controller", {
|
||||||
|
"load": function (deckIndex) {
|
||||||
|
sendControl("deck" + String(deckIndex) + ".load");
|
||||||
|
},
|
||||||
|
"play_pause": function (deckIndex) {
|
||||||
|
sendControl("deck" + String(deckIndex) + ".play_pause");
|
||||||
|
},
|
||||||
|
"volume": function (deckIndex, value) {
|
||||||
|
sendControl("deck" + String(deckIndex) + ".volume", value);
|
||||||
|
},
|
||||||
|
"speed": function (deckIndex, value) {
|
||||||
|
sendControl("deck" + String(deckIndex) + ".speed", value);
|
||||||
|
},
|
||||||
|
"crossfade": function (value) {
|
||||||
|
sendControl("master.crossfade", value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcRenderer.on("signal", function (event, name, value = 0x00) {
|
||||||
|
switch (name) {
|
||||||
|
case "master.volume.left":
|
||||||
|
volumeMeterLeft.style.height = String(Math.round((value / 80) * 100)) + "%";
|
||||||
|
break;
|
||||||
|
case "master.volume.right":
|
||||||
|
volumeMeterRight.style.height = String(Math.round((value / 80) * 100)) + "%";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcRenderer.invoke("load-driver", "linux-rawmidi");
|
||||||
|
electron.ipcRenderer.invoke("load-mappings", "numark-mixtrackplatinum");
|
||||||
|
|
||||||
|
(async function () {
|
||||||
|
var devices = await electron.ipcRenderer.invoke("list-devices");
|
||||||
|
|
||||||
|
if (devices.length > 0) {
|
||||||
|
electron.ipcRenderer.invoke("open-device", devices[0]);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
window.onload = function (event) {
|
||||||
|
volumeMeterLeft = document.querySelector("#volume-meter-left");
|
||||||
|
volumeMeterRight = document.querySelector("#volume-meter-right");
|
||||||
|
};
|
153
server.js
Normal file
153
server.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
const path = require("node:path");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const http = require("node:http");
|
||||||
|
const ws = require("ws");
|
||||||
|
|
||||||
|
let driver;
|
||||||
|
let mappings;
|
||||||
|
|
||||||
|
const webServer = new http.Server();
|
||||||
|
|
||||||
|
const socketServer = new ws.WebSocketServer({
|
||||||
|
"noServer": true
|
||||||
|
});
|
||||||
|
|
||||||
|
function sendToClients(message) {
|
||||||
|
socketServer.clients.forEach(function (client) {
|
||||||
|
if (client.readyState === ws.OPEN) {
|
||||||
|
client.send(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
webServer.on("request", function (request, response) {
|
||||||
|
switch (request.method) {
|
||||||
|
case "GET":
|
||||||
|
switch (request.url) {
|
||||||
|
default:
|
||||||
|
var filePath = path.join(__dirname, "view", request.url);
|
||||||
|
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
response.writeHead(404);
|
||||||
|
return response.end("No entry. ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.statSync(filePath).isDirectory()) {
|
||||||
|
var entryPath = path.join(filePath, "index.html");
|
||||||
|
|
||||||
|
if (!fs.existsSync(entryPath)) {
|
||||||
|
response.writeHead(404);
|
||||||
|
return response.end("No entry. ");
|
||||||
|
}
|
||||||
|
|
||||||
|
response.writeHead(200);
|
||||||
|
fs.createReadStream(entryPath).pipe(response);
|
||||||
|
} else {
|
||||||
|
response.writeHead(200);
|
||||||
|
fs.createReadStream(filePath).pipe(response);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
response.writeHead(404);
|
||||||
|
return response.end("No entry. ");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
webServer.on("upgrade", function (request, socket, head) {
|
||||||
|
if (request.url === "/socket") {
|
||||||
|
socketServer.handleUpgrade(request, socket, head, function (ws) {
|
||||||
|
socketServer.emit("connection", ws, request);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socketServer.on("connection", function (socket) {
|
||||||
|
socket.on("error", console.error);
|
||||||
|
|
||||||
|
socket.on("message", function (event) {
|
||||||
|
var data = JSON.parse(event);
|
||||||
|
|
||||||
|
switch (data.event) {
|
||||||
|
case "load-driver":
|
||||||
|
if (driver) {
|
||||||
|
driver.close();
|
||||||
|
delete driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
var driverPath = path.join(__dirname, "drivers", path.basename(data.data.name) + ".js");
|
||||||
|
driver = require(driverPath);
|
||||||
|
break;
|
||||||
|
case "load-mappings":
|
||||||
|
if (mappings) {
|
||||||
|
delete mappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mappingsPath = path.join(__dirname, "mappings", path.basename(data.data.name) + ".json");
|
||||||
|
mappings = JSON.parse(fs.readFileSync(mappingsPath));
|
||||||
|
break;
|
||||||
|
case "list-devices":
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
"event": "device-list",
|
||||||
|
"data": {
|
||||||
|
"devices": driver.list()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
case "open-device":
|
||||||
|
driver.open(data.data.name, function (message) {
|
||||||
|
var byteArray = new Array(...message);
|
||||||
|
var hexStringifiedMessage = byteArray.map(b => b.toString(16)).map(c => ("00".substring(c.length) + c)).join(" ");
|
||||||
|
|
||||||
|
for (var mappingRegExpString in mappings.inbound) {
|
||||||
|
if (mappings.inbound.hasOwnProperty(mappingRegExpString)) {
|
||||||
|
var mappingRegExp = new RegExp(mappingRegExpString);
|
||||||
|
|
||||||
|
var mappingMatches = hexStringifiedMessage.match(mappingRegExp);
|
||||||
|
if (!mappingMatches) continue;
|
||||||
|
|
||||||
|
var mappingName = mappings.inbound[mappingRegExpString];
|
||||||
|
|
||||||
|
mappingMatches.shift();
|
||||||
|
|
||||||
|
var value = parseInt(mappingMatches[0], 16);
|
||||||
|
|
||||||
|
sendToClients(JSON.stringify({
|
||||||
|
"event": "signal",
|
||||||
|
"data": {
|
||||||
|
"name": mappingName,
|
||||||
|
"value": value
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "control":
|
||||||
|
if (!driver) return;
|
||||||
|
if (!mappings.outbound[data.data.name]) return;
|
||||||
|
|
||||||
|
var message = mappings.outbound[data.data.name].map(function (byte) {
|
||||||
|
if (typeof byte != "number") {
|
||||||
|
return data.data.value;
|
||||||
|
} else {
|
||||||
|
return byte;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
driver.send(message);
|
||||||
|
break;
|
||||||
|
case "close-device":
|
||||||
|
driver.close();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
webServer.listen(23272);
|
105
view/assets/scripts/main.js
Normal file
105
view/assets/scripts/main.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
if (!("controller" in window)) {
|
||||||
|
let socket;
|
||||||
|
|
||||||
|
let volumeMeterLeft;
|
||||||
|
let volumeMeterRight;
|
||||||
|
|
||||||
|
function connect(url) {
|
||||||
|
if (socket) {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
socket = new WebSocket(url);
|
||||||
|
|
||||||
|
socket.onmessage = function (event) {
|
||||||
|
var data = JSON.parse(event.data);
|
||||||
|
|
||||||
|
switch (data.event) {
|
||||||
|
case "device-list":
|
||||||
|
if (data.data.devices.length > 0) {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
"event": "open-device",
|
||||||
|
"data": {
|
||||||
|
"name": data.data.devices[0]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "signal":
|
||||||
|
onSignal(event, data.data.name, data.data.value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onopen = function () {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
"event": "load-driver",
|
||||||
|
"data": {
|
||||||
|
"name": "linux-rawmidi"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
"event": "load-mappings",
|
||||||
|
"data": {
|
||||||
|
"name": "numark-mixtrackplatinum"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
"event": "list-devices"
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendControl(name, value = 0x00) {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
"event": "control",
|
||||||
|
"data": {
|
||||||
|
"name": name,
|
||||||
|
"value": value
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.controller = {
|
||||||
|
"load": function (deckIndex) {
|
||||||
|
sendControl("deck" + String(deckIndex) + ".load");
|
||||||
|
},
|
||||||
|
"play_pause": function (deckIndex) {
|
||||||
|
sendControl("deck" + String(deckIndex) + ".play_pause");
|
||||||
|
},
|
||||||
|
"volume": function (deckIndex, value) {
|
||||||
|
sendControl("deck" + String(deckIndex) + ".volume", value);
|
||||||
|
},
|
||||||
|
"speed": function (deckIndex, value) {
|
||||||
|
sendControl("deck" + String(deckIndex) + ".speed", value);
|
||||||
|
},
|
||||||
|
"pad": function (deckIndex, padIndex) {
|
||||||
|
sendControl("deck" + String(deckIndex) + ".pad" + String(padIndex));
|
||||||
|
},
|
||||||
|
"crossfade": function (value) {
|
||||||
|
sendControl("master.crossfade", value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var onSignal = function (event, name, value = 0x00) {
|
||||||
|
switch (name) {
|
||||||
|
case "master.volume.left":
|
||||||
|
volumeMeterLeft.style.height = String(Math.round((value / 80) * 100)) + "%";
|
||||||
|
break;
|
||||||
|
case "master.volume.right":
|
||||||
|
volumeMeterRight.style.height = String(Math.round((value / 80) * 100)) + "%";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onload = function (event) {
|
||||||
|
volumeMeterLeft = document.querySelector("#volume-meter-left");
|
||||||
|
volumeMeterRight = document.querySelector("#volume-meter-right");
|
||||||
|
|
||||||
|
connect("ws://" + window.location.host + "/socket");
|
||||||
|
};
|
||||||
|
}
|
114
view/assets/stylesheets/style.css
Normal file
114
view/assets/stylesheets/style.css
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
:root {
|
||||||
|
--text-color: #FDFDFD;
|
||||||
|
|
||||||
|
background-color: rgba(8, 12, 16, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root, body, main, #sections {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
min-width: 96px;
|
||||||
|
min-height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sections, .deck, #mixer, #volume, #volume-meters, .volume-meter, .deck-pads {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sections, #volume, #volume-meters, .deck-pads {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mixer, .deck {
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deck {
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reverse.deck {
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
#volume {
|
||||||
|
/* flex-grow: 1; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider, .speed-slider {
|
||||||
|
appearance: slider-vertical !important;
|
||||||
|
/* width: 8px;
|
||||||
|
height: 100%; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-meter {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
flex-grow: 1;
|
||||||
|
background-image: linear-gradient(0deg, lime 0%, orange 80%, red 100%);
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-meter-level {
|
||||||
|
width: 100%;
|
||||||
|
background-color: white;
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
transition: height 0.05s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.silence-meter-level {
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 100%;
|
||||||
|
background-color: grey;
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deck-pads, .play {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deck-pads {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 70%;
|
||||||
|
aspect-ratio: 4 / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deck-pad {
|
||||||
|
width: 25%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* input[type="range"] {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
background: var(--text-color);
|
||||||
|
width: 16px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-moz-range-thumb {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
background: var(--text-color);
|
||||||
|
width: 16px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 0px;
|
||||||
|
} */
|
85
view/index.html
Normal file
85
view/index.html
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>DJ Controller Emulator</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="assets/stylesheets/style.css">
|
||||||
|
<script src="assets/scripts/main.js" charset="utf-8"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div id="sections">
|
||||||
|
<div class="deck">
|
||||||
|
<div class="deck-effects">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="deck-playback">
|
||||||
|
<input class="speed-slider" type="range" min="0" value="63" step="1" max="127" oninput="controller.speed(0, 127 - this.value);" ondblclick="controller.speed(0, this.value = 63);">
|
||||||
|
</div>
|
||||||
|
<div class="play">
|
||||||
|
<div class="deck-pads">
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(0, 0);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(0, 1);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(0, 2);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(0, 3);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(0, 4);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(0, 5);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(0, 6);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(0, 7);"></button>
|
||||||
|
</div>
|
||||||
|
<button type="button" onclick="controller.load(0);">Load</button>
|
||||||
|
<button type="button" onclick="controller.play_pause(0);">PlayPause</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="mixer">
|
||||||
|
<div id="knobs">
|
||||||
|
<div class="deck-eq-knobs">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="deck-eq-knobs">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="volume">
|
||||||
|
<input class="volume-slider" type="range" min="0" value="127" step="1" max="127" oninput="controller.volume(0, this.value);">
|
||||||
|
<div id="volume-meters">
|
||||||
|
<div class="volume-meter">
|
||||||
|
<div id="volume-meter-left" class="volume-meter-level"></div>
|
||||||
|
<div class="silence-meter-level"></div>
|
||||||
|
</div>
|
||||||
|
<div class="volume-meter">
|
||||||
|
<div id="volume-meter-right" class="volume-meter-level"></div>
|
||||||
|
<div class="silence-meter-level"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input class="volume-slider" type="range" min="0" value="127" step="1" max="127" oninput="controller.volume(1, this.value);">
|
||||||
|
</div>
|
||||||
|
<input id="crossfader" type="range" min="0" value="63" step="1" max="127" oninput="controller.crossfade(this.value);">
|
||||||
|
</div>
|
||||||
|
<div class="reverse deck">
|
||||||
|
<div class="deck-effects">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="deck-playback">
|
||||||
|
<input class="speed-slider" type="range" min="0" value="63" step="1" max="127" oninput="controller.speed(1, 127 - this.value);" ondblclick="controller.speed(1, this.value = 63);">
|
||||||
|
</div>
|
||||||
|
<div class="play">
|
||||||
|
<div class="deck-pads">
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(1, 0);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(1, 1);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(1, 2);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(1, 3);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(1, 4);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(1, 5);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(1, 6);"></button>
|
||||||
|
<button type="button" class="deck-pad" onclick="controller.pad(1, 7);"></button>
|
||||||
|
</div>
|
||||||
|
<button type="button" onclick="controller.load(1);">Load</button>
|
||||||
|
<button type="button" onclick="controller.play_pause(1);">PlayPause</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user