HeavenStudio/Assets/Scripts/Conductor.cs

402 lines
12 KiB
C#
Raw Normal View History

using System;
2021-12-19 04:10:43 +00:00
using System.Collections.Generic;
using UnityEngine;
using Starpelly;
2022-03-14 14:21:05 +00:00
namespace HeavenStudio
2021-12-19 04:10:43 +00:00
{
2022-01-03 22:42:43 +00:00
// [RequireComponent(typeof(AudioSource))]
2021-12-21 01:10:49 +00:00
public class Conductor : MonoBehaviour
{
// Song beats per minute
// This is determined by the song you're trying to sync up to
2021-12-21 01:10:49 +00:00
public float songBpm;
2021-12-19 04:10:43 +00:00
// The number of seconds for each song beat
2021-12-21 01:10:49 +00:00
public float secPerBeat;
2021-12-19 04:10:43 +00:00
// The number of seconds for each song beat, inversely scaled to song pitch (higer pitch = shorter time)
public float pitchedSecPerBeat => (secPerBeat / SongPitch);
// Current song position, in seconds
private double songPos; // for Conductor use only
public float songPosition => (float) songPos;
public double songPositionAsDouble => songPos;
2021-12-19 04:10:43 +00:00
// Current song position, in beats
private double songPosBeat; // for Conductor use only
public float songPositionInBeats => (float) songPosBeat;
public double songPositionInBeatsAsDouble => songPosBeat;
2021-12-19 04:10:43 +00:00
// Current time of the song
private double time;
2021-12-19 04:10:43 +00:00
double lastAbsTime;
// the dspTime we started at
private double dspStartTime;
public double dspStartTimeAsDouble => dspStartTime;
//the beat we started at
private double startBeat;
public double startBeatAsDouble => startBeat;
// an AudioSource attached to this GameObject that will play the music.
public AudioSource musicSource;
2021-12-19 04:10:43 +00:00
// The offset to the first beat of the song in seconds
public float firstBeatOffset;
2021-12-19 04:10:43 +00:00
// Conductor instance
2021-12-21 01:10:49 +00:00
public static Conductor instance;
2021-12-19 04:10:43 +00:00
2022-01-15 17:45:08 +00:00
// Conductor is currently playing song
public bool isPlaying;
2022-01-15 17:45:08 +00:00
// Conductor is currently paused, but not fully stopped
public bool isPaused;
2022-01-15 17:45:08 +00:00
// Last reported beat based on song position
private float lastReportedBeat = 0f;
// Metronome tick sound enabled
public bool metronome = false;
Util.Sound metronomeSound;
2022-01-15 17:45:08 +00:00
// pitch values
private float timelinePitch = 1f;
private float minigamePitch = 1f;
public float SongPitch { get => timelinePitch * minigamePitch; }
public void SetTimelinePitch(float pitch)
{
timelinePitch = pitch;
musicSource.pitch = SongPitch;
}
2022-01-19 05:40:49 +00:00
public void SetMinigamePitch(float pitch)
{
minigamePitch = pitch;
musicSource.pitch = SongPitch;
}
2021-12-21 01:10:49 +00:00
void Awake()
{
instance = this;
}
2021-12-19 04:10:43 +00:00
2022-01-08 16:42:48 +00:00
public void SetBeat(float beat)
{
float secFromBeat = (float) GetSongPosFromBeat(beat);
2021-12-19 04:10:43 +00:00
2022-01-09 23:35:55 +00:00
if (musicSource.clip != null)
{
if (secFromBeat < musicSource.clip.length)
musicSource.time = secFromBeat;
else
musicSource.time = 0;
}
2022-01-08 16:42:48 +00:00
GameManager.instance.SetCurrentEventToClosest(beat);
songPosBeat = beat;
}
2022-01-08 16:42:48 +00:00
public void Play(float beat)
{
GameManager.instance.SortEventsList();
bool negativeOffset = firstBeatOffset < 0f;
bool negativeStartTime = false;
// Debug.Log("starting playback @ beat " + beat + ", offset is " + firstBeatOffset);
2022-02-24 14:02:21 +00:00
var startPos = GetSongPosFromBeat(beat);
if (negativeOffset)
{
time = startPos;
}
else
{
negativeStartTime = startPos - firstBeatOffset < 0f;
if (negativeStartTime)
time = startPos - firstBeatOffset;
else
time = startPos;
}
//TODO: make this take into account past tempo changes
songPosBeat = GetBeatFromSongPos(time - firstBeatOffset);
// Debug.Log("corrected starting playback @ beat " + songPosBeat);
isPlaying = true;
isPaused = false;
2022-02-24 14:02:21 +00:00
if (SongPosLessThanClipLength(startPos))
2022-01-08 16:42:48 +00:00
{
if (negativeOffset)
{
var musicStartTime = startPos + firstBeatOffset;
if (musicStartTime < 0f)
{
musicSource.time = (float) startPos;
musicSource.PlayScheduled(AudioSettings.dspTime - firstBeatOffset / SongPitch);
}
else
{
musicSource.time = (float) musicStartTime;
musicSource.PlayScheduled(AudioSettings.dspTime);
}
}
else
{
if (negativeStartTime)
{
musicSource.time = (float) startPos;
}
else
{
musicSource.time = (float) startPos + firstBeatOffset;
}
musicSource.PlayScheduled(AudioSettings.dspTime);
}
2022-01-08 16:42:48 +00:00
}
lastAbsTime = Time.realtimeSinceStartupAsDouble;
dspStartTime = AudioSettings.dspTime;
startBeat = beat;
2022-01-08 16:42:48 +00:00
// GameManager.instance.SetCurrentEventToClosest(songPositionInBeats);
2021-12-21 01:10:49 +00:00
}
2021-12-19 04:10:43 +00:00
public void Pause()
{
isPlaying = false;
isPaused = true;
musicSource.Pause();
}
2022-01-08 16:42:48 +00:00
public void Stop(float time)
{
2022-01-08 16:42:48 +00:00
this.time = time;
songPosBeat = 0;
isPlaying = false;
isPaused = false;
musicSource.Stop();
2021-12-31 14:46:11 +00:00
}
float test;
2021-12-31 14:46:11 +00:00
public void Update()
2021-12-19 04:10:43 +00:00
{
if (isPlaying)
{
double absTime = Time.realtimeSinceStartupAsDouble;
double dt = (absTime - lastAbsTime) * SongPitch;
lastAbsTime = absTime;
2022-02-24 14:02:21 +00:00
time += dt;
2021-12-19 04:10:43 +00:00
songPos = time;
2021-12-19 04:10:43 +00:00
songPosBeat = GetBeatFromSongPos(songPos - firstBeatOffset);
}
}
2022-01-15 17:45:08 +00:00
public void LateUpdate()
{
if (metronome && isPlaying)
{
if (ReportBeat(ref lastReportedBeat))
2022-01-15 17:45:08 +00:00
{
metronomeSound = Util.Jukebox.PlayOneShot("metronome", lastReportedBeat);
}
else if (songPositionInBeats < lastReportedBeat)
{
lastReportedBeat = Mathf.Round(songPositionInBeats);
2022-01-15 17:45:08 +00:00
}
}
else
{
if (metronomeSound != null)
{
metronomeSound.Delete();
metronomeSound = null;
}
}
2021-12-21 01:10:49 +00:00
}
2021-12-19 04:10:43 +00:00
public bool ReportBeat(ref float lastReportedBeat, float offset = 0, bool shiftBeatToOffset = true)
2022-01-19 05:40:49 +00:00
{
bool result = songPositionInBeats + (shiftBeatToOffset ? offset : 0f) >= (lastReportedBeat) + 1f;
if (result)
2022-01-19 05:40:49 +00:00
{
lastReportedBeat += 1f;
if (lastReportedBeat < songPositionInBeats)
{
lastReportedBeat = Mathf.Round(songPositionInBeats);
}
2022-01-19 05:40:49 +00:00
}
return result;
}
2022-02-03 22:20:26 +00:00
public float GetLoopPositionFromBeat(float beatOffset, float length)
{
2022-02-09 03:58:25 +00:00
return Mathf.Repeat((songPositionInBeats / length) + beatOffset, 1);
2022-02-03 22:20:26 +00:00
}
public float GetPositionFromBeat(float startBeat, float length)
2021-12-21 01:10:49 +00:00
{
float a = Mathp.Normalize(songPositionInBeats, startBeat, startBeat + length);
return a;
2021-12-21 01:10:49 +00:00
}
2022-03-01 06:38:38 +00:00
public float GetBeatFromPosition(float position, float startBeat, float length)
{
return Mathp.DeNormalize(position, startBeat, startBeat + length);
}
public float GetPositionFromMargin(float targetBeat, float margin)
{
return GetPositionFromBeat(targetBeat - margin, margin);
}
2022-03-01 06:38:38 +00:00
public float GetBeatFromPositionAndMargin(float position, float targetBeat, float margin)
{
return GetBeatFromPosition(position, targetBeat - margin, margin);
}
private List<DynamicBeatmap.TempoChange> GetSortedTempoChanges(DynamicBeatmap chart)
{
GameManager.instance.SortEventsList();
return GameManager.instance.Beatmap.tempoChanges;
}
public float GetBpmAtBeat(float beat)
{
var chart = GameManager.instance.Beatmap;
Game Overlays (#280) * add accuracy display * temp BG for show * separate overlays prefab make proper shader for star effects * aim shakiness display * implement testing skill star * fully functional skill star * separate section display from editor * fully separate chart section display from timeline * add section to overlays * fix nullreference issues * start game layout settings * add game settings script * fix nonfunctioning scoring * invert y position logic on timing bar * add perfect challenge functionality * fix section not showing up in editor add perfect challenge option * add timing display minimal mode * Update PerfectAndPractice.png * show gismo for minigame bounds in editor * add ability to disable overlays in editor * prepare medals add new timing display graphic * hide screen preview * per-axis camera control added per request * section medals basic functionality * add medal get animations * fix bug with perfect icons * visual enhancements * adjust look of timing display minmode address audio ducking issues(?) * prepare overlay lyt editor add viewport pan, rotate, scale adjust audio setting * add layout editor UI elements * dynamic overlay creation * fix default single timing disp * set up overlay settings controls * start UI events * runtime uuid for component reference * layout editor affects overlay elements * show overlay element previews while editing * advanced audio settings * fix bug in drop-down creation * fallback defaults for the new stuff * fix textbox & overlay visibility bugs
2023-03-11 04:51:22 +00:00
if (chart.tempoChanges.Count == 0)
return chart.bpm;
float bpm = chart.bpm;
Game Overlays (#280) * add accuracy display * temp BG for show * separate overlays prefab make proper shader for star effects * aim shakiness display * implement testing skill star * fully functional skill star * separate section display from editor * fully separate chart section display from timeline * add section to overlays * fix nullreference issues * start game layout settings * add game settings script * fix nonfunctioning scoring * invert y position logic on timing bar * add perfect challenge functionality * fix section not showing up in editor add perfect challenge option * add timing display minimal mode * Update PerfectAndPractice.png * show gismo for minigame bounds in editor * add ability to disable overlays in editor * prepare medals add new timing display graphic * hide screen preview * per-axis camera control added per request * section medals basic functionality * add medal get animations * fix bug with perfect icons * visual enhancements * adjust look of timing display minmode address audio ducking issues(?) * prepare overlay lyt editor add viewport pan, rotate, scale adjust audio setting * add layout editor UI elements * dynamic overlay creation * fix default single timing disp * set up overlay settings controls * start UI events * runtime uuid for component reference * layout editor affects overlay elements * show overlay element previews while editing * advanced audio settings * fix bug in drop-down creation * fallback defaults for the new stuff * fix textbox & overlay visibility bugs
2023-03-11 04:51:22 +00:00
foreach (DynamicBeatmap.TempoChange t in chart.tempoChanges)
{
if (t.beat > beat)
{
break;
}
bpm = t.tempo;
}
return bpm;
}
public double GetSongPosFromBeat(double beat)
2021-12-31 14:46:11 +00:00
{
var chart = GameManager.instance.Beatmap;
float bpm = chart.bpm;
double counter = 0f;
float lastTempoChangeBeat = 0f;
Game Overlays (#280) * add accuracy display * temp BG for show * separate overlays prefab make proper shader for star effects * aim shakiness display * implement testing skill star * fully functional skill star * separate section display from editor * fully separate chart section display from timeline * add section to overlays * fix nullreference issues * start game layout settings * add game settings script * fix nonfunctioning scoring * invert y position logic on timing bar * add perfect challenge functionality * fix section not showing up in editor add perfect challenge option * add timing display minimal mode * Update PerfectAndPractice.png * show gismo for minigame bounds in editor * add ability to disable overlays in editor * prepare medals add new timing display graphic * hide screen preview * per-axis camera control added per request * section medals basic functionality * add medal get animations * fix bug with perfect icons * visual enhancements * adjust look of timing display minmode address audio ducking issues(?) * prepare overlay lyt editor add viewport pan, rotate, scale adjust audio setting * add layout editor UI elements * dynamic overlay creation * fix default single timing disp * set up overlay settings controls * start UI events * runtime uuid for component reference * layout editor affects overlay elements * show overlay element previews while editing * advanced audio settings * fix bug in drop-down creation * fallback defaults for the new stuff * fix textbox & overlay visibility bugs
2023-03-11 04:51:22 +00:00
foreach (DynamicBeatmap.TempoChange t in chart.tempoChanges)
{
if (t.beat > beat)
{
break;
}
counter += (t.beat - lastTempoChangeBeat) * 60/bpm;
bpm = t.tempo;
lastTempoChangeBeat = t.beat;
}
counter += (beat - lastTempoChangeBeat) * 60/bpm;
return counter;
2021-12-31 14:46:11 +00:00
}
//thank you @wooningcharithri#7419 for the psuedo-code
public double BeatsToSecs(double beats, float bpm)
{
return beats / bpm * 60f;
}
public double SecsToBeats(double s, float bpm)
{
return s / 60f * bpm;
}
public double GetBeatFromSongPos(double seconds)
{
double lastTempoChangeBeat = 0f;
double counterSeconds = -firstBeatOffset;
float lastBpm = GameManager.instance.Beatmap.bpm;
foreach (DynamicBeatmap.TempoChange t in GameManager.instance.Beatmap.tempoChanges)
{
double beatToNext = t.beat - lastTempoChangeBeat;
double secToNext = BeatsToSecs(beatToNext, lastBpm);
double nextSecs = counterSeconds + secToNext;
if (nextSecs >= seconds)
break;
lastTempoChangeBeat = t.beat;
lastBpm = t.tempo;
counterSeconds = nextSecs;
}
return lastTempoChangeBeat + SecsToBeats(seconds - counterSeconds, lastBpm);
}
//
// convert real seconds to beats
public float GetRestFromRealTime(float seconds)
{
return seconds/pitchedSecPerBeat;
}
2021-12-23 02:28:05 +00:00
public void SetBpm(float bpm)
{
this.songBpm = bpm;
secPerBeat = 60f / songBpm;
}
2022-01-06 00:11:33 +00:00
public void SetVolume(float percent)
2022-03-19 12:46:38 +00:00
{
musicSource.volume = percent / 100f;
}
2022-01-06 00:11:33 +00:00
public float SongLengthInBeats()
{
if (!musicSource.clip) return 0;
return (float) GetBeatFromSongPos(musicSource.clip.length);
2022-01-06 00:11:33 +00:00
}
2022-01-08 16:42:48 +00:00
public bool SongPosLessThanClipLength(float t)
{
2022-01-09 23:35:55 +00:00
if (musicSource.clip != null)
return t < musicSource.clip.length;
else
return false;
2022-01-08 16:42:48 +00:00
}
2022-01-11 00:17:29 +00:00
public bool SongPosLessThanClipLength(double t)
{
if (musicSource.clip != null)
return t < musicSource.clip.length;
else
return false;
}
2022-01-11 00:17:29 +00:00
public bool NotStopped()
{
return Conductor.instance.isPlaying == true || Conductor.instance.isPaused == true;
2022-01-11 00:17:29 +00:00
}
2021-12-19 04:10:43 +00:00
}
}