using System.Collections; using System.Collections.Generic; using UnityEngine; namespace SynapticPro { /// /// Component that controls the adaptive music system /// Automatically manages intro→loop, crossfade transitions, beat synchronization, etc. /// public class AdaptiveMusicController : MonoBehaviour { [System.Serializable] public class MusicSegment { public string name; public AudioSource audioSource; public float startTime; public float endTime; public float loopPoint; public bool isLoop; public float fadeInDuration = 0f; public float fadeOutDuration = 0f; public List transitions = new List(); } [System.Serializable] public class Transition { public string toSegment; public TransitionType type = TransitionType.Crossfade; public float duration = 1f; } public enum TransitionType { Immediate, Crossfade, OnBeat, OnBar } [Header("Music Settings")] public float bpm = 120f; public int beatsPerBar = 4; public bool playOnStart = true; [Header("Segments")] public List segments = new List(); private MusicSegment currentSegment; private MusicSegment nextSegment; private float currentTime; private bool isTransitioning; private Coroutine musicCoroutine; void Start() { InitializeSegments(); if (playOnStart) { PlayFromBeginning(); } } void InitializeSegments() { // Automatically detect AudioSource from child objects if (segments.Count == 0) { AudioSource[] sources = GetComponentsInChildren(); foreach (var source in sources) { var segment = new MusicSegment { name = source.gameObject.name, audioSource = source, isLoop = source.loop }; // Infer settings from segment name if (segment.name.ToLower().Contains("intro")) { segment.isLoop = false; segment.transitions.Add(new Transition { toSegment = "MainLoop", type = TransitionType.Crossfade, duration = 1.5f }); } else if (segment.name.ToLower().Contains("mainloop") || segment.name.ToLower().Contains("loop")) { segment.isLoop = true; } segments.Add(segment); } } // Set end time from AudioClip length foreach (var segment in segments) { if (segment.audioSource && segment.audioSource.clip) { if (segment.endTime <= 0) { segment.endTime = segment.audioSource.clip.length; } } } } public void PlayFromBeginning() { if (musicCoroutine != null) { StopCoroutine(musicCoroutine); } musicCoroutine = StartCoroutine(MusicPlaybackCoroutine()); } IEnumerator MusicPlaybackCoroutine() { // Find intro segment var introSegment = segments.Find(s => s.name.ToLower().Contains("intro")); if (introSegment == null && segments.Count > 0) { introSegment = segments[0]; } if (introSegment == null) { Debug.LogWarning("No music segments found!"); yield break; } // Play intro currentSegment = introSegment; PlaySegment(currentSegment); while (true) { currentTime = currentSegment.audioSource.time; // Loop when reaching loop point if (currentSegment.isLoop && currentSegment.loopPoint > 0 && currentTime >= currentSegment.loopPoint) { currentSegment.audioSource.time = currentSegment.startTime; } // Check transitions if (!isTransitioning) { // Check for automatic transition if (!currentSegment.isLoop && currentTime >= currentSegment.endTime - 2f) { // Find transition to next segment if (currentSegment.transitions.Count > 0) { var transition = currentSegment.transitions[0]; var next = segments.Find(s => s.name == transition.toSegment); if (next != null) { StartCoroutine(TransitionToSegment(next, transition)); } } } } yield return null; } } void PlaySegment(MusicSegment segment) { if (segment.audioSource == null) return; segment.audioSource.time = segment.startTime; segment.audioSource.Play(); if (segment.fadeInDuration > 0) { StartCoroutine(FadeIn(segment.audioSource, segment.fadeInDuration)); } else { segment.audioSource.volume = 1f; } } IEnumerator TransitionToSegment(MusicSegment targetSegment, Transition transition) { if (isTransitioning) yield break; isTransitioning = true; nextSegment = targetSegment; switch (transition.type) { case TransitionType.Immediate: currentSegment.audioSource.Stop(); currentSegment = targetSegment; PlaySegment(currentSegment); break; case TransitionType.Crossfade: // Start next segment PlaySegment(targetSegment); targetSegment.audioSource.volume = 0; // Crossfade float elapsed = 0; while (elapsed < transition.duration) { elapsed += Time.deltaTime; float t = elapsed / transition.duration; currentSegment.audioSource.volume = 1 - t; targetSegment.audioSource.volume = t; yield return null; } currentSegment.audioSource.Stop(); currentSegment = targetSegment; break; case TransitionType.OnBeat: case TransitionType.OnBar: // Wait until end of beat/bar float beatDuration = 60f / bpm; float barDuration = beatDuration * beatsPerBar; float waitTime = transition.type == TransitionType.OnBeat ? beatDuration : barDuration; // Wait for next beat/bar float timeToWait = waitTime - (currentSegment.audioSource.time % waitTime); yield return new WaitForSeconds(timeToWait); // Crossfade transition StartCoroutine(TransitionToSegment(targetSegment, new Transition { toSegment = targetSegment.name, type = TransitionType.Crossfade, duration = transition.duration })); yield break; } isTransitioning = false; } IEnumerator FadeIn(AudioSource source, float duration) { float elapsed = 0; source.volume = 0; while (elapsed < duration) { elapsed += Time.deltaTime; source.volume = elapsed / duration; yield return null; } source.volume = 1f; } IEnumerator FadeOut(AudioSource source, float duration) { float elapsed = 0; float startVolume = source.volume; while (elapsed < duration) { elapsed += Time.deltaTime; source.volume = startVolume * (1 - elapsed / duration); yield return null; } source.volume = 0; source.Stop(); } /// /// Transition to specific segment /// public void TransitionTo(string segmentName, float transitionDuration = 2f) { var targetSegment = segments.Find(s => s.name == segmentName); if (targetSegment != null && !isTransitioning) { StartCoroutine(TransitionToSegment(targetSegment, new Transition { toSegment = segmentName, type = TransitionType.Crossfade, duration = transitionDuration })); } } /// /// Stop music /// public void StopMusic(float fadeOutDuration = 1f) { if (musicCoroutine != null) { StopCoroutine(musicCoroutine); musicCoroutine = null; } foreach (var segment in segments) { if (segment.audioSource && segment.audioSource.isPlaying) { if (fadeOutDuration > 0) { StartCoroutine(FadeOut(segment.audioSource, fadeOutDuration)); } else { segment.audioSource.Stop(); } } } } } }