/// <summary> /// Section 재생 준비 /// </summary> public void StartSection(Data.Section section, BeatSyncClock clock, TransitionType ttype, double startTime = 0) { EndSection(); // 이전에 재생중이었을 수도 있는 섹션을 강제 종료 // 초기화 m_sectionData = section; m_clock = clock; m_startTransition = ttype; m_endTransition = TransitionType.None; isOnTransition = false; // 텐션 관련 설정 for (int i = 0; i < Data.Section.c_maxLayerPerSection; i++) { var layer = section.GetLayer(i); if (layer != null) { layer.SetAutomationTarget(m_tensionAutomationTargets[i]); } } section.tension = m_tension; if (startTime == 0) // 시작 시간이 지정되지 않은 경우 다음 안전한 비트를 시작 시간으로 { startTime = m_clock.CalcNextSafeBeatTime(); } m_updateCo = m_context.StartCoroutine(UpdateStateCo(startTime)); // 코루틴 시작 }
/// <summary> /// Track을 새로 지정 /// </summary> /// <param name="track"></param> /// <param name="clockToSync">기존에 존재하는 clock과 싱크를 맞춰야할 경우 지정</param> public void SetTrack(Track track, BeatSyncClock clockToSync = null) { m_track = track; m_sectionIdx = -1; m_suppressProgress = false; if (clockToSync == null) // 클럭이 지정되지 않은 경우, 새로 생성 { m_clock = new BeatSyncClock(track.BPM); } else if (clockToSync.BPM != track.BPM) // 기존 클럭과 bpm이 다른 경우, 기존 클럭의 다음번 비트와 동기화하여 새로 클럭 생성 { m_clock = new BeatSyncClock(track.BPM, clockToSync.CalcNextSafeBeatTime()); } else { // 아니면 기존 클럭 그대로 사용 m_clock = clockToSync; } }
/// <summary> /// 업데이트 코루틴 /// </summary> /// <returns></returns> IEnumerator UpdateStateCo(double startTime) { var curplayer = currentPlayerComponent; var beatTime = m_clock.SecondPerBeat; // *** 재생하기 전 m_state = State.BeforePlay; SetSectionToCurrentPlayer(); // 각 layer정보 세팅 //m_sectionAudioLength = curplayer.fullAudioLength; // 전체 오디오 길이 구하기 while (!curplayer.readyToPlay) // 로딩 대기 { yield return(null); } // 각종 시간값 계산 var timePlay = startTime; var timeFillIn = timePlay + beatTime * m_sectionData.beatFillIn; var timeLoopStart = timePlay + beatTime * m_sectionData.beatStart; m_loopLengthDspTime = beatTime * m_sectionData.beatLoopLength; var timeNextLoopEnd = timeLoopStart + m_loopLengthDspTime; // 현재 재생 종류, section 세팅값을 보고 음원의 어느 지점부터 재생할지 정한다. double offset = 0; if (m_startTransition == TransitionType.Manual) // 강제 전환 시작시에는 fill-in 앞부분은 스킵한다. { offset = timeFillIn - timePlay; timeFillIn -= offset; // 시간값들 조정 timeLoopStart -= offset; timeNextLoopEnd -= offset; } else if (m_startTransition == TransitionType.Instant) { offset = timeLoopStart - timePlay; timeFillIn -= offset; // 시간값들 조정 timeLoopStart -= offset; timeNextLoopEnd -= offset; } //Debug.Log("PlayScheduled time : " + timePlay + ", offset : " + offset); curplayer.PlayScheduled(timePlay, offset); // 다음번 비트에 음원 재생 // 필요한 시간값은 멤버로 보관 //m_timePlayStart = timePlay; firstLoopStartDspTime = timeLoopStart; nextLoopEndDspTime = timeNextLoopEnd; // 전환 오토메이션을 미리 앞부분으로 적용해둔다. var auto = GetAutomation(m_startTransition == TransitionType.Manual? m_sectionData.inTypeManual : m_sectionData.inTypeNatural); if (auto != null) { m_transitionAutomationTarget.Set(auto.targetParam, auto.GetValue(0)); } while (AudioSettings.dspTime < timePlay) // 재생될 때까지 대기 { yield return(null); } // *** 재생 후, Fill-in 이전 m_state = State.BeforeFillIn; //Debug.Log(m_state.ToString() + " ... dspTime : " + AudioSettings.dspTime); if (m_startTransition == TransitionType.Natural) // 자연 전환일 경우 여기에서 트랜지션 { while (AudioSettings.dspTime < timeFillIn) { if (auto != null) { var t = (AudioSettings.dspTime - timePlay) / (timeLoopStart - timePlay); m_transitionAutomationTarget.Set(auto.targetParam, auto.GetValue((float)t)); //Debug.LogFormat("t = {0}, value = {1}", t, auto.GetValue((float)t)); } yield return(null); } } else { while (AudioSettings.dspTime < timeFillIn) // Fill-in 지점까지 대기 { yield return(null); } } // *** Fill - in m_state = State.FillIn; //Debug.Log(m_state.ToString() + " ... dspTime : " + AudioSettings.dspTime); if (m_startTransition == TransitionType.Natural) // 자연 전환일 경우, 루프 시작까지 남은 부분에 대해서 전환 { while (AudioSettings.dspTime < timeLoopStart) { if (auto != null) { var t = (AudioSettings.dspTime - timePlay) / (timeLoopStart - timePlay); m_transitionAutomationTarget.Set(auto.targetParam, auto.GetValue((float)t)); //Debug.LogFormat("t = {0}, value = {1}", t, auto.GetValue((float)t)); } yield return(null); } m_transitionAutomationTarget.Set(auto.targetParam, auto.GetValue(1)); // 마지막 값으로 보정 } else { // 강제 전환 while (AudioSettings.dspTime < timeLoopStart) { if (auto != null) { var t = (AudioSettings.dspTime - timeFillIn) / (timeLoopStart - timeFillIn); m_transitionAutomationTarget.Set(auto.targetParam, auto.GetValue((float)t)); //Debug.LogFormat("t = {0}, value = {1}", t, auto.GetValue((float)t)); } yield return(null); } m_transitionAutomationTarget.Set(auto.targetParam, auto.GetValue(1)); // 마지막 값으로 보정 } // *** Looping m_state = State.Looping; m_loopReserved = false; // 루핑 예약을 했는지 여부 m_suppressLooping = false; // 루프 억제 여부 while (true) // fadeout 설정이 되지 않는다면 계속 루프한다. { var nextSafeBeat = m_clock.CalcNextSafeBeatTime(); if (System.Math.Abs(nextSafeBeat - nextLoopEndDspTime) < beatTime && !m_loopReserved && !m_suppressLooping) // 다음번 비트에 루프를 해야하는 경우 { var loopStartOffset = beatTime * m_sectionData.beatStart; // 다른쪽 플레이어에 섹션 정보 세팅, 루프 시작 위치로 재생 예약 SwitchPlayerComponent(); SetSectionToCurrentPlayer(); while (!currentPlayerComponent.readyToPlay) // 로딩될 때까지 대기 { yield return(null); } if (!m_suppressLooping) // 로딩 대기 중에 이 플래그값이 true가 될 수도 있다. 그렇지 않을 때만 실제 재생 { currentPlayerComponent.PlayScheduled(nextLoopEndDspTime, loopStartOffset); //Debug.Log("Loop reserved"); m_loopReserved = true; // 루프 예약 지정 } } yield return(null); if (AudioSettings.dspTime >= nextLoopEndDspTime) // 루프 시간을 넘을 때마다 { m_loopReserved = false; // 루프 예약 여부 해제 nextLoopEndDspTime += m_loopLengthDspTime; // 다음 루프 시간 지정 } } }
TransitionTimeInfo DoProgress(SectionPlayer.TransitionType ttype, bool reverse, int sectionIdxOverride = int.MinValue) { // 트랜지션을 해도 괜찮은 상황인지 체크 if (sidePlayer.isReadyOrFinished) // 다른쪽 플레이어가 재생중이 아니거나 루프 종료된 경우 스위칭. 새로 재생할 플레이어가 currentPlayer가 된다 { SwitchPlayer(); if (sectionIdxOverride == int.MinValue) // 다음 섹션 idx를 오버라이드하지 않은 경우엔 자동으로 +1 { m_sectionIdx++; } else { // 오버라이드한 경우 m_sectionIdx = sectionIdxOverride; } //Debug.Log("switch player. section idx : " + m_sectionIdx); } // 트랜지션 시간 구하기 double transitionTime = CalcSectionTransitionTimeLength(m_sectionIdx, ttype); // 여기서부터는 sidePlayer => 기존에 재생중이던 플레이어가 됨 TransitionTimeInfo tinfo = new TransitionTimeInfo(); tinfo.transitionStart = -1; tinfo.transitionEnd = -1; // 기존 재생중이던 플레이어가 트랜지션 진행중이지 않고, 좀더 상위의 트랜지션을 걸 때 if (!sidePlayer.isOnTransition && (int)ttype > (int)sidePlayer.currentEndTransition) { //Debug.LogWarningFormat("transition check - m_sectionIdx : {0}, m_suppressProgress : {1}", m_sectionIdx, m_suppressProgress); if (((!reverse && m_sectionIdx > 0) || (reverse && m_sectionIdx < m_track.sectionCount - 1)) && !m_suppressProgress && !sidePlayer.isReadyOrFinished) // 처음 섹션이 아니고 재생을 끝내는 경우도 아닐 때만 { tinfo.transitionStart = sidePlayer.FadeoutSection(ttype, transitionTime); //Debug.LogWarning("fadeout triggered"); } else { // 그 외의 경우엔 전부 다음 비트에 트랜지션 시작하는 것으로 tinfo.transitionStart = m_clock.CalcNextSafeBeatTime(); } if ((!reverse && m_sectionIdx < m_track.sectionCount) || (reverse && m_sectionIdx >= 0)) // 더 진행할 섹션이 있는 경우에만 { //Debug.LogWarning("start : " + ttype); currentPlayer.StartSection(m_track.GetSection(m_sectionIdx), m_clock, ttype, tinfo.transitionStart); tinfo.transitionEnd = currentPlayer.firstLoopStartDspTime; } else { // TODO : 필요할지는 모르겠는데, 새 섹션이 시작되지 않는 경우에도 올바른 트랜지션 종료 타이밍을 계산해서 집어넣게 수정해야 할지도... tinfo.transitionEnd = tinfo.transitionStart; } // 트랜지션 추적 (step을 올바르게 업데이트하기 위해서) m_curTransition = ttype; StartTransitionMonitor(tinfo); } else { Debug.Log(string.Format("sidePlayer.isOnTransition : {0}, sidePlayer.currentEndTransition : {1}", sidePlayer.isOnTransition, sidePlayer.currentEndTransition)); } return(tinfo); }