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
parent 2d289376e9
commit 25cae2aff9
7 changed files with 80 additions and 31 deletions

4
.gitignore vendored
View file

@ -83,4 +83,6 @@ crashlytics-build.properties
# Built AssetBundles
/[Aa]ssets/[Ss]treamingAssets/*/*
/[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

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,7 @@
"com.unity.nuget.newtonsoft-json": "3.2.1",
"jillejr.newtonsoft.json-for-unity.converters": "1.5.1"
},
"hash": "2fb235a6da261fb1ae60112f9e9ba0d924e96bb8"
"hash": "ac56f4d95417af5dbedf20f3b8d79b9f67c72659"
},
"com.unity.2d.sprite": {
"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:
- 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 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.