Skip to content

Commit

Permalink
New scanning (YARC-Official#261)
Browse files Browse the repository at this point in the history
* 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]>
10 people authored May 6, 2023

Partially verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
1 parent a1de23a commit 7f89f6d
Showing 76 changed files with 2,596 additions and 1,316 deletions.
3 changes: 2 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -22,4 +22,5 @@ csharp_new_line_before_finally = false
csharp_new_line_before_members_in_object_initializers = false
csharp_new_line_before_members_in_anonymous_types = false
csharp_new_line_between_query_expression_clauses = false
csharp_space_after_cast = true
csharp_space_after_cast = true
csharp_space_around_declaration_statements = ignore
3 changes: 3 additions & 0 deletions Assets/Art/UI/LoadingBackground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
123 changes: 123 additions & 0 deletions Assets/Art/UI/LoadingBackground.png.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Assets/Plugins/MoonscraperChartParser/MoonSong.cs
Original file line number Diff line number Diff line change
@@ -233,6 +233,11 @@ public bool ChartExistsForInstrument(MoonInstrument moonInstrument)

return false;
}

public bool DoesChartExist(MoonInstrument moonInstrument, Difficulty difficulty)
{
return GetChart(moonInstrument, difficulty).chartObjects.Count > 0;
}

/// <summary>
/// Converts a time value into a tick position value. May be inaccurate due to interger rounding.
366 changes: 366 additions & 0 deletions Assets/Scenes/PersistantScene.unity
Original file line number Diff line number Diff line change
@@ -600,6 +600,142 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 441968690}
m_CullTransparentMesh: 1
--- !u!1 &468169331
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 468169332}
- component: {fileID: 468169334}
- component: {fileID: 468169333}
m_Layer: 5
m_Name: Sub Phrase
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &468169332
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 468169331}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 782876829}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: -112.5}
m_SizeDelta: {x: 0, y: -225}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &468169333
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 468169331}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Sub Phrase
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: ae170e91fd29a90479e906ddffb1d8ee, type: 2}
m_sharedMaterial: {fileID: -5480317595949376055, guid: ae170e91fd29a90479e906ddffb1d8ee,
type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 16
m_fontSizeBase: 16
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 256
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!222 &468169334
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 468169331}
m_CullTransparentMesh: 1
--- !u!1 &482627352
GameObject:
m_ObjectHideFlags: 0
@@ -1208,6 +1344,99 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 713365588}
m_CullTransparentMesh: 1
--- !u!1 &782876828
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 782876829}
- component: {fileID: 782876831}
- component: {fileID: 782876830}
- component: {fileID: 782876832}
m_Layer: 5
m_Name: Loading Screen
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &782876829
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 782876828}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1156818522}
- {fileID: 468169332}
m_Father: {fileID: 1694016463}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &782876830
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 782876828}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 88873eaba76d8bb4d8f28cae7a5e6c0a, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!222 &782876831
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 782876828}
m_CullTransparentMesh: 1
--- !u!114 &782876832
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 782876828}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 57047e1c972a7ac46a6d000a08c356a5, type: 3}
m_Name:
m_EditorClassIdentifier:
loadingPhrase: {fileID: 1156818523}
subPhrase: {fileID: 468169333}
--- !u!1 &788257159
GameObject:
m_ObjectHideFlags: 0
@@ -1592,6 +1821,142 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_HorizontalFit: 0
m_VerticalFit: 2
--- !u!1 &1156818521
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1156818522}
- component: {fileID: 1156818524}
- component: {fileID: 1156818523}
m_Layer: 5
m_Name: Loading Phrase
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1156818522
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1156818521}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 782876829}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0.5}
m_AnchorMax: {x: 1, y: 0.5}
m_AnchoredPosition: {x: 0, y: 16}
m_SizeDelta: {x: 0, y: 32}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1156818523
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1156818521}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Loading Phrase
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 19b7115073766a24aa48ceca884dcbf5, type: 2}
m_sharedMaterial: {fileID: 4249517445589659644, guid: 19b7115073766a24aa48ceca884dcbf5,
type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 16
m_fontSizeBase: 16
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!222 &1156818524
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1156818521}
m_CullTransparentMesh: 1
--- !u!1 &1179509444
GameObject:
m_ObjectHideFlags: 0
@@ -2727,6 +3092,7 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 340418942}
- {fileID: 782876829}
- {fileID: 1330279921}
m_Father: {fileID: 0}
m_RootOrder: 0
6 changes: 3 additions & 3 deletions Assets/Script/Audio/Bass/BassStemMixer.cs
Original file line number Diff line number Diff line change
@@ -76,7 +76,7 @@ public bool SetupMogg(bool isSpeedUp) {
return false;
}

foreach((var stem, int[] channelIndexes) in _moggData.stemMaps) {
foreach((var stem, int[] channelIndexes) in _moggData.StemMaps) {
// For every channel index in this stem, add it to the list of channels
int[] channelStreams = channelIndexes.Select(i => splitStreams[i]).ToArray();
var channel = new BassMoggStem(_manager, stem, channelStreams);
@@ -87,8 +87,8 @@ public bool SetupMogg(bool isSpeedUp) {
var matrixes = new List<float[]>();
foreach (var channelIndex in channelIndexes) {
var matrix = new float[2];
matrix[0] = _moggData.matrixRatios[channelIndex, 0];
matrix[1] = _moggData.matrixRatios[channelIndex, 1];
matrix[0] = _moggData.MatrixRatios[channelIndex, 0];
matrix[1] = _moggData.MatrixRatios[channelIndex, 1];
matrixes.Add(matrix);
}

13 changes: 13 additions & 0 deletions Assets/Script/Data/Difficulty.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System;

namespace YARG.Data {
public enum Difficulty {
EASY = 0,
@@ -29,5 +31,16 @@ public static Difficulty FromChar(char diff) {
_ => throw new System.Exception("Unknown difficulty.")
};
}

public static string ToStringName(this Difficulty difficulty) {
return difficulty switch {
Difficulty.EASY => "Easy",
Difficulty.MEDIUM => "Medium",
Difficulty.HARD => "Hard",
Difficulty.EXPERT => "Expert",
Difficulty.EXPERT_PLUS => "Expert+",
_ => "Unknown"
};
}
}
}
192 changes: 0 additions & 192 deletions Assets/Script/Data/SongInfo.cs

This file was deleted.

11 changes: 0 additions & 11 deletions Assets/Script/Data/SongInfo.cs.meta

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace YARG.Data {
public partial class SongInfo {
public static class SongSources {
private static readonly Dictionary<string, string> DEFAULT_SOURCES = new() {
{ "yarg", "YARG" },

{ "gh1", "Guitar Hero" },
{ "gh", "Guitar Hero" },
{ "gh2", "Guitar Hero II" },
@@ -181,21 +188,87 @@ public partial class SongInfo {
{ "zancharted", "Zancharted" },
{ "zerogravity", "Zero Gravity" },
{ "zgsb", "Zero Gravity - Space Battle" },
{ "yarg", "YARG" },
};

/// <value>
/// The URL of the Clone Hero sources list.
/// </value>
private const string SOURCES_URL = "https://sources.clonehero.net/sources.txt";

/// <value>
/// The location of the local sources file.
/// </value>
private static string SourcesFile => Path.Combine(GameManager.PersistentDataPath, "sources.txt");

/// <value>
/// A dictionary of source IDs to source names.<br/>
/// You must call <see cref="FetchSourcesFromWeb"/> first.
/// </value>
private static Dictionary<string, string> webSourceNames = null;

private static async UniTask FetchSourcesFromWeb() {
try {
// Retrieve sources file
var request = WebRequest.Create(SOURCES_URL);
request.UseDefaultCredentials = true;
request.Timeout = 5000;

// Send the request and wait for the response
using var response = await request.GetResponseAsync();

// Store sources locally and load them
using var fileWriter = File.Create(SourcesFile);
await response.GetResponseStream().CopyToAsync(fileWriter);
} catch (Exception e) {
Debug.LogException(e);
}
}

private static async UniTask ReadSources() {
// Skip if the sources file doesn't exist
if (!File.Exists(SourcesFile)) {
return;
}

webSourceNames ??= new();

var sources = (await File.ReadAllTextAsync(SourcesFile)).Split("\n");
foreach (string source in sources) {
if (string.IsNullOrWhiteSpace(source)) {
continue;
}

// The sources are formatted as follows:
// iconName '=' Display Name
var pair = source.Split("'='", 2);
if (pair.Length < 2) {
Debug.LogWarning($"Invalid source entry when reading sources: {source}");
continue;
}

webSourceNames.Add(pair[0].Trim(), pair[1].Trim());
}
}

public static async UniTask LoadSources() {
await FetchSourcesFromWeb();
await ReadSources();
}

/// <returns>
/// The converted short name (gh1) into the game name (Guitar Hero 1).
/// The converted short name (e.g. gh1) into the game name (e.g. Guitar Hero 1).
/// </returns>
private static string SourceToGameName(string source) {
if (source is null) {
public static string SourceToGameName(string source) {
if (string.IsNullOrEmpty(source)) {
return "Unknown Source";
}

if (SongLibrary.SourceNames != null && SongLibrary.SourceNames.TryGetValue(source, out string name)) {
// Try get from web sources
if (webSourceNames != null && webSourceNames.TryGetValue(source, out string name)) {
return name;
}

// If not, get from default sources
if (DEFAULT_SOURCES.TryGetValue(source, out name)) {
return name;
}
File renamed without changes.
17 changes: 12 additions & 5 deletions Assets/Script/GameManager.cs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
using UnityEngine.Profiling;
using UnityEngine.SceneManagement;
using YARG.Settings;
using YARG.Song;

namespace YARG {
public enum SceneIndex {
@@ -24,6 +25,8 @@ public class GameManager : MonoBehaviour {
/// "Application.persistentDataPath" is main thread only. Why? I don't know.
/// </summary>
public static string PersistentDataPath { get; private set; }
public static string ApplicationDataPath { get; private set; }
public static string ExecutablePath { get; private set; }

public static IAudioManager AudioManager { get; private set; }

@@ -40,11 +43,6 @@ private void Awake() {

Debug.Log($"YARG {Constants.VERSION_TAG}");

AudioManager = gameObject.AddComponent<BassAudioManager>();
AudioManager.Initialize();
}

private void Start() {
// this is to handle a strange edge case in path naming in windows.
// modern windows can handle / or \ in path names with seemingly one exception, if there is a space in the user name then try forward slash appdata, it will break at the first space so:
// c:\users\joe blow\appdata <- okay!
@@ -53,6 +51,15 @@ private void Start() {
// so let's just set them all to \ on windows to be sure.
// For linux Path.DirectorySeparatorChar should return /, and this should work fine, but this should be double checked once those builds are being worked on
PersistentDataPath = Application.persistentDataPath.Replace("/", Path.DirectorySeparatorChar.ToString());
ApplicationDataPath = Application.dataPath.Replace("/", Path.DirectorySeparatorChar.ToString());
ExecutablePath = Directory.GetParent(ApplicationDataPath)?.FullName;
Debug.Log(ExecutablePath);

AudioManager = gameObject.AddComponent<BassAudioManager>();
AudioManager.Initialize();
}

private void Start() {
SettingsManager.LoadSettings();

// High polling rate
102 changes: 102 additions & 0 deletions Assets/Script/LoadingManager.cs
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;
}
}
}
28 changes: 15 additions & 13 deletions Assets/Script/PlayMode/Play.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using MoonscraperChartEditor.Song;
using MoonscraperChartEditor.Song.IO;
using UnityEngine;
@@ -10,6 +11,7 @@
using YARG.Data;
using YARG.Serialization.Parser;
using YARG.Settings;
using YARG.Song;
using YARG.UI;

namespace YARG.PlayMode {
@@ -23,12 +25,12 @@ public static Play Instance {

public const float SONG_START_OFFSET = 1f;

public static SongInfo song = null;
public static SongEntry song = null;

public delegate void BeatAction();
public static event BeatAction BeatEvent;

public delegate void SongStateChangeAction(SongInfo songInfo);
public delegate void SongStateChangeAction(SongEntry songInfo);
public static event SongStateChangeAction OnSongStart;
public static event SongStateChangeAction OnSongEnd;

@@ -47,7 +49,7 @@ public bool SongStarted {
private OccurrenceList<string> audioReverb = new();

private int stemsReverbed;

private float realSongTime;
public float SongTime {
get => realSongTime + PlayerManager.GlobalCalibration * speed;
@@ -111,12 +113,12 @@ private IEnumerator StartSong() {
bool isSpeedUp = Math.Abs(speed - 1) > float.Epsilon;

// Load MOGG if RB_CON, otherwise load stems
if (song.songType == SongInfo.SongType.RB_CON_RAW) {
Debug.Log(song.moggInfo.ChannelCount);
if (song is ExtractedConSongEntry rawConSongEntry) {
Debug.Log(rawConSongEntry.MoggInfo.ChannelCount);

GameManager.AudioManager.LoadMogg(song.moggInfo, isSpeedUp);
GameManager.AudioManager.LoadMogg(rawConSongEntry.MoggInfo, isSpeedUp);
} else {
var stems = AudioHelpers.GetSupportedStems(song.RootFolder);
var stems = AudioHelpers.GetSupportedStems(song.Location);

GameManager.AudioManager.LoadSong(stems, isSpeedUp);
}
@@ -184,7 +186,7 @@ private IEnumerator StartSong() {
private void LoadChart() {
// Add main file
var files = new List<string> {
song.mainFile
Path.Combine(song.Location, song.NotesFile)
};

// Look for upgrades and add
@@ -198,18 +200,18 @@ private void LoadChart() {
// Parse

MoonSong moonSong = null;
if (song.mainFile.EndsWith(".chart")) {
if (song.NotesFile.EndsWith(".chart")) {
Debug.Log("Reading .chart file");
moonSong = ChartReader.ReadChart(song.mainFile);
moonSong = ChartReader.ReadChart(files[0]);
}

chart = new YargChart(moonSong);
if (song.mainFile.EndsWith(".mid")) {
if (song.NotesFile.EndsWith(".mid")) {
// Parse
var parser = new MidiParser(song, files.ToArray());
chart.InitializeArrays();
parser.Parse(chart);
} else if (song.mainFile.EndsWith(".chart")) {
} else if (song.NotesFile.EndsWith(".chart")) {
var handler = new BeatHandler(moonSong);
handler.GenerateBeats();
chart.beats = handler.Beats;
@@ -372,7 +374,7 @@ private void UpdateAudio(string[] trackNames, string[] stemNames) {

if (GameManager.AudioManager.UseStarpowerFx) {
GameManager.AudioManager.ApplyReverb(SongStem.Song, stemsReverbed > 0);

foreach (var name in stemNames) {
var stem = AudioHelpers.GetStemFromName(name);

23 changes: 13 additions & 10 deletions Assets/Script/ScoreManager.cs
Original file line number Diff line number Diff line change
@@ -2,9 +2,12 @@
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using Newtonsoft.Json;
using UnityEngine;
using YARG.Data;
using YARG.Song;

namespace YARG {
public static class ScoreManager {
@@ -18,16 +21,16 @@ public static class ScoreManager {
/// <summary>
/// Should be called before you access any scores.
/// </summary>
public static void FetchScores() {
public static async UniTask FetchScores() {
if (scores != null) {
return;
}

// Read from score file OR create new
try {
if (File.Exists(ScoreFile)) {
string json = File.ReadAllText(ScoreFile.ToString());
scores = JsonConvert.DeserializeObject<Dictionary<string, SongScore>>(json);
string json = await File.ReadAllTextAsync(ScoreFile);
scores = await Task.Run(() => JsonConvert.DeserializeObject<Dictionary<string, SongScore>>(json));
} else {
scores = new();

@@ -42,10 +45,10 @@ public static void FetchScores() {
}
}

public static void PushScore(SongInfo song, SongScore score) {
if (!scores.TryGetValue(song.hash, out var oldScore)) {
public static void PushScore(SongEntry song, SongScore score) {
if (!scores.TryGetValue(song.Checksum, out var oldScore)) {
// If the score info doesn't exist, just add the new one.
scores.Add(song.hash, score);
scores.Add(song.Checksum, score);
} else {
// Otherwise, MERGE!
oldScore.lastPlayed = score.lastPlayed;
@@ -79,8 +82,8 @@ public static void PushScore(SongInfo song, SongScore score) {
SaveScore();
}

public static SongScore GetScore(SongInfo song) {
if (scores.TryGetValue(song.hash, out var o)) {
public static SongScore GetScore(SongEntry song) {
if (scores.TryGetValue(song.Checksum, out var o)) {
return o;
}

@@ -97,11 +100,11 @@ public static void SaveScore() {
});
}

public static List<SongInfo> SongsByPlayCount() {
public static List<SongEntry> SongsByPlayCount() {
return scores
.OrderByDescending(i => i.Value.lastPlayed)
.Select(i => {
if (SongLibrary.SongsByHash.TryGetValue(i.Key, out var song)) {
if (SongContainer.SongsByHash.TryGetValue(i.Key, out var song)) {
return song;
}

19 changes: 10 additions & 9 deletions Assets/Script/Serialization/OuvertExport.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using YARG.Song;

namespace YARG.Serialization {
public static class OuvertExport {
@@ -27,21 +28,21 @@ public static void ExportOuvertSongsTo(string path) {
var songs = new List<SongData>();

// Convert SongInfo to SongData
foreach (var song in SongLibrary.Songs) {
foreach (var song in SongContainer.Songs) {
songs.Add(new SongData {
songName = song.SongName,
artistName = song.artistName,
album = song.album,
genre = song.genre,
charter = song.charter,
year = song.year,
songLength = (int) (song.songLength * 1000f)
songName = song.Name,
artistName = song.Artist,
album = song.Album,
genre = song.Genre,
charter = song.Charter,
year = song.Year,
songLength = (int) (song.SongLength * 1000f)
});
}

// Create file
var json = JsonConvert.SerializeObject(songs);
File.WriteAllText(path, json.ToString());
File.WriteAllText(path, json);
}
}
}
7 changes: 4 additions & 3 deletions Assets/Script/Serialization/Parser/AbstractParser.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using YARG.Data;
using YARG.Song;

namespace YARG.Serialization.Parser {
public abstract class AbstractParser {
protected SongInfo songInfo;
protected SongEntry songEntry;
protected string[] files;

public AbstractParser(SongInfo songInfo, string[] files) {
this.songInfo = songInfo;
public AbstractParser(SongEntry songEntry, string[] files) {
this.songEntry = songEntry;
this.files = files;
}

5 changes: 3 additions & 2 deletions Assets/Script/Serialization/Parser/MidiParser.Drums.cs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
using Melanchall.DryWetMidi.MusicTheory;
using UnityEngine;
using YARG.Data;
using YARG.Song;

namespace YARG.Serialization.Parser {
public partial class MidiParser : AbstractParser {
@@ -32,10 +33,10 @@ private struct DrumFlagIR {
public DrumFlags drumFlags;
}

private List<NoteInfo> ParseDrums(TrackChunk trackChunk, bool pro, int difficulty, SongInfo.DrumType drumType, List<NoteInfo> ghEquivalent) {
private List<NoteInfo> ParseDrums(TrackChunk trackChunk, bool pro, int difficulty, DrumType drumType, List<NoteInfo> ghEquivalent) {
var tempoMap = midi.GetTempoMap();

if (drumType == SongInfo.DrumType.FOUR_LANE) {
if (drumType == DrumType.FourLane) {
var notes = DrumNotePass(trackChunk, difficulty, tempoMap);

if (pro) {
2 changes: 1 addition & 1 deletion Assets/Script/Serialization/Parser/MidiParser.FiveFret.cs
Original file line number Diff line number Diff line change
@@ -298,7 +298,7 @@ private void FiveFretNoteStatePass(List<FiveFretIR> noteIR, List<ForceStateIR> f

// Use HOPO frequency value from song info.
// Convert the ticks to a musical time span.
if (distance <= new MusicalTimeSpan(songInfo.hopoFreq, 480 * 4)) {
if (distance <= new MusicalTimeSpan(songEntry.HopoThreshold, 480 * 4)) {
note.hopo = true;
note.autoHopo = true;
}
5 changes: 3 additions & 2 deletions Assets/Script/Serialization/Parser/MidiParser.GHDrums.cs
Original file line number Diff line number Diff line change
@@ -3,13 +3,14 @@
using Melanchall.DryWetMidi.Interaction;
using Melanchall.DryWetMidi.MusicTheory;
using YARG.Data;
using YARG.Song;

namespace YARG.Serialization.Parser {
public partial class MidiParser : AbstractParser {
private List<NoteInfo> ParseGHDrums(TrackChunk trackChunk, int difficulty, SongInfo.DrumType drumType, List<NoteInfo> stdEquivalent) {
private List<NoteInfo> ParseGHDrums(TrackChunk trackChunk, int difficulty, DrumType drumType, List<NoteInfo> stdEquivalent) {
var tempoMap = midi.GetTempoMap();

if (drumType == SongInfo.DrumType.FIVE_LANE) {
if (drumType == DrumType.FourLane) {
return GHDrumNotePass(trackChunk, difficulty, tempoMap);
} else {
return DrumFromStandard(stdEquivalent);
25 changes: 13 additions & 12 deletions Assets/Script/Serialization/Parser/MidiParser.cs
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
using YARG.Chart;
using YARG.Data;
using YARG.DiffDownsample;
using YARG.Song;

namespace YARG.Serialization.Parser {
public partial class MidiParser : AbstractParser {
@@ -21,7 +22,7 @@ private struct EventIR {

public MidiFile midi;

public MidiParser(SongInfo songInfo, string[] files) : base(songInfo, files) {
public MidiParser(SongEntry songEntry, string[] files) : base(songEntry, files) {
midi = MidiFile.Read(files[0], new ReadingSettings() { TextEncoding = System.Text.Encoding.UTF8 });

// Merge midi files
@@ -150,7 +151,7 @@ public override void Parse(YargChart chart) {
case "PART DRUMS":
var drumType = GetDrumType(trackChunk);

if (drumType == SongInfo.DrumType.FOUR_LANE) {
if (drumType == DrumType.FourLane) {
for (int i = 0; i < 5; i++) {
chart.Drums[i] = ParseDrums(trackChunk, false, i, drumType, null);
chart.RealDrums[i] = ParseDrums(trackChunk, true, i, drumType, null);
@@ -226,7 +227,7 @@ public override void Parse(YargChart chart) {

// Add delay
foreach (var note in difficulty) {
note.time += songInfo.delay;
note.time += (float)songEntry.Delay;
}

// Last note time
@@ -239,16 +240,16 @@ public override void Parse(YargChart chart) {
// Add delay to vocals

foreach (var lyric in chart.genericLyrics) {
lyric.time += songInfo.delay;
lyric.time += (float)songEntry.Delay;
}

foreach (var lyric in chart.realLyrics) {
lyric.time += songInfo.delay;
lyric.time += (float)songEntry.Delay;
}

foreach (var lyricList in chart.harmLyrics) {
foreach (var lyric in lyricList) {
lyric.time += songInfo.delay;
lyric.time += (float)songEntry.Delay;
}
}

@@ -278,7 +279,7 @@ public override void Parse(YargChart chart) {

chart.events.Sort(new Comparison<EventInfo>((a, b) => a.time.CompareTo(b.time)));
foreach (var ev in chart.events) {
ev.time += songInfo.delay;
ev.time += (float)songEntry.Delay;
}

// Add beats to chart
@@ -432,9 +433,9 @@ private void ParseDrumFills(List<EventIR> eventIR, TrackChunk trackChunk, string
}
}

private SongInfo.DrumType GetDrumType(TrackChunk trackChunk) {
if (songInfo.drumType != SongInfo.DrumType.UNKNOWN) {
return songInfo.drumType;
private DrumType GetDrumType(TrackChunk trackChunk) {
if (songEntry.DrumType != DrumType.Unknown) {
return songEntry.DrumType;
}

// If we don't know the drum type...
@@ -445,12 +446,12 @@ private SongInfo.DrumType GetDrumType(TrackChunk trackChunk) {

// Look for the expert 5th-lane note
if (note.NoteNumber == 101) {
return SongInfo.DrumType.FIVE_LANE;
return DrumType.FiveLane;
}
}

// If we didn't find the note, assume 4-lane
return SongInfo.DrumType.FOUR_LANE;
return DrumType.FourLane;
}
}
}
156 changes: 0 additions & 156 deletions Assets/Script/Serialization/SongIni.cs

This file was deleted.

5 changes: 3 additions & 2 deletions Assets/Script/Serialization/Xbox/XboxImage.cs
Original file line number Diff line number Diff line change
@@ -15,8 +15,9 @@ public class XboxImage {
public int Format { get; set; }
public short Width { get; set; }
public short Height { get; set; }
private uint ImgSize;
private uint[] ImgOffsets;
public uint ImgSize { get; }
public uint[] ImgOffsets { get; }

private bool isFromCON = false;

[JsonIgnore]
101 changes: 53 additions & 48 deletions Assets/Script/Serialization/Xbox/XboxMoggData.cs
Original file line number Diff line number Diff line change
@@ -20,8 +20,9 @@ public class XboxMoggData {
public int ChannelCount { get; set; }
public int Header { get; set; }

private uint MoggSize = 0;
private uint[] MoggOffsets = null;
public uint MoggSize { get; }
public uint[] MoggOffsets { get; }

private bool isFromCON = false;

public int MoggAddressAudioOffset { get; set; }
@@ -30,21 +31,25 @@ public class XboxMoggData {
public float[] PanData { get; set; }
public float[] VolumeData { get; set; }

public Dictionary<string, int[]> tracks;
public int[] crowdChannels;
public Dictionary<string, int[]> Tracks { get; set; }
public int[] CrowdChannels { get; set; }

public Dictionary<SongStem, int[]> stemMaps;
public float[,] matrixRatios;
public Dictionary<SongStem, int[]> StemMaps { get; set; }
public float[,] MatrixRatios { get; set; }

public XboxMoggData(string str) {
MoggPath = str;

CrowdChannels = Array.Empty<int>();
}

public XboxMoggData(string str, uint size, uint[] offsets) {
MoggPath = str;
MoggSize = size;
MoggOffsets = offsets;
isFromCON = true;

CrowdChannels = Array.Empty<int>();
}

public void ParseMoggHeader() {
@@ -85,7 +90,7 @@ public void ParseFromDta(DataArray dta) {
switch (dtaArray[0].ToString()) {
case "tracks":
var trackArray = (DataArray) dtaArray[1];
tracks = new Dictionary<string, int[]>();
Tracks = new Dictionary<string, int[]>();

for (int x = 0; x < trackArray.Count; x++) {
if (trackArray[x] is not DataArray instrArray) continue;
@@ -98,11 +103,11 @@ public void ParseFromDta(DataArray dta) {
val = new int[trackNums.Count];
for (int y = 0; y < trackNums.Count; y++)
val[y] = ((DataAtom) trackNums[y]).Int;
tracks.Add(key, val);
Tracks.Add(key, val);
} else if (instrArray[1] is DataAtom trackNum) {
val = new int[1];
val[0] = trackNum.Int;
tracks.Add(key, val);
Tracks.Add(key, val);
}
}
break;
@@ -118,17 +123,17 @@ public void ParseFromDta(DataArray dta) {
for (int v = 0; v < volArray.Count; v++) VolumeData[v] = ((DataAtom) volArray[v]).Float;
break;
case "crowd_channels":
crowdChannels = new int[dtaArray.Count - 1];
CrowdChannels = new int[dtaArray.Count - 1];
for (int cc = 1; cc < dtaArray.Count; cc++)
crowdChannels[cc - 1] = ((DataAtom) dtaArray[cc]).Int;
CrowdChannels[cc - 1] = ((DataAtom) dtaArray[cc]).Int;
break;
}
}
}

public override string ToString() {
string debugTrackStr = "";
foreach (var kvp in tracks) {
foreach (var kvp in Tracks) {
debugTrackStr += $"{kvp.Key}, ({string.Join(", ", kvp.Value)}) ";
}

@@ -142,39 +147,39 @@ public override string ToString() {
}

public void CalculateMoggBassInfo() {
stemMaps = new Dictionary<SongStem, int[]>();
StemMaps = new Dictionary<SongStem, int[]>();
var mapped = new bool[ChannelCount];

// BEGIN BASS Stem Mapping ----------------------------------------------------------------------

if (tracks.TryGetValue("drum", out var drumArray)) {
if (Tracks.TryGetValue("drum", out var drumArray)) {
switch (drumArray.Length) {
//drum (0 1): stereo kit --> (0 1)
case 2:
stemMaps[SongStem.Drums] = new[] { drumArray[0], drumArray[1] };
StemMaps[SongStem.Drums] = new[] { drumArray[0], drumArray[1] };
break;
//drum (0 1 2): mono kick, stereo snare/kit --> (0) (1 2)
case 3:
stemMaps[SongStem.Drums1] = new[] { drumArray[0] };
stemMaps[SongStem.Drums2] = new[] { drumArray[1], drumArray[2] };
StemMaps[SongStem.Drums1] = new[] { drumArray[0] };
StemMaps[SongStem.Drums2] = new[] { drumArray[1], drumArray[2] };
break;
//drum (0 1 2 3): mono kick, mono snare, stereo kit --> (0) (1) (2 3)
case 4:
stemMaps[SongStem.Drums1] = new[] { drumArray[0] };
stemMaps[SongStem.Drums2] = new[] { drumArray[1] };
stemMaps[SongStem.Drums3] = new[] { drumArray[2], drumArray[3] };
StemMaps[SongStem.Drums1] = new[] { drumArray[0] };
StemMaps[SongStem.Drums2] = new[] { drumArray[1] };
StemMaps[SongStem.Drums3] = new[] { drumArray[2], drumArray[3] };
break;
//drum (0 1 2 3 4): mono kick, stereo snare, stereo kit --> (0) (1 2) (3 4)
case 5:
stemMaps[SongStem.Drums1] = new[] { drumArray[0] };
stemMaps[SongStem.Drums2] = new[] { drumArray[1], drumArray[2] };
stemMaps[SongStem.Drums3] = new[] { drumArray[3], drumArray[4] };
StemMaps[SongStem.Drums1] = new[] { drumArray[0] };
StemMaps[SongStem.Drums2] = new[] { drumArray[1], drumArray[2] };
StemMaps[SongStem.Drums3] = new[] { drumArray[3], drumArray[4] };
break;
//drum (0 1 2 3 4 5): stereo kick, stereo snare, stereo kit --> (0 1) (2 3) (4 5)
case 6:
stemMaps[SongStem.Drums1] = new[] { drumArray[0], drumArray[1] };
stemMaps[SongStem.Drums2] = new[] { drumArray[2], drumArray[3] };
stemMaps[SongStem.Drums3] = new[] { drumArray[4], drumArray[5] };
StemMaps[SongStem.Drums1] = new[] { drumArray[0], drumArray[1] };
StemMaps[SongStem.Drums2] = new[] { drumArray[2], drumArray[3] };
StemMaps[SongStem.Drums3] = new[] { drumArray[4], drumArray[5] };
break;
}

@@ -183,58 +188,58 @@ public void CalculateMoggBassInfo() {
}
}

if (tracks.TryGetValue("bass", out var bassArray)) {
stemMaps[SongStem.Bass] = new int[bassArray.Length];
if (Tracks.TryGetValue("bass", out var bassArray)) {
StemMaps[SongStem.Bass] = new int[bassArray.Length];
for (int i = 0; i < bassArray.Length; i++) {
stemMaps[SongStem.Bass][i] = bassArray[i];
StemMaps[SongStem.Bass][i] = bassArray[i];
mapped[bassArray[i]] = true;
}
}

if (tracks.TryGetValue("guitar", out var gtrArray)) {
stemMaps[SongStem.Guitar] = new int[gtrArray.Length];
if (Tracks.TryGetValue("guitar", out var gtrArray)) {
StemMaps[SongStem.Guitar] = new int[gtrArray.Length];
for (int i = 0; i < gtrArray.Length; i++) {
stemMaps[SongStem.Guitar][i] = gtrArray[i];
StemMaps[SongStem.Guitar][i] = gtrArray[i];
mapped[gtrArray[i]] = true;
}
}

if (tracks.TryGetValue("vocals", out var voxArray)) {
stemMaps[SongStem.Vocals] = new int[voxArray.Length];
if (Tracks.TryGetValue("vocals", out var voxArray)) {
StemMaps[SongStem.Vocals] = new int[voxArray.Length];
for (int i = 0; i < voxArray.Length; i++) {
stemMaps[SongStem.Vocals][i] = voxArray[i];
StemMaps[SongStem.Vocals][i] = voxArray[i];
mapped[voxArray[i]] = true;
}
}

if (tracks.TryGetValue("keys", out var keysArray)) {
stemMaps[SongStem.Keys] = new int[keysArray.Length];
if (Tracks.TryGetValue("keys", out var keysArray)) {
StemMaps[SongStem.Keys] = new int[keysArray.Length];
for (int i = 0; i < keysArray.Length; i++) {
stemMaps[SongStem.Keys][i] = keysArray[i];
StemMaps[SongStem.Keys][i] = keysArray[i];
mapped[keysArray[i]] = true;
}
}

if (crowdChannels != null) {
stemMaps[SongStem.Crowd] = new int[crowdChannels.Length];
for (int i = 0; i < crowdChannels.Length; i++) {
stemMaps[SongStem.Crowd][i] = crowdChannels[i];
mapped[crowdChannels[i]] = true;
if (CrowdChannels.Length > 0) {
StemMaps[SongStem.Crowd] = new int[CrowdChannels.Length];
for (int i = 0; i < CrowdChannels.Length; i++) {
StemMaps[SongStem.Crowd][i] = CrowdChannels[i];
mapped[CrowdChannels[i]] = true;
}
}

// every index in mapped that is still false, goes in the backing
var fakeIndices = Enumerable.Range(0, mapped.Length).Where(i => !mapped[i]).ToList();
stemMaps[SongStem.Song] = new int[fakeIndices.Count];
StemMaps[SongStem.Song] = new int[fakeIndices.Count];
for (int i = 0; i < fakeIndices.Count; i++) {
stemMaps[SongStem.Song][i] = fakeIndices[i];
StemMaps[SongStem.Song][i] = fakeIndices[i];
}

// END BASS Stem Mapping ------------------------------------------------------------------------

// BEGIN BASS Matrix calculation ----------------------------------------------------------------

matrixRatios = new float[PanData.Length, 2];
MatrixRatios = new float[PanData.Length, 2];

Parallel.For(0, PanData.Length, i => {
float theta = PanData[i] * ((float) Math.PI / 4);
@@ -243,8 +248,8 @@ public void CalculateMoggBassInfo() {

float volRatio = (float) Math.Pow(10, VolumeData[i] / 20);

matrixRatios[i, 0] = volRatio * ratioL;
matrixRatios[i, 1] = volRatio * ratioR;
MatrixRatios[i, 0] = volRatio * ratioL;
MatrixRatios[i, 1] = volRatio * ratioR;
});

// END BASS Matrix calculation ------------------------------------------------------------------
55 changes: 26 additions & 29 deletions Assets/Script/Serialization/Xbox/XboxSong.cs
Original file line number Diff line number Diff line change
@@ -3,14 +3,15 @@
using DtxCS.DataTypes;
using UnityEngine;
using YARG.Data;
using YARG.Song;

namespace YARG.Serialization {
public class XboxSong {
public string ShortName { get; private set; }
public string MidiFile { get; private set; }
public string MidiUpdateFile { get; private set; }

private string songFolderPath;
public string SongFolderPath { get; }

private XboxSongData songDta;
private XboxMoggData moggDta;
@@ -25,19 +26,19 @@ public XboxSong(string pathName, DataArray dta) {
ShortName = songDta.GetShortName();

// Get song folder path for mid, mogg, png_xbox
songFolderPath = Path.Combine(pathName, ShortName);
SongFolderPath = Path.Combine(pathName, ShortName);

// Set midi file
MidiFile = Path.Combine(songFolderPath, $"{ShortName}.mid");
MidiFile = Path.Combine(SongFolderPath, $"{ShortName}.mid");

// Parse the mogg
moggDta = new XboxMoggData(Path.Combine(songFolderPath, $"{ShortName}.mogg"));
moggDta = new XboxMoggData(Path.Combine(SongFolderPath, $"{ShortName}.mogg"));
moggDta.ParseMoggHeader();
moggDta.ParseFromDta(dta.Array("song"));
moggDta.CalculateMoggBassInfo();

// Parse the image
string imgPath = Path.Combine(songFolderPath, "gen", $"{ShortName}_keep.png_xbox");
string imgPath = Path.Combine(SongFolderPath, "gen", $"{ShortName}_keep.png_xbox");
if (songDta.AlbumArtRequired() && File.Exists(imgPath)) {
img = new XboxImage(imgPath);
}
@@ -93,45 +94,41 @@ public bool IsValidSong() {
public override string ToString() {
return string.Join(Environment.NewLine,
$"XBOX SONG {ShortName}",
$"song folder path: {songFolderPath}",
$"song folder path: {SongFolderPath}",
"",
songDta.ToString(),
"",
moggDta.ToString()
);
}

public void CompleteSongInfo(SongInfo song, bool rb) {
if (song.fetched) {
return;
}
song.fetched = true;

public void CompleteSongInfo(ExtractedConSongEntry song, bool rb) {
// Set infos
song.SongName = songDta.name;
song.source = songDta.gameOrigin;
song.Name = songDta.name;
song.Source = songDta.gameOrigin;

// if the source is UGC/UGC_plus but no "UGC_" in shortname, assume it's a custom
if(songDta.gameOrigin == "ugc" || songDta.gameOrigin == "ugc_plus"){
if(!(songDta.shortname.Contains("UGC_"))){
song.source = "customs";
if (songDta.gameOrigin == "ugc" || songDta.gameOrigin == "ugc_plus") {
if (!(songDta.shortname.Contains("UGC_"))) {
song.Source = "customs";
}
}

song.songLength = songDta.songLength / 1000f;
song.SongLength = (int) songDta.songLength / 1000;
// song.delay
song.drumType = rb ? SongInfo.DrumType.FOUR_LANE : SongInfo.DrumType.FIVE_LANE;
if (songDta.hopoThreshold != 0) song.hopoFreq = songDta.hopoThreshold;
song.artistName = songDta.artist ?? "Unknown Artist";
song.album = songDta.albumName;
song.genre = songDta.genre;
song.DrumType = rb ? DrumType.FourLane : DrumType.FiveLane;
if (songDta.hopoThreshold != 0)
song.HopoThreshold = songDta.hopoThreshold;
song.Artist = songDta.artist ?? "Unknown Artist";
song.Album = songDta.albumName;
song.Genre = songDta.genre;
// song.charter
song.year = songDta.yearReleased?.ToString();
song.Year = songDta.yearReleased?.ToString();
// song.loadingPhrase

// Set CON specific info
song.moggInfo = moggDta;
song.imageInfo = img;
song.MoggInfo = moggDta;
song.ImageInfo = img;

// Set difficulties
foreach (var (key, value) in songDta.ranks) {
@@ -140,15 +137,15 @@ public void CompleteSongInfo(SongInfo song, bool rb) {
continue;
}

song.partDifficulties[instrument] = DtaDifficulty.ToNumberedDiff(instrument, value);
song.PartDifficulties[instrument] = DtaDifficulty.ToNumberedDiff(instrument, value);
}

// Set pro drums
song.partDifficulties[Instrument.REAL_DRUMS] = song.partDifficulties[Instrument.DRUMS];
song.PartDifficulties[Instrument.REAL_DRUMS] = song.PartDifficulties[Instrument.DRUMS];

// Set harmony difficulty (if exists)
if (songDta.vocalParts > 1) {
song.partDifficulties[Instrument.HARMONY] = song.partDifficulties[Instrument.VOCALS];
song.PartDifficulties[Instrument.HARMONY] = song.PartDifficulties[Instrument.VOCALS];
}
}
}
38 changes: 14 additions & 24 deletions Assets/Script/Settings/SettingsDirectory.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
using SFB;
using TMPro;
using UnityEngine;
using YARG.UI;
using YARG.Song;
using YARG.Util;

namespace YARG.Settings {
@@ -16,14 +17,14 @@ public class SettingsDirectory : MonoBehaviour {
private bool isUpgradeFolder;
private int index;

private string[] _pathsReference;
private string[] PathsReference {
private List<string> _pathsReference;
private List<string> PathsReference {
get => _pathsReference;
set {
if (isUpgradeFolder) {
SongLibrary.SongUpgradeFolders = value;
SettingsManager.Settings.SongUpgradeFolders = value;
} else {
SongLibrary.SongFolders = value;
SettingsManager.Settings.SongFolders = value;
}
}
}
@@ -33,9 +34,9 @@ public void SetIndex(int index, bool isUpgradeFolder) {
this.isUpgradeFolder = isUpgradeFolder;

if (isUpgradeFolder) {
_pathsReference = SongLibrary.SongUpgradeFolders;
_pathsReference = SettingsManager.Settings.SongUpgradeFolders;
} else {
_pathsReference = SongLibrary.SongFolders;
_pathsReference = SettingsManager.Settings.SongFolders;
}

RefreshText();
@@ -51,18 +52,16 @@ private void RefreshText() {
if (isUpgradeFolder) {
songCountText.text = "";
} else {
int songCount = SongLibrary.Songs.Count(i =>
Utils.PathsEqual(i.cacheRoot, PathsReference[index]));
int songCount = SongContainer.Songs.Count(i =>
Utils.PathsEqual(i.CacheRoot, PathsReference[index]));
songCountText.text = $"{songCount} <alpha=#60>SONGS";
}
}
}

public void Remove() {
// Remove the element
var list = PathsReference.ToList();
list.RemoveAt(index);
PathsReference = list.ToArray();
PathsReference.RemoveAt(index);

// Refresh
GameManager.Instance.SettingsMenu.UpdateSongFolderManager();
@@ -81,17 +80,8 @@ public void Browse() {
}

public void Refresh() {
GameManager.Instance.SettingsMenu.hasSongLibraryChanged = false;

// Delete it
var file = SongLibrary.HashFilePath(PathsReference[index]);
var path = Path.Combine(SongLibrary.CacheFolder, file + ".json");
if (File.Exists(path)) {
File.Delete(path);
}

// Refresh
MainMenu.Instance.RefreshSongLibrary();
LoadingManager.Instance.QueueSongFolderRefresh(PathsReference[index]);
LoadingManager.Instance.StartLoad().Forget();
}
}
}
10 changes: 3 additions & 7 deletions Assets/Script/Settings/SettingsManager.Settings.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using SFB;
using UnityEngine;
using YARG.PlayMode;
@@ -9,8 +10,8 @@ public static partial class SettingsManager {
public class SettingContainer {
#pragma warning disable format

public string[] SongFolders = { };
public string[] SongUpgradeFolders = { };
public List<string> SongFolders = new();
public List<string> SongUpgradeFolders = new();

public IntSetting CalibrationNumber { get; private set; } = new(-120);

@@ -51,11 +52,6 @@ public class SettingContainer {
#pragma warning restore format

public void OpenSongFolderManager() {
// if (MainMenu.Instance != null) {
// MainMenu.Instance.ShowSongFolderManager();
// GameManager.Instance.SettingsMenu.gameObject.SetActive(false);
// }

GameManager.Instance.SettingsMenu.CurrentTab = "_SongFolderManager";
}

55 changes: 25 additions & 30 deletions Assets/Script/Settings/SettingsMenu.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
using System;
using System.IO;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Localization;
using UnityEngine.Localization.Components;
using UnityEngine.UI;
using YARG.Metadata;
using YARG.Settings.Visuals;
using YARG.UI;
using YARG.Util;

namespace YARG.Settings {
@@ -55,24 +53,25 @@ public string CurrentTab {
}
}

public bool hasSongLibraryChanged = false;
public bool UpdateSongLibraryOnExit { get; set; } = false;

private void OnEnable() {
ReturnToFirstTab();
UpdateTabs();
}

private void OnDisable() {
private async UniTask OnDisable() {
DestroyPreview();

// Save on close
SettingsManager.SaveSettings();

if (hasSongLibraryChanged) {
// Refresh
MainMenu.Instance.RefreshSongLibrary();
if (UpdateSongLibraryOnExit) {
UpdateSongLibraryOnExit = false;

hasSongLibraryChanged = false;
// Do a song refresh if requested
LoadingManager.Instance.QueueSongRefresh(true);
await LoadingManager.Instance.StartLoad();
}
}

@@ -161,7 +160,7 @@ private void UpdateSettings(Transform container) {
}

public void UpdateSongFolderManager() {
hasSongLibraryChanged = true;
UpdateSongLibraryOnExit = true;

// Destroy all previous settings
foreach (Transform t in settingsContainer) {
@@ -174,16 +173,9 @@ public void UpdateSongFolderManager() {
// Spawn refresh all button
{
var go = Instantiate(buttonPrefab, settingsContainer);
go.GetComponent<SettingsButton>().SetCustomCallback(() => {
hasSongLibraryChanged = false;

if (Directory.Exists(SongLibrary.CacheFolder)) {
// Delete cache folder
Directory.Delete(SongLibrary.CacheFolder, true);

// Refresh
MainMenu.Instance.RefreshSongLibrary();
}
go.GetComponent<SettingsButton>().SetCustomCallback(async () => {
LoadingManager.Instance.QueueSongRefresh(false);
await LoadingManager.Instance.StartLoad();
}, "RefreshAllCaches");
}

@@ -194,46 +186,46 @@ public void UpdateSongFolderManager() {
{
var go = Instantiate(buttonPrefab, settingsContainer);
go.GetComponent<SettingsButton>().SetCustomCallback(() => {
// Use a list to add the new folder to the end
Array.Resize(ref SettingsManager.Settings.SongFolders,
SongLibrary.SongFolders.Length + 1);
SettingsManager.Settings.SongFolders.Add(string.Empty);

// Refresh everything
UpdateSongFolderManager();
}, "AddFolder");
}

// Create all of the directories
for (int i = 0; i < SongLibrary.SongFolders.Length; i++) {
var path = SongLibrary.SongFolders[i];
for (int i = 0; i < SettingsManager.Settings.SongFolders.Count; i++) {
var path = SettingsManager.Settings.SongFolders[i];

var go = Instantiate(directoryPrefab, settingsContainer);
go.GetComponent<SettingsDirectory>().SetIndex(i, false);
}

/*
// Spawn header
SpawnHeader(settingsContainer, "Header.SongUpgrades");
// Spawn add upgrade folder button
{
var go = Instantiate(buttonPrefab, settingsContainer);
go.GetComponent<SettingsButton>().SetCustomCallback(() => {
// Use a list to add the new folder to the end
Array.Resize(ref SettingsManager.Settings.SongUpgradeFolders,
SongLibrary.SongUpgradeFolders.Length + 1);
SettingsManager.Settings.SongUpgradeFolders.Add(string.Empty);
// Refresh everything
UpdateSongFolderManager();
}, "AddUpgradeFolder");
}
// Create all of the song upgrade directories
for (int i = 0; i < SongLibrary.SongUpgradeFolders.Length; i++) {
var path = SongLibrary.SongUpgradeFolders[i];
for (int i = 0; i < SettingsManager.Settings.SongUpgradeFolders.Count; i++) {
var path = SettingsManager.Settings.SongUpgradeFolders[i];
var go = Instantiate(directoryPrefab, settingsContainer);
go.GetComponent<SettingsDirectory>().SetIndex(i, true);
}
*/
}

private void SpawnHeader(Transform container, string localizationKey) {
@@ -267,6 +259,9 @@ private void UpdatePreview(SettingsManager.Tab tabInfo) {
}

private void DestroyPreview() {
if (previewContainer == null)
return;

foreach (Transform t in previewContainer) {
Destroy(t.gameObject);
}
3 changes: 3 additions & 0 deletions Assets/Script/Song.meta
184 changes: 184 additions & 0 deletions Assets/Script/Song/CacheHelpers.cs
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;
}

}
}
3 changes: 3 additions & 0 deletions Assets/Script/Song/CacheHelpers.cs.meta
17 changes: 17 additions & 0 deletions Assets/Script/Song/NullStringBinaryWriter.cs
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);
}
}
}
3 changes: 3 additions & 0 deletions Assets/Script/Song/NullStringBinaryWriter.cs.meta
3 changes: 3 additions & 0 deletions Assets/Script/Song/Preparsers.meta
106 changes: 106 additions & 0 deletions Assets/Script/Song/Preparsers/ChartPreparser.cs
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;
}
}
}
3 changes: 3 additions & 0 deletions Assets/Script/Song/Preparsers/ChartPreparser.cs.meta
81 changes: 81 additions & 0 deletions Assets/Script/Song/Preparsers/MidPreparser.cs
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;
}
}
}
3 changes: 3 additions & 0 deletions Assets/Script/Song/Preparsers/MidPreparser.cs.meta
95 changes: 95 additions & 0 deletions Assets/Script/Song/ScanHelpers.cs
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));
}
}
}
3 changes: 3 additions & 0 deletions Assets/Script/Song/ScanHelpers.cs.meta
3 changes: 3 additions & 0 deletions Assets/Script/Song/Scanning.meta
245 changes: 245 additions & 0 deletions Assets/Script/Song/Scanning/SongScanThread.cs
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;
}
}

}
3 changes: 3 additions & 0 deletions Assets/Script/Song/Scanning/SongScanThread.cs.meta
Loading

0 comments on commit 7f89f6d

Please sign in to comment.