2022-09-23 02:05:04 +00:00
|
|
|
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
|
|
|
|
{
|
2022-01-07 11:36:23 +00:00
|
|
|
// 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
|
|
|
|
2022-01-07 11:36:23 +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
|
|
|
|
2022-03-22 18:29:15 +00:00
|
|
|
// The number of seconds for each song beat, inversely scaled to song pitch (higer pitch = shorter time)
|
|
|
|
public float pitchedSecPerBeat => (secPerBeat / musicSource.pitch);
|
|
|
|
|
2022-01-07 11:36:23 +00:00
|
|
|
// Current song position, in seconds
|
2022-09-23 02:05:04 +00:00
|
|
|
private double songPos; // for Conductor use only
|
|
|
|
public float songPosition => (float) songPos;
|
2023-01-05 04:04:31 +00:00
|
|
|
public double songPositionAsDouble => songPos;
|
2021-12-19 04:10:43 +00:00
|
|
|
|
2022-01-07 11:36:23 +00:00
|
|
|
// Current song position, in beats
|
2022-09-23 02:05:04 +00:00
|
|
|
private double songPosBeat; // for Conductor use only
|
|
|
|
public float songPositionInBeats => (float) songPosBeat;
|
2023-01-05 04:04:31 +00:00
|
|
|
public double songPositionInBeatsAsDouble => songPosBeat;
|
2021-12-19 04:10:43 +00:00
|
|
|
|
2022-01-07 11:36:23 +00:00
|
|
|
// Current time of the song
|
2022-09-23 02:05:04 +00:00
|
|
|
private double time;
|
2021-12-19 04:10:43 +00:00
|
|
|
|
2022-09-18 20:48:14 +00:00
|
|
|
double lastAbsTime;
|
|
|
|
|
2023-01-05 04:04:31 +00:00
|
|
|
// the dspTime we started at
|
|
|
|
private double dspStartTime;
|
|
|
|
public double dspStartTimeAsDouble => dspStartTime;
|
|
|
|
|
2023-01-12 01:42:12 +00:00
|
|
|
//the beat we started at
|
|
|
|
private double startBeat;
|
|
|
|
public double startBeatAsDouble => startBeat;
|
|
|
|
|
2022-01-07 11:36:23 +00:00
|
|
|
// an AudioSource attached to this GameObject that will play the music.
|
|
|
|
public AudioSource musicSource;
|
2021-12-19 04:10:43 +00:00
|
|
|
|
2022-01-07 11:36:23 +00:00
|
|
|
// The offset to the first beat of the song in seconds
|
|
|
|
public float firstBeatOffset;
|
2021-12-19 04:10:43 +00:00
|
|
|
|
2022-01-07 11:36:23 +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
|
2022-01-07 11:36:23 +00:00
|
|
|
public bool isPlaying;
|
2022-01-15 17:45:08 +00:00
|
|
|
|
|
|
|
// Conductor is currently paused, but not fully stopped
|
2022-01-07 11:36:23 +00:00
|
|
|
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;
|
2023-01-05 04:04:31 +00:00
|
|
|
Util.Sound metronomeSound;
|
2022-01-15 17:45:08 +00:00
|
|
|
|
2022-09-23 02:05:04 +00:00
|
|
|
public float timeSinceLastTempoChange = Single.MinValue;
|
2022-01-28 02:50:57 +00:00
|
|
|
|
2022-01-19 05:40:49 +00:00
|
|
|
private bool beat;
|
|
|
|
|
2022-01-07 11:36:23 +00:00
|
|
|
// private AudioDspTimeKeeper timeKeeper;
|
2021-12-28 02:36:27 +00:00
|
|
|
|
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)
|
2022-01-07 11:36:23 +00:00
|
|
|
{
|
2022-09-23 02:05:04 +00:00
|
|
|
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-07 23:51:08 +00:00
|
|
|
|
2022-01-08 16:42:48 +00:00
|
|
|
GameManager.instance.SetCurrentEventToClosest(beat);
|
2022-02-08 01:07:03 +00:00
|
|
|
songPosBeat = beat;
|
2022-01-07 23:51:08 +00:00
|
|
|
}
|
|
|
|
|
2022-01-08 16:42:48 +00:00
|
|
|
public void Play(float beat)
|
2022-01-07 23:51:08 +00:00
|
|
|
{
|
2022-09-23 02:05:04 +00:00
|
|
|
GameManager.instance.SortEventsList();
|
2022-02-25 00:09:14 +00:00
|
|
|
bool negativeOffset = firstBeatOffset < 0f;
|
|
|
|
bool negativeStartTime = false;
|
|
|
|
|
2022-06-06 16:54:57 +00:00
|
|
|
// Debug.Log("starting playback @ beat " + beat + ", offset is " + firstBeatOffset);
|
|
|
|
|
2022-02-24 14:02:21 +00:00
|
|
|
var startPos = GetSongPosFromBeat(beat);
|
2022-02-25 00:09:14 +00:00
|
|
|
if (negativeOffset)
|
|
|
|
{
|
|
|
|
time = startPos;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
negativeStartTime = startPos - firstBeatOffset < 0f;
|
|
|
|
|
|
|
|
if (negativeStartTime)
|
|
|
|
time = startPos - firstBeatOffset;
|
|
|
|
else
|
|
|
|
time = startPos;
|
|
|
|
}
|
2022-06-06 16:54:57 +00:00
|
|
|
|
|
|
|
//TODO: make this take into account past tempo changes
|
|
|
|
songPosBeat = GetBeatFromSongPos(time - firstBeatOffset);
|
|
|
|
// Debug.Log("corrected starting playback @ beat " + songPosBeat);
|
2022-01-07 11:36:23 +00:00
|
|
|
|
|
|
|
isPlaying = true;
|
|
|
|
isPaused = false;
|
|
|
|
|
2022-02-24 14:02:21 +00:00
|
|
|
if (SongPosLessThanClipLength(startPos))
|
2022-01-08 16:42:48 +00:00
|
|
|
{
|
2022-02-25 00:09:14 +00:00
|
|
|
if (negativeOffset)
|
|
|
|
{
|
|
|
|
var musicStartTime = startPos + firstBeatOffset;
|
|
|
|
|
|
|
|
if (musicStartTime < 0f)
|
|
|
|
{
|
2022-09-23 02:05:04 +00:00
|
|
|
musicSource.time = (float) startPos;
|
2022-02-25 04:37:04 +00:00
|
|
|
musicSource.PlayScheduled(AudioSettings.dspTime - firstBeatOffset / musicSource.pitch);
|
2022-02-25 00:09:14 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-09-23 02:05:04 +00:00
|
|
|
musicSource.time = (float) musicStartTime;
|
2022-02-25 00:09:14 +00:00
|
|
|
musicSource.PlayScheduled(AudioSettings.dspTime);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (negativeStartTime)
|
|
|
|
{
|
2022-09-23 02:05:04 +00:00
|
|
|
musicSource.time = (float) startPos;
|
2022-02-25 00:09:14 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-09-23 02:05:04 +00:00
|
|
|
musicSource.time = (float) startPos + firstBeatOffset;
|
2022-02-25 00:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
musicSource.PlayScheduled(AudioSettings.dspTime);
|
|
|
|
}
|
2022-01-08 16:42:48 +00:00
|
|
|
}
|
2022-09-18 20:48:14 +00:00
|
|
|
lastAbsTime = Time.realtimeSinceStartupAsDouble;
|
2023-01-05 04:04:31 +00:00
|
|
|
dspStartTime = AudioSettings.dspTime;
|
2023-01-12 01:42:12 +00:00
|
|
|
startBeat = beat;
|
2022-01-08 16:42:48 +00:00
|
|
|
|
2022-01-14 02:33:51 +00:00
|
|
|
// GameManager.instance.SetCurrentEventToClosest(songPositionInBeats);
|
2021-12-21 01:10:49 +00:00
|
|
|
}
|
2021-12-19 04:10:43 +00:00
|
|
|
|
2022-01-07 11:36:23 +00:00
|
|
|
public void Pause()
|
2021-12-28 02:36:27 +00:00
|
|
|
{
|
2022-01-07 11:36:23 +00:00
|
|
|
isPlaying = false;
|
|
|
|
isPaused = true;
|
|
|
|
|
|
|
|
musicSource.Pause();
|
|
|
|
}
|
|
|
|
|
2022-01-08 16:42:48 +00:00
|
|
|
public void Stop(float time)
|
2022-01-07 11:36:23 +00:00
|
|
|
{
|
2022-01-08 16:42:48 +00:00
|
|
|
this.time = time;
|
2022-02-02 01:11:42 +00:00
|
|
|
|
|
|
|
songPosBeat = 0;
|
|
|
|
|
2022-01-07 11:36:23 +00:00
|
|
|
isPlaying = false;
|
|
|
|
isPaused = false;
|
|
|
|
|
|
|
|
musicSource.Stop();
|
2021-12-31 14:46:11 +00:00
|
|
|
}
|
2022-01-28 02:50:57 +00:00
|
|
|
float test;
|
2021-12-31 14:46:11 +00:00
|
|
|
|
2021-12-27 04:48:39 +00:00
|
|
|
public void Update()
|
2021-12-19 04:10:43 +00:00
|
|
|
{
|
2022-01-07 11:36:23 +00:00
|
|
|
if (isPlaying)
|
|
|
|
{
|
2022-09-18 20:48:14 +00:00
|
|
|
double absTime = Time.realtimeSinceStartupAsDouble;
|
2022-09-23 02:05:04 +00:00
|
|
|
double dt = (absTime - lastAbsTime) * musicSource.pitch;
|
2022-09-18 20:48:14 +00:00
|
|
|
lastAbsTime = absTime;
|
2022-02-24 14:02:21 +00:00
|
|
|
|
|
|
|
time += dt;
|
2021-12-19 04:10:43 +00:00
|
|
|
|
2022-02-02 01:11:42 +00:00
|
|
|
songPos = time;
|
2021-12-19 04:10:43 +00:00
|
|
|
|
2022-09-23 02:05:04 +00:00
|
|
|
songPosBeat = GetBeatFromSongPos(songPos - firstBeatOffset);
|
|
|
|
}
|
|
|
|
}
|
2022-01-15 17:45:08 +00:00
|
|
|
|
2022-09-23 02:05:04 +00:00
|
|
|
public void LateUpdate()
|
|
|
|
{
|
2023-01-12 01:42:12 +00:00
|
|
|
if (metronome && isPlaying)
|
2022-09-23 02:05:04 +00:00
|
|
|
{
|
|
|
|
if (ReportBeat(ref lastReportedBeat))
|
2022-01-15 17:45:08 +00:00
|
|
|
{
|
2023-01-14 04:53:25 +00:00
|
|
|
metronomeSound = Util.Jukebox.PlayOneShot("metronome", lastReportedBeat);
|
2022-09-23 02:05:04 +00:00
|
|
|
}
|
|
|
|
else if (songPositionInBeats < lastReportedBeat)
|
|
|
|
{
|
|
|
|
lastReportedBeat = Mathf.Round(songPositionInBeats);
|
2022-01-15 17:45:08 +00:00
|
|
|
}
|
2022-01-07 11:36:23 +00:00
|
|
|
}
|
2023-01-05 04:04:31 +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
|
|
|
|
2022-06-09 03:35:15 +00:00
|
|
|
public bool ReportBeat(ref float lastReportedBeat, float offset = 0, bool shiftBeatToOffset = true)
|
2022-01-19 05:40:49 +00:00
|
|
|
{
|
2022-06-09 03:35:15 +00:00
|
|
|
bool result = songPositionInBeats + (shiftBeatToOffset ? offset : 0f) >= (lastReportedBeat) + 1f;
|
|
|
|
if (result)
|
2022-01-19 05:40:49 +00:00
|
|
|
{
|
2022-06-09 03:35:15 +00:00
|
|
|
lastReportedBeat += 1f;
|
2022-06-09 06:46:51 +00:00
|
|
|
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
|
|
|
{
|
2021-12-27 04:48:39 +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);
|
|
|
|
}
|
|
|
|
|
2022-02-10 11:59:20 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-08-21 23:46:45 +00:00
|
|
|
private List<DynamicBeatmap.TempoChange> GetSortedTempoChanges(DynamicBeatmap chart)
|
|
|
|
{
|
2022-09-23 02:05:04 +00:00
|
|
|
GameManager.instance.SortEventsList();
|
|
|
|
return GameManager.instance.Beatmap.tempoChanges;
|
2022-08-21 23:46:45 +00:00
|
|
|
}
|
|
|
|
|
2023-01-12 01:42:12 +00:00
|
|
|
public double GetSongPosFromBeat(double beat)
|
2021-12-31 14:46:11 +00:00
|
|
|
{
|
2022-08-21 23:46:45 +00:00
|
|
|
var chart = GameManager.instance.Beatmap;
|
2023-01-14 04:53:25 +00:00
|
|
|
float bpm = chart.bpm;
|
2022-06-06 16:54:57 +00:00
|
|
|
|
2022-09-23 02:05:04 +00:00
|
|
|
double counter = 0f;
|
2022-06-06 16:54:57 +00:00
|
|
|
|
|
|
|
float lastTempoChangeBeat = 0f;
|
|
|
|
|
2023-01-14 04:53:25 +00:00
|
|
|
foreach (DynamicBeatmap.TempoChange t in GameManager.instance.Beatmap.tempoChanges)
|
2022-06-06 16:54:57 +00:00
|
|
|
{
|
|
|
|
if (t.beat > beat)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2023-01-14 04:53:25 +00:00
|
|
|
counter += (t.beat - lastTempoChangeBeat) * 60/bpm;
|
|
|
|
bpm = t.tempo;
|
2022-06-06 16:54:57 +00:00
|
|
|
lastTempoChangeBeat = t.beat;
|
|
|
|
}
|
|
|
|
|
2023-01-14 04:53:25 +00:00
|
|
|
counter += (beat - lastTempoChangeBeat) * 60/bpm;
|
2022-06-06 16:54:57 +00:00
|
|
|
|
|
|
|
return counter;
|
2021-12-31 14:46:11 +00:00
|
|
|
}
|
|
|
|
|
2022-06-06 16:54:57 +00:00
|
|
|
//thank you @wooningcharithri#7419 for the psuedo-code
|
2022-09-23 02:05:04 +00:00
|
|
|
private double BeatsToSecs(double beats, float bpm)
|
2022-06-06 16:54:57 +00:00
|
|
|
{
|
|
|
|
return beats / bpm * 60f;
|
|
|
|
}
|
2022-09-23 02:05:04 +00:00
|
|
|
private double SecsToBeats(double s, float bpm)
|
2022-06-06 16:54:57 +00:00
|
|
|
{
|
|
|
|
return s / 60f * bpm;
|
|
|
|
}
|
|
|
|
|
2022-09-23 02:05:04 +00:00
|
|
|
public double GetBeatFromSongPos(double seconds)
|
2022-06-06 16:54:57 +00:00
|
|
|
{
|
2022-09-23 02:05:04 +00:00
|
|
|
double lastTempoChangeBeat = 0f;
|
|
|
|
double counterSeconds = -firstBeatOffset;
|
|
|
|
float lastBpm = GameManager.instance.Beatmap.bpm;
|
2022-06-06 16:54:57 +00:00
|
|
|
|
2023-01-14 04:53:25 +00:00
|
|
|
foreach (DynamicBeatmap.TempoChange t in GameManager.instance.Beatmap.tempoChanges)
|
2022-06-06 16:54:57 +00:00
|
|
|
{
|
2022-09-23 02:05:04 +00:00
|
|
|
double beatToNext = t.beat - lastTempoChangeBeat;
|
|
|
|
double secToNext = BeatsToSecs(beatToNext, lastBpm);
|
|
|
|
double nextSecs = counterSeconds + secToNext;
|
2022-06-06 16:54:57 +00:00
|
|
|
|
|
|
|
if (nextSecs >= seconds)
|
|
|
|
break;
|
|
|
|
|
|
|
|
lastTempoChangeBeat = t.beat;
|
|
|
|
lastBpm = t.tempo;
|
|
|
|
counterSeconds = nextSecs;
|
|
|
|
}
|
|
|
|
return lastTempoChangeBeat + SecsToBeats(seconds - counterSeconds, lastBpm);
|
|
|
|
}
|
|
|
|
//
|
|
|
|
|
2022-03-20 23:46:12 +00:00
|
|
|
// convert real seconds to beats
|
|
|
|
public float GetRestFromRealTime(float seconds)
|
|
|
|
{
|
2022-03-22 18:29:15 +00:00
|
|
|
return seconds/pitchedSecPerBeat;
|
2022-03-20 23:46:12 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2022-09-18 20:48:14 +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()
|
|
|
|
{
|
2022-01-07 11:36:23 +00:00
|
|
|
if (!musicSource.clip) return 0;
|
2022-09-23 02:05:04 +00:00
|
|
|
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
|
|
|
|
2022-09-23 02:05:04 +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()
|
|
|
|
{
|
2022-01-17 02:31:49 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|