diff --git a/Assets/Scripts/GameManager.cs b/Assets/Scripts/GameManager.cs index bca688241..1aa809fad 100644 --- a/Assets/Scripts/GameManager.cs +++ b/Assets/Scripts/GameManager.cs @@ -73,6 +73,9 @@ namespace HeavenStudio public event Action onBeatChanged; public event Action onSectionChange; public event Action onBeatPulse; + public event Action onPlay; + public event Action onPause; + public event Action onUnPause; public int BeatmapEntities() { @@ -143,9 +146,6 @@ namespace HeavenStudio eventCaller = this.gameObject.AddComponent(); eventCaller.GamesHolder = GamesHolder.transform; eventCaller.Init(); - Conductor.instance.SetBpm(120f); - Conductor.instance.SetVolume(100f); - Conductor.instance.firstBeatOffset = Beatmap.data.offset; // note: serialize this shit in the inspector // GameObject textbox = Instantiate(Resources.Load("Prefabs/Common/Textbox")); @@ -177,6 +177,9 @@ namespace HeavenStudio } SortEventsList(); + Conductor.instance.SetBpm(Beatmap.TempoChanges[0]["tempo"]); + Conductor.instance.SetVolume(Beatmap.VolumeChanges[0]["volume"]); + Conductor.instance.firstBeatOffset = Beatmap.data.offset; if (Beatmap.Entities.Count >= 1) { @@ -231,6 +234,7 @@ namespace HeavenStudio public void NewRemix() { + Debug.Log("Creating new remix"); AudioLoadDone = false; Beatmap = new("1", "HeavenStudio"); Beatmap.data.properties = Minigames.propertiesModel; @@ -238,7 +242,6 @@ namespace HeavenStudio Beatmap.AddNewVolumeChange(0, 100f); Beatmap.data.offset = 0f; Conductor.instance.musicSource.clip = null; - RiqFileHandler.UnlockCache(); RiqFileHandler.WriteRiq(Beatmap); AudioLoadDone = true; } @@ -680,6 +683,11 @@ namespace HeavenStudio Minigame miniGame = currentGameO?.GetComponent(); if (miniGame != null) miniGame.OnPlay(beat); + onPlay?.Invoke(beat); + } + else + { + onUnPause?.Invoke(beat); } if (playMode) @@ -693,17 +701,21 @@ namespace HeavenStudio public void Pause() { Conductor.instance.Pause(); - // Util.SoundByte.PauseOneShots(); + Util.SoundByte.PauseOneShots(); + onPause?.Invoke(Conductor.instance.songPositionInBeatsAsDouble); canInput = false; } public void Stop(double beat, bool restart = false, float restartDelay = 0f) { // I feel like I should standardize the names - SkillStarManager.instance.KillStar(); - TimingAccuracyDisplay.instance.StopStarFlash(); - GoForAPerfect.instance.Disable(); - SectionMedalsManager.instance.OnRemixEnd(endBeat, currentSection); + if (Conductor.instance.isPlaying) + { + SkillStarManager.instance.KillStar(); + TimingAccuracyDisplay.instance.StopStarFlash(); + GoForAPerfect.instance.Disable(); + SectionMedalsManager.instance.OnRemixEnd(endBeat, currentSection); + } Minigame miniGame = currentGameO.GetComponent(); if (miniGame != null) @@ -739,22 +751,27 @@ namespace HeavenStudio private IEnumerator WaitReadyAndPlayCo(double beat, float delay = 1f, bool discord = true) { WaitUntil yieldOverlays = new WaitUntil(() => OverlaysManager.OverlaysReady); - WaitUntil yieldBeatmap = new WaitUntil(() => Beatmap != null && Beatmap.Entities.Count > 0); + WaitUntil yieldBeatmap = new WaitUntil(() => Beatmap != null && BeatmapEntities() > 0); WaitUntil yieldAudio = new WaitUntil(() => AudioLoadDone || (ChartLoadError && !GlobalGameManager.IsShowingDialog)); WaitUntil yieldGame = null; List gamesToPreload = SeekAheadAndPreload(beat, 4f); + Debug.Log($"Preloading {gamesToPreload.Count} games"); if (gamesToPreload.Count > 0) { yieldGame = new WaitUntil(() => gamesToPreload.All(x => x.AssetsLoaded)); } // wait for overlays to be ready + Debug.Log("waiting for overlays"); yield return yieldOverlays; // wait for beatmap to be loaded + Debug.Log("waiting for beatmap"); yield return yieldBeatmap; //wait for audio clip to be loaded + Debug.Log("waiting for audio"); yield return yieldAudio; //wait for games to be loaded + Debug.Log("waiting for minigames"); if (yieldGame != null) yield return yieldGame; diff --git a/Assets/Scripts/LevelEditor/Editor.cs b/Assets/Scripts/LevelEditor/Editor.cs index ee635424d..f03b0e6c8 100644 --- a/Assets/Scripts/LevelEditor/Editor.cs +++ b/Assets/Scripts/LevelEditor/Editor.cs @@ -414,7 +414,6 @@ namespace HeavenStudio.Editor { try { - RiqFileHandler.UnlockCache(); RiqFileHandler.WriteRiq(GameManager.instance.Beatmap); RiqFileHandler.PackRiq(path, true); Debug.Log("Packed RIQ successfully!"); @@ -465,7 +464,6 @@ namespace HeavenStudio.Editor try { - RiqFileHandler.UnlockCache(); string tmpDir = RiqFileHandler.ExtractRiq(path); Debug.Log("Imported RIQ successfully!"); LoadRemix(); diff --git a/Assets/Scripts/Minigames.cs b/Assets/Scripts/Minigames.cs index b8e1faaf4..a44d4154d 100644 --- a/Assets/Scripts/Minigames.cs +++ b/Assets/Scripts/Minigames.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Cysharp.Threading.Tasks; using UnityEngine; +using UnityEngine.Networking; using DG.Tweening; using HeavenStudio.Util; @@ -32,6 +33,7 @@ namespace HeavenStudio public static void InitPreprocessor() { RiqBeatmap.OnUpdateBeatmap += PreProcessBeatmap; + RiqFileHandler.AudioConverter = JukeboxAudioConverter; } public static Dictionary propertiesModel = new() @@ -142,6 +144,35 @@ namespace HeavenStudio } } + public static string JukeboxAudioConverter(string filePath, AudioType audioType, string specificType) + { + if (audioType == AudioType.MPEG) + { + Debug.Log($"mp3 loaded, Converting {filePath} to wav..."); + // convert mp3 to wav + // import the mp3 as an audioclip + string url = "file://" + filePath; + using (var www = UnityWebRequestMultimedia.GetAudioClip(url, audioType)) + { + www.SendWebRequest(); + while (!www.isDone) { } + if (www.result == UnityWebRequest.Result.ConnectionError) + { + Debug.LogError($"Could not load audio file {filePath}! Error: {www.error}"); + return filePath; + } + AudioClip clip = DownloadHandlerAudioClip.GetContent(www); + string fileName = Path.GetFileNameWithoutExtension(filePath); + Debug.Log(fileName); + SavWav.Save("/RIQCache/" + fileName, clip, true); + filePath = Path.Combine(Application.temporaryCachePath, "/RIQCache/" + fileName + ".wav"); + + clip = null; + } + } + return filePath; + } + /// /// processes an riq beatmap after it is loaded /// diff --git a/Assets/Scripts/UI/Overlays/SectionMedalsManager.cs b/Assets/Scripts/UI/Overlays/SectionMedalsManager.cs index 2ccc105fc..82000f966 100644 --- a/Assets/Scripts/UI/Overlays/SectionMedalsManager.cs +++ b/Assets/Scripts/UI/Overlays/SectionMedalsManager.cs @@ -82,7 +82,7 @@ namespace HeavenStudio.Common public void OnRemixEnd(double beat, RiqEntity lastSection) { if (!PersistentDataManager.gameSettings.isMedalOn) return; - if (PersistentDataManager.gameSettings.isMedalOn && isMedalsStarted) + if (PersistentDataManager.gameSettings.isMedalOn && isMedalsStarted && lastSection != null) { GameManager.instance.DoSectionCompletion(beat, isMedalsEligible, lastSection["sectionName"], 1); GameObject medal = Instantiate(isMedalsEligible ? MedalOkPrefab : MedalMissPrefab, MedalsHolder.transform); diff --git a/Assets/Scripts/Util/SavWav.cs b/Assets/Scripts/Util/SavWav.cs new file mode 100644 index 000000000..3ea7d8e1e --- /dev/null +++ b/Assets/Scripts/Util/SavWav.cs @@ -0,0 +1,187 @@ + +// Copyright (c) 2012 Calvin Rien +// http://the.darktable.com +// +// This software is provided 'as-is', without any express or implied warranty. In +// no event will the authors be held liable for any damages arising from the use +// of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not claim +// that you wrote the original software. If you use this software in a product, +// an acknowledgment in the product documentation would be appreciated but is not +// required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +// ============================================================================= +// +// derived from Gregorio Zanon's script +// http://forum.unity3d.com/threads/119295-Writing-AudioListener.GetOutputData-to-wav-problem?p=806734&viewfull=1#post806734 + +// +// Fork by R-WebsterNoble +// Optimized! Now 20 times quicker +// Easy to get byte[] instead of saving file + +// Note: GetWav() with trimming returns the full buffer with a load of zeros on the end. +// Use the length out parameter to know where the data stops. +// + +// RHeavenStudio +// modified to use temporary cache path instead of persistent data path + +using System; +using System.IO; +using System.Text; +using UnityEngine; + +public static class SavWav +{ + private const uint HeaderSize = 44; + private const float RescaleFactor = 32767; //to convert float to Int16 + + public static void Save(string filename, AudioClip clip, bool trim = false) + { + if (!filename.ToLower().EndsWith(".wav")) + { + filename += ".wav"; + } + + var filepath = Path.Combine(Application.temporaryCachePath, filename); + + // Make sure directory exists if user is saving to sub dir. + if (!Directory.Exists(Path.GetDirectoryName(filepath))) + { + Directory.CreateDirectory(Path.GetDirectoryName(filepath)); + } + + using (var fileStream = new FileStream(filepath, FileMode.Create)) + using (var writer = new BinaryWriter(fileStream)) + { + var wav = GetWav(clip, out var length, trim); + writer.Write(wav, 0, (int)length); + } + } + + public static byte[] GetWav(AudioClip clip, out uint length, bool trim = false) + { + var data = ConvertAndWrite(clip, out length, out var samples, trim); + + WriteHeader(data, clip, length, samples); + + return data; + } + + private static byte[] ConvertAndWrite(AudioClip clip, out uint length, out uint samplesAfterTrimming, bool trim) + { + var samples = new float[clip.samples * clip.channels]; + + clip.GetData(samples, 0); + + var sampleCount = samples.Length; + + var start = 0; + var end = sampleCount - 1; + + if (trim) + { + for (var i = 0; i < sampleCount; i++) + { + if ((short)(samples[i] * RescaleFactor) == 0) + continue; + + start = i; + break; + } + + for (var i = sampleCount -1; i >= 0; i--) + { + if ((short)(samples[i] * RescaleFactor) == 0) + continue; + + end = i; + break; + } + } + + var buffer = new byte[(sampleCount * 2) + HeaderSize]; + + var p = HeaderSize; + for (var i = start; i <= end; i++) + { + var value = (short) (samples[i] * RescaleFactor); + buffer[p++] = (byte) (value >> 0); + buffer[p++] = (byte) (value >> 8); + } + + length = p; + samplesAfterTrimming = (uint) (end - start + 1); + return buffer; + } + + private static void AddDataToBuffer(byte[] buffer, ref uint offset, byte[] addBytes) + { + foreach (var b in addBytes) + { + buffer[offset++] = b; + } + } + + private static void WriteHeader(byte[] stream, AudioClip clip, uint length, uint samples) + { + var hz = (uint)clip.frequency; + var channels = (ushort)clip.channels; + + var offset = 0u; + + var riff = Encoding.UTF8.GetBytes("RIFF"); + AddDataToBuffer(stream, ref offset, riff); + + var chunkSize = BitConverter.GetBytes(length - 8); + AddDataToBuffer(stream, ref offset, chunkSize); + + var wave = Encoding.UTF8.GetBytes("WAVE"); + AddDataToBuffer(stream, ref offset, wave); + + var fmt = Encoding.UTF8.GetBytes("fmt "); + AddDataToBuffer(stream, ref offset, fmt); + + var subChunk1 = BitConverter.GetBytes(16u); + AddDataToBuffer(stream, ref offset, subChunk1); + + const ushort two = 2; + const ushort one = 1; + + var audioFormat = BitConverter.GetBytes(one); + AddDataToBuffer(stream, ref offset, audioFormat); + + var numChannels = BitConverter.GetBytes(channels); + AddDataToBuffer(stream, ref offset, numChannels); + + var sampleRate = BitConverter.GetBytes(hz); + AddDataToBuffer(stream, ref offset, sampleRate); + + var byteRate = BitConverter.GetBytes(hz * channels * 2); // sampleRate * bytesPerSample*number of channels, here 44100*2*2 + AddDataToBuffer(stream, ref offset, byteRate); + + var blockAlign = (ushort)(channels * 2); + AddDataToBuffer(stream, ref offset, BitConverter.GetBytes(blockAlign)); + + ushort bps = 16; + var bitsPerSample = BitConverter.GetBytes(bps); + AddDataToBuffer(stream, ref offset, bitsPerSample); + + var dataString = Encoding.UTF8.GetBytes("data"); + AddDataToBuffer(stream, ref offset, dataString); + + var subChunk2 = BitConverter.GetBytes(samples * 2); + AddDataToBuffer(stream, ref offset, subChunk2); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Util/SavWav.cs.meta b/Assets/Scripts/Util/SavWav.cs.meta new file mode 100644 index 000000000..180ef6059 --- /dev/null +++ b/Assets/Scripts/Util/SavWav.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a24cd489c15c51408c78bb5c145360e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 410b213b3..d09545e31 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -22,7 +22,7 @@ "com.unity.nuget.newtonsoft-json": "3.2.1", "jillejr.newtonsoft.json-for-unity.converters": "1.5.1" }, - "hash": "33fa051b5a6954a365df23038085caca9a844cab" + "hash": "4628637dd95ff16553a55e0cdb08f6c4fbce2906" }, "com.sr4dev.unity-spriteassist": { "version": "https://github.com/sr4dev/Unity-SpriteAssist.git?path=Assets/SpriteAssist",