@mfcc64/ytms 15.1.0 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/script.mjs +302 -122
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mfcc64/ytms",
3
- "version": "15.1.0",
3
+ "version": "17.0.0",
4
4
  "description": "YouTube Musical Spectrum",
5
5
  "main": "script.mjs",
6
6
  "scripts": {
package/script.mjs CHANGED
@@ -42,11 +42,16 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
42
42
  left_color: { def:0xdcb900, min:0, max:0xffffff },
43
43
  right_color:{ def:0x00b9dc, min:0, max:0xffffff },
44
44
  mid_color: { def:0xdcdcdc, min:0, max:0xffffff },
45
+ saturation: { def: 0, min: 0, max: 30 },
46
+ hue: { def: 0, min:-18, max: 19 },
47
+ hue_range: { def: 18, min:-36, max: 36 },
45
48
  interval: { def: 1, min: 1, max: 4 },
46
49
  bar_scale: { def: 0, min: 0, max: 4 },
47
50
  line_mode: { def: 0, min: 0, max: 4 },
48
51
  line_width: { def: 1, min: 1, max: 3 },
49
52
  line_color: { def:0xffffff, min:0, max:0xffffff },
53
+ scroll: { def: 0, min: 0, max: 1 },
54
+ coord_color:{ def:0x000000, min:0, max:0xffffff },
50
55
  transparent:{ def: 1, min: 0, max: 1 },
51
56
  visible: { def: document.location.hostname != "www.youtube.com" ? 1 : 0,
52
57
  min: 0, max: 1 },
@@ -94,7 +99,7 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
94
99
  af_links.style.color = "#FFFFFF";
95
100
  af_links.style.fontSize = "10pt";
96
101
  af_links.style.right = "8px";
97
- af_links.style.bottom = "8px";
102
+ af_links.style.bottom = "calc(var(--ytms-cqt-bottom, 0px) + 8px)";
98
103
  af_links.style.opacity = 1;
99
104
  {
100
105
  const e = (name, ...args) => {
@@ -114,14 +119,14 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
114
119
  e("div", {id: "message"},
115
120
  e("h3", "YouTube Musical Spectrum"),
116
121
  e("ul",
117
- e("li", "By default, the visualization is visible on YT Music page but hidden on YouTube page."),
122
+ e("li", "By default, the visualization is visible on YT Music, Spotify, and SoundCloud but hidden on YouTube page."),
118
123
  e("li", "Press ", e("b", "Ctrl+Alt+G"), " as a shortcut to show/hide visualization. This is equivalent to check/uncheck ", e("b", "Visible"), " setting."),
119
124
  e("li", "Click the ", e("img", {alt: "YTMS"}, {src: icon_16}), " icon at the top left corner to open/close settings."),
120
125
  e("li", "Press ", e("b", "Ctrl+Alt+H"), " to open/close settings and show/hide the ", e("img", {alt: "YTMS"}, {src: icon_16}), " icon."),
121
126
  e("li", "If you want to change the axis, click it."),
122
127
  e("li", "If you want to make your change persistent, click ", e("b", "Set as Default Settings"), " button."),
123
128
  e("li", e("b", "New Features:"), " Custom color, custom range," +
124
- " peak color, bar scale, presets, line visualizer."),
129
+ " peak color, bar scale, line visualizer, hue rotation, more color presets, horizontal scroll, coordinate line."),
125
130
  e("li", e("a", {href: "https://github.com/mfcc64/youtube-musical-spectrum#settings"}, {target: "_blank"}, "Read more..."))
126
131
  ),
127
132
  e("p",
@@ -133,7 +138,6 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
133
138
  e("img", {alt: "YTMS"}, {src: icon_16}, {style: "curson: pointer;"}, {id: "open_message"}),
134
139
  " Support me on ",
135
140
  e("a", {href: "https://www.youtube.com/@mfcc64"}, {target: "_blank"}, "Youtube"), " ",
136
- e("a", {href: "https://www.patreon.com/mfcc64"}, {target: "_blank"}, "Patreon"), " ",
137
141
  e("a", {href: "https://github.com/mfcc64"}, {target: "_blank"}, "GitHub"), " ",
138
142
  e("a", {href: "https://paypal.me/mfcc64"}, {target: "_blank"}, "PayPal"), " ",
139
143
  e("a", {href: "https://saweria.co/mfcc64"}, {target: "_blank"}, "Saweria")
@@ -152,7 +156,7 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
152
156
  af_links.style.display = !af_links_timeout || (child_menu.visible?.checked ?? true) ? "block" : "none";
153
157
  }
154
158
 
155
- const message_version = 11;
159
+ const message_version = 13;
156
160
  af_links.shadowRoot.getElementById("message").style.display = get_opt("message_version") == message_version ? "none" : "block";
157
161
  af_links.shadowRoot.getElementById("close_message").addEventListener("click", function() {
158
162
  set_opt("message_version", message_version);
@@ -162,20 +166,36 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
162
166
  af_links.shadowRoot.getElementById("message").style.display = "block";
163
167
  });
164
168
 
169
+ const media_symbol = Symbol("media_symbol");
170
+ let svideos = [];
171
+ if (document.location.hostname == "soundcloud.com") {
172
+ await new Promise((res, rej) => {
173
+ const old = AudioContext.prototype.createMediaElementSource;
174
+ AudioContext.prototype.createMediaElementSource = function(media) {
175
+ const retval = old.call(this, media);
176
+ ShowCQTElement.global_audio_context = this;
177
+ svideos.push(media);
178
+ media[media_symbol] = retval;
179
+ res();
180
+ return retval;
181
+ };
182
+ });
183
+ }
184
+
165
185
  const cqt = new ShowCQTElement();
166
186
  set_fixed_style(cqt, 9999999);
167
- cqt.style.left = cqt.style.bottom = 0;
168
- cqt.style.width = "100%";
169
187
  let stop_count = 0;
170
188
  const videos = document.getElementsByTagName("video");
171
- let svideos = [];
189
+ const require_refresh = ["open.spotify.com", "soundcloud.com"].indexOf(document.location.hostname) < 0;
172
190
  cqt.render_callback = function() {
173
- let need_refresh = (videos.length != svideos.length);
174
- for (let k = 0; k < videos.length && !need_refresh; k++)
175
- need_refresh = (videos[k] != svideos[k]);
176
- if (need_refresh) {
177
- this.dataset.inputs = "video";
178
- svideos = Array.from(videos);
191
+ if (require_refresh) {
192
+ let need_refresh = (videos.length != svideos.length);
193
+ for (let k = 0; k < videos.length && !need_refresh; k++)
194
+ need_refresh = (videos[k] != svideos[k]);
195
+ if (need_refresh) {
196
+ this.dataset.inputs = "video";
197
+ svideos = Array.from(videos);
198
+ }
179
199
  }
180
200
 
181
201
  let state = 0;
@@ -194,15 +214,14 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
194
214
  #player { margin-top: 0 !important; }`;
195
215
  document.head.appendChild(style);
196
216
  af_links.style.zIndex = 11;
197
- af_links.style.bottom = "calc(var(--ytmusic-player-bar-height, 0) + 8px)";
198
217
  cqt.style.zIndex = 10;
199
218
 
200
219
  function update_cqt_bottom() {
201
220
  if (document.fullscreenElement) {
202
- cqt.style.bottom = 0;
221
+ document.body.style.setProperty("--ytms-cqt-bottom", "0px");
203
222
  af_links.style.visibility = "hidden";
204
223
  } else {
205
- cqt.style.bottom = "var(--ytmusic-player-bar-height, 0)";
224
+ document.body.style.setProperty("--ytms-cqt-bottom", "var(--ytmusic-player-bar-height, 0px)");
206
225
  af_links.style.visibility = "visible";
207
226
  }
208
227
  }
@@ -211,8 +230,84 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
211
230
  update_cqt_bottom();
212
231
  }
213
232
 
214
- if (document.location.hostname == "music.youtube.com")
215
- ytmusic_layout();
233
+ function spotify_layout() {
234
+ cqt.style.zIndex = 4;
235
+ af_links.style.zIndex = 5;
236
+ document.body.style.setProperty("--ytms-cqt-bottom", "88px"); // FIXME
237
+ const old_play = HTMLMediaElement.prototype.play;
238
+ HTMLMediaElement.prototype.play = async function() {
239
+ const ret = old_play.call(this);
240
+ if (this.isConnected || this instanceof HTMLAudioElement)
241
+ return ret;
242
+ for (const video of svideos)
243
+ if (this == video)
244
+ return ret;
245
+
246
+ svideos.push(this);
247
+ const source = cqt.audio_context.createMediaElementSource(this);
248
+ source.connect(cqt.audio_input);
249
+ source.connect(cqt.audio_context.destination);
250
+ return ret;
251
+ };
252
+ }
253
+
254
+ function soundcloud_layout() {
255
+ cqt.style.zIndex = 1000;
256
+ af_links.style.zIndex = 1001;
257
+ svideos[0][media_symbol].connect(cqt.audio_input);
258
+ document.body.style.setProperty("--ytms-cqt-bottom", "var(--play-controls-height, 0px)");
259
+ }
260
+
261
+ switch (document.location.hostname) {
262
+ case "music.youtube.com": ytmusic_layout(); break;
263
+ case "open.spotify.com": spotify_layout(); break;
264
+ case "soundcloud.com": soundcloud_layout(); break;
265
+ }
266
+
267
+ const coord_line_h = document.createElement("div");
268
+ const coord_line_v = document.createElement("div");
269
+ set_fixed_style(coord_line_h, cqt.style.zIndex);
270
+ set_fixed_style(coord_line_v, cqt.style.zIndex);
271
+ coord_line_h.style.left = 0;
272
+ coord_line_h.style.width = "100%";
273
+ coord_line_h.style.height = "1px";
274
+ coord_line_v.style.top = 0;
275
+ coord_line_v.style.height = "100%";
276
+ coord_line_v.style.width = "1px";
277
+ coord_line_h.style.pointerEvents = coord_line_v.style.pointerEvents = "none";
278
+ coord_line_h.style.display = coord_line_v.style.display = "none";
279
+
280
+ function update_cqt_layout(child) {
281
+ if (!child_menu.height || !child_menu.scroll || !child_menu.base_note || !child_menu.semitones)
282
+ return;
283
+
284
+ const width = child_menu.semitones.value * 1;
285
+ const base = child_menu.base_note.value - 16;
286
+ if (base + width > 120) {
287
+ child == child_menu.semitones ?
288
+ (child_menu.base_note.value = 120 - width + 16, child_menu.base_note.onchange()) :
289
+ (child_menu.semitones.value = 120 - base, child_menu.semitones.onchange());
290
+ return;
291
+ }
292
+
293
+ const vleft = -base / width;
294
+ const vwidth = 120 / width;
295
+
296
+ if (child_menu.scroll.value == "0") {
297
+ cqt.style.height = `calc(${ child_menu.height.value / 100} * (100% - var(--ytms-cqt-bottom, 0px)))`;
298
+ cqt.style.width = vwidth * 100 + "%";
299
+ cqt.style.left = vleft * 100 + "%";
300
+ cqt.style.bottom = "var(--ytms-cqt-bottom, 0px)";
301
+ cqt.style.transform = "";
302
+ } else {
303
+ cqt.style.height = child_menu.height.value + "vw";
304
+ cqt.style.width = `calc(${vwidth} * (100vh - var(--ytms-cqt-bottom, 0px)))`;
305
+ cqt.style.left = "100vw";
306
+ cqt.style.bottom = `calc(var(--ytms-cqt-bottom, 0px) + ${vleft} * (100vh - var(--ytms-cqt-bottom, 0px)))`;
307
+ cqt.style.transform = "rotate(-90deg)";
308
+ cqt.style.transformOrigin = "bottom left";
309
+ }
310
+ }
216
311
 
217
312
  child_menu.axis = { value: get_opt("axis") };
218
313
  child_menu.axis.onchange = function() { cqt.dataset.axis = axis_list[child_menu.axis.value]; };
@@ -249,10 +344,14 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
249
344
  menu_div.style.backgroundColor = "#000000DD";
250
345
  menu_div.style.verticalAlign = "middle";
251
346
  menu_div.style.maxHeight = "90%";
347
+ menu_div.style.maxWidth = "90%";
252
348
  menu_div.style.overflow = "auto";
253
- menu_div.style.scrollbarWidth = "none";
349
+ menu_div.style.scrollbarWidth = "8px";
350
+ menu_div.style.scrollbarColor = "#555555 #222222dd";
254
351
  menu_div.style.visibility = "hidden";
255
352
  menu_div.style.cursor = "default";
353
+ menu_table.style.width = "860px";
354
+ menu_div.style.display = "block";
256
355
 
257
356
  var current_tr = null;
258
357
  var current_tr_count = 0;
@@ -316,7 +415,7 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
316
415
  mic.pan.connect(cqt.audio_input);
317
416
  mic.gain.connect(mic.pan);
318
417
 
319
- create_child_range_menu("Height", "height", (child) => cqt.style.height = child.value + "%");
418
+ create_child_range_menu("Height", "height", update_cqt_layout);
320
419
  create_child_range_menu("Bar", "bar", (child) => cqt.dataset.bar = child.value);
321
420
  create_child_range_menu("Waterfall", "waterfall", (child) => cqt.dataset.waterfall = child.value);
322
421
  create_child_range_menu("Brightness", "brightness", (child) => cqt.dataset.brightness = child.value);
@@ -355,8 +454,7 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
355
454
  create_child_range_menu("Scale X", "scale_x", (child) => cqt.dataset.scaleX = child.value);
356
455
  create_child_range_menu("Scale Y", "scale_y", (child) => cqt.dataset.scaleY = child.value);
357
456
 
358
- let bar_scale_func = null;
359
- function create_child_select_bar_scale(title, name) {
457
+ function create_child_select_menu(title, name, select_opt, callback) {
360
458
  var tr = get_menu_table_tr();
361
459
  set_common_tr_style(tr);
362
460
  var td = document.createElement("td");
@@ -365,60 +463,48 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
365
463
  tr.appendChild(td);
366
464
  td = document.createElement("td");
367
465
  td.colSpan = 3;
368
- var child = child_menu[name] = document.createElement("select");
466
+ var child = document.createElement("select");
467
+ if (name) child_menu[name] = child;
369
468
  child.style.cursor = "pointer";
370
469
  child.style.width = "100%";
371
- var select_opt = [ "Linear", "Sqrt", "Log (40 dB)", "Log (60 dB)", "Log (80 dB)" ];
372
- for (var k = 0; k < select_opt.length; k++) {
373
- var opt = document.createElement("option");
470
+ for (let k = 0; k < select_opt.length; k++) {
471
+ const opt = document.createElement("option");
374
472
  opt.textContent = select_opt[k];
375
473
  opt.value = k;
376
474
  child.appendChild(opt);
377
475
  }
378
- child.value = get_opt("bar_scale");
476
+ if (name) child.value = get_opt(name);
379
477
  td.appendChild(child);
380
478
  tr.appendChild(td);
381
- child.onchange = function() {
382
- switch (child.value) {
383
- case "1": bar_scale_func = bar_scale_sqrt; break;
384
- case "2": bar_scale_func = c => bar_scale_db(c, 2); break;
385
- case "3": bar_scale_func = c => bar_scale_db(c, 3); break;
386
- case "4": bar_scale_func = c => bar_scale_db(c, 4); break;
387
- default: bar_scale_func = null;
388
- }
389
- };
479
+ child.onchange = () => callback?.(child);
390
480
  child.onchange();
481
+ }
391
482
 
392
- function bar_scale_sqrt(color) {
393
- for (let k = 3; k < color.length; k += 4)
394
- color[k] = 0.5 * Math.sqrt(color[k]);
395
- }
483
+ let bar_scale_func = null;
396
484
 
397
- function bar_scale_db(color, range) {
398
- for (let k = 3; k < color.length; k += 4)
399
- color[k] = Math.max(0, (Math.log10(color[k]) + range) / range);
400
- }
485
+ function bar_scale_sqrt(color) {
486
+ for (let k = 3; k < color.length; k += 4)
487
+ color[k] = 0.5 * Math.sqrt(color[k]);
401
488
  }
402
- create_child_select_bar_scale("Bar Scale", "bar_scale");
403
489
 
404
- function update_range(child) {
405
- if (!child_menu.base_note || !child_menu.semitones)
406
- return;
490
+ function bar_scale_db(color, range) {
491
+ for (let k = 3; k < color.length; k += 4)
492
+ color[k] = Math.max(0, (Math.log10(color[k]) + range) / range);
493
+ }
407
494
 
408
- const width = child_menu.semitones.value * 1;
409
- const base = child_menu.base_note.value - 16;
410
- if (base + width > 120) {
411
- child == child_menu.semitones ?
412
- (child_menu.base_note.value = 120 - width + 16, child_menu.base_note.onchange()) :
413
- (child_menu.semitones.value = 120 - base, child_menu.semitones.onchange());
414
- return;
495
+ create_child_select_menu("Bar Scale", "bar_scale",
496
+ [ "Linear", "Sqrt", "Log (40 dB)", "Log (60 dB)", "Log (80 dB)" ], (child) => {
497
+ switch (child.value) {
498
+ case "1": bar_scale_func = bar_scale_sqrt; break;
499
+ case "2": bar_scale_func = c => bar_scale_db(c, 2); break;
500
+ case "3": bar_scale_func = c => bar_scale_db(c, 3); break;
501
+ case "4": bar_scale_func = c => bar_scale_db(c, 4); break;
502
+ default: bar_scale_func = null;
415
503
  }
416
- cqt.style.left = -base / width * 100 + "%";
417
- cqt.style.width = 12000 / width + "%";
418
- }
504
+ });
419
505
 
420
- create_child_range_menu("Base Note", "base_note", child => update_range(child));
421
- create_child_range_menu("Semitones", "semitones", child => update_range(child));
506
+ create_child_range_menu("Base Note", "base_note", update_cqt_layout);
507
+ create_child_range_menu("Semitones", "semitones", update_cqt_layout);
422
508
 
423
509
  const number2color = n => "#" + (n|0).toString(16).padStart(6, "0");
424
510
  const color2number = c => Number.parseInt(c.slice(1), 16);
@@ -426,6 +512,78 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
426
512
  const color_table = new Array(9);
427
513
  const peak_color = new Array(3);
428
514
 
515
+ class ColorRotation {
516
+ constructor() {
517
+ const kr = 0.2126;
518
+ const kb = 0.0722;
519
+ const kg = 1 - (kr + kb);
520
+ const kmul = 2 / Math.sqrt(3);
521
+
522
+ this.mat_r = [ 1, 2 * (kb + kg), kmul * (kb - kg) ];
523
+ this.mat_g = [ 1, -2 * kr, kmul * (kb - kg + 1) ];
524
+ this.mat_b = [ 1, -2 * kr, kmul * (kb - kg - 1) ];
525
+
526
+ this.saturation = 0;
527
+ this.hue = 0;
528
+ this.hue_range = 0;
529
+ this.table_len = 1000;
530
+ this.table = new Float32Array(this.table_len * 3 + 6);
531
+ }
532
+
533
+ rgb_from_yuv(res, y, u, v) {
534
+ res[0] = y + this.mat_r[1] * u + this.mat_r[2] * v;
535
+ res[1] = y + this.mat_g[1] * u + this.mat_g[2] * v;
536
+ res[2] = y + this.mat_b[1] * u + this.mat_b[2] * v;
537
+ }
538
+
539
+ update(saturation, hue, hue_range) {
540
+ this.saturation = saturation;
541
+ this.hue = hue;
542
+ this.hue_range = hue_range;
543
+
544
+ if (this.saturation == 0)
545
+ return;
546
+
547
+ const res = [ 0, 0, 0 ];
548
+ const hue_step = Math.PI / 18 * hue_range;
549
+ const hue_start = Math.PI / 18 * hue - 0.5 * hue_step;
550
+ const sat_g = 10 ** (saturation / 10 - 1);
551
+
552
+ for (let k = 0; k <= this.table_len + 1; k++) {
553
+ let y = Math.min(1, k / this.table_len);
554
+ let h = hue_start + Math.sin(0.5 * Math.PI * y) * hue_step;
555
+ h = h - 0.23 * Math.sin(3 * h);
556
+ y = y*y;
557
+ let u = Math.cos(h), v = Math.sin(h), s = 0, sum_rcp = 0;
558
+ this.rgb_from_yuv(res, y, u, v);
559
+ for (let c = 0; c < 3; c++) {
560
+ sum_rcp += Math.exp((res[c] - y) * y * sat_g);
561
+ sum_rcp += Math.exp((y - res[c]) * (1 - y) * sat_g);
562
+ }
563
+ s = y * (1 - y) * sat_g / Math.log(sum_rcp);
564
+ this.rgb_from_yuv(res, y, s*u, s*v);
565
+ const gamma = 1/2.2;
566
+ this.table[3*k+0] = Math.max(0, res[0])**gamma;
567
+ this.table[3*k+1] = Math.max(0, res[1])**gamma;
568
+ this.table[3*k+2] = Math.max(0, res[2])**gamma;
569
+ }
570
+ }
571
+
572
+ transform(color) {
573
+ const len = color.length / 4;
574
+ for (let k = 0; k < len; k++) {
575
+ const y = Math.max(0, Math.min(1, color[4*k+1])) * this.table_len;
576
+ const idx = Math.floor(y);
577
+ const frac = y - idx;
578
+ const ifrac = 1 - frac;
579
+ color[4*k+0] = this.table[3*idx+0] * ifrac + this.table[3*idx+3] * frac;
580
+ color[4*k+1] = this.table[3*idx+1] * ifrac + this.table[3*idx+4] * frac;
581
+ color[4*k+2] = this.table[3*idx+2] * ifrac + this.table[3*idx+5] * frac;
582
+ }
583
+ }
584
+ }
585
+ const color_rotation = new ColorRotation();
586
+
429
587
  function create_child_color_menu(title, name, callback) {
430
588
  var tr = get_menu_table_tr();
431
589
  set_common_tr_style(tr);
@@ -466,7 +624,10 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
466
624
  if (color[k+3] <= color[k-1] || color[k+3] < color[k+7])
467
625
  continue;
468
626
 
469
- const alpha = (1 - (k+2) / color.length) ** 2;
627
+ const alpha = (1 - (k+2) / color.length) ** 2 - 0.16;
628
+ if (alpha <= 0)
629
+ break;
630
+
470
631
  for (let m = 0; m < 3; m++)
471
632
  color[k+m] = Math.min(color[k+m], 1) * (1 - alpha + peak_color[m] * alpha);
472
633
  }
@@ -496,7 +657,25 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
496
657
  create_child_color_menu("Mid Color", "mid_color", child => color_int[1] = color2number(child.value));
497
658
  create_child_color_menu("Right Color", "right_color", child => color_int[2] = color2number(child.value));
498
659
 
660
+ function update_color_rotation() {
661
+ color_rotation.update(Number(child_menu.saturation?.value ?? 0),
662
+ Number(child_menu.hue?.value ?? 0),
663
+ Number(child_menu.hue_range?.value ?? 0));
664
+ }
665
+ create_child_range_menu("Saturation", "saturation", update_color_rotation);
666
+ create_child_range_menu("Hue", "hue", child => {
667
+ if (child.value == 19)
668
+ return child.value = -17, child.onchange();
669
+ if (child.value == -18)
670
+ return child.value = 18, child.onchange();
671
+ update_color_rotation();
672
+ });
673
+ create_child_range_menu("Hue Range", "hue_range", update_color_rotation);
674
+
499
675
  function transform_color(color) {
676
+ if (color_rotation.saturation > 0)
677
+ return color_rotation.transform(color);
678
+
500
679
  if (color_int[0] == 0xdcb900 && color_int[1] == 0xdcdcdc && color_int[2] == 0x00b9dc)
501
680
  return;
502
681
 
@@ -508,31 +687,7 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
508
687
  }
509
688
  }
510
689
 
511
- function create_child_select_line_mode(title, name) {
512
- var tr = get_menu_table_tr();
513
- set_common_tr_style(tr);
514
- var td = document.createElement("td");
515
- set_common_left_td_style(td);
516
- td.textContent = title;
517
- tr.appendChild(td);
518
- td = document.createElement("td");
519
- td.colSpan = 3;
520
- var child = child_menu[name] = document.createElement("select");
521
- child.style.cursor = "pointer";
522
- child.style.width = "100%";
523
- var select_opt = [ "Off", "Static", "Mid Color", "Average", "Spectrum" ];
524
- for (var k = 0; k < select_opt.length; k++) {
525
- var opt = document.createElement("option");
526
- opt.textContent = select_opt[k];
527
- opt.value = k;
528
- child.appendChild(opt);
529
- }
530
- child.value = get_opt("line_mode");
531
- td.appendChild(child);
532
- tr.appendChild(td);
533
- child.onchange = () => {};
534
- }
535
- create_child_select_line_mode("Line Mode", "line_mode");
690
+ create_child_select_menu("Line Mode", "line_mode", [ "Off", "Static", "Mid Color", "Average", "Spectrum" ]);
536
691
  create_child_range_menu("Line Width", "line_width");
537
692
  create_child_color_menu("Line Color", "line_color");
538
693
 
@@ -639,50 +794,55 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
639
794
  tr.appendChild(td);
640
795
  }
641
796
 
797
+ create_child_select_menu("Scroll", "scroll", [ "Vertical", "Horizontal" ], update_cqt_layout);
642
798
  create_child_checkbox_menu("Transparent", "transparent", (child) => cqt.dataset.opacity = child.checked ? "transparent" : "opaque");
643
799
  create_child_checkbox_menu("Visible", "visible", (child) => {
644
800
  cqt.style.display = child.checked ? "block" : "none";
801
+ if (!child.checked)
802
+ coord_line_h.style.display = coord_line_v.style.display = "none";
645
803
  update_af_links();
646
804
  });
647
805
 
648
- function create_child_select_presets() {
649
- var tr = get_menu_table_tr();
650
- set_common_tr_style(tr);
651
- var td = document.createElement("td");
652
- set_common_left_td_style(td);
653
- td.textContent = "Presets";
654
- tr.appendChild(td);
655
- td = document.createElement("td");
656
- td.colSpan = 3;
657
- var child = document.createElement("select");
658
- child.style.cursor = "pointer";
659
- child.style.width = "100%";
806
+ function cqt_coord_line_leave(ev) {
807
+ coord_line_h.style.display = coord_line_v.style.display = "none";
808
+ }
660
809
 
661
- const presets = {
662
- none: { text: "-- Choose Preset --" },
663
- color_default: { text: "Color: Default", command: set_color_default },
664
- color_deep_blue: { text: "Color: Deep Blue", command: set_color_deep_blue },
665
- color_mono_fire: { text: "Color: Mono Fire", command: set_color_mono_fire },
666
- color_juicy_lemon: { text: "Color: Juicy Lemon", command: set_color_juicy_lemon },
667
- color_rain_forest: { text: "Color: Rain Forest", command: set_color_rain_forest },
668
- scale_960: { text: "Scale: 960", command: () => set_scale_preset(960) },
669
- scale_1280: { text: "Scale: 1280", command: () => set_scale_preset(1280) }
670
- };
810
+ function cqt_coord_line_move(ev) {
811
+ coord_line_h.style.display = coord_line_v.style.display = "block";
812
+ coord_line_h.style.top = ev.clientY + "px";
813
+ coord_line_v.style.left = ev.clientX + "px";
814
+ }
671
815
 
672
- for (const name of Object.keys(presets)) {
673
- var opt = document.createElement("option");
674
- opt.textContent = presets[name].text;
675
- opt.value = name;
676
- child.appendChild(opt);
816
+ create_child_color_menu("Coord Color", "coord_color", child => {
817
+ if (child.value == "#000000") {
818
+ coord_line_h.style.display = coord_line_v.style.display = "none";
819
+ cqt.removeEventListener("mouseleave", cqt_coord_line_leave);
820
+ cqt.removeEventListener("mousemove", cqt_coord_line_move);
821
+ } else {
822
+ cqt.addEventListener("mouseleave", cqt_coord_line_leave);
823
+ cqt.addEventListener("mousemove", cqt_coord_line_move);
824
+ coord_line_h.style.backgroundColor = coord_line_v.style.backgroundColor = child.value;
677
825
  }
826
+ });
678
827
 
679
- child.onchange = function() {
680
- presets[child.value]?.command?.();
681
- child.value = "none";
682
- };
683
-
684
- td.appendChild(child);
685
- tr.appendChild(td);
828
+ function create_child_select_presets() {
829
+ const presets = [
830
+ { title: "-- Choose Preset --" },
831
+ { title: "Color: Default", callback: set_color_default },
832
+ { title: "Color: Deep Blue", callback: set_color_deep_blue },
833
+ { title: "Color: Flame on Violet", callback: set_color_flame_on_violet },
834
+ { title: "Color: Mono Fire", callback: set_color_mono_fire },
835
+ { title: "Color: Evergreen", callback: set_color_evergreen },
836
+ { title: "Color: Juicy Lemon", callback: set_color_juicy_lemon },
837
+ { title: "Color: Rain Forest", callback: set_color_rain_forest },
838
+ { title: "Scale: 960", callback: () => set_scale_preset(960) },
839
+ { title: "Scale: 1280", callback: () => set_scale_preset(1280) }
840
+ ];
841
+
842
+ create_child_select_menu("Presets", null, presets.map(v => v.title), child => {
843
+ presets[child.value|0]?.callback?.();
844
+ child.value = 0;
845
+ });
686
846
 
687
847
  function set_color_preset(...args) {
688
848
  child_menu.left_color.value = number2color(args[0]), child_menu.left_color.onchange();
@@ -690,6 +850,15 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
690
850
  child_menu.right_color.value = number2color(args[2]), child_menu.right_color.onchange();
691
851
  child_menu.peak_color.value = number2color(args[3]), child_menu.peak_color.onchange();
692
852
  child_menu.brightness.value = args[4], child_menu.brightness.onchange();
853
+ child_menu.saturation.value = 0, child_menu.saturation.onchange();
854
+ }
855
+
856
+ function set_color_hue_preset(sat, hue, range, br, peak) {
857
+ child_menu.saturation.value = sat, child_menu.saturation.onchange();
858
+ child_menu.hue.value = hue, child_menu.hue.onchange();
859
+ child_menu.hue_range.value = range, child_menu.hue_range.onchange();
860
+ child_menu.brightness.value = br, child_menu.brightness.onchange();
861
+ child_menu.peak_color.value = number2color(peak), child_menu.peak_color.onchange();
693
862
  }
694
863
 
695
864
  function set_color_default() {
@@ -701,10 +870,18 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
701
870
  set_color_preset(0x6400b9, 0x6464dc, 0x0064b9, 0x0000ff, 50);
702
871
  }
703
872
 
873
+ function set_color_flame_on_violet() {
874
+ set_color_hue_preset(20, -3, 18, 10, 0x6633cc);
875
+ }
876
+
704
877
  function set_color_mono_fire() {
705
878
  set_color_preset(0xb94a25, 0xdc582c, 0xb94a25, 0xff0000, 70);
706
879
  }
707
880
 
881
+ function set_color_evergreen() {
882
+ set_color_hue_preset(20, 15, -18, 10, 0x3366cc);
883
+ }
884
+
708
885
  function set_color_juicy_lemon() {
709
886
  set_color_preset(0xff004a, 0xffff58, 0x00ff4a, 0xffffff, 33);
710
887
  }
@@ -778,7 +955,8 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
778
955
  setTimeout(function(){ child.value = "Reset Default Settings"; }, 300);
779
956
  });
780
957
 
781
- menu_div.appendChild(menu_table);
958
+ menu_div.attachShadow({mode: "open"});
959
+ menu_div.shadowRoot.appendChild(menu_table);
782
960
  menu.onclick = function() {
783
961
  menu_is_hidden = !menu_is_hidden;
784
962
  if (menu_is_hidden)
@@ -819,6 +997,8 @@ import {ShowCQTElement} from "../../showcqt-element@2/showcqt-element.mjs";
819
997
 
820
998
  create_menu();
821
999
  document.body.appendChild(cqt);
1000
+ document.body.appendChild(coord_line_h);
1001
+ document.body.appendChild(coord_line_v);
822
1002
  document.body.appendChild(af_links);
823
1003
  dispatchEvent(new CustomEvent("youtube-musical-spectrum-is-available"));
824
1004
  })().catch(e => console.error(e));