Sound Scheduling Improvements (#491)

* port conductor adjustments

* scheduled sounds prebake

* allow aiff files to be imported

add vbr mp3 warning to readme

* improve wording
This commit is contained in:
minenice55 2023-06-24 22:32:08 -04:00 committed by GitHub
parent 8ec054749e
commit dde1a3a287
7 changed files with 80 additions and 31 deletions

2
.gitignore vendored
View file

@ -84,3 +84,5 @@ crashlytics-build.properties
/[Aa]ssets/[Ss]treamingAssets/*/* /[Aa]ssets/[Ss]treamingAssets/*/*
/[Aa]ssets/[Ss]treamingAssets/*.manifest /[Aa]ssets/[Ss]treamingAssets/*.manifest
/[Aa]ssets/[Ss]treamingAssets/*.meta /[Aa]ssets/[Ss]treamingAssets/*.meta
/[Aa]ssets/[Ss]treamingAssets/[Ss]treamingAssets
Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF - Fallback.asset

View file

@ -89,26 +89,32 @@ namespace HeavenStudio
musicSource.pitch = SongPitch; musicSource.pitch = SongPitch;
} }
void Awake() void Awake()
{ {
instance = this; instance = this;
} }
public void SetBeat(double beat) void Start()
{ {
double secFromBeat = GetSongPosFromBeat(beat); musicSource.priority = 0;
if (musicSource.clip != null)
{
if (secFromBeat < musicSource.clip.length)
musicSource.time = (float) secFromBeat;
else
musicSource.time = 0;
} }
GameManager.instance.SetCurrentEventToClosest((float) beat); public void SetBeat(double beat)
songPosBeat = beat; {
var chart = GameManager.instance.Beatmap;
double offset = chart.data.offset;
double startPos = GetSongPosFromBeat(beat);
double dspTime = AudioSettings.dspTime;
time = startPos;
firstBeatOffset = offset;
SeekMusicToTime(startPos);
songPosBeat = GetBeatFromSongPos(time);
GameManager.instance.SetCurrentEventToClosest(beat);
} }
public void Play(double beat) public void Play(double beat)
@ -116,35 +122,35 @@ namespace HeavenStudio
if (isPlaying) return; if (isPlaying) return;
var chart = GameManager.instance.Beatmap; var chart = GameManager.instance.Beatmap;
double offset = chart.data.offset; double offset = chart.data.offset;
bool negativeOffset = offset < 0;
double dspTime = AudioSettings.dspTime; double dspTime = AudioSettings.dspTime;
GameManager.instance.SortEventsList(); GameManager.instance.SortEventsList();
double startPos = GetSongPosFromBeat(beat); double startPos = GetSongPosFromBeat(beat);
firstBeatOffset = offset;
time = startPos; time = startPos;
if (musicSource.clip != null && startPos < musicSource.clip.length - offset) if (musicSource.clip != null && startPos < musicSource.clip.length - offset)
{ {
// https://www.desmos.com/calculator/81ywfok6xk SeekMusicToTime(startPos);
double musicStartDelay = -offset - startPos; double musicStartDelay = -offset - startPos;
if (musicStartDelay > 0) if (musicStartDelay > 0)
{ {
musicSource.time = 0;
// this can break if the user changes pitch before the audio starts playing
musicScheduledTime = dspTime + musicStartDelay / SongPitch; musicScheduledTime = dspTime + musicStartDelay / SongPitch;
musicScheduledPitch = SongPitch; musicScheduledPitch = SongPitch;
musicSource.PlayScheduled(musicScheduledTime); musicSource.PlayScheduled(musicScheduledTime);
} }
else else
{ {
musicSource.time = (float)-musicStartDelay; musicScheduledTime = dspTime;
musicSource.PlayScheduled(dspTime); musicScheduledPitch = SongPitch;
musicSource.Play();
} }
} }
songPosBeat = GetBeatFromSongPos(time); songPosBeat = GetBeatFromSongPos(time);
startTime = DateTime.Now; startTime = DateTime.Now;
lastAbsTime = (DateTime.Now - startTime).TotalSeconds; lastAbsTime = 0;
lastDspTime = AudioSettings.dspTime; lastDspTime = AudioSettings.dspTime;
dspStart = dspTime; dspStart = dspTime;
startBeat = songPosBeat; startBeat = songPosBeat;
@ -175,6 +181,26 @@ namespace HeavenStudio
musicSource.Stop(); musicSource.Stop();
} }
void SeekMusicToTime(double startPos)
{
double offset = GameManager.instance.Beatmap.data.offset;
if (musicSource.clip != null && startPos < musicSource.clip.length - offset)
{
// https://www.desmos.com/calculator/81ywfok6xk
double musicStartDelay = -offset - startPos;
if (musicStartDelay > 0)
{
musicSource.timeSamples = 0;
}
else
{
int freq = musicSource.clip.frequency;
int samples = (int)(freq * (startPos + offset));
musicSource.timeSamples = samples;
}
}
}
double deltaTimeReal { get { double deltaTimeReal { get {
double ret = absTime - lastAbsTime; double ret = absTime - lastAbsTime;
lastAbsTime = absTime; lastAbsTime = absTime;
@ -217,7 +243,7 @@ namespace HeavenStudio
time += dt * SongPitch; time += dt * SongPitch;
songPos = time; songPos = time;
songPosBeat = GetBeatFromSongPos(songPos - firstBeatOffset); songPosBeat = GetBeatFromSongPos(songPos);
} }
} }
@ -349,7 +375,7 @@ namespace HeavenStudio
public double GetBeatFromSongPos(double seconds) public double GetBeatFromSongPos(double seconds)
{ {
double lastTempoChangeBeat = 0f; double lastTempoChangeBeat = 0f;
double counterSeconds = -firstBeatOffset; double counterSeconds = 0;
float lastBpm = 120f; float lastBpm = 120f;
foreach (RiqEntity t in GameManager.instance.Beatmap.TempoChanges) foreach (RiqEntity t in GameManager.instance.Beatmap.TempoChanges)

View file

@ -250,7 +250,7 @@ namespace HeavenStudio.Editor
{ {
var extensions = new[] var extensions = new[]
{ {
new ExtensionFilter("Music Files", "mp3", "ogg", "wav") new ExtensionFilter("Music Files", "mp3", "ogg", "wav", "aiff", "aif", "aifc")
}; };
#if UNITY_STANDALONE_WINDOWS #if UNITY_STANDALONE_WINDOWS

View file

@ -33,7 +33,9 @@ namespace HeavenStudio.Util
bool playInstant = false; bool playInstant = false;
bool played = false; bool played = false;
bool queued = false;
const double PREBAKE_TIME = 0.5;
private void Start() private void Start()
{ {
audioSource = GetComponent<AudioSource>(); audioSource = GetComponent<AudioSource>();
@ -56,18 +58,29 @@ namespace HeavenStudio.Util
playInstant = false; playInstant = false;
scheduledPitch = cnd.SongPitch; scheduledPitch = cnd.SongPitch;
startTime = (AudioSettings.dspTime + (cnd.GetSongPosFromBeat(beat) - cnd.songPositionAsDouble)/(double)scheduledPitch) - offset; startTime = (AudioSettings.dspTime + (cnd.GetSongPosFromBeat(beat) - cnd.songPositionAsDouble)/(double)scheduledPitch) - offset;
if (scheduledPitch != 0 && AudioSettings.dspTime >= startTime)
{
audioSource.PlayScheduled(startTime); audioSource.PlayScheduled(startTime);
queued = true;
}
} }
} }
private void Update() private void Update()
{ {
Conductor cnd = Conductor.instance; Conductor cnd = Conductor.instance;
double dspTime = AudioSettings.dspTime;
if (!played) if (!played)
{ {
if (scheduled) if (scheduled)
{ {
if (scheduledPitch != 0 && AudioSettings.dspTime > scheduledTime) if (!queued && dspTime > scheduledTime - PREBAKE_TIME)
{
audioSource.PlayScheduled(scheduledTime);
queued = true;
}
if (scheduledPitch != 0 && dspTime > scheduledTime)
{ {
StartCoroutine(NotRelyOnBeatSound()); StartCoroutine(NotRelyOnBeatSound());
played = true; played = true;
@ -75,7 +88,13 @@ namespace HeavenStudio.Util
} }
else if (!playInstant) else if (!playInstant)
{ {
if (scheduledPitch != 0 && AudioSettings.dspTime > startTime) if (!queued && dspTime > startTime - PREBAKE_TIME)
{
startTime = (dspTime + (cnd.GetSongPosFromBeat(beat) - cnd.songPositionAsDouble)/(double)scheduledPitch) - offset;
audioSource.PlayScheduled(startTime);
queued = true;
}
if (scheduledPitch != 0 && dspTime > startTime)
{ {
played = true; played = true;
StartCoroutine(NotRelyOnBeatSound()); StartCoroutine(NotRelyOnBeatSound());
@ -87,6 +106,7 @@ namespace HeavenStudio.Util
if (cnd.SongPitch == 0) if (cnd.SongPitch == 0)
{ {
scheduledPitch = cnd.SongPitch; scheduledPitch = cnd.SongPitch;
if (queued)
audioSource.Pause(); audioSource.Pause();
} }
else else
@ -96,7 +116,8 @@ namespace HeavenStudio.Util
audioSource.UnPause(); audioSource.UnPause();
} }
scheduledPitch = cnd.SongPitch; scheduledPitch = cnd.SongPitch;
startTime = (AudioSettings.dspTime + (cnd.GetSongPosFromBeat(beat) - cnd.songPositionAsDouble)/(double)scheduledPitch); startTime = (dspTime + (cnd.GetSongPosFromBeat(beat) - cnd.songPositionAsDouble)/(double)scheduledPitch);
if (queued)
audioSource.SetScheduledStartTime(startTime); audioSource.SetScheduledStartTime(startTime);
} }
} }
@ -121,7 +142,7 @@ namespace HeavenStudio.Util
{ {
if (!looping) // Looping sounds are destroyed manually. if (!looping) // Looping sounds are destroyed manually.
{ {
yield return new WaitForSeconds(clip.length / pitch); yield return new WaitUntil(() => !audioSource.isPlaying);
Delete(); Delete();
} }
} }

View file

@ -235,7 +235,6 @@ namespace HeavenStudio.Util
snd.scheduled = true; snd.scheduled = true;
snd.scheduledTime = targetTime; snd.scheduledTime = targetTime;
audioSource.PlayScheduled(targetTime);
GameManager.instance.SoundObjects.Add(oneShot); GameManager.instance.SoundObjects.Add(oneShot);

View file

@ -8,7 +8,7 @@
"com.unity.nuget.newtonsoft-json": "3.2.1", "com.unity.nuget.newtonsoft-json": "3.2.1",
"jillejr.newtonsoft.json-for-unity.converters": "1.5.1" "jillejr.newtonsoft.json-for-unity.converters": "1.5.1"
}, },
"hash": "2fb235a6da261fb1ae60112f9e9ba0d924e96bb8" "hash": "ac56f4d95417af5dbedf20f3b8d79b9f67c72659"
}, },
"com.unity.2d.sprite": { "com.unity.2d.sprite": {
"version": "1.0.0", "version": "1.0.0",

View file

@ -39,6 +39,7 @@ These builds include experimental new features that will not be included in Rele
#### Important Notes: #### Important Notes:
- MP3 audio with variable bitrate encoding may [desync when seeking in the editor](https://github.com/RHeavenStudio/HeavenStudio/issues/490). Either use MP3s with constant bitrate encoding or use one of our other supported formats (OGG Vorbis, WAV...)
- On MacOS and Linux builds you may [experience bugs with audio-related tasks](https://github.com/RHeavenStudio/HeavenStudio/issues/72), but in most cases Heaven Studio works perfectly. - On MacOS and Linux builds you may [experience bugs with audio-related tasks](https://github.com/RHeavenStudio/HeavenStudio/issues/72), but in most cases Heaven Studio works perfectly.
- On MacOS you'll need to have Discord open in the background for now, there's a bug that causes the DiscordSDK library to crash when the rich presence is updated while Discord is not open in the background. - On MacOS you'll need to have Discord open in the background for now, there's a bug that causes the DiscordSDK library to crash when the rich presence is updated while Discord is not open in the background.