add cc curve
Browse files- javascript/app.js +70 -15
javascript/app.js
CHANGED
|
@@ -229,9 +229,12 @@ class MidiVisualizer extends HTMLElement{
|
|
| 229 |
}
|
| 230 |
|
| 231 |
addTrack(id, tr, cl, name, color){
|
| 232 |
-
const track = {id, tr, cl, name, color,
|
|
|
|
| 233 |
instrument: cl===9?"Standard Drum":"Acoustic Grand",
|
| 234 |
-
svg: document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
|
|
|
|
|
|
| 235 |
this.svg.appendChild(track.svg)
|
| 236 |
const trackItem = this.createTrackItem(track);
|
| 237 |
this.trackList.appendChild(trackItem);
|
|
@@ -269,12 +272,18 @@ class MidiVisualizer extends HTMLElement{
|
|
| 269 |
const content = document.createElement('div');
|
| 270 |
content.style.paddingLeft = '30px';
|
| 271 |
content.style.flexGrow = '1';
|
|
|
|
| 272 |
content.innerHTML = `<p>${track.name}<br>${track.instrument}</p>`;
|
| 273 |
trackItem.appendChild(content);
|
| 274 |
track.updateInstrument = function (instrument){
|
| 275 |
track.instrument = instrument;
|
| 276 |
content.innerHTML = `<p>${track.name}<br>${track.instrument}</p>`;
|
| 277 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
|
| 279 |
const toggleSwitch = document.createElement('input');
|
| 280 |
toggleSwitch.type = 'checkbox';
|
|
@@ -342,25 +351,31 @@ class MidiVisualizer extends HTMLElement{
|
|
| 342 |
duration = midiEvent[6]
|
| 343 |
}
|
| 344 |
let vis_track = this.getTrack(track, channel);
|
| 345 |
-
|
| 346 |
let x = (t/this.timePreBeat)*this.config.beatWidth
|
| 347 |
let y = (127 - pitch)*this.config.noteHeight
|
| 348 |
let w = (duration/this.timePreBeat)*this.config.beatWidth
|
| 349 |
let h = this.config.noteHeight
|
| 350 |
this.svgWidth = Math.ceil(Math.max(x + w, this.svgWidth))
|
| 351 |
-
let color = vis_track.color
|
| 352 |
let opacity = Math.min(1, velocity/127 + 0.1).toFixed(2)
|
| 353 |
-
let rect = this.drawNote(vis_track
|
| 354 |
-
|
| 355 |
-
midiEvent.push(rect)
|
| 356 |
this.setPlayTime(t);
|
| 357 |
this.pianoRoll.scrollTo(this.svgWidth - this.pianoRoll.offsetWidth, this.pianoRoll.scrollTop)
|
| 358 |
}else if(midiEvent[0] === "patch_change"){
|
| 359 |
-
let track = midiEvent[2]
|
| 360 |
-
let channel = midiEvent[3]
|
| 361 |
-
this.patches[channel].push([t, midiEvent[4]])
|
| 362 |
-
this.patches[channel].sort((a, b) => a[0] - b[0])
|
| 363 |
this.getTrack(track, channel);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
}
|
| 365 |
this.midiEvents.push(midiEvent);
|
| 366 |
this.svg.style.width = `${this.svgWidth}px`;
|
|
@@ -368,22 +383,54 @@ class MidiVisualizer extends HTMLElement{
|
|
| 368 |
|
| 369 |
}
|
| 370 |
|
| 371 |
-
drawNote(
|
| 372 |
-
if (!svg) {
|
| 373 |
return null;
|
| 374 |
}
|
| 375 |
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
| 376 |
rect.classList.add('note');
|
| 377 |
-
|
|
|
|
| 378 |
// Round values to the nearest integer to avoid partially filled pixels.
|
| 379 |
rect.setAttribute('x', `${Math.round(x)}`);
|
| 380 |
rect.setAttribute('y', `${Math.round(y)}`);
|
| 381 |
rect.setAttribute('width', `${Math.round(w)}`);
|
| 382 |
rect.setAttribute('height', `${Math.round(h)}`);
|
| 383 |
-
svg.appendChild(rect);
|
| 384 |
return rect
|
| 385 |
}
|
| 386 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
finishAppendMidiEvent(){
|
| 388 |
this.pause()
|
| 389 |
let midiEvents = this.midiEvents.sort((a, b)=>a[1]-b[1])
|
|
@@ -405,6 +452,14 @@ class MidiVisualizer extends HTMLElement{
|
|
| 405 |
}
|
| 406 |
lastT = t;
|
| 407 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 408 |
}
|
| 409 |
|
| 410 |
setPlayTime(t){
|
|
|
|
| 229 |
}
|
| 230 |
|
| 231 |
addTrack(id, tr, cl, name, color){
|
| 232 |
+
const track = {id, tr, cl, name, color, empty: true,
|
| 233 |
+
lastCC: new Map(),
|
| 234 |
instrument: cl===9?"Standard Drum":"Acoustic Grand",
|
| 235 |
+
svg: document.createElementNS('http://www.w3.org/2000/svg', 'g'),
|
| 236 |
+
ccPaths: new Map()
|
| 237 |
+
}
|
| 238 |
this.svg.appendChild(track.svg)
|
| 239 |
const trackItem = this.createTrackItem(track);
|
| 240 |
this.trackList.appendChild(trackItem);
|
|
|
|
| 272 |
const content = document.createElement('div');
|
| 273 |
content.style.paddingLeft = '30px';
|
| 274 |
content.style.flexGrow = '1';
|
| 275 |
+
content.style.color = "grey"
|
| 276 |
content.innerHTML = `<p>${track.name}<br>${track.instrument}</p>`;
|
| 277 |
trackItem.appendChild(content);
|
| 278 |
track.updateInstrument = function (instrument){
|
| 279 |
track.instrument = instrument;
|
| 280 |
content.innerHTML = `<p>${track.name}<br>${track.instrument}</p>`;
|
| 281 |
}
|
| 282 |
+
track.setEmpty = function (empty){
|
| 283 |
+
if (empty!==track.empty){
|
| 284 |
+
content.style.color = empty?"grey":"black";
|
| 285 |
+
}
|
| 286 |
+
}
|
| 287 |
|
| 288 |
const toggleSwitch = document.createElement('input');
|
| 289 |
toggleSwitch.type = 'checkbox';
|
|
|
|
| 351 |
duration = midiEvent[6]
|
| 352 |
}
|
| 353 |
let vis_track = this.getTrack(track, channel);
|
| 354 |
+
vis_track.setEmpty(false);
|
| 355 |
let x = (t/this.timePreBeat)*this.config.beatWidth
|
| 356 |
let y = (127 - pitch)*this.config.noteHeight
|
| 357 |
let w = (duration/this.timePreBeat)*this.config.beatWidth
|
| 358 |
let h = this.config.noteHeight
|
| 359 |
this.svgWidth = Math.ceil(Math.max(x + w, this.svgWidth))
|
|
|
|
| 360 |
let opacity = Math.min(1, velocity/127 + 0.1).toFixed(2)
|
| 361 |
+
let rect = this.drawNote(vis_track, x,y,w,h, opacity)
|
| 362 |
+
midiEvent.push(rect);
|
|
|
|
| 363 |
this.setPlayTime(t);
|
| 364 |
this.pianoRoll.scrollTo(this.svgWidth - this.pianoRoll.offsetWidth, this.pianoRoll.scrollTop)
|
| 365 |
}else if(midiEvent[0] === "patch_change"){
|
| 366 |
+
let track = midiEvent[2];
|
| 367 |
+
let channel = midiEvent[3];
|
| 368 |
+
this.patches[channel].push([t, midiEvent[4]]);
|
| 369 |
+
this.patches[channel].sort((a, b) => a[0] - b[0]);
|
| 370 |
this.getTrack(track, channel);
|
| 371 |
+
}else if(midiEvent[0] === "control_change"){
|
| 372 |
+
let track = midiEvent[2];
|
| 373 |
+
let channel = midiEvent[3];
|
| 374 |
+
let controller = midiEvent[4];
|
| 375 |
+
let value = midiEvent[5];
|
| 376 |
+
let vis_track = this.getTrack(track, channel);
|
| 377 |
+
this.drawCC(vis_track, t, controller, value);
|
| 378 |
+
this.setPlayTime(t);
|
| 379 |
}
|
| 380 |
this.midiEvents.push(midiEvent);
|
| 381 |
this.svg.style.width = `${this.svgWidth}px`;
|
|
|
|
| 383 |
|
| 384 |
}
|
| 385 |
|
| 386 |
+
drawNote(track, x, y, w, h, opacity) {
|
| 387 |
+
if (!track.svg) {
|
| 388 |
return null;
|
| 389 |
}
|
| 390 |
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
| 391 |
rect.classList.add('note');
|
| 392 |
+
const color = track.color;
|
| 393 |
+
rect.setAttribute('fill', `rgba(${color.r}, ${color.g}, ${color.b}, ${opacity})`);
|
| 394 |
// Round values to the nearest integer to avoid partially filled pixels.
|
| 395 |
rect.setAttribute('x', `${Math.round(x)}`);
|
| 396 |
rect.setAttribute('y', `${Math.round(y)}`);
|
| 397 |
rect.setAttribute('width', `${Math.round(w)}`);
|
| 398 |
rect.setAttribute('height', `${Math.round(h)}`);
|
| 399 |
+
track.svg.appendChild(rect);
|
| 400 |
return rect
|
| 401 |
}
|
| 402 |
|
| 403 |
+
drawCC(track, t, controller, value){
|
| 404 |
+
if (!track.svg) {
|
| 405 |
+
return null;
|
| 406 |
+
}
|
| 407 |
+
let path = track.ccPaths.get(controller);
|
| 408 |
+
let x = (t/this.timePreBeat)*this.config.beatWidth
|
| 409 |
+
let y = (127 - value)*this.config.noteHeight
|
| 410 |
+
if (!path){
|
| 411 |
+
path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
| 412 |
+
path.setAttribute('visibility',"hidden");
|
| 413 |
+
path.setAttribute('fill', "transparent");
|
| 414 |
+
const color = track.color;
|
| 415 |
+
path.setAttribute('stroke', `rgba(${color.r}, ${color.g}, ${color.b}, 0.6)`);
|
| 416 |
+
path.setAttribute('stroke-width', "1");
|
| 417 |
+
path.setAttribute('d',
|
| 418 |
+
t===0?`M ${x} ${y}`:`M 0 ${127*this.config.noteHeight} H ${x} L ${x} ${y}`);
|
| 419 |
+
track.svg.appendChild(path);
|
| 420 |
+
track.ccPaths.set(controller, path);
|
| 421 |
+
track.lastCC.set(controller, value);
|
| 422 |
+
return path;
|
| 423 |
+
}
|
| 424 |
+
let lastVal = track.lastCC.get(controller);
|
| 425 |
+
if(lastVal !== value){
|
| 426 |
+
path.removeAttribute('visibility');
|
| 427 |
+
}
|
| 428 |
+
let d = path.getAttribute("d");
|
| 429 |
+
d += `H ${x} L ${x} ${y}`
|
| 430 |
+
path.setAttribute('d', d);
|
| 431 |
+
return path
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
finishAppendMidiEvent(){
|
| 435 |
this.pause()
|
| 436 |
let midiEvents = this.midiEvents.sort((a, b)=>a[1]-b[1])
|
|
|
|
| 452 |
}
|
| 453 |
lastT = t;
|
| 454 |
})
|
| 455 |
+
let x = (lastT/this.timePreBeat)*this.config.beatWidth;
|
| 456 |
+
this.trackMap.forEach((track, id)=>{
|
| 457 |
+
track.ccPaths.forEach((path, controller)=>{
|
| 458 |
+
let d = path.getAttribute("d");
|
| 459 |
+
d += `H ${x}`
|
| 460 |
+
path.setAttribute('d', d);
|
| 461 |
+
})
|
| 462 |
+
})
|
| 463 |
}
|
| 464 |
|
| 465 |
setPlayTime(t){
|