Skip to content

feat: 增加谱面延迟自动测量功能#815

Draft
sjfhsjfh wants to merge 19 commits into
mainfrom
auto-offset
Draft

feat: 增加谱面延迟自动测量功能#815
sjfhsjfh wants to merge 19 commits into
mainfrom
auto-offset

Conversation

@sjfhsjfh

Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an automatic chart offset detection capability to the Phira ecosystem by adding a new prpr-auto-offset library, integrating an “Analyze” workflow into the in-game offset adjustment UI, and providing a standalone CLI for batch/manual use.

Changes:

  • Add prpr-auto-offset crate implementing note/audio novelty signals and cross-correlation-based alignment.
  • Add prpr-auto-offset-cli tool to run offset estimation on a Phira chart zip from the command line.
  • Integrate auto-offset analysis UI (graph + async computation) into prpr’s tweak-offset panel, plus new i18n keys.

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tools/auto-offset-cli/src/main.rs New CLI entrypoint to parse chart/audio and run offset estimation.
tools/auto-offset-cli/Cargo.toml Adds tool crate dependencies (clap/anyhow/prpr/prpr-auto-offset/prpr-avc).
prpr/src/scene/game.rs Adds auto-offset analysis state, background computation, and result graph in tweak-offset UI.
prpr/locales/en-US/game.ftl Adds UI strings for analysis prompt/button.
prpr/locales/zh-CN/game.ftl Adds UI strings for analysis prompt/button.
prpr/Cargo.toml Adds prpr-auto-offset dependency and reorganizes dependency blocks/lints.
prpr-avc/Cargo.toml Reorders target-specific sasa dependency blocks.
prpr-auto-offset/src/lib.rs New crate module wiring + public exports.
prpr-auto-offset/src/types.rs Defines AlignConfig and AlignmentResult.
prpr-auto-offset/src/signal.rs Introduces Signal trait abstraction for sampling.
prpr-auto-offset/src/estimate.rs Implements cross-correlation based offset estimation.
prpr-auto-offset/src/audio/* Implements audio novelty signals (SuperFlux / SpectralFlux / EnergyDiff).
prpr-auto-offset/src/note/* Implements note-event signal (Gaussian).
prpr-auto-offset/Cargo.toml New crate dependencies (rayon/realfft/rustfft).
prpr-auto-offset/.gitignore Ignores local test chart artifacts.
phira/Cargo.toml Dependency block/lints reorganization.
phira-monitor/Cargo.toml Adds/reorders deps (prpr/phira-mp-*).
Cargo.toml Adds prpr-auto-offset + includes tools/* in workspace members; workspace dep entries reorganized.
Cargo.lock Locks new crates and dependency updates.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +59 to +75
for i in 0..num_bins {
prev_mags[i] = core::mem::replace(&mut prev_mags[i], buffer[i].norm());
}

if frame == 0 {
novelty.push(0.0);
continue;
}

let flux: f32 = buffer[..num_bins]
.iter()
.enumerate()
.map(|(i, c)| (c.norm() - prev_mags[i]).max(0.0))
.sum();

novelty.push(flux);
}
Comment on lines +80 to +83
fn highpass_50hz(samples: &mut [f32], sample_rate: u32) {
// 1st-order Butterworth: y[n] = alpha*y[n-1] + alpha*(x[n] - x[n-1])
// Remove DC offset first, then initialize state to avoid transient
let dc = samples.iter().take((sample_rate as usize / 10).min(samples.len())).sum::<f32>() / (sample_rate as f32 / 10.0).min(samples.len() as f32);
Comment on lines +13 to +17
impl NoteGaussian {
pub fn new(times: Vec<f64>, sigma: f64) -> Self {
Self { times, sigma }
}
}
Comment on lines +87 to +95
pub fn estimate_with<A: Signal, N: Signal>(audio: &A, note: &N, duration_sec: f64, config: &AlignConfig) -> AlignmentResult {
if duration_sec <= 0.0 {
return AlignmentResult {
offset: 0.0,
correlation: 0.0,
reliable: false,
correlation_curve: Vec::new(),
};
}
Comment thread prpr-auto-offset/src/estimate.rs Outdated
Comment on lines +97 to +118
// Build shared sampling grid centered at search_center_sec
let t_min = config.search_center_sec - config.search_range_sec;
let t_max = config.search_center_sec + duration_sec + config.search_range_sec;
let ts = build_ts_grid(t_min, t_max, config.sampling_interval_sec);

// Sample both signals
let audio_samples = audio.samples(&ts);
let note_samples = note.samples(&ts);

if audio_samples.is_empty() || note_samples.is_empty() {
return AlignmentResult {
offset: 0.0,
correlation: 0.0,
reliable: false,
correlation_curve: Vec::new(),
};
}

// Cross-correlation
let max_lag_bins = (config.search_range_sec / config.sampling_interval_sec).ceil() as usize;
let (corr, best_lag, best_val) = cross_correlation(&note_samples, &audio_samples, max_lag_bins);
let offset = (best_lag as isize - max_lag_bins as isize) as f64 * config.sampling_interval_sec;
Comment thread prpr/src/scene/game.rs
Comment on lines +133 to +140
/// Extract non-fake note hit times from the chart, sorted and filtered to t >= 0.
fn extract_note_times(chart: &Chart) -> Vec<f64> {
let mut times: Vec<f64> = chart
.lines
.iter()
.flat_map(|line| line.notes.iter().map(|note| note.time))
.filter(|&t| t >= 0.0)
.collect();
Comment on lines +47 to +53
fn extract_note_times(chart: &prpr::core::Chart) -> Vec<f64> {
let mut times: Vec<f64> = chart
.lines
.iter()
.flat_map(|line| line.notes.iter().map(|note| note.time))
.filter(|&t| t >= 0.0)
.collect();
Comment on lines +7 to +9
analysis-prompt = Press "Analyze" to auto-analyze
analysis-computing = Analyzing...
auto-offset-btn = Analyze
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants