forked from YARC-Official/YARG
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Start of new song scanning (YARC-Official#223) * Add UniTask and EasySharpIni * Start new song scanning * Read song cache * New settings (YARC-Official#220) * Started new settings * Started to add settings back * Added localizations * Tabs * Text input texture * Almost there! * Added Buttons * Fixed some bugs * Fixed merge issue * Updated button texture * Icons and headers * Abstraction * Started track preview in settings * Track camera settings! * Simplified camera positioning * Song folder manager * Fixed render texture issue * Fixed scene merge * Fixed prefab merge * add song update functionality (YARC-Official#185) (YARC-Official#221) Co-authored-by: rjkiv <[email protected]> * "Fixed" errors * note count hit, solo reset*, slightly easier tap recovery (YARC-Official#222) * Fix StarDisplay gold progress pulse call not resetting the pulse animation (YARC-Official#226) * fixed anim warnings triggering after gold stars achieved * Move ScoreDisplay if MicPlayer isn't present * fix star display pulse call not resetting the pulse animation * remove accidentally left-in log * Sustain animation update (YARC-Official#225) * Sustain Lines thicker and emission fixed * Update sustain animations --------- Co-authored-by: Mia Berth <[email protected]> * Don't do anything on debounce override if disabled or timer isn't running (YARC-Official#224) * Start new song scanning * Read song cache --------- Co-authored-by: EliteAsian <[email protected]> Co-authored-by: rjkiv <[email protected]> Co-authored-by: Raphael Goulart <[email protected]> Co-authored-by: muskit <[email protected]> Co-authored-by: Narrik Synthfox <[email protected]> Co-authored-by: Mia Berth <[email protected]> Co-authored-by: Nathan <[email protected]> * Migrate everything to use new song scanning (YARC-Official#247) * Add UniTask and EasySharpIni * Start new song scanning * Read song cache * Fix StarDisplay gold progress pulse call not resetting the pulse animation (YARC-Official#226) * fixed anim warnings triggering after gold stars achieved * Move ScoreDisplay if MicPlayer isn't present * fix star display pulse call not resetting the pulse animation * remove accidentally left-in log * Sustain animation update (YARC-Official#225) * Sustain Lines thicker and emission fixed * Update sustain animations --------- Co-authored-by: Mia Berth <[email protected]> * Don't do anything on debounce override if disabled or timer isn't running (YARC-Official#224) * New settings (YARC-Official#220) * Started new settings * Started to add settings back * Added localizations * Tabs * Text input texture * Almost there! * Added Buttons * Fixed some bugs * Fixed merge issue * Updated button texture * Icons and headers * Abstraction * Started track preview in settings * Track camera settings! * Simplified camera positioning * Song folder manager * Fixed render texture issue * Fixed scene merge * Fixed prefab merge * add song update functionality (YARC-Official#185) (YARC-Official#221) Co-authored-by: rjkiv <[email protected]> * "Fixed" errors * note count hit, solo reset*, slightly easier tap recovery (YARC-Official#222) * Fix StarDisplay gold progress pulse call not resetting the pulse animation (YARC-Official#226) * fixed anim warnings triggering after gold stars achieved * Move ScoreDisplay if MicPlayer isn't present * fix star display pulse call not resetting the pulse animation * remove accidentally left-in log * Sustain animation update (YARC-Official#225) * Sustain Lines thicker and emission fixed * Update sustain animations --------- Co-authored-by: Mia Berth <[email protected]> * Don't do anything on debounce override if disabled or timer isn't running (YARC-Official#224) * Start new song scanning * Read song cache * Update README.md * Slide reverb volume up and down (YARC-Official#229) * GOT HER DONE (Temporary fix) * Fixed particle group issue * Add notes file and song type to SongEntry * minor overstrum-related fix (YARC-Official#239) * small RB song fixes (YARC-Official#238) * add hopo_threshold support to please jnack * fix DXT3 image byte parsing * DXT3 loop cleanup * hopo threshold fix * actually reference hopo_threshold this time * small RB fixes * fix solo bonus minimum to be at 60% instead of 50% (YARC-Official#234) * Change semitone shift calculation to linear functions (YARC-Official#231) * Fix Discord Unpause Timer (YARC-Official#230) - Using Play.Instance.SongTime to calculate the time left of the song; - Removed unused variables; - Add code for a future implementation * Updated credits * New song select (YARC-Official#245) * New Music Library + Added base design for the new library + Source Icons added (78 Icons for now, will implement more in the future, for now mainly base games) - Still need to add scrollbar to the list - Need to rework the whole Filter/Search bar * Music Library Update + Added default icon for songs that don't have a source - Removed extra folders in the Sources folder (also EA need to fix this to use the right path, I'm just dumb) * Fix song select being active by default * Updated Library + Added search function to the tags (Album, Source Name and Icon, Charter, Genre and Year) + Added new filters for album: charter: genre: and year: - Instrument Difficulty display needs rework with the new visual - Need help adding the search function to the artist name, not sure how since we're using just one component for now (help pls) - Still need to adjust the Subheader (Working on Figma first) * Source Icons UPDATED: + Rock Band + Rock Band 2 + Rock Band 3 + Rock Band 4 + Guitar Hero Aerosmith NEW: + Band Hero DS + Guitar Hero: On Tour Decades + Guitar Hero: On Tour Modern Hits + Guitar Hero TV + Goulart + Hugh + Power Gig + Rock Revolution + Phase Shift * Music Library Update + Source Icons (YARC-Official#236) * New Music Library + Added base design for the new library + Source Icons added (78 Icons for now, will implement more in the future, for now mainly base games) - Still need to add scrollbar to the list - Need to rework the whole Filter/Search bar * Music Library Update + Added default icon for songs that don't have a source - Removed extra folders in the Sources folder (also EA need to fix this to use the right path, I'm just dumb) * Fix song select being active by default * Updated Library + Added search function to the tags (Album, Source Name and Icon, Charter, Genre and Year) + Added new filters for album: charter: genre: and year: - Instrument Difficulty display needs rework with the new visual - Need help adding the search function to the artist name, not sure how since we're using just one component for now (help pls) - Still need to adjust the Subheader (Working on Figma first) * Source Icons UPDATED: + Rock Band + Rock Band 2 + Rock Band 3 + Rock Band 4 + Guitar Hero Aerosmith NEW: + Band Hero DS + Guitar Hero: On Tour Decades + Guitar Hero: On Tour Modern Hits + Guitar Hero TV + Goulart + Hugh + Power Gig + Rock Revolution + Phase Shift --------- Co-authored-by: Nathan <[email protected]> * Small changes * Music Library Update + Added new Source Icons + Fixed category header text (1 Song, X Songs) * Difficulty rings + scroll bar * Fixed search --------- Co-authored-by: Kadu Waengertner <[email protected]> Co-authored-by: Nathan <[email protected]> * Parallel scanning if folders are on different drives * Migrate everything to use SongEntry, quite non functional right now --------- Co-authored-by: muskit <[email protected]> Co-authored-by: Narrik Synthfox <[email protected]> Co-authored-by: Mia Berth <[email protected]> Co-authored-by: Nathan <[email protected]> Co-authored-by: EliteAsian <[email protected]> Co-authored-by: rjkiv <[email protected]> Co-authored-by: Raphael Goulart <[email protected]> Co-authored-by: Paulo Cardoso <[email protected]> Co-authored-by: Kadu Waengertner <[email protected]> * Remove duplicate files and unused imports * Renamed "RawCon" to "ExtractedCon" * Added LoadingManager * Added back score loading * Added extracted CON read/write for song cache (YARC-Official#248) * Added extracted CON read/write for song cache * Difficulty loading from ini * Did an oopsie and realised difficulties would not work * Fixed bug loading mid files * Added back refresh per folder * [New Caching] Fix excon caching and music library bugs (YARC-Official#253) * Added extracted CON read/write for song cache * Difficulty loading from ini * Did an oopsie and realised difficulties would not work * Fixed bug loading mid files * Added BinaryWriter wrapper to handle null strings * Always fast scan on startup * Fix missing difficulties breaking music library * Fix some issues with EXCON caching * Fix incorrect notes file assignment * Added "ErroredCaches" to scanning * Fixed null song folder issue * .chart preparsing (YARC-Official#255) * Fixed warning * Fixed duplicate folder issue * [New Caching] .mid Preparsing, badsong logging and difficulty ring fix (YARC-Official#263) * .chart preparsing * Basic mid preparsing (only instruments) * Lots of preparsing fixes and fixed hopo frequencies * Change where badsongs are written to * Fix difficulty rings showing when no track is present but difficulty is defined * Fix duplicate chart preparsing causing compile error * Settings menu now auto updates cache --------- Co-authored-by: RileyTheFox <[email protected]> Co-authored-by: rjkiv <[email protected]> Co-authored-by: Raphael Goulart <[email protected]> Co-authored-by: muskit <[email protected]> Co-authored-by: Narrik Synthfox <[email protected]> Co-authored-by: Mia Berth <[email protected]> Co-authored-by: Nathan <[email protected]> Co-authored-by: Paulo Cardoso <[email protected]> Co-authored-by: Kadu Waengertner <[email protected]>
1 parent
a1de23a
commit 7f89f6d
Showing
76 changed files
with
2,596 additions
and
1,316 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using Cysharp.Threading.Tasks; | ||
using TMPro; | ||
using UnityEngine; | ||
using YARG.Data; | ||
using YARG.Song; | ||
|
||
namespace YARG { | ||
public class LoadingManager : MonoBehaviour { | ||
public static LoadingManager Instance { get; private set; } | ||
|
||
[SerializeField] | ||
private TextMeshProUGUI loadingPhrase; | ||
[SerializeField] | ||
private TextMeshProUGUI subPhrase; | ||
|
||
private Queue<Func<UniTask>> loadQueue = new(); | ||
|
||
private void Awake() { | ||
Instance = this; | ||
} | ||
|
||
private async UniTask Start() { | ||
Queue(async () => { | ||
SetLoadingText("Fetching sources from web..."); | ||
await SongSources.LoadSources(); | ||
}); | ||
|
||
// Fast scan (cache read) on startup | ||
QueueSongRefresh(true); | ||
|
||
Queue(async () => { | ||
SetLoadingText("Reading scores..."); | ||
await ScoreManager.FetchScores(); | ||
}); | ||
|
||
await StartLoad(); | ||
} | ||
|
||
public async UniTask StartLoad() { | ||
if (loadQueue.Count <= 0) { | ||
return; | ||
} | ||
|
||
gameObject.SetActive(true); | ||
|
||
while (loadQueue.Count > 0) { | ||
var func = loadQueue.Dequeue(); | ||
await func(); | ||
} | ||
|
||
gameObject.SetActive(false); | ||
} | ||
|
||
public void Queue(Func<UniTask> func) { | ||
loadQueue.Enqueue(func); | ||
} | ||
|
||
public void QueueSongRefresh(bool fast) { | ||
Queue(async () => { | ||
await ScanSongFolders(fast); | ||
}); | ||
} | ||
|
||
public void QueueSongFolderRefresh(string path) { | ||
Queue(async () => { | ||
await ScanSongFolder(path); | ||
}); | ||
} | ||
|
||
private async UniTask ScanSongFolders(bool fast) { | ||
SetLoadingText("Loading songs..."); | ||
var errors = await SongContainer.ScanAllFolders(fast, scanner => { | ||
subPhrase.text = $"Folders Scanned: {scanner.TotalFoldersScanned}" + | ||
$"\nSongs Scanned: {scanner.TotalSongsScanned}" + | ||
$"\nErrors: {scanner.TotalErrorsEncountered}"; | ||
}); | ||
|
||
// Only scan folders again on fast mode, as on slow mode they were already scanned | ||
if (fast) { | ||
foreach (var error in errors) { | ||
await ScanSongFolder(error); | ||
} | ||
} | ||
} | ||
|
||
private async UniTask ScanSongFolder(string path) { | ||
SetLoadingText("Loading songs from folder..."); | ||
await SongContainer.ScanFolder(path, scanner => { | ||
subPhrase.text = $"Folders Scanned: {scanner.TotalFoldersScanned}" + | ||
$"\nSongs Scanned: {scanner.TotalSongsScanned}" + | ||
$"\nErrors: {scanner.TotalErrorsEncountered}"; | ||
}); | ||
} | ||
|
||
private void SetLoadingText(string phrase, string sub = null) { | ||
loadingPhrase.text = phrase; | ||
subPhrase.text = sub; | ||
} | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
Assets/Script/Serialization/SongIni.cs.meta → Assets/Script/LoadingManager.cs.meta
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using YARG.Serialization; | ||
|
||
namespace YARG.Song { | ||
public static class CacheHelpers { | ||
|
||
public static void WriteExtractedConData(BinaryWriter writer, ExtractedConSongEntry conSong) { | ||
/*/ | ||
MOGG data | ||
*/ | ||
|
||
writer.Write(conSong.MoggInfo.MoggPath); | ||
writer.Write(conSong.MoggInfo.ChannelCount); | ||
writer.Write(conSong.MoggInfo.Header); | ||
writer.Write(conSong.MoggInfo.MoggAddressAudioOffset); | ||
writer.Write(conSong.MoggInfo.MoggAudioLength); | ||
|
||
// Write Pan Data | ||
writer.Write(conSong.MoggInfo.PanData.Length); | ||
foreach (float pan in conSong.MoggInfo.PanData) { | ||
writer.Write(pan); | ||
} | ||
|
||
// Write Volume Data | ||
writer.Write(conSong.MoggInfo.VolumeData.Length); | ||
foreach (float vol in conSong.MoggInfo.VolumeData) { | ||
writer.Write(vol); | ||
} | ||
|
||
// Write Track Data | ||
writer.Write(conSong.MoggInfo.Tracks.Count); | ||
foreach (var track in conSong.MoggInfo.Tracks) { | ||
writer.Write(track.Key); | ||
writer.Write(track.Value.Length); | ||
foreach (int i in track.Value) { | ||
writer.Write(i); | ||
} | ||
} | ||
|
||
// Write Crowd Data | ||
writer.Write(conSong.MoggInfo.CrowdChannels.Length); | ||
foreach (int i in conSong.MoggInfo.CrowdChannels) { | ||
writer.Write(i); | ||
} | ||
|
||
// Write Stem Data | ||
writer.Write(conSong.MoggInfo.StemMaps.Count); | ||
foreach (var stem in conSong.MoggInfo.StemMaps) { | ||
writer.Write((int)stem.Key); | ||
writer.Write(stem.Value.Length); | ||
foreach (int i in stem.Value) { | ||
writer.Write(i); | ||
} | ||
} | ||
|
||
// Write Matrix Data | ||
writer.Write(conSong.MoggInfo.MatrixRatios.GetLength(0)); | ||
writer.Write(conSong.MoggInfo.MatrixRatios.GetLength(1)); | ||
for (int i = 0; i < conSong.MoggInfo.MatrixRatios.GetLength(0); i++) { | ||
for (int j = 0; j < conSong.MoggInfo.MatrixRatios.GetLength(1); j++) { | ||
writer.Write(conSong.MoggInfo.MatrixRatios[i, j]); | ||
} | ||
} | ||
|
||
/*/ | ||
Image data | ||
*/ | ||
|
||
// ImageInfo can be null if the song has no image so need to detect this | ||
writer.Write(conSong.ImageInfo is not null); | ||
if (conSong.ImageInfo is not null) { | ||
writer.Write(conSong.ImageInfo.ImagePath); | ||
writer.Write(conSong.ImageInfo.BitsPerPixel); | ||
writer.Write(conSong.ImageInfo.Format); | ||
} | ||
} | ||
|
||
public static void ReadExtractedConData(BinaryReader reader, ExtractedConSongEntry conSong) { | ||
string path = reader.ReadString(); | ||
int channelCount = reader.ReadInt32(); | ||
int header = reader.ReadInt32(); | ||
int moggAddressAudioOffset = reader.ReadInt32(); | ||
long moggAudioLength = reader.ReadInt64(); | ||
|
||
// Read Pan Data | ||
int panDataLength = reader.ReadInt32(); | ||
var panData = new float[panDataLength]; | ||
for (int i = 0; i < panDataLength; i++) { | ||
panData[i] = reader.ReadSingle(); | ||
} | ||
|
||
// Read Volume Data | ||
int volumeDataLength = reader.ReadInt32(); | ||
var volumeData = new float[volumeDataLength]; | ||
for (int i = 0; i < volumeDataLength; i++) { | ||
volumeData[i] = reader.ReadSingle(); | ||
} | ||
|
||
// Read Track Data | ||
int trackCount = reader.ReadInt32(); | ||
var tracks = new Dictionary<string, int[]>(); | ||
for (int i = 0; i < trackCount; i++) { | ||
string trackName = reader.ReadString(); | ||
int trackLength = reader.ReadInt32(); | ||
var track = new int[trackLength]; | ||
for (int j = 0; j < trackLength; j++) { | ||
track[j] = reader.ReadInt32(); | ||
} | ||
tracks.Add(trackName, track); | ||
} | ||
|
||
// Read Crowd Data | ||
int crowdChannelCount = reader.ReadInt32(); | ||
var crowdChannels = new int[crowdChannelCount]; | ||
for (int i = 0; i < crowdChannelCount; i++) { | ||
crowdChannels[i] = reader.ReadInt32(); | ||
} | ||
|
||
// Read Stem Data | ||
int stemCount = reader.ReadInt32(); | ||
var stemMaps = new Dictionary<SongStem, int[]>(); | ||
for (int i = 0; i < stemCount; i++) { | ||
var stem = (SongStem)reader.ReadInt32(); | ||
int stemLength = reader.ReadInt32(); | ||
var stemMap = new int[stemLength]; | ||
for (int j = 0; j < stemLength; j++) { | ||
stemMap[j] = reader.ReadInt32(); | ||
} | ||
stemMaps.Add(stem, stemMap); | ||
} | ||
|
||
// Read Matrix Data | ||
int matrixRowCount = reader.ReadInt32(); | ||
int matrixColCount = reader.ReadInt32(); | ||
var matrixRatios = new float[matrixRowCount, matrixColCount]; | ||
for (int i = 0; i < matrixRowCount; i++) { | ||
for (int j = 0; j < matrixColCount; j++) { | ||
matrixRatios[i, j] = reader.ReadSingle(); | ||
} | ||
} | ||
|
||
var moggData = new XboxMoggData(path) { | ||
ChannelCount = channelCount, | ||
Header = header, | ||
MoggAddressAudioOffset = moggAddressAudioOffset, | ||
MoggAudioLength = moggAudioLength, | ||
PanData = panData, | ||
VolumeData = volumeData, | ||
Tracks = tracks, | ||
CrowdChannels = crowdChannels, | ||
StemMaps = stemMaps, | ||
MatrixRatios = matrixRatios | ||
}; | ||
|
||
/*/ | ||
Image data | ||
*/ | ||
|
||
// ImageInfo can be null if the song has no image so need to detect this | ||
if (reader.ReadBoolean()) { | ||
string imagePath = reader.ReadString(); | ||
byte bitsPerPixel = reader.ReadByte(); | ||
int format = reader.ReadInt32(); | ||
|
||
var imageInfo = new XboxImage(imagePath) { | ||
BitsPerPixel = bitsPerPixel, | ||
Format = format | ||
}; | ||
|
||
conSong.ImageInfo = imageInfo; | ||
} | ||
|
||
conSong.MoggInfo = moggData; | ||
} | ||
|
||
} | ||
} |
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
using System.IO; | ||
|
||
namespace YARG.Song { | ||
public class NullStringBinaryWriter : BinaryWriter { | ||
|
||
public NullStringBinaryWriter(Stream output) : base(output) | ||
{ | ||
} | ||
|
||
public override void Write(string value) { | ||
if (string.IsNullOrEmpty(value)) { | ||
value = string.Empty; | ||
} | ||
base.Write(value); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Text.RegularExpressions; | ||
using YARG.Data; | ||
|
||
namespace YARG.Song.Preparsers { | ||
public static class ChartPreparser { | ||
|
||
private static readonly Regex ChartEventRegex = new Regex(@"(\d+)\s?=\s?[NSE]\s?((\d+\s?\d+)|\w+)", RegexOptions.Compiled); | ||
|
||
private static readonly IReadOnlyDictionary<Difficulty, string> DifficultyLookup = new Dictionary<Difficulty, string> { | ||
{ Difficulty.EASY, "Easy" }, | ||
{ Difficulty.MEDIUM, "Medium" }, | ||
{ Difficulty.HARD, "Hard" }, | ||
{ Difficulty.EXPERT, "Expert" } | ||
}; | ||
|
||
private static readonly IReadOnlyDictionary<Instrument, string> InstrumentLookup = new Dictionary<Instrument, string> { | ||
{ Instrument.GUITAR, "Single" }, | ||
{ Instrument.GUITAR_COOP, "DoubleGuitar" }, | ||
{ Instrument.BASS, "DoubleBass" }, | ||
{ Instrument.RHYTHM, "DoubleRhythm" }, | ||
{ Instrument.DRUMS, "Drums" }, | ||
{ Instrument.KEYS, "Keyboard" }, | ||
// { Instrument.GHLiveGuitar, "GHLGuitar" }, | ||
// { Instrument.GHLiveBass, "GHLBass" }, | ||
// { Instrument.GHLiveRhythm, "GHLRhythm" }, | ||
// { Instrument.GHLiveCoop, "GHLCoop" } | ||
}; | ||
|
||
public static ulong GetAvailableTracks(byte[] chartData) { | ||
using var stream = new MemoryStream(chartData); | ||
|
||
using var reader = new StreamReader(stream); | ||
return ReadStream(reader); | ||
} | ||
|
||
public static ulong GetAvailableTracks(SongEntry song) { | ||
using var reader = File.OpenText(Path.Combine(song.Location, song.NotesFile)); | ||
|
||
return ReadStream(reader); | ||
} | ||
|
||
private static ulong ReadStream(StreamReader reader) { | ||
ulong tracks = 0; | ||
|
||
while (!reader.EndOfStream) { | ||
string line = reader.ReadLine()?.Trim(); | ||
if (line is null) { | ||
continue; | ||
} | ||
|
||
if (line.Length <= 0) | ||
continue; | ||
|
||
if (line[0] != '[' && line[^1] != ']') continue; | ||
|
||
string headerName = line[1..^1]; | ||
if (reader.ReadLine()?.Trim() != "{") continue; | ||
|
||
string eventLine = reader.ReadLine()?.Trim(); | ||
if (eventLine is null) { | ||
continue; | ||
} | ||
|
||
// This track has an event in it! | ||
if (!ChartEventRegex.IsMatch(eventLine) || !GetTrackFromHeader(headerName, out var track)) { | ||
continue; | ||
} | ||
|
||
int shiftAmount = (int)track.instrument * 4 + (int)track.difficulty; | ||
tracks |= (uint)(1 << shiftAmount); | ||
} | ||
|
||
return tracks; | ||
} | ||
|
||
private static bool GetTrackFromHeader(string header, out (Instrument instrument, Difficulty difficulty) track) { | ||
var diffEnums = (Difficulty[]) Enum.GetValues(typeof(Difficulty)); | ||
var instrumentEnums = (Instrument[]) Enum.GetValues(typeof(Instrument)); | ||
|
||
foreach (var instrument in instrumentEnums) { | ||
if (!InstrumentLookup.ContainsKey(instrument)) | ||
continue; | ||
|
||
foreach (var difficulty in diffEnums) { | ||
if(!DifficultyLookup.ContainsKey(difficulty)) | ||
continue; | ||
|
||
var trackName = $"{DifficultyLookup[difficulty]}{InstrumentLookup[instrument]}"; | ||
|
||
if (header != trackName) { | ||
continue; | ||
} | ||
|
||
track = (instrument, difficulty); | ||
return true; | ||
} | ||
} | ||
|
||
track = (Instrument.INVALID, (Difficulty)(-1)); | ||
return false; | ||
} | ||
} | ||
} |
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using Melanchall.DryWetMidi.Core; | ||
using MoonscraperChartEditor.Song.IO; | ||
using UnityEngine; | ||
using YARG.Data; | ||
|
||
namespace YARG.Song.Preparsers { | ||
public static class MidPreparser { | ||
|
||
private static readonly ReadingSettings ReadSettings = new() { | ||
InvalidChunkSizePolicy = InvalidChunkSizePolicy.Ignore, | ||
NotEnoughBytesPolicy = NotEnoughBytesPolicy.Ignore, | ||
NoHeaderChunkPolicy = NoHeaderChunkPolicy.Ignore, | ||
InvalidChannelEventParameterValuePolicy = InvalidChannelEventParameterValuePolicy.ReadValid, | ||
}; | ||
|
||
private static readonly IReadOnlyDictionary<string, Instrument> PartLookup = new Dictionary<string, Instrument> { | ||
{ MidIOHelper.GUITAR_TRACK, Instrument.GUITAR }, | ||
{ MidIOHelper.GH1_GUITAR_TRACK, Instrument.GUITAR }, | ||
{ MidIOHelper.GUITAR_COOP_TRACK, Instrument.GUITAR_COOP }, | ||
{ MidIOHelper.BASS_TRACK, Instrument.BASS }, | ||
{ MidIOHelper.RHYTHM_TRACK, Instrument.RHYTHM }, | ||
{ MidIOHelper.DRUMS_TRACK, Instrument.DRUMS }, | ||
{ "PART DRUM", Instrument.DRUMS }, | ||
{ MidIOHelper.DRUMS_REAL_TRACK, Instrument.REAL_DRUMS }, | ||
{ MidIOHelper.KEYS_TRACK, Instrument.KEYS }, | ||
{ MidIOHelper.VOCALS_TRACK, Instrument.VOCALS }, | ||
{ "PART REAL_GUITAR", Instrument.REAL_GUITAR }, | ||
{ "PART REAL_BASS", Instrument.REAL_BASS }, | ||
|
||
}; | ||
|
||
public static ulong GetAvailableTracks(byte[] chartData) { | ||
using var stream = new MemoryStream(chartData); | ||
try { | ||
var midi = MidiFile.Read(stream, ReadSettings); | ||
return ReadStream(midi); | ||
} catch(Exception e) { | ||
Debug.LogError(e.Message); | ||
Debug.LogError(e.StackTrace); | ||
return ulong.MaxValue; | ||
} | ||
} | ||
|
||
public static ulong GetAvailableTracks(SongEntry song) { | ||
try { | ||
var midi = MidiFile.Read(Path.Combine(song.Location, song.NotesFile), ReadSettings); | ||
return ReadStream(midi); | ||
} catch(Exception e) { | ||
Debug.LogError(e.Message); | ||
Debug.LogError(e.StackTrace); | ||
return ulong.MaxValue; | ||
} | ||
} | ||
|
||
private static ulong ReadStream(MidiFile midi) { | ||
ulong tracks = 0; | ||
|
||
foreach(var chunk in midi.GetTrackChunks()) { | ||
foreach (var trackEvent in chunk.Events) { | ||
if (trackEvent is not SequenceTrackNameEvent trackName) { | ||
continue; | ||
} | ||
|
||
string trackNameKey = trackName.Text.ToUpper(); | ||
|
||
if (!PartLookup.TryGetValue(trackNameKey, out var instrument)) { | ||
continue; | ||
} | ||
|
||
int shiftAmount = (int)instrument * 4; | ||
tracks |= (uint)0xF << shiftAmount; | ||
} | ||
} | ||
|
||
return tracks; | ||
} | ||
} | ||
} |
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
using EasySharpIni; | ||
using EasySharpIni.Converters; | ||
using EasySharpIni.Models; | ||
using YARG.Data; | ||
|
||
namespace YARG.Song { | ||
public static class ScanHelpers { | ||
|
||
private static readonly IntConverter IntConverter = new(); | ||
|
||
public static ScanResult ParseSongIni(string iniFile, IniSongEntry entry) { | ||
var file = new IniFile(iniFile); | ||
file.Parse(); | ||
|
||
string sectionName = file.ContainsSection("song") ? "song" : "Song"; | ||
|
||
if (!file.ContainsSection(sectionName)) { | ||
return ScanResult.NotASong; | ||
} | ||
|
||
var section = file.GetSection(sectionName); | ||
|
||
entry.Name = section.GetField("name"); | ||
entry.Artist = section.GetField("artist"); | ||
entry.Charter = section.GetField("charter"); | ||
|
||
entry.Album = section.GetField("album"); | ||
entry.AlbumTrack = section.GetField("album_track", "0").Get(IntConverter); | ||
if (section.ContainsField("track")) { | ||
entry.AlbumTrack = section.GetField("track", "0").Get(IntConverter); | ||
} | ||
|
||
entry.PlaylistTrack = section.GetField("playlist_track", "0").Get(IntConverter); | ||
|
||
entry.Genre = section.GetField("genre"); | ||
entry.Year = section.GetField("year"); | ||
|
||
entry.SongLength = section.GetField("song_length", "-1").Get(IntConverter); | ||
entry.PreviewStart = section.GetField("preview_start_time", "0").Get(IntConverter); | ||
entry.PreviewEnd = section.GetField("preview_end", "-1").Get(IntConverter); | ||
|
||
int rawDelay = section.GetField("delay").Get(IntConverter); | ||
entry.Delay = rawDelay / 1000.0; | ||
|
||
entry.HopoThreshold = section.GetField("hopo_frequency", "170").Get(IntConverter); | ||
entry.EighthNoteHopo = section.GetField("eighthnote_hopo", "false").Get().ToLower() == "true"; | ||
entry.MultiplierNote = section.GetField("multiplier_note", "116").Get(IntConverter); | ||
|
||
ReadDifficulties(section, entry); | ||
|
||
if (section.ContainsField("pro_drums")) { | ||
switch (section.GetField("pro_drums")) { | ||
case "true": | ||
case "1": | ||
entry.DrumType = DrumType.FourLane; | ||
break; | ||
} | ||
} else if (section.ContainsField("five_lane_drums")) { | ||
switch (section.GetField("five_lane_drums")) { | ||
case "true": | ||
case "1": | ||
entry.DrumType = DrumType.FiveLane; | ||
break; | ||
} | ||
} else { | ||
entry.DrumType = DrumType.Unknown; | ||
} | ||
|
||
entry.LoadingPhrase = section.GetField("loading_phrase"); | ||
entry.Source = section.GetField("icon"); | ||
entry.HasLyrics = section.GetField("lyrics").Get().ToLower() == "true"; | ||
entry.IsModChart = section.GetField("modchart").Get().ToLower() == "true"; | ||
|
||
return ScanResult.Ok; | ||
} | ||
|
||
private static void ReadDifficulties(IniSection section, SongEntry entry) { | ||
entry.PartDifficulties.Clear(); | ||
|
||
entry.PartDifficulties.Add(Instrument.GUITAR, section.GetField("diff_guitar", "-1").Get(IntConverter)); | ||
entry.PartDifficulties.Add(Instrument.GUITAR_COOP, section.GetField("diff_guitar_coop", "-1").Get(IntConverter)); | ||
entry.PartDifficulties.Add(Instrument.REAL_GUITAR, section.GetField("diff_guitar_real", "-1").Get(IntConverter)); | ||
entry.PartDifficulties.Add(Instrument.RHYTHM, section.GetField("diff_rhythm", "-1").Get(IntConverter)); | ||
entry.PartDifficulties.Add(Instrument.BASS, section.GetField("diff_bass", "-1").Get(IntConverter)); | ||
entry.PartDifficulties.Add(Instrument.REAL_BASS, section.GetField("diff_bass_real", "-1").Get(IntConverter)); | ||
entry.PartDifficulties.Add(Instrument.DRUMS, section.GetField("diff_drums", "-1").Get(IntConverter)); | ||
entry.PartDifficulties.Add(Instrument.GH_DRUMS, section.GetField("diff_drums", "-1").Get(IntConverter)); | ||
entry.PartDifficulties.Add(Instrument.REAL_DRUMS, section.GetField("diff_drums_real", "-1").Get(IntConverter)); | ||
entry.PartDifficulties.Add(Instrument.KEYS, section.GetField("diff_keys", "-1").Get(IntConverter)); | ||
entry.PartDifficulties.Add(Instrument.REAL_KEYS, section.GetField("diff_keys_real", "-1").Get(IntConverter)); | ||
entry.PartDifficulties.Add(Instrument.VOCALS, section.GetField("diff_vocals", "-1").Get(IntConverter)); | ||
entry.PartDifficulties.Add(Instrument.HARMONY, section.GetField("diff_vocals_harm", "-1").Get(IntConverter)); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Security.Cryptography; | ||
using System.Threading; | ||
using UnityEngine; | ||
using YARG.Serialization; | ||
using YARG.Song.Preparsers; | ||
|
||
namespace YARG.Song { | ||
public class SongScanThread { | ||
|
||
private Thread _thread; | ||
|
||
public volatile int foldersScanned; | ||
public volatile int songsScanned; | ||
public volatile int errorsEncountered; | ||
|
||
private int _foldersScanned; | ||
private int _songsScanned; | ||
private int _errorsEncountered; | ||
|
||
private readonly Dictionary<string, List<SongEntry>> _songsByCacheFolder; | ||
private readonly Dictionary<string, List<SongError>> _songErrors; | ||
private readonly Dictionary<string, SongCache> _songCaches; | ||
private readonly List<string> _cacheErrors; | ||
|
||
public IReadOnlyList<SongEntry> Songs => _songsByCacheFolder.Values.SelectMany(x => x).ToList(); | ||
public IReadOnlyDictionary<string, List<SongError>> SongErrors => _songErrors; | ||
public IReadOnlyList<string> CacheErrors => _cacheErrors; | ||
|
||
public SongScanThread(bool fast) { | ||
_songsByCacheFolder = new Dictionary<string, List<SongEntry>>(); | ||
_songErrors = new Dictionary<string, List<SongError>>(); | ||
_songCaches = new Dictionary<string, SongCache>(); | ||
_cacheErrors = new List<string>(); | ||
|
||
_thread = fast ? new Thread(FastScan) : new Thread(FullScan); | ||
} | ||
|
||
public void AddFolder(string folder) { | ||
if (IsScanning()) { | ||
throw new Exception("Cannot add folder while scanning"); | ||
} | ||
|
||
if (_songsByCacheFolder.ContainsKey(folder)) { | ||
Debug.LogWarning("Two song folders with same directory!"); | ||
return; | ||
} | ||
|
||
_songsByCacheFolder.Add(folder, new List<SongEntry>()); | ||
_songErrors.Add(folder, new List<SongError>()); | ||
_songCaches.Add(folder, new SongCache(folder)); | ||
} | ||
|
||
public bool IsScanning() { | ||
return _thread is not null && _thread.IsAlive; | ||
} | ||
|
||
public void Abort() { | ||
if (_thread is null || !_thread.IsAlive) { | ||
return; | ||
} | ||
|
||
_thread.Abort(); | ||
_thread = null; | ||
} | ||
|
||
public bool StartScan() { | ||
if (_thread.IsAlive) { | ||
Debug.LogError("This scan thread is already in progress!"); | ||
return false; | ||
} | ||
|
||
if (_songsByCacheFolder.Keys.Count == 0) { | ||
Debug.LogWarning("No song folders added to this thread"); | ||
return false; | ||
} | ||
|
||
_thread.Start(); | ||
return true; | ||
} | ||
|
||
private void FullScan() { | ||
Debug.Log("Performing full scan"); | ||
foreach (string cache in _songsByCacheFolder.Keys) { | ||
// Folder doesn't exist, so report as an error and skip | ||
if (!Directory.Exists(cache)) { | ||
_songErrors[cache].Add(new SongError(cache, ScanResult.InvalidDirectory)); | ||
|
||
Debug.LogError($"Invalid song directory: {cache}"); | ||
continue; | ||
} | ||
|
||
Debug.Log($"Scanning folder: {cache}"); | ||
ScanSubDirectory(cache, cache, _songsByCacheFolder[cache]); | ||
|
||
Debug.Log($"Finished scanning {cache}, writing cache"); | ||
|
||
_songCaches[cache].WriteCache(_songsByCacheFolder[cache]); | ||
Debug.Log("Wrote cache"); | ||
} | ||
} | ||
|
||
private void FastScan() { | ||
Debug.Log("Performing fast scan"); | ||
|
||
// This is stupid | ||
var caches = new Dictionary<string, List<SongEntry>>(); | ||
foreach (string folder in _songsByCacheFolder.Keys) { | ||
try { | ||
Debug.Log($"Reading cache of {folder}"); | ||
caches.Add(folder, _songCaches[folder].ReadCache()); | ||
Debug.Log($"Read cache of {folder}"); | ||
} catch (Exception e) { | ||
_cacheErrors.Add(folder); | ||
|
||
Debug.LogException(e); | ||
Debug.LogError($"Failed to read cache of {folder}"); | ||
} | ||
} | ||
|
||
foreach (var cache in caches) { | ||
_songsByCacheFolder[cache.Key] = cache.Value; | ||
Debug.Log($"Songs read from {cache.Key}: {cache.Value.Count}"); | ||
} | ||
} | ||
|
||
private void ScanSubDirectory(string cacheFolder, string subDir, ICollection<SongEntry> songs) { | ||
_foldersScanned++; | ||
foldersScanned = _foldersScanned; | ||
|
||
// Raw CON folder, so don't scan anymore subdirectories here | ||
string songsPath = Path.Combine(subDir, "songs"); | ||
if (File.Exists(Path.Combine(songsPath, "songs.dta"))) { | ||
var files = XboxRawfileBrowser.BrowseFolder(songsPath, Path.Combine(songsPath, "TODO CHANGE")); | ||
|
||
foreach (var file in files) { | ||
ScanConSong(cacheFolder, file, out var conSong); | ||
|
||
_songsScanned++; | ||
songsScanned = _songsScanned; | ||
songs.Add(conSong); | ||
} | ||
|
||
return; | ||
} | ||
|
||
string[] subdirectories = Directory.GetDirectories(subDir); | ||
|
||
foreach (string subdirectory in subdirectories) { | ||
ScanSubDirectory(cacheFolder, subdirectory, songs); | ||
} | ||
|
||
var result = ScanIniSong(cacheFolder, subDir, out var song); | ||
switch (result) { | ||
case ScanResult.Ok: | ||
_songsScanned++; | ||
songsScanned = _songsScanned; | ||
|
||
songs.Add(song); | ||
break; | ||
case ScanResult.NotASong: | ||
break; | ||
default: | ||
_errorsEncountered++; | ||
errorsEncountered = _errorsEncountered; | ||
_songErrors[cacheFolder].Add(new SongError(subDir, result)); | ||
Debug.LogWarning($"Error encountered with {subDir}"); | ||
break; | ||
} | ||
} | ||
|
||
private static ScanResult ScanIniSong(string cache, string directory, out SongEntry song) { | ||
// Usually we could infer metadata from a .chart file if no ini exists | ||
// But for now we just skip this song. | ||
song = null; | ||
if (!File.Exists(Path.Combine(directory, "song.ini"))) { | ||
return ScanResult.NotASong; | ||
} | ||
|
||
if (!File.Exists(Path.Combine(directory, "notes.chart")) && | ||
!File.Exists(Path.Combine(directory, "notes.mid"))) { | ||
|
||
return ScanResult.NoNotesFile; | ||
} | ||
|
||
if (AudioHelpers.GetSupportedStems(directory).Count == 0) { | ||
return ScanResult.NoAudioFile; | ||
} | ||
|
||
string notesFile = File.Exists(Path.Combine(directory, "notes.chart")) ? "notes.chart" : "notes.mid"; | ||
|
||
// Windows has a 260 character limit for file paths, so we need to check this | ||
if (Path.Combine(directory, notesFile).Length >= 255) { | ||
return ScanResult.NoNotesFile; | ||
} | ||
|
||
byte[] bytes = File.ReadAllBytes(Path.Combine(directory, notesFile)); | ||
|
||
string checksum = BitConverter.ToString(SHA1.Create().ComputeHash(bytes)).Replace("-", ""); | ||
|
||
var tracks = ulong.MaxValue; | ||
|
||
if (notesFile.EndsWith(".mid")) { | ||
tracks = MidPreparser.GetAvailableTracks(bytes); | ||
} else if (notesFile.EndsWith(".chart")) { | ||
tracks = ChartPreparser.GetAvailableTracks(bytes); | ||
} | ||
|
||
// We have a song.ini, notes file and audio. The song is scannable. | ||
song = new IniSongEntry { | ||
CacheRoot = cache, | ||
Location = directory, | ||
Checksum = checksum, | ||
NotesFile = notesFile, | ||
AvailableParts = tracks, | ||
}; | ||
|
||
return ScanHelpers.ParseSongIni(Path.Combine(directory, "song.ini"), (IniSongEntry) song); | ||
} | ||
|
||
private static ScanResult ScanConSong(string cache, XboxSong file, out ExtractedConSongEntry songEntry) { | ||
byte[] bytes = File.ReadAllBytes(Path.Combine(file.SongFolderPath, file.MidiFile)); | ||
|
||
string checksum = BitConverter.ToString(SHA1.Create().ComputeHash(bytes)).Replace("-", ""); | ||
|
||
ulong tracks = MidPreparser.GetAvailableTracks(bytes); | ||
|
||
songEntry = new ExtractedConSongEntry { | ||
CacheRoot = cache, | ||
Location = file.SongFolderPath, | ||
NotesFile = file.MidiFile, | ||
Checksum = checksum, | ||
AvailableParts = tracks, | ||
}; | ||
|
||
file.CompleteSongInfo(songEntry, true); | ||
|
||
return ScanResult.Ok; | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.
Oops, something went wrong.