mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-21 12:21:49 +00:00
fix: address loop practice review feedback
Signed-off-by: Viktor Olausson <viktor.olausson@gmail.com>
This commit is contained in:
parent
3d6e7e5dac
commit
a8db82b61e
@ -7,7 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
### Added
|
||||
|
||||
- **Practice loops are easier to control and understand** — A/B and section
|
||||
loops now share clear start/count-in/repeat choices, can be saved per song,
|
||||
show their bounds on the timeline, and remain visible in the game HUD.
|
||||
Seeking stays free inside the loop while outside seeks restart from A using
|
||||
the selected first-pass behavior.
|
||||
- **`chart-transform` capability domain (#952)** — plugins can now remap the
|
||||
chart before rendering and scoring through a core-owned provider
|
||||
coordinator. Synchronous transforms run after difficulty filtering; host
|
||||
|
||||
@ -1034,6 +1034,7 @@
|
||||
else if (name === 'loop-restarted') {
|
||||
const startTime = _number(source.loopA, null);
|
||||
const endTime = _number(source.loopB, null);
|
||||
const lastRestartAt = _now();
|
||||
if (startTime != null && endTime != null) {
|
||||
// The core loop bridge emits one legacy loop:restart event for
|
||||
// both the initial pass and later wraps. Promote a previously
|
||||
@ -1046,9 +1047,12 @@
|
||||
enabled: true,
|
||||
state: 'active',
|
||||
requesterId: source.requesterId,
|
||||
lastRestartAt,
|
||||
});
|
||||
} else if (currentSession.loop) {
|
||||
currentSession.loop.lastRestartAt = lastRestartAt;
|
||||
currentSession.media.loop = _clone(currentSession.loop);
|
||||
}
|
||||
if (currentSession.loop) currentSession.loop.lastRestartAt = _now();
|
||||
} else if (name === 'loop-stale') {
|
||||
if (currentSession.loop) currentSession.loop.state = 'stale';
|
||||
}
|
||||
|
||||
@ -900,7 +900,7 @@ export let _resetJuceAudioShimChain = function () {};
|
||||
if (wantsPause && seekTime !== undefined) {
|
||||
enqueue(async (gen) => {
|
||||
const r = await _audioSeek(seekTime, 'audio-element-shim', {
|
||||
restartActiveLoopWhilePlaying: true,
|
||||
restartActiveLoopWhilePlaying: forUpcomingPlay,
|
||||
});
|
||||
if (!r.completed) return; // seek cancelled by teardown
|
||||
if (gen !== _juceShimGen) return;
|
||||
|
||||
@ -77,6 +77,10 @@ html { scroll-behavior: smooth; }
|
||||
border-color: rgba(64, 128, 224, 0.5);
|
||||
color: #bfdbfe;
|
||||
}
|
||||
.section-practice-pill.section-practice-pill--section-selected {
|
||||
border-color: #4080e0;
|
||||
color: #93c5fd;
|
||||
}
|
||||
.section-practice-pill.section-practice-pill--active {
|
||||
border-color: #4080e0;
|
||||
color: #93c5fd;
|
||||
|
||||
@ -630,6 +630,7 @@ html[data-scoreboard="off"] #v3-live-performance-hud { display: none !important;
|
||||
border-color: rgba(203, 213, 225, .85);
|
||||
}
|
||||
#v3-player-rail .section-practice-control--v3 .section-practice-pill[aria-expanded="true"],
|
||||
#v3-player-rail .section-practice-control--v3 .section-practice-pill.section-practice-pill--section-selected,
|
||||
#v3-player-rail .section-practice-control--v3 .section-practice-pill.section-practice-pill--active,
|
||||
#v3-player-rail .section-practice-control--v3 .section-practice-pill.section-practice-pill--armed {
|
||||
color: #e2e8f0;
|
||||
@ -637,6 +638,7 @@ html[data-scoreboard="off"] #v3-live-performance-hud { display: none !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
#v3-player-rail .section-practice-control--v3 .section-practice-pill[aria-expanded="true"] .v3-rail-border,
|
||||
#v3-player-rail .section-practice-control--v3 .section-practice-pill.section-practice-pill--section-selected .v3-rail-border,
|
||||
#v3-player-rail .section-practice-control--v3 .section-practice-pill.section-practice-pill--active .v3-rail-border,
|
||||
#v3-player-rail .section-practice-control--v3 .section-practice-pill.section-practice-pill--armed .v3-rail-border {
|
||||
border-color: #22d3ee;
|
||||
@ -824,14 +826,14 @@ html[data-scoreboard="off"] #v3-live-performance-hud { display: none !important;
|
||||
width: .85rem;
|
||||
height: .85rem;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke: currentcolor;
|
||||
stroke-width: 1.8;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
opacity: .9;
|
||||
}
|
||||
.v3-loop-indicator-label {
|
||||
color: currentColor;
|
||||
color: currentcolor;
|
||||
font-weight: 750;
|
||||
letter-spacing: .08em;
|
||||
text-transform: uppercase;
|
||||
|
||||
@ -10,7 +10,9 @@ const ROOT = path.join(__dirname, '..', '..');
|
||||
const HTML = fs.readFileSync(path.join(ROOT, 'static', 'v3', 'index.html'), 'utf8');
|
||||
const SECTION = fs.readFileSync(path.join(ROOT, 'static', 'js', 'section-practice.js'), 'utf8');
|
||||
const LOOPS = fs.readFileSync(path.join(ROOT, 'static', 'js', 'loops.js'), 'utf8');
|
||||
const JUCE_AUDIO = fs.readFileSync(path.join(ROOT, 'static', 'js', 'juce-audio.js'), 'utf8');
|
||||
const APP = fs.readFileSync(path.join(ROOT, 'static', 'app.js'), 'utf8');
|
||||
const STYLE_CSS = fs.readFileSync(path.join(ROOT, 'static', 'style.css'), 'utf8');
|
||||
const V3_CSS = fs.readFileSync(path.join(ROOT, 'static', 'v3', 'v3.css'), 'utf8');
|
||||
|
||||
function practiceMarkup() {
|
||||
@ -123,6 +125,29 @@ test('returning to A gives the loop indicator one restrained pulse', () => {
|
||||
assert.match(V3_CSS, /@media\s*\(prefers-reduced-motion:\s*reduce\)/);
|
||||
});
|
||||
|
||||
test('selected Section Practice remains visible without replacing loop lifecycle styling', () => {
|
||||
assert.match(SECTION, /classList\.toggle\('section-practice-pill--section-selected',\s*_sectionPracticeMode\)/);
|
||||
assert.match(STYLE_CSS, /\.section-practice-pill\.section-practice-pill--section-selected\s*\{/);
|
||||
assert.match(V3_CSS, /\.section-practice-pill\.section-practice-pill--section-selected/);
|
||||
assert.match(V3_CSS, /\.section-practice-pill\.section-practice-pill--active/);
|
||||
assert.match(V3_CSS, /\.section-practice-pill\.section-practice-pill--armed/);
|
||||
});
|
||||
|
||||
test('JUCE pause-and-seek restarts an outside loop only when playback follows', () => {
|
||||
const start = JUCE_AUDIO.indexOf('function flushJuceShimBatchNow(');
|
||||
const end = JUCE_AUDIO.indexOf('function scheduleJuceShimBatchFlush(', start);
|
||||
assert.notEqual(start, -1);
|
||||
assert.notEqual(end, -1);
|
||||
const flush = JUCE_AUDIO.slice(start, end);
|
||||
const pauseAndSeekStart = flush.indexOf('if (wantsPause && seekTime !== undefined)');
|
||||
const pauseAndSeekEnd = flush.indexOf('if (wantsPause) {', pauseAndSeekStart);
|
||||
assert.notEqual(pauseAndSeekStart, -1);
|
||||
assert.notEqual(pauseAndSeekEnd, -1);
|
||||
const pauseAndSeek = flush.slice(pauseAndSeekStart, pauseAndSeekEnd);
|
||||
assert.match(pauseAndSeek, /restartActiveLoopWhilePlaying:\s*forUpcomingPlay/);
|
||||
assert.doesNotMatch(pauseAndSeek, /restartActiveLoopWhilePlaying:\s*true/);
|
||||
});
|
||||
|
||||
test('timeline seeks stay free while paused and restart active loops while playing', () => {
|
||||
assert.match(APP, /setLoopPlayStartTargetResolver\(\(requestedTime\)/);
|
||||
assert.match(APP, /setLoopRestartHandler\(async\s*\(\{\s*trigger\s*\}\)\s*=>\s*\{[\s\S]*startLoop\(\{/);
|
||||
|
||||
@ -177,10 +177,11 @@ test('a loop restart promotes an armed observer snapshot without a second loop-s
|
||||
loopB: 7,
|
||||
currentTime: 3,
|
||||
});
|
||||
const loop = diagnosticsSnapshot(window).state.loop;
|
||||
assert.equal(loop.state, 'active');
|
||||
assert.equal(loop.enabled, true);
|
||||
assert.ok(loop.lastRestartAt);
|
||||
const state = diagnosticsSnapshot(window).state;
|
||||
assert.equal(state.loop.state, 'active');
|
||||
assert.equal(state.loop.enabled, true);
|
||||
assert.ok(state.loop.lastRestartAt);
|
||||
assert.equal(state.media.loop.lastRestartAt, state.loop.lastRestartAt);
|
||||
assert.equal(loopSetEvents.length, 1);
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user