public Soundtrack(psai.ProtoBuf_PsaiCoreSoundtrack pbSoundtrack) : this() { for (int i = 0; i < pbSoundtrack.themes.Count; i++) { psai.ProtoBuf_Theme pbTheme = pbSoundtrack.themes[i]; Theme tempTheme = new Theme(pbTheme); m_themes[tempTheme.id] = tempTheme; } for (int i = 0; i < pbSoundtrack.snippets.Count; i++) { psai.ProtoBuf_Snippet pbSnippet = pbSoundtrack.snippets[i]; Segment tempSnippet = new Segment(pbSnippet); m_snippets[tempSnippet.Id] = tempSnippet; Theme tmpTheme = m_themes[tempSnippet.ThemeId]; if (tmpTheme != null) { tmpTheme.m_segments.Add(tempSnippet); } else { #if !(PSAI_NOLOG) { if (LogLevel.errors <= Logger.Instance.LogLevel) { string s = "INTERNAL ERROR! could not find Theme for Theme id " + tempSnippet.ThemeId + " of snippet" + tempSnippet.Id; Logger.Instance.Log(s, LogLevel.errors); } } #endif } } }
public psai.net.Segment CreatePsaiDotNetVersion(PsaiProject parentProject) { psai.net.Segment netSegment = new psai.net.Segment(); netSegment.audioData = this.AudioData.CreatePsaiDotNetVersion(); netSegment.Id = this.Id; netSegment.Intensity = this.Intensity; netSegment.SnippetTypeBitfield = this.CreateSegmentSuitabilityBitfield(parentProject); netSegment.ThemeId = this.ThemeId; netSegment.Name = this.Name; for (int i = 0; i < this.CompatibleSnippetsIds.Count; i++) { int snippetId = this.CompatibleSnippetsIds.Keys.ElementAt(i); netSegment.Followers.Add(new Follower(snippetId, CompatibleSnippetsIds[snippetId])); } return(netSegment); }
private void EnterSilenceMode() { // enter silence mode #if !(PSAI_NOLOG) if (LogLevel.info <= Logger.Instance.LogLevel) { Logger.Instance.Log("entering Silence Mode", LogLevel.info); } #endif m_timerStartSnippetPlayback.Stop(); m_timerSegmentEndApproaching.Stop(); m_targetSegment = null; m_effectiveTheme = null; m_scheduleFadeoutUponSnippetPlayback = false; m_psaiStateIntended = PsaiState.silence; m_psaiState = PsaiState.silence; }
/** evaluates and returns the best compatible follow-up-Segment (or compatible HighlightLayer-Segment respectively) to the given Segment. * The evaluation is based on the list of registered followers-tiles to the predecessor Segment, the number of replays, * the intensity and the desired suitabilities of the follower Segment * @param sourceSegment the predecessor Segment * @param intensity the desired intensity of the follower Segment * @param allowedSegmentSuitabilities the allowed suitabilities of the follower Segment as a bitwise OR combination * @return a pointer to the best follower Segment */ Segment GetBestCompatibleSegment(Segment sourceSegment, int targetThemeId, float intensity, int allowedSegmentSuitabilities) { //boost::recursive_mutex::scoped_lock block(m_pnxLogicMutex); #if !(PSAI_NOLOG) { /* if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("GetBestCompatibleSnippet() currentSnippetId="); sb.Append(currentSnippetId); sb.Append(" intensity="); sb.Append(intensity); sb.Append(" allowedSnippetTypes="); sb.Append(Snippet.GetStringOfSnippetTypes(allowedSnippetTypes)); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } */ } #endif float maxDeltaIntensity = 0.0f; int minPlaycount = 0; int maxPlaycount = 0; //Snippet currentSnippet = GetSnippetById(currentSnippetId); // TODO: overhead, lieber gleich m_currentTheme als Pointer übergeben if (sourceSegment == null) { return null; } // Vergleichsliste aufbauen List<Follower> snippetList = new List<Follower>(); int nachfolgerCount = sourceSegment.Followers.Count; for (int i=0; i < nachfolgerCount; i++) { int id = sourceSegment.Followers[i].snippetId; Segment tempTeil = m_soundtrack.GetSegmentById(id); // TODO: overhead. extend Nachfolger datastructure by snippet type ? if (tempTeil != null) { if ((allowedSegmentSuitabilities & tempTeil.SnippetTypeBitfield) > 0 && tempTeil.ThemeId == targetThemeId) { if (i==0) { minPlaycount = tempTeil.Playcount; } else { if (tempTeil.Playcount < minPlaycount) minPlaycount = tempTeil.Playcount; } if (tempTeil.Playcount > maxPlaycount) maxPlaycount = tempTeil.Playcount; float tDeltaIntensity = intensity - tempTeil.Intensity; if (tDeltaIntensity < 0.0f) tDeltaIntensity = tDeltaIntensity * -1.0f; if (tDeltaIntensity > maxDeltaIntensity) maxDeltaIntensity = tDeltaIntensity; snippetList.Add(sourceSegment.Followers[i]); } } } if (snippetList.Count == 0) { #if !(PSAI_NOLOG) { if (LogLevel.errors <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("no Segment of type "); sb.Append(Segment.GetStringFromSegmentSuitabilities(allowedSegmentSuitabilities)); sb.Append(" found for Theme "); sb.Append(targetThemeId); sb.Append( " , that would be a compatible follower/layer of Segment "); sb.Append(sourceSegment.Name); Logger.Instance.Log(sb.ToString(), LogLevel.errors); } } #endif return null; } else { Weighting weighting = null; Theme pTargetTheme = m_soundtrack.getThemeById(targetThemeId); if (pTargetTheme != null) { weighting = pTargetTheme.weightings; } return ChooseBestSegmentFromList(snippetList, weighting, intensity, maxPlaycount, minPlaycount, maxDeltaIntensity ); } }
internal Logik() { #if PSAI_STANDALONE m_platformLayer = new PlatformLayerStandalone(this); #else m_platformLayer = new PlatformLayerUnity(); #endif m_platformLayer.Initialize(); m_soundtrack = new Soundtrack(); m_themeQueue = new List<ThemeQueueEntry>(); m_fadeVoices = new List<FadeData>(); for (int i = 0; i < PSAI_CHANNEL_COUNT; i++) { m_playbackChannels[i] = new PlaybackChannel(); } m_hilightVoiceIndex = -1; m_lastRegularVoiceNumberReturned = -1; m_currentVoiceNumber = -1; m_targetVoice = -1; m_psaiMasterVolume = 1.0f; m_effectiveTheme = null; m_currentSegmentPlaying = null; m_currentSnippetTypeRequested = 0; m_targetSegment = null; m_targetSegmentSuitabilitiesRequested = 0; m_psaiState = PsaiState.notready; m_psaiStateIntended = PsaiState.notready; m_paused = false; m_fullVersionString = "psai Version " + PSAI_VERSION; #if !(PSAI_NOLOG) Logger.Instance.LogLevel = LogLevel.info; Logger.Instance.Log(m_fullVersionString, LogLevel.info); #endif s_instance = this; }
private List<Segment> GetSetOfAllSourceSegmentsCompatibleToSegment(Segment targetSnippet, float minCompatibilityThreshold, SegmentSuitability doNotIncludeSegmentsWithThisSuitability) { List<Segment> sourceSnippets = new List<Segment>(); foreach (Segment tmpSnippet in m_segments) { if (tmpSnippet.IsUsableAs(doNotIncludeSegmentsWithThisSuitability) == false) { foreach (Follower tmpFollower in tmpSnippet.Followers) { if (tmpFollower.snippetId == targetSnippet.Id && tmpFollower.compatibility >= minCompatibilityThreshold) { sourceSnippets.Add(tmpSnippet); } } } } return sourceSnippets; }
PsaiResult IAudioPlaybackLayerChannel.ScheduleSegmentPlayback(Segment snippet, int delayMilliseconds) { if (_segmentToLoad != null && _segmentToLoad.Id == snippet.Id) { bool readyToPlay = IsReadyToPlay(); #if (!PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("ScheduleSegmentPlayback() Segment:" + snippet.Name + " isReadyToPlay:" + readyToPlay, LogLevel.debug); } #endif // new method PlayDelayed introduced in Unity Version 4.1.0. if (readyToPlay) { _audioSource.PlayDelayed((uint)delayMilliseconds / 1000.0f); PlaybackIsPending = false; #if (!PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("_audioSource.PlayDelayed() fired, delayInMs=" + delayMilliseconds, LogLevel.debug); } #endif } else { #if (!PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("... play has not fired yet, ImmediatePlaybackIsPending is now set to true. this.GetHashCode()=" + this.GetHashCode(), LogLevel.debug); } #endif PlaybackIsPending = true; } return PsaiResult.OK; } else { Logger.Instance.Log("ScheduleSegmentPlayback(): COULD NOT PLAY! No Segment loaded, or Segment Id to play did not match! Segment loaded: " + _segmentToLoad, LogLevel.errors); } return PsaiResult.notReady; }
PsaiResult IAudioPlaybackLayerChannel.LoadSegment(Segment segment) { _segmentToLoad = segment; AudioClip = null; #if UNITY_PRO_LICENSE if (_psaiAsyncLoader == null) { GameObject psaiObject = PsaiCoreManager.Instance.gameObject; if (psaiObject == null) { #if !(PSAI_NOLOG) if (LogLevel.errors <= Logger.Instance.LogLevel) { Logger.Instance.Log("No 'Psai' object found in the Scene! Please make sure to add the Psai.prefab from the Psai.unitypackage to your Scene", LogLevel.errors); } #endif return PsaiResult.initialization_error; } _psaiAsyncLoader = psaiObject.AddComponent<PsaiAsyncLoader>(); } #endif // careful! Using Path.Combine for the subfolders does not work for the Resources subfolders, // neither does "\\" double backslashes. So leave it like this, it works for WebPlayer and Standalone. // not checked yet for iOS and Android. If in doubt, leave out the subfolders. //string pathToClip = null; string psaiBinaryDirectoryName = Logik.Instance.m_psaiCoreBinaryDirectoryName; if (psaiBinaryDirectoryName.Length > 0) { PathToClip = psaiBinaryDirectoryName + "/" + segment.audioData.filePathRelativeToProjectDir; } else { PathToClip = segment.audioData.filePathRelativeToProjectDir; } #if UNITY_PRO_LICENSE { _audioSource.clip = null; // we reset the clip to prevent the situation in ScheduleSegmentPlayback(), where the previous clip was reported as readyToPlay, causing problems. _psaiAsyncLoader.LoadSegmentAsync(this); } return PsaiResult.OK; #else if (_tryToUseWrappers) { GameObject wrapperGameObject = (GameObject)UnityEngine.Resources.Load(PathToClipWrapper, typeof(GameObject)); if (wrapperGameObject != null) { PsaiAudioClipWrapper wrapperComponent = wrapperGameObject.GetComponent<PsaiAudioClipWrapper>(); if (wrapperComponent == null || wrapperComponent._audioClip == null) { Debug.LogError("a Wrapper prefab for AudioClip '" + _segmentToLoad.audioData.filePathRelativeToProjectDir + "' was found, but it was invalid. Please re-run the psaiMultiAudioObjectEditor on your soundtrack folder, and make sure 'create Wrappers' is enabled."); } else { AudioClip = wrapperComponent._audioClip; #if (!PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("success: audioClip loaded from Wrapper (synchronous)", LogLevel.debug); } #endif } } } // Fallback: Load Clip directly. if (AudioClip == null) { AudioClip = UnityEngine.Resources.Load(PathToClip) as AudioClip; #if (!PSAI_NOLOG) if (_tryToUseWrappers && !_psaiWrapperWarningHasBeenShown && LogLevel.warnings <= Logger.Instance.LogLevel) { Logger.Instance.Log("Due to an issue in Unity 4.x AudioClips will in some cases not be streamed from disk, even if their import settings are set accordingly. This may cause framerate drops whenever a new Segment is loaded. Psai provides a workaround for this, by wrapping each AudioClip in a dedicated GameObject. To have the Wrappers created, please right-click on your soundtrack folder in the Project window, and run the 'psai Multi Audio Object Editor'. Make sure 'use Wrappers' is enabled.", LogLevel.warnings); _psaiWrapperWarningHasBeenShown = true; } #endif } /* final check to return PsaiResult and write Log */ if (AudioClip == null) { #if (!PSAI_NOLOG) if (LogLevel.errors <= Logger.Instance.LogLevel) { Logger.Instance.Log("Segment not found: " + PathToClipWrapper, LogLevel.errors); } #endif return PsaiResult.file_notFound; } else { #if (!PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("LoadSegment() OK - segment.Name:" + segment.Name + " _audioSource.clip: " + _audioSource.clip + " PathToClip:" + PathToClip, LogLevel.debug); } #endif return PsaiResult.OK; } #endif }
/** plays the given snippet either immediately or schedules its playback to the end of current snippet. * */ PsaiResult PlaySegment(Segment targetSnippet, bool immediately) { #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("PlaySegment() name="); sb.Append(targetSnippet.Name); sb.Append(" immediately="); sb.Append(immediately); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } } #endif if (m_initializationFailure) { #if !(PSAI_NOLOG) { Logger.Instance.Log("PlaySegment() - abortion due to initialization failure", LogLevel.errors); } #endif return PsaiResult.initialization_error; } PsaiResult psaiResult; // if another target snippet is already set, first check if it's the same, so we can ignore the call /* if (m_targetSnippet != null) { if (m_targetSnippet.themeId == targetSnippet.themeId) { #if !(PSAI_NOLOG) { Logger.Instance.Log("ignoring PlaySnippet(), because m_targetSnippet is already set and has the same theme id", LogLevel.debug); } #endif m_targetIntensity = intensityAtStart; m_recalculateIntensitySlopeAtSnippetPlayback = recalculateIntensitySlope; return PsaiResult.OK; // better return PsaiResult.OK than PSAI_INFO_COMMAND_IGNORED, because it may cause confusion when this return value // is passed down to triggerMusicTheme(). } else { if (!immediately) { // Check if we still have enough time to preload the new targetSnippet if (m_timeStampCurrentSnippetEndApproaching - GetTimestampMillisElapsedSinceInitialisation() < s_audioLayerMaximumLatencyForBufferingSounds) { // we don't have enough time to preload the snippet // if (targetSnippet.themeId == m_targetSnippet.themeId && (m_targetSnippetTypeRequested & targetSnippet.SnippetTypeBitfield) > 0) { #if !(PSAI_NOLOG) { Logger.Instance.Log("ignoring PlaySnippet(), because preload time is not sufficient and the m_targetSnippet->snippetType matches the requested snippet type", LogLevel.debug); } #endif m_targetIntensity = intensityAtStart; m_recalculateIntensitySlopeAtSnippetPlayback = recalculateIntensitySlope; return PsaiResult.commandIgnored; } else { #if !(PSAI_NOLOG) { Logger.Instance.Log("INTERNAL WARNING! we don't have enough time to preload the snippet, and the preloaded snippet has a different themeId and / or a different snippet type. Playback will be delayed", LogLevel.debug); } #endif } } } } } */ ////////////////////////////////////////////////////////////////////////// // at this point it's sure that the requested Segment will be played back, either immediately or after the end of the current Segment // (exception: if stopMusic(immediately) is called) ////////////////////////////////////////////////////////////////////////// m_timerSegmentEndApproaching.Stop(); m_timerStartSnippetPlayback.Stop(); m_targetVoice = getNextVoiceNumber(false); // stop channel if necessary /* TODO: this still exists in psaiCORE. Commented out because it's screwing with updateFades(). why is this necessary ? */ //m_playbackChannels[m_targetVoice].StopChannel(); psaiResult = LoadSegment(targetSnippet, m_targetVoice); PsaiErrorCheck(psaiResult, "LoadSegment()"); // schedule snippet playback int millisUntilNextSnippetPlayback = 0; m_targetSegment = targetSnippet; if (immediately || m_currentSegmentPlaying == null ) { if (m_playbackChannels[m_targetVoice].CheckIfSegmentHadEnoughTimeToLoad()) { m_estimatedTimestampOfTargetSnippetPlayback = GetTimestampMillisElapsedSinceInitialisation() + s_audioLayerMaximumLatencyForPlayingbackPrebufferedSounds; } else { m_estimatedTimestampOfTargetSnippetPlayback = GetTimestampMillisElapsedSinceInitialisation() + s_audioLayerMaximumLatencyForPlayingBackUnbufferedSounds; } PlayTargetSegmentImmediately(); } else { int millisElapsed = GetMillisElapsedAfterCurrentSnippetPlaycall(); millisUntilNextSnippetPlayback = (int)(m_currentSegmentPlaying.audioData.GetFullLengthInMilliseconds() - m_currentSegmentPlaying.audioData.GetPostBeatZoneInMilliseconds() - targetSnippet.audioData.GetPreBeatZoneInMilliseconds() - millisElapsed); if (millisUntilNextSnippetPlayback > s_audioLayerMaximumLatencyForPlayingBackUnbufferedSounds) { m_estimatedTimestampOfTargetSnippetPlayback = Logik.GetTimestampMillisElapsedSinceInitialisation() + millisUntilNextSnippetPlayback; m_timerStartSnippetPlayback.SetTimer(millisUntilNextSnippetPlayback, s_audioLayerMaximumLatencyForPlayingbackPrebufferedSounds); } else { #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("!!! millisUntilNextSnippetPlayback="); sb.Append(millisUntilNextSnippetPlayback); sb.Append(" so we're playing immediately !"); sb.Append(" s_audioLayerMaximumLatencyForPlayingBackUnbufferedSounds="); sb.Append(s_audioLayerMaximumLatencyForPlayingBackUnbufferedSounds); sb.Append(" m_currentSegmentPlaying->FullLengthInMs="); sb.Append(m_currentSegmentPlaying.audioData.GetFullLengthInMilliseconds()); sb.Append(" m_currentSnippetPlaying->PostBeatMs="); sb.Append(m_currentSegmentPlaying.audioData.GetPostBeatZoneInMilliseconds()); sb.Append(" targetSnippet->PreBeatMs="); sb.Append(targetSnippet.audioData.GetPreBeatZoneInMilliseconds()); sb.Append(" m_timeStampCurrentSnippetPlaycall="); sb.Append(m_timeStampCurrentSnippetPlaycall); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } } #endif m_estimatedTimestampOfTargetSnippetPlayback = GetTimestampMillisElapsedSinceInitialisation() + s_audioLayerMaximumLatencyForPlayingbackPrebufferedSounds; PlayTargetSegmentImmediately(); } } return PsaiResult.OK; }
/** Immediate playback of the given Segment on a separate layer, without affecting the playback logic. */ internal void PlaySegmentLayeredAndImmediately(Segment segment) { m_hilightVoiceIndex = getNextVoiceNumber(true); m_playbackChannels[m_hilightVoiceIndex].StopChannel(); m_playbackChannels[m_hilightVoiceIndex].ReleaseSegment(); m_playbackChannels[m_hilightVoiceIndex].FadeOutVolume = 1.0f; m_playbackChannels[m_hilightVoiceIndex].ScheduleSegmentPlayback(segment, s_audioLayerMaximumLatencyForBufferingSounds + s_audioLayerMaximumLatencyForPlayingbackPrebufferedSounds); }
// this will play back the requested Snippet in delayInMilliseconds. // Playback of a scheduled Snippet cannot be canceled. internal void ScheduleSegmentPlayback(Segment snippet, int delayInMilliseconds) { #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("ScheduleSegmentPlayback() Segment="); sb.Append(snippet.Name); sb.Append(" delayInMilliseconds="); sb.Append(delayInMilliseconds); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } } #endif if (delayInMilliseconds < 0) { #if !(PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("delayInMilliseconds was negative, thus set to 0" , LogLevel.debug); } #endif delayInMilliseconds = 0; } if (snippet != Segment) { LoadSegment(snippet); } m_stoppedExplicitly = false; m_playbackIsScheduled = true; m_timeStampOfPlaybackStart = Logik.GetTimestampMillisElapsedSinceInitialisation() + delayInMilliseconds; if (m_audioPlaybackLayerChannel != null) { m_audioPlaybackLayerChannel.ScheduleSegmentPlayback(snippet, delayInMilliseconds); } else { #if !(PSAI_NOLOG) { if (LogLevel.errors <= Logger.Instance.LogLevel) { Logger.Instance.Log("m_audioPlaybackLayerChannel is null!", LogLevel.errors); } } #endif } }
internal void ReleaseSegment() { Segment = null; if (m_audioPlaybackLayerChannel != null) { m_audioPlaybackLayerChannel.ReleaseSegment(); } }
internal void LoadSegment(Segment snippet) { Segment = snippet; m_timeStampOfSnippetLoad = Logik.GetTimestampMillisElapsedSinceInitialisation(); m_playbackIsScheduled = false; m_stoppedExplicitly = false; if (m_audioPlaybackLayerChannel != null) { m_audioPlaybackLayerChannel.LoadSegment(snippet); } }
/** * @param listOfSnippetsWithValidEndSequences - a list of Snippets that are either END-Snippets or have a valid NextSnippetToShortestEndSequence set. * returns the array of Snippets for which a valid NextSnippetToValidEndSequence has been set in this call. */ private void SetTheNextSnippetToShortestEndSequenceForAllSourceSnippetsOfTheSnippetsInThisList(Segment[] listOfSnippetsWithValidEndSequences) { /* #if !(PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("SetTheNextSnippetToShortestEndSequenceForAllSourceSnippetsOfTheSnippetsInThisList() called, listOfSnippets.Length=" + listOfSnippetsWithValidEndSequences.Length, LogLevel.debug); StringBuilder sb = new StringBuilder(); foreach (Snippet argSnippet in listOfSnippetsWithValidEndSequences) { sb.Append(argSnippet.name); sb.Append(" intensity="); sb.Append(argSnippet.intensity); sb.Append(" nextSnippetToShEndSeq="); sb.Append(argSnippet.nextSnippetToShortestEndSequence); sb.Append(" "); } Logger.Instance.Log(sb.ToString(), LogLevel.debug); } #endif */ Dictionary<Segment, List<Segment>> mapWaypointAlternativesForSnippet = new Dictionary<Segment, List<Segment>>(); foreach (Segment endSnippet in listOfSnippetsWithValidEndSequences) { List<Segment> sourceSnippets = GetSetOfAllSourceSegmentsCompatibleToSegment(endSnippet, Logik.COMPATIBILITY_PERCENTAGE_SAME_GROUP, SegmentSuitability.end); foreach (Segment sourceSnippet in sourceSnippets) { if (sourceSnippet.nextSnippetToShortestEndSequence == null && sourceSnippet.ThemeId == endSnippet.ThemeId) { if (!mapWaypointAlternativesForSnippet.ContainsKey(sourceSnippet)) { mapWaypointAlternativesForSnippet[sourceSnippet] = new List<Segment>(); } mapWaypointAlternativesForSnippet[sourceSnippet].Add(endSnippet); } } } foreach (Segment snippet in mapWaypointAlternativesForSnippet.Keys) { snippet.nextSnippetToShortestEndSequence = snippet.ReturnSegmentWithLowestIntensityDifference(mapWaypointAlternativesForSnippet[snippet]); } Segment[] snippetsAdded = new Segment[mapWaypointAlternativesForSnippet.Count]; mapWaypointAlternativesForSnippet.Keys.CopyTo(snippetsAdded, 0); if (snippetsAdded.Length > 0) { SetTheNextSnippetToShortestEndSequenceForAllSourceSnippetsOfTheSnippetsInThisList(snippetsAdded); } }
/** * @param listOfSnippetsWithValidTransitionSequencesToTargetTheme - a list of Snippets that have a valid Transition-Sequence to or directly compatible followers in a given TargetTheme. */ private void SetTheNextSegmentToShortestTransitionSequenceToTargetThemeForAllSourceSegmentsOfTheSegmentsInThisList(Segment[] listOfSnippetsWithValidTransitionSequencesToTargetTheme, Soundtrack soundtrack, Theme targetTheme) { /* #if !(PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("SetTheNextSegmentToShortestTransitionSequenceToTargetThemeForAllSourceSegmentsOfTheSegmentsInThisList() called, listOfSnippets.Length=" + listOfSnippetsWithValidEndSequences.Length, LogLevel.debug); StringBuilder sb = new StringBuilder(); foreach (Snippet argSnippet in listOfSnippetsWithValidEndSequences) { sb.Append(argSnippet.name); sb.Append(" intensity="); sb.Append(argSnippet.intensity); sb.Append(" nextSnippetToShEndSeq="); sb.Append(argSnippet.nextSnippetToShortestEndSequence); sb.Append(" "); } Logger.Instance.Log(sb.ToString(), LogLevel.debug); } #endif */ Dictionary<Segment, List<Segment>> mapWaypointAlternativesForSnippet = new Dictionary<Segment, List<Segment>>(); foreach (Segment transitionSnippet in listOfSnippetsWithValidTransitionSequencesToTargetTheme) { List<Segment> sourceSnippets = GetSetOfAllSourceSegmentsCompatibleToSegment(transitionSnippet, Logik.COMPATIBILITY_PERCENTAGE_SAME_GROUP, SegmentSuitability.none); sourceSnippets.Remove(transitionSnippet); foreach (Segment sourceSnippet in sourceSnippets) { if (sourceSnippet.MapOfNextTransitionSegmentToTheme.ContainsKey(targetTheme.id) == false && sourceSnippet.CheckIfAtLeastOneDirectTransitionOrLayeringIsPossible(soundtrack, targetTheme.id) == false && sourceSnippet.ThemeId == transitionSnippet.ThemeId) { if (mapWaypointAlternativesForSnippet.ContainsKey(sourceSnippet) == false) { mapWaypointAlternativesForSnippet[sourceSnippet] = new List<Segment>(); } mapWaypointAlternativesForSnippet[sourceSnippet].Add(transitionSnippet); } } } foreach (Segment snippet in mapWaypointAlternativesForSnippet.Keys) { snippet.MapOfNextTransitionSegmentToTheme[targetTheme.id] = snippet.ReturnSegmentWithLowestIntensityDifference(mapWaypointAlternativesForSnippet[snippet]); } Segment[] snippetsAdded = new Segment[mapWaypointAlternativesForSnippet.Count]; mapWaypointAlternativesForSnippet.Keys.CopyTo(snippetsAdded, 0); if (snippetsAdded.Length > 0) { SetTheNextSegmentToShortestTransitionSequenceToTargetThemeForAllSourceSegmentsOfTheSegmentsInThisList(snippetsAdded, soundtrack, targetTheme); } }
PsaiResult IAudioPlaybackLayerChannel.ScheduleSegmentPlayback(Segment segment, int delayMilliseconds) { if (_segmentToLoad != null && _segmentToLoad.Id == segment.Id) { bool readyToPlay = IsReadyToPlay(); #if (!PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log(string.Format("ScheduleSegmentPlayback() Segment: {0} isReadyToPlay: {1}", segment.Name, readyToPlay), LogLevel.debug); } #endif // new method PlayDelayed introduced in Unity Version 4.1.0. if (readyToPlay) { PlayBufferedClip(delayMilliseconds); #if (!PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log(string.Format("_audioSource.PlayDelayed() fired, delayInMs:{0}", delayMilliseconds), LogLevel.debug); } #endif return PsaiResult.OK; } else { TargetPlaybackTimestamp = Logik.GetTimestampMillisElapsedSinceInitialisation() + delayMilliseconds; PlaybackIsPending = true; #if (!PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("... play has not fired yet, PlaybackIsPending is now set to true. TargetPlaybackTimestamp=" + TargetPlaybackTimestamp, LogLevel.debug); } #endif } return PsaiResult.OK; } else { Logger.Instance.Log("ScheduleSegmentPlayback(): COULD NOT PLAY! No Segment loaded, or Segment Id to play did not match! Segment loaded: " + _segmentToLoad, LogLevel.errors); } return PsaiResult.notReady; }
PsaiResult IAudioPlaybackLayerChannel.LoadSegment(Segment segment) { _segmentToLoad = segment; AudioClip = null; #if (!PSAI_BUILT_BY_VS) if (_psaiAsyncLoader == null) { GameObject psaiObject = PsaiCoreManager.Instance.gameObject; if (psaiObject == null) { #if !(PSAI_NOLOG) if (LogLevel.errors <= Logger.Instance.LogLevel) { Logger.Instance.Log("No 'Psai' object found in the Scene! Please make sure to add the Psai.prefab from the Psai.unitypackage to your Scene", LogLevel.errors); } #endif return PsaiResult.initialization_error; } _psaiAsyncLoader = psaiObject.AddComponent<PsaiAsyncLoader>(); } #endif // careful! Using Path.Combine for the subfolders does not work for the Resources subfolders, // neither does "\\" double backslashes. So leave it like this, it works for WebPlayer and Standalone. // not checked yet for iOS and Android. If in doubt, leave out the subfolders. //string pathToClip = null; string psaiBinaryDirectoryName = Logik.Instance.m_psaiCoreBinaryDirectoryName; if (psaiBinaryDirectoryName.Length > 0) { PathToClip = psaiBinaryDirectoryName + "/" + segment.audioData.filePathRelativeToProjectDir; } else { PathToClip = segment.audioData.filePathRelativeToProjectDir; } _audioSource.clip = null; // we reset the clip to prevent the situation in ScheduleSegmentPlayback(), where the previous clip was reported as readyToPlay, causing problems. _psaiAsyncLoader.LoadSegmentAsync(this); return PsaiResult.OK; /* // Fallback: Load Clip directly. if (AudioClip == null) { AudioClip = UnityEngine.Resources.Load(PathToClip) as AudioClip; } if (AudioClip == null) { #if (!PSAI_NOLOG) if (LogLevel.errors <= Logger.Instance.LogLevel) { Logger.Instance.Log("Segment not found: " + PathToClip, LogLevel.errors); } #endif return PsaiResult.file_notFound; } else { #if (!PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("LoadSegment() OK - segment.Name:" + segment.Name + " _audioSource.clip: " + _audioSource.clip + " PathToClip:" + PathToClip, LogLevel.debug); } #endif return PsaiResult.OK; } */ }
PsaiResult LoadSegment(Segment snippet, int channelIndex) { if (snippet == null || channelIndex >= PSAI_CHANNEL_COUNT) { return PsaiResult.invalidHandle; } else { m_playbackChannels[channelIndex].LoadSegment(snippet); return PsaiResult.OK; } }
PsaiResult IAudioPlaybackLayerChannel.ReleaseSegment() { if (_audioSource.clip != null) { /* this only has an effect after calling Resources.UnloadUnusedAssets(). * Only calling Resources.UnloadUnusedAssets() is also possible, but it will free the chached clips * more seldomly */ Resources.UnloadAsset(_audioSource.clip); _audioSource.clip = null; _segmentToLoad = null; } return PsaiResult.OK; }
/* This method will play the target Segment with minimum delay, and set the m_targetSnipet to m_currentSnippet, and m_targetVoice to m_currentVoice. */ void PlayTargetSegmentImmediately() { #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("PlayTargetSegmentImmediately() m_targetSegmentTypesRequested="); sb.Append(Segment.GetStringFromSegmentSuitabilities(m_targetSegmentSuitabilitiesRequested)); sb.Append(" targetSegment="); sb.Append(m_targetSegment.Name); sb.Append(" id="); sb.Append(m_targetSegment.Id); sb.Append(" m_targetVoice="); sb.Append(m_targetVoice); sb.Append(" themeId="); sb.Append(m_targetSegment.ThemeId); sb.Append(" playbackChannel.Segment=" ); sb.Append(m_playbackChannels[m_targetVoice].Segment.Name); sb.Append(" millisSinceSegmentLoad="); sb.Append( m_playbackChannels[m_targetVoice].GetMillisecondsSinceSegmentLoad()); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } } #endif int snippetPlaybackDelayMillis = 0; if (m_playbackChannels[m_targetVoice].CheckIfSegmentHadEnoughTimeToLoad()) { snippetPlaybackDelayMillis = m_estimatedTimestampOfTargetSnippetPlayback - GetTimestampMillisElapsedSinceInitialisation(); #if !(PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("Segment had enough time to load. m_estimatedTimestampOfTargetSnippetPlayback=" + m_estimatedTimestampOfTargetSnippetPlayback.ToString(), LogLevel.debug); } #endif } else { int millisUntilLoadingWillHaveFinished = m_playbackChannels[m_targetVoice].GetMillisecondsUntilLoadingWillHaveFinished(); #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("Segment DID NOT have enough time to load! missing milliSeconds=" + millisUntilLoadingWillHaveFinished, LogLevel.debug); } } #endif snippetPlaybackDelayMillis = millisUntilLoadingWillHaveFinished + s_audioLayerMaximumLatencyForPlayingbackPrebufferedSounds; } m_playbackChannels[m_targetVoice].FadeOutVolume = 1.0f; m_playbackChannels[m_targetVoice].ScheduleSegmentPlayback(m_targetSegment, snippetPlaybackDelayMillis); if (m_scheduleFadeoutUponSnippetPlayback) { startFade(m_currentVoiceNumber, PSAI_FADEOUTMILLIS_PLAYIMMEDIATELY, m_targetSegment.audioData.GetPreBeatZoneInMilliseconds() + snippetPlaybackDelayMillis); m_scheduleFadeoutUponSnippetPlayback = false; } m_psaiPlayMode = m_psaiPlayModeIntended; m_currentVoiceNumber = m_targetVoice; m_currentSegmentPlaying = m_targetSegment; m_currentSnippetTypeRequested = m_targetSegmentSuitabilitiesRequested; m_currentSegmentPlaying.Playcount++; //TODO: this should not be increased within menu mode, should it? m_timeStampCurrentSnippetPlaycall = GetTimestampMillisElapsedSinceInitialisation() + snippetPlaybackDelayMillis; // now set the timers for snippet end approaching and snippet end reached int millisUntilUpcomingSnippetEnd = m_targetSegment.audioData.GetFullLengthInMilliseconds() + snippetPlaybackDelayMillis; int millisUntilNextCalculateCall = (int)(millisUntilUpcomingSnippetEnd - m_targetSegment.audioData.GetPostBeatZoneInMilliseconds() - m_targetSegment.MaxPreBeatMsOfCompatibleSnippetsWithinSameTheme - s_audioLayerMaximumLatencyForPlayingBackUnbufferedSounds // deterministic calculation up to here... - 2 * s_updateIntervalMillis); // ... now add some extra headroom for processing time if (millisUntilNextCalculateCall < 0) { #if !(PSAI_NOLOG) { if (LogLevel.warnings <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append(" psai did not have enough time to evaluate the next Segment in time (missing milliseconds: "); sb.Append(millisUntilNextCalculateCall); sb.Append(")."); sb.Append(" This means that either the main region of Segment '"); sb.Append(m_currentSegmentPlaying.Name); sb.Append("' (ThemeId="); sb.Append(m_currentSegmentPlaying.ThemeId); sb.Append(") is too short, or that the Prebeat region of at least one of its compatible Segments is too long. "); sb.Append("See the 'best practice' section in the psai manual for more information."); Logger.Instance.Log(sb.ToString(), LogLevel.warnings); } } #endif millisUntilNextCalculateCall = 0; } m_targetSegment = null; m_psaiState = PsaiState.playing; if (millisUntilNextCalculateCall < 0) { millisUntilNextCalculateCall = 0; } m_timerSegmentEndApproaching.SetTimer(millisUntilNextCalculateCall, s_updateIntervalMillis); // at the moment the SnippetEndReached Timer only fires for the very last snippet // so it will be extended with every following snippet m_timerSegmentEndReached.SetTimer(millisUntilUpcomingSnippetEnd, 0); #if !(PSAI_NOLOG) { /* if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("m_timer_SegmentEndApproaching should fire in "); sb.Append(millisUntilNextCalculateCall); sb.Append(" ms"); Logger.Instance.Log(sb.ToString(), LogLevel.debug); StringBuilder sb2 = new StringBuilder(); sb2.Append("m_timer_SegmentEndReached should fire in "); sb2.Append(millisUntilUpcomingSnippetEnd); sb2.Append(" ms"); Logger.Instance.Log(sb2.ToString(), LogLevel.debug); } */ } #endif }
public psai.net.Segment CreatePsaiDotNetVersion(PsaiProject parentProject) { psai.net.Segment netSegment = new psai.net.Segment(); netSegment.audioData = this.AudioData.CreatePsaiDotNetVersion(); netSegment.Id = this.Id; netSegment.Intensity = this.Intensity; netSegment.SnippetTypeBitfield = this.CreateSegmentSuitabilityBitfield(parentProject); netSegment.ThemeId = this.ThemeId; netSegment.Name = this.Name; for (int i = 0; i < this.CompatibleSnippetsIds.Count; i++) { int snippetId = this.CompatibleSnippetsIds.Keys.ElementAt(i); netSegment.Followers.Add(new Follower(snippetId, CompatibleSnippetsIds[snippetId])); } return netSegment; }
void SegmentEndReachedHandler() { #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("SegmentEndReachedHandler() m_psaiStateIntended="); sb.Append(m_psaiStateIntended); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } } #endif if (m_targetSegment == null) { m_currentSegmentPlaying = null; } }