diff --git a/Assets/Script/Audio/Bass/BassAudioManager.cs b/Assets/Script/Audio/Bass/BassAudioManager.cs index cc0344314..8261dd3a3 100644 --- a/Assets/Script/Audio/Bass/BassAudioManager.cs +++ b/Assets/Script/Audio/Bass/BassAudioManager.cs @@ -204,22 +204,18 @@ public void LoadSong(ICollection stems, bool isSpeedUp) { IsAudioLoaded = true; } - public void LoadMogg(XboxMoggData moggData, bool isSpeedUp) { + public void LoadMogg(byte[] moggArray, + Dictionary stemMaps, float[,] matrixRatios, bool isSpeedUp) { Debug.Log("Loading mogg song"); UnloadSong(); - int moggOffset = moggData.MoggAddressAudioOffset; - long moggLength = moggData.MoggAudioLength; - - byte[] moggArray = moggData.GetOggDataFromMogg(); - - int moggStreamHandle = Bass.CreateStream(moggArray, 0, moggLength, BassFlags.Prescan | BassFlags.Decode | BassFlags.AsyncFile); + int moggStreamHandle = Bass.CreateStream(moggArray, 0, moggArray.Length, BassFlags.Prescan | BassFlags.Decode | BassFlags.AsyncFile); if (moggStreamHandle == 0) { Debug.LogError($"Failed to load mogg file or position: {Bass.LastError}"); return; } - _mixer = new BassStemMixer(this, moggStreamHandle, moggData); + _mixer = new BassStemMixer(this, moggStreamHandle, stemMaps, matrixRatios); if (!_mixer.Create()) { throw new Exception($"Failed to create mixer: {Bass.LastError}"); } diff --git a/Assets/Script/Audio/Bass/BassStemMixer.cs b/Assets/Script/Audio/Bass/BassStemMixer.cs index 5704cdf81..75b7db3f1 100644 --- a/Assets/Script/Audio/Bass/BassStemMixer.cs +++ b/Assets/Script/Audio/Bass/BassStemMixer.cs @@ -19,9 +19,11 @@ public class BassStemMixer : IStemMixer { private readonly IAudioManager _manager; private readonly Dictionary _channels; - private readonly XboxMoggData _moggData; private readonly bool _isMogg; + private readonly Dictionary _stemMaps; + private readonly float[,] _matrixRatios; + private int _mixerHandle; private int _moggSourceHandle; @@ -36,10 +38,11 @@ public BassStemMixer(IAudioManager manager) { IsPlaying = false; } - public BassStemMixer(IAudioManager manager, int moggHandle, XboxMoggData moggData) : this(manager) { + public BassStemMixer(IAudioManager manager, int moggHandle, Dictionary maps, float[,] ratios) : this(manager) { _isMogg = true; _moggSourceHandle = moggHandle; - _moggData = moggData; + _stemMaps = maps; + _matrixRatios = ratios; } ~BassStemMixer() { @@ -76,7 +79,7 @@ public bool SetupMogg(bool isSpeedUp) { return false; } - foreach((var stem, int[] channelIndexes) in _moggData.StemMaps) { + foreach((var stem, int[] channelIndexes) in _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 +90,8 @@ public bool SetupMogg(bool isSpeedUp) { var matrixes = new List(); foreach (var channelIndex in channelIndexes) { var matrix = new float[2]; - matrix[0] = _moggData.MatrixRatios[channelIndex, 0]; - matrix[1] = _moggData.MatrixRatios[channelIndex, 1]; + matrix[0] = _matrixRatios[channelIndex, 0]; + matrix[1] = _matrixRatios[channelIndex, 1]; matrixes.Add(matrix); } @@ -304,7 +307,7 @@ private void Dispose(bool disposing) { } private int[] SplitMoggIntoChannels() { - var channels = new int[_moggData.ChannelCount]; + var channels = new int[_matrixRatios.GetLength(0)]; var channelMap = new int[2]; channelMap[1] = -1; diff --git a/Assets/Script/Audio/Interfaces/IAudioManager.cs b/Assets/Script/Audio/Interfaces/IAudioManager.cs index fb6449e46..6a47f4ea8 100644 --- a/Assets/Script/Audio/Interfaces/IAudioManager.cs +++ b/Assets/Script/Audio/Interfaces/IAudioManager.cs @@ -27,7 +27,8 @@ public interface IAudioManager { public void LoadSfx(); public void LoadSong(ICollection stems, bool isSpeedUp); - public void LoadMogg(XboxMoggData moggData, bool isSpeedUp); + // public void LoadMogg(XboxMoggData moggData, bool isSpeedUp); + public void LoadMogg(byte[] moggArray, Dictionary stemMaps, float[,] matrixRatios, bool isSpeedUp); public void UnloadSong(); public void LoadPreviewAudio(SongEntry song); diff --git a/Assets/Script/DtxCs/DTX.cs b/Assets/Script/DtxCs/DTX.cs index 069cdcdc4..bc91040e0 100644 --- a/Assets/Script/DtxCs/DTX.cs +++ b/Assets/Script/DtxCs/DTX.cs @@ -304,17 +304,6 @@ private static void ParseString(string data, DataArray root) case '\n': case '\t': throw new Exception("Whitespace encountered in symbol."); - case '}': - case ')': - case ']': - current.AddNode(DataSymbol.Symbol(tmp_literal)); - if (data[i] != current.ClosingChar) - { - throw new Exception("Mismatched brace types encountered."); - } - current = current.Parent; - state = ParseState.whitespace; - break; case '\'': current.AddNode(DataSymbol.Symbol(tmp_literal)); state = ParseState.whitespace; diff --git a/Assets/Script/PlayMode/Play.cs b/Assets/Script/PlayMode/Play.cs index a4ac187fb..35f38542e 100644 --- a/Assets/Script/PlayMode/Play.cs +++ b/Assets/Script/PlayMode/Play.cs @@ -9,6 +9,7 @@ using UnityEngine.InputSystem; using YARG.Chart; using YARG.Data; +using YARG.Serialization; using YARG.Serialization.Parser; using YARG.Settings; using YARG.Song; @@ -113,12 +114,21 @@ private void StartSong() { // Determine if speed is not 1 bool isSpeedUp = Math.Abs(speed - 1) > float.Epsilon; - // Load MOGG if RB_CON, otherwise load stems + // Load MOGG if CON, otherwise load stems if (song is ExtractedConSongEntry rawConSongEntry) { - Debug.Log(rawConSongEntry.MoggInfo.ChannelCount); + Debug.Log(rawConSongEntry.MatrixRatios.GetLength(0)); - GameManager.AudioManager.LoadMogg(rawConSongEntry.MoggInfo, isSpeedUp); - } else { + GameManager.AudioManager.LoadMogg(File.ReadAllBytes(rawConSongEntry.MoggPath)[rawConSongEntry.MoggAddressAudioOffset..], + rawConSongEntry.StemMaps, rawConSongEntry.MatrixRatios, isSpeedUp); + } + else if(song is ConSongEntry conSongEntry){ + Debug.Log(conSongEntry.MatrixRatios.GetLength(0)); + + GameManager.AudioManager.LoadMogg(XboxCONInnerFileRetriever.RetrieveFile(conSongEntry.Location, conSongEntry.MoggPath, + conSongEntry.MoggFileSize, conSongEntry.MoggFileMemBlockOffsets)[conSongEntry.MoggAddressAudioOffset..], + conSongEntry.StemMaps, conSongEntry.MatrixRatios, isSpeedUp); + } + else { var stems = AudioHelpers.GetSupportedStems(song.Location); GameManager.AudioManager.LoadSong(stems, isSpeedUp); diff --git a/Assets/Script/Serialization/Xbox/MoggBassInfoGenerator.cs b/Assets/Script/Serialization/Xbox/MoggBassInfoGenerator.cs new file mode 100644 index 000000000..21aaa5ce0 --- /dev/null +++ b/Assets/Script/Serialization/Xbox/MoggBassInfoGenerator.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using DtxCS; +using DtxCS.DataTypes; +using UnityEngine; +using YARG.Data; +using YARG.Song; + +namespace YARG.Serialization { + public static class MoggBASSInfoGenerator { + public static void Generate(ConSongEntry song, DataArray dta){ + var Tracks = new Dictionary(); + float[] PanData = null, VolumeData = null; + int[] CrowdChannels = null; + int ChannelCount = 0; + + for (int i = 1; i < dta.Count; i++) { + var dtaArray = (DataArray) dta[i]; + switch (dtaArray[0].ToString()) { + case "tracks": + var trackArray = (DataArray) dtaArray[1]; + for (int x = 0; x < trackArray.Count; x++) { + if (trackArray[x] is not DataArray instrArray) continue; + string key = ((DataSymbol) instrArray[0]).Name; + int[] val; + if (instrArray[1] is DataArray trackNums) { + if (trackNums.Count <= 0) continue; + val = new int[trackNums.Count]; + for (int y = 0; y < trackNums.Count; y++) + val[y] = ((DataAtom) trackNums[y]).Int; + Tracks.Add(key, val); + } else if (instrArray[1] is DataAtom trackNum) { + val = new int[1]; + val[0] = trackNum.Int; + Tracks.Add(key, val); + } + } + break; + case "pans": + var panArray = dtaArray[1] as DataArray; + PanData = new float[panArray.Count]; + for (int p = 0; p < panArray.Count; p++) PanData[p] = ((DataAtom) panArray[p]).Float; + ChannelCount = panArray.Count; + break; + case "vols": + var volArray = dtaArray[1] as DataArray; + VolumeData = new float[volArray.Count]; + for (int v = 0; v < volArray.Count; v++){ + var volAtom = (DataAtom) volArray[v]; + if(volAtom.Type == DataType.FLOAT) VolumeData[v] = ((DataAtom) volArray[v]).Float; + else VolumeData[v] = ((DataAtom) volArray[v]).Int; + } + break; + case "crowd_channels": + CrowdChannels = new int[dtaArray.Count - 1]; + for (int cc = 1; cc < dtaArray.Count; cc++) + CrowdChannels[cc - 1] = ((DataAtom) dtaArray[cc]).Int; + break; + } + } + + // now that we have all the info we need from dta, calculate BASS info + var mapped = new bool[ChannelCount]; + + // BEGIN BASS Stem Mapping ---------------------------------------------------------------------- + + if (Tracks.TryGetValue("drum", out var drumArray)) { + switch (drumArray.Length) { + //drum (0 1): stereo kit --> (0 1) + case 2: + song.StemMaps[SongStem.Drums] = new[] { drumArray[0], drumArray[1] }; + break; + //drum (0 1 2): mono kick, stereo snare/kit --> (0) (1 2) + case 3: + song.StemMaps[SongStem.Drums1] = new[] { drumArray[0] }; + song.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: + song.StemMaps[SongStem.Drums1] = new[] { drumArray[0] }; + song.StemMaps[SongStem.Drums2] = new[] { drumArray[1] }; + song.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: + song.StemMaps[SongStem.Drums1] = new[] { drumArray[0] }; + song.StemMaps[SongStem.Drums2] = new[] { drumArray[1], drumArray[2] }; + song.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: + song.StemMaps[SongStem.Drums1] = new[] { drumArray[0], drumArray[1] }; + song.StemMaps[SongStem.Drums2] = new[] { drumArray[2], drumArray[3] }; + song.StemMaps[SongStem.Drums3] = new[] { drumArray[4], drumArray[5] }; + break; + } + + foreach (int arr in drumArray) { + mapped[arr] = true; + } + } + + if (Tracks.TryGetValue("bass", out var bassArray)) { + song.StemMaps[SongStem.Bass] = new int[bassArray.Length]; + for (int i = 0; i < bassArray.Length; i++) { + song.StemMaps[SongStem.Bass][i] = bassArray[i]; + mapped[bassArray[i]] = true; + } + } + + if (Tracks.TryGetValue("guitar", out var gtrArray)) { + song.StemMaps[SongStem.Guitar] = new int[gtrArray.Length]; + for (int i = 0; i < gtrArray.Length; i++) { + song.StemMaps[SongStem.Guitar][i] = gtrArray[i]; + mapped[gtrArray[i]] = true; + } + } + + if (Tracks.TryGetValue("vocals", out var voxArray)) { + song.StemMaps[SongStem.Vocals] = new int[voxArray.Length]; + for (int i = 0; i < voxArray.Length; i++) { + song.StemMaps[SongStem.Vocals][i] = voxArray[i]; + mapped[voxArray[i]] = true; + } + } + + if (Tracks.TryGetValue("keys", out var keysArray)) { + song.StemMaps[SongStem.Keys] = new int[keysArray.Length]; + for (int i = 0; i < keysArray.Length; i++) { + song.StemMaps[SongStem.Keys][i] = keysArray[i]; + mapped[keysArray[i]] = true; + } + } + + if (CrowdChannels != null) { + song.StemMaps[SongStem.Crowd] = new int[CrowdChannels.Length]; + for (int i = 0; i < CrowdChannels.Length; i++) { + song.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(); + song.StemMaps[SongStem.Song] = new int[fakeIndices.Count]; + for (int i = 0; i < fakeIndices.Count; i++) { + song.StemMaps[SongStem.Song][i] = fakeIndices[i]; + } + + // END BASS Stem Mapping ------------------------------------------------------------------------ + + // BEGIN BASS Matrix calculation ---------------------------------------------------------------- + + song.MatrixRatios = new float[PanData.Length, 2]; + + for(int i = 0; i < PanData.Length; i++){ + float theta = PanData[i] * ((float) Math.PI / 4); + float ratioL = (float) (Math.Sqrt(2) / 2) * ((float) Math.Cos(theta) - (float) Math.Sin(theta)); + float ratioR = (float) (Math.Sqrt(2) / 2) * ((float) Math.Cos(theta) + (float) Math.Sin(theta)); + + float volRatio = (float) Math.Pow(10, VolumeData[i] / 20); + + song.MatrixRatios[i, 0] = volRatio * ratioL; + song.MatrixRatios[i, 1] = volRatio * ratioR; + } + + // END BASS Matrix calculation ------------------------------------------------------------------ + + } + } +} \ No newline at end of file diff --git a/Assets/Script/Serialization/Xbox/XboxSong.cs.meta b/Assets/Script/Serialization/Xbox/MoggBassInfoGenerator.cs.meta similarity index 83% rename from Assets/Script/Serialization/Xbox/XboxSong.cs.meta rename to Assets/Script/Serialization/Xbox/MoggBassInfoGenerator.cs.meta index a5157bab4..2b9b3f2cd 100644 --- a/Assets/Script/Serialization/Xbox/XboxSong.cs.meta +++ b/Assets/Script/Serialization/Xbox/MoggBassInfoGenerator.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 10b04cf2a2f9d45f0a02be354b95b947 +guid: 4899ab9c3023a4fb4bf486c0d0f93ad6 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Script/Serialization/Xbox/XboxCONFileBrowser.cs b/Assets/Script/Serialization/Xbox/XboxCONFileBrowser.cs index 399355af6..e54a55fca 100644 --- a/Assets/Script/Serialization/Xbox/XboxCONFileBrowser.cs +++ b/Assets/Script/Serialization/Xbox/XboxCONFileBrowser.cs @@ -6,46 +6,75 @@ using DtxCS.DataTypes; using UnityEngine; using XboxSTFS; +using YARG.Song; namespace YARG.Serialization { - public static class XboxCONFileBrowser { - public static List BrowseCON(string conName) { - var songList = new List(); + public static class XboxCONFileBrowser { + public static List BrowseCON(string conName){ + Debug.Log($"con name = {conName}"); + var songList = new List(); var dtaTree = new DataArray(); - // Attempt to read songs.dta - STFS thisCON = new STFS(conName); - try{ - dtaTree = DTX.FromPlainTextBytes(thisCON.GetFile("songs/songs.dta")); - Debug.Log("Successfully read dta"); + // Attempt to read songs.dta + STFS theCON = new STFS(conName); + try { + dtaTree = DTX.FromPlainTextBytes(theCON.GetFile("songs/songs.dta")); } catch (Exception e) { Debug.LogError($"Failed to parse songs.dta for `{conName}`."); Debug.LogException(e); return null; } - - // Read each song the dta file lists - for(int i = 0; i < dtaTree.Count; i++){ - try { + + // Read each song the dta file lists + for (int i = 0; i < dtaTree.Count; i++) { + try { var currentArray = (DataArray) dtaTree[i]; - var currentSong = new XboxCONSong(conName, currentArray, thisCON); - currentSong.ParseSong(); - - if (currentSong.IsValidSong()) { - songList.Add(currentSong); - } else { - Debug.LogError($"Song with shortname `{currentSong.shortname}` is invalid. Skipping."); - } + // Parse songs.dta + // Get song metadata from songs.dta + ConSongEntry currentSong = XboxDTAParser.ParseFromDta(currentArray); + + // since Location is currently set to the name of the folder before mid/mogg/png, set those paths now + // since we're dealing with a CON and not an ExCON, grab each relevant file's sizes and memory block offsets + currentSong.NotesFile = Path.Combine("songs", currentSong.Location, $"{currentSong.Location}.mid"); + currentSong.MidiFileSize = theCON.GetFileSize(currentSong.NotesFile); + currentSong.MidiFileMemBlockOffsets = theCON.GetMemOffsets(currentSong.NotesFile); + + currentSong.MoggPath = Path.Combine("songs", currentSong.Location, $"{currentSong.Location}.mogg"); + currentSong.MoggFileSize = theCON.GetFileSize(currentSong.MoggPath); + currentSong.MoggFileMemBlockOffsets = theCON.GetMemOffsets(currentSong.MoggPath); + + string imgPath = Path.Combine("songs", currentSong.Location, "gen", $"{currentSong.Location}_keep.png_xbox"); + currentSong.ImageFileSize = theCON.GetFileSize(imgPath); + currentSong.ImageFileMemBlockOffsets = theCON.GetMemOffsets(imgPath); + + if(currentSong.HasAlbumArt && currentSong.ImageFileSize > 0 && currentSong.ImageFileMemBlockOffsets != null) + currentSong.ImagePath = imgPath; + + // Set this song's "Location" to the path of the CON file + currentSong.Location = conName; + + // Parse the mogg + using var fs = new FileStream(conName, FileMode.Open, FileAccess.Read); + using var br = new BinaryReader(fs); + fs.Seek(currentSong.MoggFileMemBlockOffsets[0], SeekOrigin.Begin); + + currentSong.MoggHeader = br.ReadInt32(); + currentSong.MoggAddressAudioOffset = br.ReadInt32(); + currentSong.MoggAudioLength = currentSong.MoggFileSize - currentSong.MoggAddressAudioOffset; + MoggBASSInfoGenerator.Generate(currentSong, currentArray.Array("song")); + + // Debug.Log($"{currentSong.ShortName}:\nMidi path: {currentSong.NotesFile}\nMogg path: {currentSong.MoggPath}\nImage path: {currentSong.ImagePath}"); + + // will validate the song outside of this class, in SongScanThread.cs + // so okay to add to song list for now + songList.Add(currentSong); } catch (Exception e) { Debug.Log($"Failed to load song, skipping..."); Debug.LogException(e); } - } - - // XboxCONSong lol = new XboxCONSong(conName, (DataArray)dtaTree[dtaTree.Count - 1], thisCON); - // lol.GetMoggFile(); + } return songList; - } - } + } + } } \ No newline at end of file diff --git a/Assets/Script/Serialization/Xbox/XboxCONInnerFileRetriever.cs b/Assets/Script/Serialization/Xbox/XboxCONInnerFileRetriever.cs new file mode 100644 index 000000000..982eec743 --- /dev/null +++ b/Assets/Script/Serialization/Xbox/XboxCONInnerFileRetriever.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace YARG.Serialization { + public static class XboxCONInnerFileRetriever { + public static byte[] RetrieveFile(string CONname, string filename, uint filesize, uint[] fileOffsets){ + + byte[] f = new byte[filesize]; + uint lastSize = filesize % 0x1000; + + Parallel.For(0, fileOffsets.Length, i => { + uint ReadLen = (i == fileOffsets.Length - 1) ? lastSize : 0x1000; + using var fs = new FileStream(CONname, FileMode.Open, FileAccess.Read); + using var br = new BinaryReader(fs, new ASCIIEncoding()); + fs.Seek(fileOffsets[i], SeekOrigin.Begin); + Array.Copy(br.ReadBytes((int)ReadLen), 0, f, i*0x1000, (int)ReadLen); + }); + + return f; + } + } +} \ No newline at end of file diff --git a/Assets/Script/Serialization/Xbox/XboxImage.cs.meta b/Assets/Script/Serialization/Xbox/XboxCONInnerFileRetriever.cs.meta similarity index 83% rename from Assets/Script/Serialization/Xbox/XboxImage.cs.meta rename to Assets/Script/Serialization/Xbox/XboxCONInnerFileRetriever.cs.meta index 35c97e52a..6d8003ebe 100644 --- a/Assets/Script/Serialization/Xbox/XboxImage.cs.meta +++ b/Assets/Script/Serialization/Xbox/XboxCONInnerFileRetriever.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 6f75ee74ebb3a47a28fcaf8a3202f41a +guid: 671d541c0eea74562b2f6071f24f6e42 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Script/Serialization/Xbox/XboxDTAParser.cs b/Assets/Script/Serialization/Xbox/XboxDTAParser.cs new file mode 100644 index 000000000..a566e6a46 --- /dev/null +++ b/Assets/Script/Serialization/Xbox/XboxDTAParser.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using DtxCS; +using DtxCS.DataTypes; +using UnityEngine; +using YARG.Data; +using YARG.Song; + +namespace YARG.Serialization { + public static class XboxDTAParser { + public static ConSongEntry ParseFromDta(DataArray dta){ + var cur = new ConSongEntry(); + cur.ShortName = dta.Name; + // Debug.Log($"this shortname: {dta.Name}"); + for (int i = 1; i < dta.Count; i++) { + DataArray dtaArray = (DataArray) dta[i]; + switch (dtaArray[0].ToString()) { + case "name": cur.Name = ((DataAtom) dtaArray[1]).Name; break; + case "artist": cur.Artist = ((DataAtom) dtaArray[1]).Name; break; + case "author": cur.Charter = ((DataAtom) dtaArray[1]).Name; break; + case "master": + if (dtaArray[1] is DataSymbol symMaster) + cur.IsMaster = (symMaster.Name.ToUpper() == "TRUE"); + else if (dtaArray[1] is DataAtom atmMaster) + cur.IsMaster = (atmMaster.Int != 0); + break; + case "song_id": + if (dtaArray[1] is DataAtom atmSongId) + if (atmSongId.Type == DataType.INT) + cur.SongID = ((DataAtom) dtaArray[1]).Int; + break; + case "song_length": cur.SongLength = ((DataAtom) dtaArray[1]).Int; break; + case "song": // we just want vocal parts and hopo threshold for songDta + if(dtaArray.Array("hopo_threshold") != null) + cur.HopoThreshold = ((DataAtom) dtaArray.Array("hopo_threshold")[1]).Int; + cur.VocalParts = (dtaArray.Array("vocal_parts") != null) ? ((DataAtom) dtaArray.Array("vocal_parts")[1]).Int : 1; + // get the path of the song files + if(dtaArray.Array("name") != null){ + if(dtaArray.Array("name")[1] is DataSymbol symPath) + cur.Location = symPath.Name.Split("/")[1]; + else if(dtaArray.Array("name")[1] is DataAtom atmPath) + cur.Location = atmPath.Name.Split("/")[1]; + } + else cur.Location = cur.ShortName; + break; + case "anim_tempo": + if (dtaArray[1] is DataSymbol symTempo) + cur.AnimTempo = symTempo.Name switch { + "kTempoSlow" => 16, + "kTempoMedium" => 32, + "kTempoFast" => 64, + _ => 0, + }; + else if (dtaArray[1] is DataAtom atom) + cur.AnimTempo = atom.Int; + break; + case "preview": + cur.PreviewStart = ((DataAtom) dtaArray[1]).Int; + cur.PreviewEnd = ((DataAtom) dtaArray[2]).Int; + break; + case "bank": + if (dtaArray[1] is DataSymbol symBank) + cur.VocalPercussionBank = symBank.Name; + else if (dtaArray[1] is DataAtom atmBank) + cur.VocalPercussionBank = atmBank.String; + break; + case "song_scroll_speed": cur.VocalSongScrollSpeed = ((DataAtom) dtaArray[1]).Int; break; + case "solo": break; //indicates which instruments have solos: not currently used for YARG + case "rank": + for(int j = 1; j < dtaArray.Count; j++){ + if (dtaArray[j] is DataArray inner){ + var inst = InstrumentHelper.FromStringName(((DataSymbol) inner[0]).Name); + if(inst == Instrument.INVALID) continue; + cur.PartDifficulties[inst] = DtaDifficulty.ToNumberedDiff(inst, ((DataAtom) inner[1]).Int); + } + } + // Set pro drums + if(cur.PartDifficulties.ContainsKey(Instrument.DRUMS)) + cur.PartDifficulties[Instrument.REAL_DRUMS] = cur.PartDifficulties[Instrument.DRUMS]; + break; + case "game_origin": + cur.Source = ((DataSymbol) dtaArray[1]).Name; + // if the source is UGC/UGC_plus but no "UGC_" in shortname, assume it's a custom + if(cur.Source == "ugc" || cur.Source == "ugc_plus"){ + if(!(cur.ShortName.Contains("UGC_"))){ + cur.Source = "customs"; + } + } + // if the source is any official RB game or its DLC, charter = Harmonix + if(cur.Source == "rb1" || cur.Source == "rb1_dlc" || cur.Source == "rb1dlc" || + cur.Source == "gdrb" || cur.Source == "greenday" || cur.Source == "beatles" || + cur.Source == "tbrb" || cur.Source == "lego" || cur.Source == "lrb" || + cur.Source == "rb2" || cur.Source == "rb3" || cur.Source == "rb3_dlc" || cur.Source == "rb3dlc"){ + cur.Charter = "Harmonix"; + } + break; + case "genre": cur.Genre = ((DataSymbol) dtaArray[1]).Name; break; + case "rating": cur.SongRating = ((DataAtom) dtaArray[1]).Int; break; + case "vocal_gender": cur.VocalGender = (((DataSymbol) dtaArray[1]).Name == "male"); break; + case "fake": cur.IsFake = (dtaArray[1].ToString().ToUpper() == "TRUE"); break; + case "album_art": + if (dtaArray[1] is DataSymbol symArt) + cur.HasAlbumArt = (symArt.Name.ToUpper() == "TRUE"); + else if (dtaArray[1] is DataAtom atmArt) + cur.HasAlbumArt = (atmArt.Int != 0); + break; + case "album_name": cur.Album = ((DataAtom) dtaArray[1]).Name; break; + case "album_track_number": cur.AlbumTrack = ((DataAtom) dtaArray[1]).Int; break; + case "year_released": cur.Year = ((DataAtom) dtaArray[1]).Int.ToString(); break; + case "year_recorded": cur.Year = ((DataAtom) dtaArray[1]).Int.ToString(); break; + case "vocal_tonic_note": cur.VocalTonicNote = ((DataAtom) dtaArray[1]).Int; break; + case "song_tonality": cur.SongTonality = ((((DataAtom) dtaArray[1]).Int) != 0); break; + case "tuning_offset_cents": + DataAtom tuningAtom = (DataAtom) dtaArray[1]; + if (tuningAtom.Type == DataType.INT) cur.TuningOffsetCents = ((DataAtom) dtaArray[1]).Int; + else cur.TuningOffsetCents = (int)((DataAtom) dtaArray[1]).Float; + break; + case "real_guitar_tuning": + DataArray guitarTunes = (DataArray) dtaArray[1]; + cur.RealGuitarTuning = new int[6]; + for (int g = 0; g < 6; g++) cur.RealGuitarTuning[g] = ((DataAtom) guitarTunes[g]).Int; + break; + case "real_bass_tuning": + DataArray bassTunes = (DataArray) dtaArray[1]; + cur.RealBassTuning = new int[4]; + for (int b = 0; b < 4; b++) cur.RealBassTuning[b] = ((DataAtom) bassTunes[b]).Int; + break; + } + } + + // must be done after the above parallel loop due to race issues with ranks and vocalParts + if(!cur.PartDifficulties.ContainsKey(Instrument.VOCALS) || cur.PartDifficulties[Instrument.VOCALS] == 0) cur.VocalParts = 0; + // Set harmony difficulty (if exists) + else if(cur.PartDifficulties.ContainsKey(Instrument.VOCALS) && cur.VocalParts > 1) { + cur.PartDifficulties[Instrument.HARMONY] = cur.PartDifficulties[Instrument.VOCALS]; + } + + return cur; + } + } +} \ No newline at end of file diff --git a/Assets/Script/Serialization/Xbox/XboxSongData.cs.meta b/Assets/Script/Serialization/Xbox/XboxDTAParser.cs.meta similarity index 83% rename from Assets/Script/Serialization/Xbox/XboxSongData.cs.meta rename to Assets/Script/Serialization/Xbox/XboxDTAParser.cs.meta index 4ca87585b..656524efb 100644 --- a/Assets/Script/Serialization/Xbox/XboxSongData.cs.meta +++ b/Assets/Script/Serialization/Xbox/XboxDTAParser.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 71647a1591f0641a3bf9a4b9eb70bcb1 +guid: 4b899a1ad28464e2c85c5c97682c46f5 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Script/Serialization/Xbox/XboxImage.cs b/Assets/Script/Serialization/Xbox/XboxImage.cs deleted file mode 100644 index 2f261113e..000000000 --- a/Assets/Script/Serialization/Xbox/XboxImage.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json; -using UnityEngine; -using UnityEngine.Experimental.Rendering; - -namespace YARG.Serialization { - [JsonObject(MemberSerialization.OptOut)] - public class XboxImage { - public string ImagePath { get; set; } - public byte BitsPerPixel { get; set; } - public int Format { get; set; } - public short Width { get; set; } - public short Height { get; set; } - public uint ImgSize { get; } - public uint[] ImgOffsets { get; } - - private bool isFromCON = false; - - [JsonIgnore] - private Texture2D _textureCache; - - public XboxImage(string path) { - ImagePath = path; - } - - public XboxImage(string path, uint size, uint[] offsets){ - ImagePath = path; - ImgSize = size; - ImgOffsets = offsets; - isFromCON = true; - } - - /// - /// A byte array of DXT1 formatted blocks to make into a Unity Texture - /// - public byte[] GetDXTBlocksFromImage() { - if(!isFromCON){ //raw - using var fs = new FileStream(ImagePath, FileMode.Open, FileAccess.Read); - using var br = new BinaryReader(fs, new ASCIIEncoding()); - - // Parse header - byte[] header = br.ReadBytes(32); - BitsPerPixel = header[1]; - Format = BitConverter.ToInt32(header, 2); - Width = BitConverter.ToInt16(header, 7); - Height = BitConverter.ToInt16(header, 9); - byte[] DXTBlocks; - - // Parse DXT-compressed blocks, depending on format - if ((BitsPerPixel == 0x04) && (Format == 0x08)) { - // If DXT-1 format already, read the bytes straight up - fs.Seek(32, SeekOrigin.Begin); - DXTBlocks = br.ReadBytes((int) (fs.Length - 32)); - } else { - // If DXT-3 format, we have to omit the alpha bytes - List extractedDXT3 = new List(); - br.ReadBytes(8); //skip the first 8 alpha bytes - for (int i = 8; i < (fs.Length - 32) / 2; i += 8) { - extractedDXT3.AddRange(br.ReadBytes(8)); // We want to read these 8 bytes - br.ReadBytes(8); // and skip these 8 bytes - } - DXTBlocks = extractedDXT3.ToArray(); - } - - // Swap bytes because xbox is weird like that - Parallel.For(0, DXTBlocks.Length / 2, i => { - (DXTBlocks[i * 2], DXTBlocks[i * 2 + 1]) = (DXTBlocks[i * 2 + 1], DXTBlocks[i * 2]); - }); - - return DXTBlocks; - } - else{ //CON - byte[] f = new byte[ImgSize]; - uint lastSize = ImgSize % 0x1000; - - Parallel.For(0, ImgOffsets.Length, i => { - uint readLen = (i == ImgOffsets.Length - 1) ? lastSize : 0x1000; - using var fs = new FileStream(ImagePath, FileMode.Open, FileAccess.Read); - using var br = new BinaryReader(fs, new ASCIIEncoding()); - fs.Seek(ImgOffsets[i], SeekOrigin.Begin); - Array.Copy(br.ReadBytes((int)readLen), 0, f, i*0x1000, (int)readLen); - }); - - MemoryStream ms = new MemoryStream(f); - - // Parse header - byte[] header = ms.ReadBytes(32); - BitsPerPixel = header[1]; - Format = BitConverter.ToInt32(header, 2); - Width = BitConverter.ToInt16(header, 7); - Height = BitConverter.ToInt16(header, 9); - byte[] DXTBlocks; - - // Parse DXT-compressed blocks, depending on format - if ((BitsPerPixel == 0x04) && (Format == 0x08)) { - // If DXT-1 format already, read the bytes straight up - DXTBlocks = ms.ReadBytes((int) (ImgSize - 32)); - } - else{ - // If DXT-3 format, we have to omit the alpha bytes - List extractedDXT3 = new List(); - ms.ReadBytes(8); //skip the first 8 alpha bytes - for (int i = 8; i < (ImgSize - 32) / 2; i += 8) { - extractedDXT3.AddRange(ms.ReadBytes(8)); // We want to read these 8 bytes - ms.ReadBytes(8); // and skip these 8 bytes - } - DXTBlocks = extractedDXT3.ToArray(); - } - - // Swap bytes because xbox is weird like that - Parallel.For(0, DXTBlocks.Length / 2, i => { - (DXTBlocks[i * 2], DXTBlocks[i * 2 + 1]) = (DXTBlocks[i * 2 + 1], DXTBlocks[i * 2]); - }); - - return DXTBlocks; - } - } - - public Texture2D GetAsTexture() { - if (_textureCache != null) { - return _textureCache; - } - // parse image for DXT blocks (and width and height from header) - byte[] data = GetDXTBlocksFromImage(); - - // Load texture - _textureCache = new Texture2D(Width, Height, GraphicsFormat.RGBA_DXT1_SRGB, TextureCreationFlags.None); - _textureCache.LoadRawTextureData(data); - _textureCache.Apply(); - - return _textureCache; - } - } -} \ No newline at end of file diff --git a/Assets/Script/Serialization/Xbox/XboxImageGenerator.cs b/Assets/Script/Serialization/Xbox/XboxImageGenerator.cs new file mode 100644 index 000000000..d62c2e268 --- /dev/null +++ b/Assets/Script/Serialization/Xbox/XboxImageGenerator.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Experimental.Rendering; + +namespace YARG.Serialization { + public static class XboxImageTextureGenerator { + public static Texture2D GetTexture(byte[] xboxImageBytes){ + var ms = new MemoryStream(xboxImageBytes); + + // Parse header + byte[] header = ms.ReadBytes(32); + byte BitsPerPixel = header[1]; + int Format = BitConverter.ToInt32(header, 2); + short Width = BitConverter.ToInt16(header, 7); + short Height = BitConverter.ToInt16(header, 9); + byte[] DXTBlocks; + + // Parse DXT-compressed blocks, depending on format + if ((BitsPerPixel == 0x04) && (Format == 0x08)) { + // If DXT-1 format already, read the bytes straight up + ms.Seek(32, SeekOrigin.Begin); + DXTBlocks = ms.ReadBytes((int) (ms.Length - 32)); + } else { + // If DXT-3 format, we have to omit the alpha bytes + List extractedDXT3 = new List(); + ms.ReadBytes(8); //skip the first 8 alpha bytes + for (int i = 8; i < (ms.Length - 32) / 2; i += 8) { + extractedDXT3.AddRange(ms.ReadBytes(8)); // We want to read these 8 bytes + ms.ReadBytes(8); // and skip these 8 bytes + } + DXTBlocks = extractedDXT3.ToArray(); + } + + // Swap bytes because xbox is weird like that + for(int i = 0; i < DXTBlocks.Length / 2; i++) + (DXTBlocks[i * 2], DXTBlocks[i * 2 + 1]) = (DXTBlocks[i * 2 + 1], DXTBlocks[i * 2]); + + // apply DXT1 formatted bytes to a Texture2D + var tex = new Texture2D(Width, Height, GraphicsFormat.RGBA_DXT1_SRGB, TextureCreationFlags.None); + tex.LoadRawTextureData(DXTBlocks); + tex.Apply(); + + return tex; + } + } +} \ No newline at end of file diff --git a/Assets/Script/Serialization/Xbox/XboxMoggData.cs.meta b/Assets/Script/Serialization/Xbox/XboxImageGenerator.cs.meta similarity index 83% rename from Assets/Script/Serialization/Xbox/XboxMoggData.cs.meta rename to Assets/Script/Serialization/Xbox/XboxImageGenerator.cs.meta index c5d4b1b38..f9cdd2929 100644 --- a/Assets/Script/Serialization/Xbox/XboxMoggData.cs.meta +++ b/Assets/Script/Serialization/Xbox/XboxImageGenerator.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 965eb80153de942768623f9fca072c2f +guid: 37069f6c5dbc14e6e89837a1374aeeb5 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Script/Serialization/Xbox/XboxMoggData.cs b/Assets/Script/Serialization/Xbox/XboxMoggData.cs deleted file mode 100644 index 3e4f86f8e..000000000 --- a/Assets/Script/Serialization/Xbox/XboxMoggData.cs +++ /dev/null @@ -1,258 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using DtxCS.DataTypes; -using Newtonsoft.Json; - -namespace YARG.Serialization { - /* - - TODO: Generalize for all .mogg's - - */ - - [JsonObject(MemberSerialization.OptOut)] - public class XboxMoggData { - public string MoggPath { get; set; } - public int ChannelCount { get; set; } - public int Header { get; set; } - - public uint MoggSize { get; } - public uint[] MoggOffsets { get; } - - private bool isFromCON = false; - - public int MoggAddressAudioOffset { get; set; } - public long MoggAudioLength { get; set; } - - public float[] PanData { get; set; } - public float[] VolumeData { get; set; } - - public Dictionary Tracks { get; set; } - public int[] CrowdChannels { get; set; } - - public Dictionary StemMaps { get; set; } - public float[,] MatrixRatios { get; set; } - - public XboxMoggData(string str) { - MoggPath = str; - - CrowdChannels = Array.Empty(); - } - - public XboxMoggData(string str, uint size, uint[] offsets) { - MoggPath = str; - MoggSize = size; - MoggOffsets = offsets; - isFromCON = true; - - CrowdChannels = Array.Empty(); - } - - public void ParseMoggHeader() { - using var fs = new FileStream(MoggPath, FileMode.Open, FileAccess.Read); - using var br = new BinaryReader(fs); - if (isFromCON) fs.Seek(MoggOffsets[0], SeekOrigin.Begin); - - Header = br.ReadInt32(); - MoggAddressAudioOffset = br.ReadInt32(); - - if (isFromCON) MoggAudioLength = MoggSize - MoggAddressAudioOffset; - else MoggAudioLength = fs.Length - MoggAddressAudioOffset; - } - - public byte[] GetOggDataFromMogg() { - if (!isFromCON) //Raw - return File.ReadAllBytes(MoggPath)[MoggAddressAudioOffset..]; - else { //CON - byte[] f = new byte[MoggSize]; - uint lastSize = MoggSize % 0x1000; - - Parallel.For(0, MoggOffsets.Length, i => { - uint readLen = (i == MoggOffsets.Length - 1) ? lastSize : 0x1000; - using var fs = new FileStream(MoggPath, FileMode.Open, FileAccess.Read); - using var br = new BinaryReader(fs, new ASCIIEncoding()); - fs.Seek(MoggOffsets[i], SeekOrigin.Begin); - Array.Copy(br.ReadBytes((int) readLen), 0, f, i * 0x1000, (int) readLen); - }); - - return f[MoggAddressAudioOffset..]; - } - } - - public void ParseFromDta(DataArray dta) { - for (int i = 1; i < dta.Count; i++) { - var dtaArray = (DataArray) dta[i]; - - switch (dtaArray[0].ToString()) { - case "tracks": - var trackArray = (DataArray) dtaArray[1]; - Tracks = new Dictionary(); - - for (int x = 0; x < trackArray.Count; x++) { - if (trackArray[x] is not DataArray instrArray) continue; - - string key = ((DataSymbol) instrArray[0]).Name; - int[] val; - if (instrArray[1] is DataArray trackNums) { - if (trackNums.Count <= 0) continue; - - val = new int[trackNums.Count]; - for (int y = 0; y < trackNums.Count; y++) - val[y] = ((DataAtom) trackNums[y]).Int; - Tracks.Add(key, val); - } else if (instrArray[1] is DataAtom trackNum) { - val = new int[1]; - val[0] = trackNum.Int; - Tracks.Add(key, val); - } - } - break; - case "pans": - var panArray = dtaArray[1] as DataArray; - PanData = new float[panArray.Count]; - for (int p = 0; p < panArray.Count; p++) PanData[p] = ((DataAtom) panArray[p]).Float; - ChannelCount = panArray.Count; - break; - case "vols": - var volArray = dtaArray[1] as DataArray; - VolumeData = new float[volArray.Count]; - for (int v = 0; v < volArray.Count; v++) VolumeData[v] = ((DataAtom) volArray[v]).Float; - break; - case "crowd_channels": - CrowdChannels = new int[dtaArray.Count - 1]; - for (int cc = 1; cc < dtaArray.Count; cc++) - CrowdChannels[cc - 1] = ((DataAtom) dtaArray[cc]).Int; - break; - } - } - } - - public override string ToString() { - string debugTrackStr = ""; - foreach (var kvp in Tracks) { - debugTrackStr += $"{kvp.Key}, ({string.Join(", ", kvp.Value)}) "; - } - - return string.Join(Environment.NewLine, - $"Mogg metadata:", - $"channel count: {ChannelCount}", - $"tracks={string.Join(", ", debugTrackStr)}", - $"pans=({string.Join(", ", PanData)})", - $"vols=({string.Join(", ", VolumeData)})" - ); - } - - public void CalculateMoggBassInfo() { - StemMaps = new Dictionary(); - var mapped = new bool[ChannelCount]; - - // BEGIN BASS Stem Mapping ---------------------------------------------------------------------- - - 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] }; - 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] }; - 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] }; - 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] }; - 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] }; - break; - } - - foreach (int arr in drumArray) { - mapped[arr] = true; - } - } - - 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]; - mapped[bassArray[i]] = true; - } - } - - 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]; - mapped[gtrArray[i]] = true; - } - } - - 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]; - mapped[voxArray[i]] = true; - } - } - - 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]; - mapped[keysArray[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]; - for (int i = 0; i < fakeIndices.Count; i++) { - StemMaps[SongStem.Song][i] = fakeIndices[i]; - } - - // END BASS Stem Mapping ------------------------------------------------------------------------ - - // BEGIN BASS Matrix calculation ---------------------------------------------------------------- - - MatrixRatios = new float[PanData.Length, 2]; - - Parallel.For(0, PanData.Length, i => { - float theta = PanData[i] * ((float) Math.PI / 4); - float ratioL = (float) (Math.Sqrt(2) / 2) * ((float) Math.Cos(theta) - (float) Math.Sin(theta)); - float ratioR = (float) (Math.Sqrt(2) / 2) * ((float) Math.Cos(theta) + (float) Math.Sin(theta)); - - float volRatio = (float) Math.Pow(10, VolumeData[i] / 20); - - MatrixRatios[i, 0] = volRatio * ratioL; - MatrixRatios[i, 1] = volRatio * ratioR; - }); - - // END BASS Matrix calculation ------------------------------------------------------------------ - } - } -} \ No newline at end of file diff --git a/Assets/Script/Serialization/Xbox/XboxRawfileBrowser.cs b/Assets/Script/Serialization/Xbox/XboxRawfileBrowser.cs index 56460eff7..56084576c 100644 --- a/Assets/Script/Serialization/Xbox/XboxRawfileBrowser.cs +++ b/Assets/Script/Serialization/Xbox/XboxRawfileBrowser.cs @@ -1,63 +1,62 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using DtxCS; using DtxCS.DataTypes; using UnityEngine; +using YARG.Data; +using YARG.Song; namespace YARG.Serialization { - public static class XboxRawfileBrowser { - public static List BrowseFolder(string folder, string folder_update) { - var songList = new List(); + public static class ExCONBrowser { + public static List BrowseFolder(string folder){ + var songList = new List(); var dtaTree = new DataArray(); - var dtaUpdate = new DataArray(); // Attempt to read songs.dta try { using var sr = new StreamReader(Path.Combine(folder, "songs.dta"), Encoding.GetEncoding("iso-8859-1")); dtaTree = DTX.FromDtaString(sr.ReadToEnd()); - - Debug.Log("Successfully read dta"); } catch (Exception e) { Debug.LogError($"Failed to parse songs.dta for `{folder}`."); Debug.LogException(e); return null; } - // Attempt to read songs_updates.dta, if update folder was provided - // if (folder_update != null) { - // try { - // using var sr = new StreamReader(Path.Combine(folder_update, "songs_updates.dta"), Encoding.GetEncoding("iso-8859-1")); - // dtaUpdate = DTX.FromDtaString(sr.ReadToEnd()); - - // Debug.Log("Successfully read update dta"); - // } catch (Exception ee) { - // Debug.LogError($"Failed to parse songs_updates.dta for `{folder_update}`."); - // Debug.LogException(ee); - // return null; - // } - // } - // Read each song the dta file lists for (int i = 0; i < dtaTree.Count; i++) { try { var currentArray = (DataArray) dtaTree[i]; - var currentSong = new XboxSong(folder, currentArray); + // Parse songs.dta + // Get song metadata from songs.dta + var currentSong = XboxDTAParser.ParseFromDta(currentArray); + + // since Location is currently set to the name of the folder before mid/mogg/png, set those paths now + currentSong.NotesFile = Path.Combine(folder, currentSong.Location, $"{currentSong.Location}.mid"); + currentSong.MoggPath = Path.Combine(folder, currentSong.Location, $"{currentSong.Location}.mogg"); + string imgPath = Path.Combine(folder, currentSong.Location, "gen", $"{currentSong.Location}_keep.png_xbox"); + if(currentSong.HasAlbumArt && File.Exists(imgPath)) + currentSong.ImagePath = imgPath; + + // Get song folder path for mid, mogg, png_xbox + currentSong.Location = Path.Combine(folder, currentSong.Location); + + // Parse the mogg + using var fs = new FileStream(currentSong.MoggPath, FileMode.Open, FileAccess.Read); + using var br = new BinaryReader(fs); + + currentSong.MoggHeader = br.ReadInt32(); + currentSong.MoggAddressAudioOffset = br.ReadInt32(); + currentSong.MoggAudioLength = fs.Length - currentSong.MoggAddressAudioOffset; + MoggBASSInfoGenerator.Generate(currentSong, currentArray.Array("song")); - // if updates were provided - // if (folder_update != null) { - // // if dtaUpdate has the matching shortname, update that XboxSong - // if (dtaUpdate.Array(currentSong.ShortName) is DataArray dtaMissing) { - // currentSong.UpdateSong(folder_update, dtaMissing); - // } - // } + // Debug.Log($"{currentSong.ShortName}:\nMidi path: {currentSong.NotesFile}\nMogg path: {currentSong.MoggPath}\nImage path: {currentSong.ImagePath}"); - if (currentSong.IsValidSong()) { - songList.Add(currentSong); - } else { - Debug.LogError($"Song with shortname `{currentSong.ShortName}` is invalid. Skipping."); - } + // will validate the song outside of this class, in SongScanThread.cs + // so okay to add to song list for now + songList.Add((ExtractedConSongEntry)currentSong); } catch (Exception e) { Debug.Log($"Failed to load song, skipping..."); Debug.LogException(e); @@ -66,27 +65,5 @@ public static List BrowseFolder(string folder, string folder_update) { return songList; } - - // public static void BrowseUpdateFolder(string folder, List baseSongs) { - // var dtaTree = new DataArray(); - - // // Attempt to read songs_updates.dta - // try { - // using var sr = new StreamReader(Path.Combine(folder, "songs_updates.dta"), Encoding.GetEncoding("iso-8859-1")); - // dtaTree = DTX.FromDtaString(sr.ReadToEnd()); - - // Debug.Log("Successfully read update dta"); - // } catch (Exception e) { - // Debug.LogError($"Failed to parse songs_updates.dta for `{folder}`."); - // Debug.LogException(e); - // return; - // } - - // // Read each song the update dta lists - // for (int i = 0; i < dtaTree.Count; i++) { - // Debug.Log(dtaTree[i].Name); - // // if(baseSongs.ShortName) - // } - // } } } \ No newline at end of file diff --git a/Assets/Script/Serialization/Xbox/XboxRedo.cs b/Assets/Script/Serialization/Xbox/XboxRedo.cs index c1adcfa11..12838e8ca 100644 --- a/Assets/Script/Serialization/Xbox/XboxRedo.cs +++ b/Assets/Script/Serialization/Xbox/XboxRedo.cs @@ -1,125 +1,125 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using DtxCS.DataTypes; -using UnityEngine; -using XboxSTFS; -using YARG.Data; +// using System; +// using System.Collections.Generic; +// using System.IO; +// using System.Text; +// using System.Threading.Tasks; +// using DtxCS.DataTypes; +// using UnityEngine; +// using XboxSTFS; +// using YARG.Data; -namespace YARG.Serialization { - public abstract class XboxSongAbs { - public abstract byte[] GetMidiFile(); - } +// namespace YARG.Serialization { +// public abstract class XboxSongAbs { +// public abstract byte[] GetMidiFile(); +// } - // TODO: fill this class out to be similar to XboxCONSong - // and replace the existing XboxSong class with this one - public class XboxRawSong : XboxSongAbs { +// // TODO: fill this class out to be similar to XboxCONSong +// // and replace the existing XboxSong class with this one +// public class XboxRawSong : XboxSongAbs { - public string shortname { get; private set; } - public string rootPath { get; private set; } +// public string shortname { get; private set; } +// public string rootPath { get; private set; } - public XboxRawSong(string path, DataArray dta){ - rootPath = path; - } - public override byte[] GetMidiFile(){ - return File.ReadAllBytes($"{rootPath}/{shortname}/{shortname}.mid"); - } +// public XboxRawSong(string path, DataArray dta){ +// rootPath = path; +// } +// public override byte[] GetMidiFile(){ +// return File.ReadAllBytes($"{rootPath}/{shortname}/{shortname}.mid"); +// } - } +// } - public class XboxCONSong : XboxSongAbs { +// public class XboxCONSong : XboxSongAbs { - public string shortname { get; private set; } - public string CONRootPath { get; private set; } +// public string shortname { get; private set; } +// public string CONRootPath { get; private set; } - private DataArray dta; +// private DataArray dta; - private uint MidiSize; - private uint[] MidiOffsets; +// private uint MidiSize; +// private uint[] MidiOffsets; - private uint MoggSize; - private uint[] MoggOffsets; +// private uint MoggSize; +// private uint[] MoggOffsets; - private uint ImgSize; - private uint[] ImgOffsets; - - XboxSongData songDta; - XboxMoggData moggDta; - XboxImage img; - - public XboxCONSong(string path, DataArray currentDTA, STFS theCON){ - // set CON file path, dta and song shortname - CONRootPath = path; - dta = currentDTA; - shortname = dta.Name; - - // get file sizes and offsets in the CON's memory - MidiSize = theCON.GetFileSize($"songs/{shortname}/{shortname}.mid"); - MidiOffsets = theCON.GetMemOffsets($"songs/{shortname}/{shortname}.mid"); - MoggSize = theCON.GetFileSize($"songs/{shortname}/{shortname}.mogg"); - MoggOffsets = theCON.GetMemOffsets($"songs/{shortname}/{shortname}.mogg"); - ImgSize = theCON.GetFileSize($"songs/{shortname}/gen/{shortname}_keep.png_xbox"); - ImgOffsets = theCON.GetMemOffsets($"songs/{shortname}/gen/{shortname}_keep.png_xbox"); - } - - public void ParseSong(){ - // first, parse songs.dta - songDta = new XboxSongData(); - songDta.ParseFromDta(dta); - - // now, parse the mogg - moggDta = new XboxMoggData(CONRootPath, MoggSize, MoggOffsets); - moggDta.ParseMoggHeader(); - moggDta.ParseFromDta(dta.Array("song")); - moggDta.CalculateMoggBassInfo(); - - // finally, parse the image - if(songDta.albumArt && ImgSize > 0 && ImgOffsets != null){ - img = new XboxImage(CONRootPath, ImgSize, ImgOffsets); - } - - } - - public bool IsValidSong() { - // Skip if the song doesn't have notes - if(MidiSize == 0 && MidiOffsets == null) return false; - // Skip if this is a "fake song" (tutorials, etc.) - if (songDta.fake) return false; - // Skip if the mogg is encrypted - if (moggDta.Header != 0xA) return false; - - return true; - } - - public override string ToString() { - return string.Join(Environment.NewLine, - $"XBOX CON SONG {shortname}", - $"CON file: {CONRootPath}", - "", - songDta.ToString(), - "", - moggDta.ToString() - ); - } - - public override byte[] GetMidiFile(){ - byte[] f = new byte[MidiSize]; - uint lastSize = MidiSize % 0x1000; - - Parallel.For(0, MidiOffsets.Length, i => { - uint readLen = (i == MidiOffsets.Length - 1) ? lastSize : 0x1000; - using var fs = new FileStream(CONRootPath, FileMode.Open, FileAccess.Read); - using var br = new BinaryReader(fs, new ASCIIEncoding()); - fs.Seek(MidiOffsets[i], SeekOrigin.Begin); - Array.Copy(br.ReadBytes((int)readLen), 0, f, i*0x1000, (int)readLen); - }); +// private uint ImgSize; +// private uint[] ImgOffsets; + +// XboxSongData songDta; +// XboxMoggData moggDta; +// XboxImage img; + +// public XboxCONSong(string path, DataArray currentDTA, STFS theCON){ +// // set CON file path, dta and song shortname +// CONRootPath = path; +// dta = currentDTA; +// shortname = dta.Name; + +// // get file sizes and offsets in the CON's memory +// MidiSize = theCON.GetFileSize($"songs/{shortname}/{shortname}.mid"); +// MidiOffsets = theCON.GetMemOffsets($"songs/{shortname}/{shortname}.mid"); +// MoggSize = theCON.GetFileSize($"songs/{shortname}/{shortname}.mogg"); +// MoggOffsets = theCON.GetMemOffsets($"songs/{shortname}/{shortname}.mogg"); +// ImgSize = theCON.GetFileSize($"songs/{shortname}/gen/{shortname}_keep.png_xbox"); +// ImgOffsets = theCON.GetMemOffsets($"songs/{shortname}/gen/{shortname}_keep.png_xbox"); +// } + +// public void ParseSong(){ +// // first, parse songs.dta +// songDta = new XboxSongData(); +// songDta.ParseFromDta(dta); + +// // now, parse the mogg +// moggDta = new XboxMoggData(CONRootPath, MoggSize, MoggOffsets); +// moggDta.ParseMoggHeader(); +// moggDta.ParseFromDta(dta.Array("song")); +// moggDta.CalculateMoggBassInfo(); + +// // finally, parse the image +// if(songDta.albumArt && ImgSize > 0 && ImgOffsets != null){ +// img = new XboxImage(CONRootPath, ImgSize, ImgOffsets); +// } + +// } + +// public bool IsValidSong() { +// // Skip if the song doesn't have notes +// if(MidiSize == 0 && MidiOffsets == null) return false; +// // Skip if this is a "fake song" (tutorials, etc.) +// if (songDta.fake) return false; +// // Skip if the mogg is encrypted +// if (moggDta.Header != 0xA) return false; + +// return true; +// } + +// public override string ToString() { +// return string.Join(Environment.NewLine, +// $"XBOX CON SONG {shortname}", +// $"CON file: {CONRootPath}", +// "", +// songDta.ToString(), +// "", +// moggDta.ToString() +// ); +// } + +// public override byte[] GetMidiFile(){ +// byte[] f = new byte[MidiSize]; +// uint lastSize = MidiSize % 0x1000; + +// Parallel.For(0, MidiOffsets.Length, i => { +// uint readLen = (i == MidiOffsets.Length - 1) ? lastSize : 0x1000; +// using var fs = new FileStream(CONRootPath, FileMode.Open, FileAccess.Read); +// using var br = new BinaryReader(fs, new ASCIIEncoding()); +// fs.Seek(MidiOffsets[i], SeekOrigin.Begin); +// Array.Copy(br.ReadBytes((int)readLen), 0, f, i*0x1000, (int)readLen); +// }); - return f; - } +// return f; +// } - //TODO: convert each XboxCONSong to its own SongInfo for YARG to use in-game +// //TODO: convert each XboxCONSong to its own SongInfo for YARG to use in-game - } -} \ No newline at end of file +// } +// } \ No newline at end of file diff --git a/Assets/Script/Serialization/Xbox/XboxSong.cs b/Assets/Script/Serialization/Xbox/XboxSong.cs deleted file mode 100644 index 058314d01..000000000 --- a/Assets/Script/Serialization/Xbox/XboxSong.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using System.IO; -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; } - - public string SongFolderPath { get; } - - private XboxSongData songDta; - private XboxMoggData moggDta; - private XboxImage img; - - public XboxSong(string pathName, DataArray dta) { - // Parse songs.dta - songDta = new XboxSongData(); - - // Get song metadata from songs.dta - songDta.ParseFromDta(dta); - ShortName = songDta.GetShortName(); - - // Get song folder path for mid, mogg, png_xbox - SongFolderPath = Path.Combine(pathName, ShortName); - - // Set midi file - MidiFile = Path.Combine(SongFolderPath, $"{ShortName}.mid"); - - // Parse the 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"); - if (songDta.AlbumArtRequired() && File.Exists(imgPath)) { - img = new XboxImage(imgPath); - } - } - - public void UpdateSong(string pathUpdateName, DataArray dta_update) { - songDta.ParseFromDta(dta_update); - // if dta_update.Array("song") is not null, parse for any MoggDta as well - if (dta_update.Array("song") is DataArray moggUpdateDta) - moggDta.ParseFromDta(moggUpdateDta); - - // if extra_authoring has disc_update, grab update midi - if (songDta.discUpdate) { - MidiUpdateFile = Path.Combine(pathUpdateName, ShortName, $"{ShortName}_update.mid"); - } - - // if update mogg exists, grab it and parse it - string moggUpdatePath = Path.Combine(pathUpdateName, ShortName, $"{ShortName}_update.mogg"); - if (File.Exists(moggUpdatePath)) { - moggDta.MoggPath = moggUpdatePath; - moggDta.ParseMoggHeader(); - // moggDta.ParseFromDta(dta_update.Array("song")); - moggDta.CalculateMoggBassInfo(); - } - - // if album_art == TRUE AND alternate_path == TRUE, grab update png - if (songDta.albumArt && songDta.alternatePath) { - Debug.Log($"new album art, grabbing it now"); - // make a new image here, cuz what if an old one exists? - img = new XboxImage(Path.Combine(pathUpdateName, ShortName, "gen", $"{ShortName}_keep.png_xbox")); - } - } - - public bool IsValidSong() { - // Skip if the song doesn't have notes - if (!File.Exists(MidiFile)) { - return false; - } - - // Skip if this is a "fake song" (tutorials, etc.) - if (songDta.IsFake()) { - return false; - } - - // Skip if the mogg is encrypted - if (moggDta.Header != 0xA) { - return false; - } - - return true; - } - - public override string ToString() { - return string.Join(Environment.NewLine, - $"XBOX SONG {ShortName}", - $"song folder path: {SongFolderPath}", - "", - songDta.ToString(), - "", - moggDta.ToString() - ); - } - - public void CompleteSongInfo(ExtractedConSongEntry song, bool rb) { - // Set infos - 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"; - } - } - - song.SongLength = (int) songDta.songLength; - // song.delay - 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.loadingPhrase - - // Set CON specific info - song.MoggInfo = moggDta; - song.ImageInfo = img; - - // Set difficulties - foreach (var (key, value) in songDta.ranks) { - var instrument = InstrumentHelper.FromStringName(key); - if (instrument == Instrument.INVALID) { - continue; - } - - song.PartDifficulties[instrument] = DtaDifficulty.ToNumberedDiff(instrument, value); - } - - // Set pro 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]; - } - } - } -} \ No newline at end of file diff --git a/Assets/Script/Serialization/Xbox/XboxSongData.cs b/Assets/Script/Serialization/Xbox/XboxSongData.cs deleted file mode 100644 index 7395e99bb..000000000 --- a/Assets/Script/Serialization/Xbox/XboxSongData.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; -using System.Collections.Generic; -using DtxCS.DataTypes; -using UnityEngine; - -// complete GH DX songs.dta additions: -// artist (songalbum, author (as in chart author), songyear, songgenre, songorigin, -// songduration, songguitarrank, songbassrank, songrhythmrank, songdrumrank, songartist) - -// common attributes: -// shortname, name, artist, master/caption, -// song (name, tracks, pans, vols, cores) -// anim_tempo, preview - -namespace YARG.Serialization { - public class XboxSongData { - // all the possible metadata you could possibly want from a particular songs.dta - public string shortname, name, artist; - public string gameOrigin, genre, albumName, bank; - public string ghOutfit, ghGuitar, ghVenue; - public uint songLength = 0, songScrollSpeed = 2300; - public short tuningOffsetCents = 0; - public uint? songId; - public ushort? yearReleased, yearRecorded, vocalTonicNote; //TODO: implement other nullable/optional variables with default values - public bool? songTonality; - public bool master = false, albumArt = false, vocalGender; //vocalGender is true if male, false if female - public byte rating = 4, animTempo; - public byte? albumTrackNumber; - public byte vocalParts = 1; - public bool fake = false; - public bool alternatePath = false; - public bool discUpdate = false; - public (uint start, uint end) preview; - public short[] realGuitarTuning, realBassTuning; - public string[] solos; - public Dictionary ranks; - public int hopoThreshold = 0; - - //TODO: implement macro support, such as #ifndef kControllerRealGuitar, or #ifdef YARG - public void ParseFromDta(DataArray dta) { - shortname = dta.Name; - for (int i = 1; i < dta.Count; i++) { - DataArray dtaArray = (DataArray) dta[i]; - switch (dtaArray[0].ToString()) { - case "name": name = ((DataAtom) dtaArray[1]).Name; break; - case "artist": artist = ((DataAtom) dtaArray[1]).Name; break; - case "master": - if (dtaArray[1] is DataSymbol symMaster) - master = (symMaster.Name.ToUpper() == "TRUE"); - else if (dtaArray[1] is DataAtom atmMaster) - master = (atmMaster.Int != 0); - break; - case "caption": master = true; break; //used in GH - case "song_id": - if (dtaArray[1] is DataAtom atmSongId) - if (atmSongId.Type == DataType.INT) - songId = (uint) ((DataAtom) dtaArray[1]).Int; - break; - case "song_length": songLength = (uint) ((DataAtom) dtaArray[1]).Int; break; - case "song": // we just want vocal parts and hopo threshold for songDta - hopoThreshold = (dtaArray.Array("hopo_threshold") != null) ? ((DataAtom) dtaArray.Array("hopo_threshold")[1]).Int : 0; - vocalParts = (dtaArray.Array("vocal_parts") != null) ? (byte) ((DataAtom) dtaArray.Array("vocal_parts")[1]).Int : (byte) 1; - break; - case "anim_tempo": - if (dtaArray[1] is DataSymbol symTempo) - animTempo = symTempo.Name switch { - "kTempoSlow" => 16, - "kTempoMedium" => 32, - "kTempoFast" => 64, - _ => 0, - }; - else if (dtaArray[1] is DataAtom atom) - animTempo = (byte) atom.Int; - break; - case "preview": - preview = ((uint) ((DataAtom) dtaArray[1]).Int, (uint) ((DataAtom) dtaArray[2]).Int); - break; - case "bank": - if (dtaArray[1] is DataSymbol symBank) - bank = symBank.Name; - else if (dtaArray[1] is DataAtom atmBank) - bank = atmBank.String; - break; - case "song_scroll_speed": songScrollSpeed = (uint) ((DataAtom) dtaArray[1]).Int; break; - case "solo": - DataArray soloInstruments = (DataArray) dtaArray[1]; - solos = new string[soloInstruments.Count]; - for (int t = 0; t < soloInstruments.Count; t++) - if (soloInstruments[t] is DataSymbol symSolo) - solos[t] = symSolo.Name; - break; - case "rank": - ranks = new Dictionary(); - for (int j = 1; j < dtaArray.Count; j++) - if (dtaArray[j] is DataArray inner) - ranks.Add(((DataSymbol) inner[0]).Name, (ushort) ((DataAtom) inner[1]).Int); - break; - case "game_origin": gameOrigin = ((DataSymbol) dtaArray[1]).Name; break; - case "genre": genre = ((DataSymbol) dtaArray[1]).Name; break; - case "rating": rating = (byte) ((DataAtom) dtaArray[1]).Int; break; - case "vocal_gender": vocalGender = (((DataSymbol) dtaArray[1]).Name == "male"); break; - case "fake": fake = (dtaArray[1].ToString().ToUpper() == "TRUE"); break; - case "album_art": - if (dtaArray[1] is DataSymbol symArt) - albumArt = (symArt.Name.ToUpper() == "TRUE"); - else if (dtaArray[1] is DataAtom atmArt) - albumArt = (atmArt.Int != 0); - break; - case "album_name": albumName = ((DataAtom) dtaArray[1]).Name; break; - case "album_track_number": albumTrackNumber = (byte) ((DataAtom) dtaArray[1]).Int; break; - case "year_released": yearReleased = (ushort) ((DataAtom) dtaArray[1]).Int; break; - case "year_recorded": yearRecorded = (ushort) ((DataAtom) dtaArray[1]).Int; break; - case "vocal_tonic_note": vocalTonicNote = (ushort) ((DataAtom) dtaArray[1]).Int; break; - case "song_tonality": songTonality = ((((DataAtom) dtaArray[1]).Int) != 0); break; //0 = major, 1 = minor - case "tuning_offset_cents": - DataAtom tuningAtom = (DataAtom) dtaArray[1]; - if (tuningAtom.Type == DataType.INT) tuningOffsetCents = (short) ((DataAtom) dtaArray[1]).Int; - else tuningOffsetCents = (short) ((DataAtom) dtaArray[1]).Float; - break; - case "real_guitar_tuning": - DataArray guitarTunes = (DataArray) dtaArray[1]; - realGuitarTuning = new short[6]; - for (int g = 0; g < 6; g++) realGuitarTuning[g] = (short) ((DataAtom) guitarTunes[g]).Int; - break; - case "real_bass_tuning": - DataArray bassTunes = (DataArray) dtaArray[1]; - realBassTuning = new short[4]; - for (int b = 0; b < 4; b++) realBassTuning[b] = (short) ((DataAtom) bassTunes[b]).Int; - break; - case "alternate_path": - if (dtaArray[1] is DataSymbol symAltPath) - alternatePath = (symAltPath.Name.ToUpper() == "TRUE"); - else if (dtaArray[1] is DataAtom atmAltPath) - alternatePath = (atmAltPath.Int != 0); - break; - case "extra_authoring": - for(int ea = 1; ea < dtaArray.Count; ea++){ - if(dtaArray[ea] is DataSymbol symEA){ - if(symEA.Name == "disc_update"){ - discUpdate = true; - break; - } - } - else if(dtaArray[ea] is DataAtom atmEA){ - if(atmEA.String == "disc_update"){ - discUpdate = true; - break; - } - } - } - break; - case "quickplay": //used in GH - for (int q = 1; q < dtaArray.Count; q++) { - DataArray innerQPArray = (DataArray) dtaArray[q]; - switch (innerQPArray[0].ToString()) { - case "character_outfit": ghOutfit = ((DataSymbol) innerQPArray[1]).Name; break; - case "guitar": ghGuitar = ((DataSymbol) innerQPArray[1]).Name; break; - case "venue": ghVenue = ((DataSymbol) innerQPArray[1]).Name; break; - } - } - break; - default: - break; - } - } - // must be done after the above parallel loop due to race issues with ranks and vocalParts - if (!ranks.ContainsKey("vocals") || ranks["vocals"] == 0) vocalParts = 0; - } - - public string GetShortName() { return shortname; } - public bool AlbumArtRequired() { return albumArt; } - public bool IsFake() { return fake; } - - public override string ToString() { - return string.Join(Environment.NewLine, - $"Song metadata:", - $"song id={songId}; shortname={shortname}: name={name}; artist={((!master) ? "as made famous by " : "")}{artist}", - $"vocal parts={vocalParts}; vocal gender={((vocalGender) ? "male" : "female")}", - $"ranks={string.Join(", ", ranks)}", - $"album art={albumArt}; album name={albumName}; album track number={albumTrackNumber}", - $"year released={yearReleased}; year recorded={yearRecorded}", - $"song length={songLength}; preview={preview}; game origin={gameOrigin}; genre={genre}; rating={rating}", - $"vocal tonic note={vocalTonicNote}", - $"song tonality={songTonality}", - $"tuning offset cents={tuningOffsetCents}", - $"real guitar tuning=({((realGuitarTuning != null) ? string.Join(", ", realGuitarTuning) : "")})", - $"real bass tuning=({((realBassTuning != null) ? string.Join(", ", realBassTuning) : "")})" - ); - } - } -} \ No newline at end of file diff --git a/Assets/Script/Song/CacheHelpers.cs b/Assets/Script/Song/CacheHelpers.cs index f7bcb5b57..d9b5e2945 100644 --- a/Assets/Script/Song/CacheHelpers.cs +++ b/Assets/Script/Song/CacheHelpers.cs @@ -5,63 +5,34 @@ namespace YARG.Song { public static class CacheHelpers { - public static void WriteExtractedConData(BinaryWriter writer, ExtractedConSongEntry conSong) { + public static void WriteExtractedConData(BinaryWriter writer, ExtractedConSongEntry ExCONSong) { /*/ 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); - } - + + writer.Write(ExCONSong.MoggPath); + writer.Write(ExCONSong.MoggHeader); + writer.Write(ExCONSong.MoggAddressAudioOffset); + writer.Write(ExCONSong.MoggAudioLength); + // Write Stem Data - writer.Write(conSong.MoggInfo.StemMaps.Count); - foreach (var stem in conSong.MoggInfo.StemMaps) { + writer.Write(ExCONSong.StemMaps.Count); + foreach(var stem in ExCONSong.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]); + writer.Write(ExCONSong.MatrixRatios.GetLength(0)); + writer.Write(ExCONSong.MatrixRatios.GetLength(1)); + for (int i = 0; i < ExCONSong.MatrixRatios.GetLength(0); i++) { + for (int j = 0; j < ExCONSong.MatrixRatios.GetLength(1); j++) { + writer.Write(ExCONSong.MatrixRatios[i, j]); } } @@ -71,55 +42,15 @@ 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); - } + // Note: ImagePath can be an empty string if the song has no image + writer.Write(ExCONSong.ImagePath); } - 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(); - 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(); - } + public static void ReadExtractedConData(BinaryReader reader, ExtractedConSongEntry ExCONSong) { + ExCONSong.MoggPath = reader.ReadString(); + ExCONSong.MoggHeader = reader.ReadInt32(); + ExCONSong.MoggAddressAudioOffset = reader.ReadInt32(); + ExCONSong.MoggAudioLength = reader.ReadInt64(); // Read Stem Data int stemCount = reader.ReadInt32(); @@ -133,6 +64,8 @@ public static void ReadExtractedConData(BinaryReader reader, ExtractedConSongEnt } stemMaps.Add(stem, stemMap); } + + ExCONSong.StemMaps = stemMaps; // Read Matrix Data int matrixRowCount = reader.ReadInt32(); @@ -144,18 +77,7 @@ public static void ReadExtractedConData(BinaryReader reader, ExtractedConSongEnt } } - 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 - }; + ExCONSong.MatrixRatios = matrixRatios; /*/ @@ -163,21 +85,8 @@ 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; + // Note: ImagePath can be an empty string if the song has no image + ExCONSong.ImagePath = reader.ReadString(); } } diff --git a/Assets/Script/Song/Scanning/SongScanThread.cs b/Assets/Script/Song/Scanning/SongScanThread.cs index 426f6ab4a..5bb4a66ef 100644 --- a/Assets/Script/Song/Scanning/SongScanThread.cs +++ b/Assets/Script/Song/Scanning/SongScanThread.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Security.Cryptography; +using System.Text; using System.Threading; using UnityEngine; using YARG.Serialization; @@ -134,19 +135,48 @@ private void ScanSubDirectory(string cacheFolder, string subDir, ICollection StemMaps { get; set; } = new(); + public float[,] MatrixRatios { get; set; } + + // image info + public string ImagePath { get; set; } = string.Empty; + + } +} \ No newline at end of file diff --git a/Assets/Script/Song/Types/ExConSongEntry.cs.meta b/Assets/Script/Song/Types/ExConSongEntry.cs.meta new file mode 100644 index 000000000..0bdaea9d3 --- /dev/null +++ b/Assets/Script/Song/Types/ExConSongEntry.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 18d3d0ec1d1944cdd8f2177614643c1c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Script/Song/Types/RawConSongEntry.cs b/Assets/Script/Song/Types/RawConSongEntry.cs deleted file mode 100644 index 5af00c9fa..000000000 --- a/Assets/Script/Song/Types/RawConSongEntry.cs +++ /dev/null @@ -1,16 +0,0 @@ -using YARG.Serialization; - -namespace YARG.Song { - public class ExtractedConSongEntry : SongEntry { - - /// - /// .mogg data for CON files. - /// - public XboxMoggData MoggInfo { get; set; } - /// - /// .xbox_png data for CON files. - /// - public XboxImage ImageInfo { get; set; } - - } -} \ No newline at end of file diff --git a/Assets/Script/Song/Types/RawConSongEntry.cs.meta b/Assets/Script/Song/Types/RawConSongEntry.cs.meta deleted file mode 100644 index e45f8595d..000000000 --- a/Assets/Script/Song/Types/RawConSongEntry.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 33feeda14131b4448bb2d91f7ec73394 -timeCreated: 1682876403 \ No newline at end of file diff --git a/Assets/Script/UI/MusicLibrary/SelectedSongView.cs b/Assets/Script/UI/MusicLibrary/SelectedSongView.cs index dfb016e35..7ea0b1257 100644 --- a/Assets/Script/UI/MusicLibrary/SelectedSongView.cs +++ b/Assets/Script/UI/MusicLibrary/SelectedSongView.cs @@ -6,6 +6,7 @@ using UnityEngine.UI; using YARG.Data; using YARG.Song; +using YARG.Serialization; #if UNITY_EDITOR_LINUX || UNITY_STANDALONE_LINUX || UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX @@ -134,13 +135,13 @@ private void LoadAlbumCover() { } } } else { - // Check if an EXCon or if there is no album image for this song - if (_songEntry is not ExtractedConSongEntry conEntry || conEntry.ImageInfo is null) { + // Check if an ExCon/Con or if there is no album image for this song + if (_songEntry is not ExtractedConSongEntry conEntry || conEntry.ImagePath == string.Empty) { return; } // Set album cover - albumCover.texture = conEntry.ImageInfo.GetAsTexture(); + albumCover.texture = XboxImageTextureGenerator.GetTexture(File.ReadAllBytes(conEntry.ImagePath)); albumCover.color = Color.white; albumCover.uvRect = new Rect(0f, 0f, 1f, -1f); }