Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6421a37
Update YARG.Core submodule.
asimard1 Jun 30, 2026
004cd32
Add a button in the the score menu to update .ini file using average …
asimard1 Jun 30, 2026
4df7780
Add button to apply song offset from score screen
asimard1 Jun 30, 2026
99fcbf9
Only show song calibration buttons if there's only one player and if …
asimard1 Jul 1, 2026
c97bd2f
Only show song calibration buttons if on the histogram view (to evalu…
asimard1 Jul 1, 2026
17e2ee4
Merge remote-tracking branch 'upstream/dev' into feature/song-calibra…
asimard1 Jul 1, 2026
9ba2488
Merge remote-tracking branch 'upstream/dev' into feature/song-calibra…
asimard1 Jul 1, 2026
594c853
Change variable name to avoid collision
asimard1 Jul 1, 2026
29032a8
Merge remote-tracking branch 'upstream/dev' into feature/song-calibra…
asimard1 Jul 1, 2026
1a5084d
Remove comma in button entry list
asimard1 Jul 1, 2026
efd5ee5
Merge branch 'feature/song-calibration' of https://github.com/asimard…
asimard1 Jul 1, 2026
3b3a407
Write to .json file instead of .ini file
asimard1 Jul 1, 2026
f7134f5
Modified YARG.Core (back to original)
asimard1 Jul 1, 2026
dbaad7f
Song load reads jsons file and adjusts offset
asimard1 Jul 2, 2026
b8fd44e
Add setting to control whether options show. Add French and English l…
asimard1 Jul 2, 2026
5c548c1
Make it so button needs to be held
asimard1 Jul 2, 2026
51447e3
Added another option to limit this to one player games
asimard1 Jul 2, 2026
b933695
Fix small bug
asimard1 Jul 2, 2026
1835821
Add player filtering in online mode
asimard1 Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Assets/Script/Gameplay/GameManager.Loading.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,21 @@ private async void Start()

FinalizeChart();

// Add the offset read from the .json file placed in PathHelper.PersistentDataPath
var totalOffsetSeconds = Song.SongOffsetSeconds;
if (SettingsManager.Settings.UseSongOffsetCalibration.Value)
{
var offsetOverrideMs = SongOffsetContainer.GetOffsetMilliseconds(Song.Hash.ToString());
totalOffsetSeconds += offsetOverrideMs / 1000.0;
}

// Initialize song runner
_songRunner = new SongRunner(
_mixer,
startTime: 0,
SONG_START_DELAY,
GlobalVariables.State.SongSpeed,
Song.SongOffsetSeconds);
totalOffsetSeconds);

// Spawn players
CreatePlayers();
Expand Down
11 changes: 11 additions & 0 deletions Assets/Script/Gameplay/GameManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,17 @@ private bool EndSong()
}).ToArray(),
BandScore = BandScore,
BandStars = (int) BandStars,

// TODO: When online comes out, change
// .Where(player => !player.Player.Profile.IsBot)
// to:
// .Where(player => !(player.Player.Profile.IsBot || player.Player.IsRemote))
MeanAverageOffset = _players
.Where(player => !player.Player.Profile.IsBot)
.Select(player => player.BaseStats.GetAverageOffset())
.DefaultIfEmpty(0)
.Average(),

ReplayInfo = replayInfo,
};

Expand Down
2 changes: 2 additions & 0 deletions Assets/Script/Menu/ScoreScreen/ScoreScreenContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public struct ScoreScreenStats
public int BandStars;
public int BandScore;

public double MeanAverageOffset;

#nullable enable
public ReplayInfo? ReplayInfo;
#nullable disable
Expand Down
75 changes: 75 additions & 0 deletions Assets/Script/Menu/ScoreScreen/ScoreScreenMenu.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using Newtonsoft.Json;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
Expand All @@ -14,6 +16,7 @@
using YARG.Core.Engine.Keys;
using YARG.Core.Engine.Vocals;
using YARG.Core.Input;
using YARG.Core.IO.Ini;
using YARG.Core.Logging;
using YARG.Core.Replays;
using YARG.Core.Replays.Analyzer;
Expand All @@ -25,11 +28,13 @@
using YARG.Scores;
using YARG.Song;
using YARG.Playlists;
using YARG.Helpers;
using YARG.Helpers.Extensions;
using YARG.Core.Engine;
using YARG.Playback;
using YARG.Settings;


namespace YARG.Menu.ScoreScreen
{
public class ScoreScreenMenu : MonoBehaviour
Expand Down Expand Up @@ -80,6 +85,10 @@ private enum ScrollDirection
private bool _analyzingReplay;
private bool _restartingSong;
private bool _showAdvancedStats;
private bool _offsetModified;
private int _humanPlayerCount;
private string _songHashKey;
private Dictionary<string, long> _offsets;

private float _horizontalScrollStep;
private Tween _horizontalScrollTween;
Expand All @@ -102,6 +111,10 @@ private void OnEnable()

ShowReplayAnalysis(song, scoreScreenStats);

_humanPlayerCount = scoreScreenStats.PlayerScores.Count(p => !p.Player.Profile.IsBot);
_songHashKey = song.Hash.ToString();
_offsets = SongOffsetContainer.LoadOffsets();

// Play audience chatter
if (SettingsManager.Settings.UseCrowdFx.Value == CrowdFxMode.Enabled)
{
Expand Down Expand Up @@ -146,6 +159,8 @@ private void OnEnable()

private void OnDisable()
{
// The offsets will not be saved if a user exits the game while in result screen
SongOffsetContainer.SaveOffsets(_offsets);
MusicLibraryMenu.CurrentlyPlaying = GlobalVariables.State.CurrentSong;
if (!GlobalVariables.State.PlayingAShow && !_restartingSong)
{
Expand Down Expand Up @@ -401,6 +416,7 @@ private bool AnalyzeReplay(SongEntry songEntry, ReplayInfo? replayEntry)
private NavigationScheme.Entry _showAdvancedButtonEntry;
private NavigationScheme.Entry _removeFavoriteButtonEntry;
private NavigationScheme.Entry _addFavoriteButtonEntry;
private NavigationScheme.Entry _toggleOffsetEntry;
private NavigationScheme.Entry _scrollLeftEntry;
private NavigationScheme.Entry _scrollRightEntry;
private NavigationScheme.Entry _scrollUpEntry;
Expand Down Expand Up @@ -459,6 +475,8 @@ private void SetNavigationScheme()

UpdateShowAdvancedButton();

UpdateAddOffsetButton();

_scrollLeftEntry = new NavigationScheme.Entry(MenuAction.Left, "Menu.Common.Scroll", context =>
{
ScrollScoresHorizontal(ScrollDirection.Left, context.IsRepeat);
Expand Down Expand Up @@ -544,6 +562,56 @@ private void UpdateShowAdvancedButton()
_showAdvancedButtonEntry = new NavigationScheme.Entry(MenuAction.Orange, key, ToggleAdvancedStats);
}

private void ToggleOffsetToJson()
{
var offset = GlobalVariables.State.ScoreScreenStats.Value.MeanAverageOffset;

var offsetMs = (long)Math.Round(offset * 1000);

if (_offsetModified)
{
YargLogger.LogFormatInfo("{0}ms offset removed", offsetMs);
AddSongOffsetJson(_songHashKey, -offsetMs);
}
else
{
YargLogger.LogFormatInfo("{0}ms offset added", offsetMs);
AddSongOffsetJson(_songHashKey, offsetMs);
}
_offsetModified = !_offsetModified;
UpdateAddOffsetButton();
UpdateNavigationScheme(true);
}

private void AddSongOffsetJson(string hashKey, long offsetMilliseconds)
{
_offsets.TryGetValue(hashKey, out var existing);
var newValue = existing + offsetMilliseconds;

if (newValue == 0)
{
_offsets.Remove(hashKey);
}
else
{
_offsets[hashKey] = newValue;
}
}


private void UpdateAddOffsetButton()
{
var key = _offsetModified ? "Menu.ScoreScreen.RemoveSongOffset" : "Menu.ScoreScreen.AddSongOffset";
// Make offset button holdable, 1 second
_toggleOffsetEntry = new NavigationScheme.Entry(
MenuAction.Select,
key,
() => { }, // tap does nothing
holdSeconds: 1f,
onHoldHandler: ToggleOffsetToJson
);
}

private void UpdateNavigationScheme(bool reset = false)
{
if (reset)
Expand Down Expand Up @@ -580,6 +648,13 @@ private void UpdateNavigationScheme(bool reset = false)
buttons.Insert(1, _endEarlyButtonEntry);
}

// Now doesn't look so great when changing quickly quickly between advanced stats
if ((_humanPlayerCount == 1 || !SettingsManager.Settings.ShowSongOffsetCalibrationOnlyOnePlayer.Value)
&& _showAdvancedStats && SettingsManager.Settings.ShowSongOffsetCalibration.Value)
{
buttons.Add(_toggleOffsetEntry);
}

buttons.Add(_scrollLeftEntry);
buttons.Add(_scrollRightEntry);
buttons.Add(_scrollUpEntry);
Expand Down
3 changes: 3 additions & 0 deletions Assets/Script/Settings/SettingsManager.Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ public void OpenCalibrator()
public IntSetting VideoCalibration { get; } = new(0);
public ToggleSetting AutoCalibrateAudio { get; } = new(false);
public ToggleSetting AutoCalibrateVideo { get; } = new(false);
public ToggleSetting ShowSongOffsetCalibration { get; } = new(true);
public ToggleSetting ShowSongOffsetCalibrationOnlyOnePlayer { get; } = new(true);
public ToggleSetting UseSongOffsetCalibration { get; } = new(true);

public ToggleSetting AccountForHardwareLatency { get; } = new(true);

Expand Down
4 changes: 4 additions & 0 deletions Assets/Script/Settings/SettingsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public static partial class SettingsManager
new ButtonRowMetadata(nameof(Settings.OpenCalibrator)),
nameof(Settings.AudioCalibration),
nameof(Settings.VideoCalibration),
// Not sure how to use nameof?
nameof(Settings.ShowSongOffsetCalibration),
nameof(Settings.ShowSongOffsetCalibrationOnlyOnePlayer),
nameof(Settings.UseSongOffsetCalibration),
new FieldMetadata(nameof(Settings.AccountForHardwareLatency), true, isAdvanced: true),

new HeaderMetadata("Venues"),
Expand Down
63 changes: 63 additions & 0 deletions Assets/Script/Song/SongOffsetContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using YARG.Core.Logging;
using YARG.Helpers;

namespace YARG.Song
{
public static class SongOffsetContainer
{
private const string OFFSETS_FILENAME = "song_offsets.json";
private static readonly string _offsetsPath = Path.Combine(PathHelper.PersistentDataPath, OFFSETS_FILENAME);
private static readonly JsonSerializerSettings _offsetJsonSettings = new() { Formatting = Formatting.Indented };

public static Dictionary<string, long> LoadOffsets()
{
if (!File.Exists(_offsetsPath))
{
return new Dictionary<string, long>();
}
try
{
return ParseOffsets(File.ReadAllText(_offsetsPath));
}
catch (Exception ex)
{
YargLogger.LogException(ex, "Failed to load song offsets");
return new Dictionary<string, long>();
}
}

public static long GetOffsetMilliseconds(string hashKey)
{
var offsets = LoadOffsets();
offsets.TryGetValue(hashKey, out var offset);
return offset;
}

private static Dictionary<string, long> ParseOffsets(string json)
{
var data = JsonConvert.DeserializeObject<Dictionary<string, long>>(json, _offsetJsonSettings);
return data ?? new Dictionary<string, long>();
}

public static void SaveOffsets(Dictionary<string, long> offsets)
{
try
{
File.WriteAllText(_offsetsPath, SerializeOffsets(offsets));
}
catch (Exception ex)
{
YargLogger.LogException(ex, "Failed to save song offsets");
}
}

private static string SerializeOffsets(Dictionary<string, long> offsets)
{
return JsonConvert.SerializeObject(offsets, _offsetJsonSettings);
}
}
}
16 changes: 15 additions & 1 deletion Assets/StreamingAssets/lang/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,9 @@
"RestartSong" : "Restart Song",
"ShowAdvanced": "Show Advanced",
"HideAdvanced": "Hide Advanced",
"EndSetlistEarly" : "End Setlist"
"EndSetlistEarly" : "End Setlist",
"AddSongOffset": "(Hold) Record song offset",
"RemoveSongOffset": "(Hold) Undo offset record"
},
"Settings": {
"ShowAdvanced": "Show Advanced",
Expand Down Expand Up @@ -1548,6 +1550,18 @@
"PauseName": "Auto Calibrate Video",
"Description": "Placeholder setting. Currently has no effect."
},
"ShowSongOffsetCalibration": {
"Name": "Show Mean Song Offset Calibration",
"Description": "Shows an option on the advanced stats screen to record mean song offset."
},
"ShowSongOffsetCalibrationOnlyOnePlayer": {
"Name": "Only Show With One Player",
"Description": "Only shows the option to record mean song offset when there is one human player."
},
"UseSongOffsetCalibration": {
"Name": "Use Mean Song Offset Calibration",
"Description": "Use the recorded song mean offset to adjust song audio delay."
},
"VocalMonitoring": {
"Name": "Vocal Monitoring Volume",
"PauseName": "Monitoring",
Expand Down
16 changes: 15 additions & 1 deletion Assets/StreamingAssets/lang/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,9 @@
"RestartSong": "Recommencer",
"ShowAdvanced": "Afficher Avancé",
"HideAdvanced": "Cacher Avancé",
"EndSetlistEarly": "Terminer la setlist"
"EndSetlistEarly": "Terminer la setlist",
"AddSongOffset": "(Maintien) Enregistrer le décalage",
"RemoveSongOffset": "(Maintien) Annuler l'enregistrement"
},
"Settings": {
"ShowAdvanced": "Afficher Avancé",
Expand Down Expand Up @@ -1492,6 +1494,18 @@
"PauseName": "Calibrage automatique de l'audio",
"Description": "Ajuste automatiquement la calibration pendant le jeu en fonction de votre timing d'input."
},
"ShowSongOffsetCalibration": {
"Name": "Montrer l'option de calibrage de décalage moyen",
"Description": "Ajoute un bouton sur l'écran de statistiques avancées pour enregistrer le décalage moyen détecté."
},
"ShowSongOffsetCalibrationOnlyOnePlayer": {
"Name": "Seulement montrer avec un joueur",
"Description": "Montrer l'option pour enregistrer le décalage moyen détecté seulement s'il n'y a qu'un seul humain connecté."
},
"UseSongOffsetCalibration": {
"Name": "Utiliser le calibrage de décalage moyen",
"Description": "Utilise la valeur enregistrée pour le décalage moyen d'une chanson pour ajuster le délai de l'audio."
},
"AutoCalibrateVideo": {
"Name": "Calibrage automatique de la vidéo",
"PauseName": "Calibrage automatique de la vidéo",
Expand Down