From 8646f3544af0b8313ca2b278c9889c0dda336f83 Mon Sep 17 00:00:00 2001 From: Vavency Date: Wed, 2 Jul 2025 14:01:40 +0300 Subject: [PATCH] [frontend] SkModPlayer cull channels that aren't visible in the view. --- .../frontend/src/components/SkModPlayer.vue | 72 ++++++++++++++----- 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/packages/frontend/src/components/SkModPlayer.vue b/packages/frontend/src/components/SkModPlayer.vue index d1c00423ae..f6d6005ed9 100644 --- a/packages/frontend/src/components/SkModPlayer.vue +++ b/packages/frontend/src/components/SkModPlayer.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
Pattern Hidden {{ i18n.ts.clickToShow }} @@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -72,6 +72,7 @@ const colours = { const CHAR_WIDTH = 6; const CHAR_HEIGHT = 12; const ROW_OFFSET_Y = 10; +const CHANNEL_WIDTH = CHAR_WIDTH * 14; const MAX_TIME_SPENT = 50; const MAX_TIME_PER_ROW = 15; const MAX_ROW_NUMBERS = 0xFF + 1; @@ -87,7 +88,7 @@ const props = defineProps<{ interface CanvasDisplay { ctx: CanvasRenderingContext2D, html: HTMLCanvasElement, - drawn: { top: number, bottom: number, left: number, right: number }, + drawn: { top: number, bottom: number }, range: { top: number, bottom: number }, vPos: number, transform: { x: number, y: number }, @@ -113,8 +114,13 @@ let position = ref(0); let patternScrollSlider = ref(); let patternScrollSliderShow = ref(false); let patternScrollSliderPos = ref(0); +let patternDisplay = ref(); const player = ref(new ChiptuneJsPlayer(new ChiptuneJsConfig())); +let suppressScrollSliderWatcher = false; +let displayScrollPos = 0; +let currentColumn = 0; +let maxChannelsInView = 10; let buffer = null; let isSeeking = false; let firstFrame = true; @@ -174,7 +180,7 @@ function setupSlice(r: Ref, channels: number) { let slice: CanvasDisplay = { ctx: chtml.getContext('2d', { alpha: false, desynchronized: false }) as CanvasRenderingContext2D, html: chtml, - drawn: { top: 0, bottom: 0, left: 0, right: 0 }, + drawn: { top: 0, bottom: 0 }, range: { top: -0xFFFFFFFF, bottom: -0xFFFFFFFF }, vPos: -0xFFFFFFFF, channels, @@ -193,7 +199,7 @@ function setupCanvas() { nbChannels = player.value.currentPlayingNode.nbChannels; nbChannels = nbChannels > MAX_CHANNEL_LIMIT ? MAX_CHANNEL_LIMIT : nbChannels; } - sliceWidth = numberRowCanvas.width + (14 * CHAR_WIDTH) * nbChannels + 2; + sliceWidth = numberRowCanvas.width + CHANNEL_WIDTH * nbChannels + 2; sliceHeight = HALF_BUFFER * CHAR_HEIGHT; setupSlice(sliceCanvas1, nbChannels); setupSlice(sliceCanvas2, nbChannels); @@ -213,7 +219,6 @@ onMounted(() => { player.value.play(buffer); progress.value!.max = player.value.duration(); bakeNumberRow(); - //populateCanvasSlices(); setupCanvas(); display(true); } catch (err) { @@ -223,6 +228,10 @@ onMounted(() => { }).catch((error) => { console.error(error); }); + if (patternDisplay.value) { + let observer = new ResizeObserver(resizeHandler); + observer.observe(patternDisplay.value); + } }); function playPause() { @@ -306,6 +315,7 @@ function togglePattern() { } function drawSlices(skipOptimizationChecks = false) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (ROW_BUFFER <= 0) { lastDrawnRow = player.value.getPattern(); lastPattern = player.value.getRow(); @@ -339,8 +349,6 @@ function drawSlices(skipOptimizationChecks = false) { sli.drawn = { top: 0xFFFFFFFF, bottom: -0xFFFFFFFF, - left: -0xFFFFFFFF, - right: -0xFFFFFFFF, }; sli.ctx.fillStyle = colours.background; @@ -369,8 +377,6 @@ function drawSlices(skipOptimizationChecks = false) { sli.drawn = { top: 0xFFFFFFFF, bottom: -0xFFFFFFFF, - left: -0xFFFFFFFF, - right: -0xFFFFFFFF, }; sli.ctx.fillStyle = colours.background; @@ -398,6 +404,7 @@ function drawRow(slice: CanvasDisplay, row: number, pattern: number, drawX = (2 if (!player.value.currentPlayingNode) return false; if (row < 0 || row > player.value.getPatternNumRows(pattern) - 1) return false; const spacer = 11; + const skipSpacer = spacer + 3; const space = ' '; let seperators = ''; let note = ''; @@ -405,8 +412,18 @@ function drawRow(slice: CanvasDisplay, row: number, pattern: number, drawX = (2 let volume = ''; let fx = ''; let op = ''; + for (let channel = 0; channel < slice.channels; channel++) { - if (channel > 9) break; + if (channel < currentColumn) { + seperators += space.repeat( skipSpacer ); + note += space.repeat( skipSpacer ); + instr += space.repeat( skipSpacer ); + volume += space.repeat( skipSpacer ); + fx += space.repeat( skipSpacer ); + op += space.repeat( skipSpacer ); + continue; + } + if (channel === maxChannelsInView + currentColumn) break; const part = player.value.getPatternRowChannel(pattern, row, channel); seperators += '|' + space.repeat( spacer + 2 ); @@ -419,8 +436,8 @@ function drawRow(slice: CanvasDisplay, row: number, pattern: number, drawX = (2 //console.debug( 'seperators: ' + seperators + '\nnote: ' + note + '\ninstr: ' + instr + '\nvolume: ' + volume + '\nfx: ' + fx + '\nop: ' + op); - //slice.ctx.fillStyle = '#ff00ff88'; - //slice.ctx.fillRect(0, drawY - CHAR_HEIGHT, 512, CHAR_HEIGHT); + //slice.ctx.fillStyle = '#00ffff88'; + //slice.ctx.fillRect(0, drawY - 10, sliceWidth, CHAR_HEIGHT); slice.ctx.fillStyle = colours.foreground.default; slice.ctx.fillText(seperators, drawX, drawY); @@ -466,17 +483,29 @@ function display(skipOptimizationChecks = false) { drawSlices(skipOptimizationChecks); } -let suppressScrollSliderWatcher = false; +function forceUpdateDisplay() { + const noNode = !player.value.currentPlayingNode; + if (noNode) player.value.play(buffer); + if (!patternHide.value) display(true); + if (noNode) player.value.togglePause(); +} function scrollHandler() { suppressScrollSliderWatcher = true; - if (!patternScrollSlider.value) return; if (!sliceDisplay.value) return; if (!sliceDisplay.value.parentElement) return; - patternScrollSliderPos.value = (sliceDisplay.value.parentElement.scrollLeft) / (sliceWidth - sliceDisplay.value.parentElement.offsetWidth) * 100; - patternScrollSlider.value.style.opacity = '1'; + if (patternScrollSlider.value) { + patternScrollSliderPos.value = (sliceDisplay.value.parentElement.scrollLeft) / (sliceWidth - sliceDisplay.value.parentElement.offsetWidth) * 100; + patternScrollSlider.value.style.opacity = '1'; + } + displayScrollPos = sliceDisplay.value.parentElement.scrollLeft; + const newColumn = Math.trunc((displayScrollPos - numberRowCanvas.width) / CHANNEL_WIDTH); + if (newColumn !== currentColumn) { + currentColumn = newColumn; + forceUpdateDisplay(); + } } function scrollEndHandle() { @@ -495,13 +524,20 @@ function handleScrollBarEnable() { } watch(patternScrollSliderPos, () => { - if (suppressScrollSliderWatcher) return; if (!sliceDisplay.value) return; if (!sliceDisplay.value.parentElement) return; + if (suppressScrollSliderWatcher) return; sliceDisplay.value.parentElement.scrollLeft = (sliceWidth - sliceDisplay.value.parentElement.offsetWidth) * patternScrollSliderPos.value / 100; }); +function resizeHandler(event: ResizeObserverEntry[]) { + if (event[0].contentRect.width === 0) return; + const newView = Math.ceil(event[0].contentRect.width / CHANNEL_WIDTH) + 1; + if (newView > maxChannelsInView) forceUpdateDisplay(); + maxChannelsInView = newView; +} + onDeactivated(() => { stop(); });