private void InitMembersAfterSoundtrackHasLoaded() { m_themeQueue.Clear(); m_fadeVoices.Clear(); foreach (Segment segment in m_soundtrack.m_snippets.Values) { segment.audioData.filePathRelativeToProjectDir = m_platformLayer.ConvertFilePathForPlatform(segment.audioData.filePathRelativeToProjectDir); #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("converted path of segment " + segment.Name + " to " + segment.audioData.filePathRelativeToProjectDir, LogLevel.debug); } } #endif } m_soundtrack.UpdateMaxPreBeatMsOfCompatibleMiddleOrBridgeSnippets(); m_lastBasicMood = m_soundtrack.getThemeById(GetLastBasicMoodId()); m_psaiState = PsaiState.silence; m_psaiStateIntended = PsaiState.silence; m_psaiPlayMode = PsaiPlayMode.regular; m_psaiPlayModeIntended = PsaiPlayMode.regular; m_returnToLastBasicMoodFlag = false; m_holdIntensity = false; m_nonInterruptingTriggerOfHighestPriority.themeId = -1; m_soundtrack.BuildAllIndirectionSequences(); }
internal PsaiResult MenuModeLeave() { // boost::recursive_mutex::scoped_lock block(m_pnxLogicMutex); #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { Logger.Instance.Log("MenuModeLeave", LogLevel.info); } if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append(" m_themeQueue.size()="); sb.Append(m_themeQueue.Count); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } } #endif if (m_initializationFailure) { #if !(PSAI_NOLOG) { Logger.Instance.Log(LOGMESSAGE_ABORTION_DUE_TO_INITIALIZATION_FAILURE, LogLevel.errors); } #endif return PsaiResult.initialization_error; } ////////////////////////////////////////////////////////////////////////// if (m_paused) { setPaused(false); } if (m_psaiPlayMode == PsaiPlayMode.menuMode) { if (getFollowingThemeQueueEntry() != null) { PopAndPlayNextFollowingTheme(true); return PsaiResult.OK; } else { m_psaiStateIntended = PsaiState.silence; m_psaiState = PsaiState.silence; SetPlayMode(PsaiPlayMode.regular); m_psaiPlayModeIntended = PsaiPlayMode.regular; StopMusic(true); return PsaiResult.OK; } } else { #if !(PSAI_NOLOG) if (LogLevel.warnings <= Logger.Instance.LogLevel) { Logger.Instance.Log("MenuModeLeave() ignored - MenuMode wasn't active. Call MenuModeEnter() first !", LogLevel.warnings); } #endif return PsaiResult.commandIgnored; } }
internal PsaiResult CutSceneLeave(bool immediately, bool reset) { //boost::recursive_mutex::scoped_lock block(m_pnxLogicMutex); #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("CutSceneLeave() immediately="); sb.Append(immediately); sb.Append(" reset="); sb.Append(reset); Logger.Instance.Log(sb.ToString(), LogLevel.info); } if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb2 = new StringBuilder(); sb2.Append(" m_themeQueue.size()="); sb2.Append(m_themeQueue.Count); Logger.Instance.Log(sb2.ToString(), LogLevel.debug); } } #endif if (m_initializationFailure) { #if !(PSAI_NOLOG) { Logger.Instance.Log(LOGMESSAGE_ABORTION_DUE_TO_INITIALIZATION_FAILURE, LogLevel.errors); } #endif return PsaiResult.initialization_error; } ////////////////////////////////////////////////////////////////////////// if (m_psaiPlayMode == PsaiPlayMode.cutScene && m_psaiPlayModeIntended == PsaiPlayMode.cutScene) { if (reset) { m_themeQueue.Clear(); #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("m_themeQueue cleared", LogLevel.debug); } } #endif } if (getFollowingThemeQueueEntry() != null) { m_psaiPlayModeIntended = PsaiPlayMode.regular; PopAndPlayNextFollowingTheme(immediately); return PsaiResult.OK; } else { m_psaiStateIntended = PsaiState.silence; m_psaiState = PsaiState.silence; m_psaiPlayModeIntended = PsaiPlayMode.regular; StopMusic(immediately); return PsaiResult.OK; } } else { #if !(PSAI_NOLOG) if (LogLevel.warnings <= Logger.Instance.LogLevel) { Logger.Instance.Log("CutSceneLeave() ignored - no CutScene was active. Call CutSceneEnter() first!", LogLevel.warnings); } #endif return PsaiResult.commandIgnored; } }
void WakeUpFromRestHandler() { //boost::recursive_mutex::scoped_lock block(m_pnxLogicMutex); #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { Logger.Instance.Log("waking up from musical rest", LogLevel.info); } } #endif if (m_effectiveTheme != null) { PlayThemeNowOrAtEndOfCurrentSegment(m_effectiveTheme.id, m_effectiveTheme.intensityAfterRest, m_effectiveTheme.musicDurationAfterRest, true, false); m_psaiState = PsaiState.playing; m_psaiStateIntended = PsaiState.playing; } }
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; }
/* sets the psaiState to PSAISTATE_REST, where no music is played for a period defined in the themes.psai file. * param themeId the theme that will affect the duration of the rest, and will be played automatically when the rest is over. * param restMillis the milliseconds to rest. Pass 0 to have the resting millis calculated based on the authored data. */ void EnterRestMode(int themeIdToWakeUpWith, int themeIdToUseForRestingTimeCalculation) { //boost::recursive_mutex::scoped_lock block(m_pnxLogicMutex); #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("--- Entering rest mode. Will wake up with Theme "); sb.Append(themeIdToWakeUpWith); Logger.Instance.Log(sb.ToString(), LogLevel.info); } } #endif m_psaiState = PsaiState.rest; m_holdIntensity = false; m_timerStartSnippetPlayback.Stop(); // may be necessary if we have been fading out immediately m_timerSegmentEndApproaching.Stop(); // may be necessary if we have been fading out immediately m_timerWakeUpFromRest.Stop(); m_effectiveTheme = m_soundtrack.getThemeById(themeIdToWakeUpWith); // the effective Theme is also valid during Rest Mode if (m_effectiveTheme != null) { int millisTimerRest = 0; if (m_restModeSecondsOverride > 0) { #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("--- resting time is based on override values."); Logger.Instance.Log(sb.ToString(), LogLevel.info); } } #endif millisTimerRest = m_restModeSecondsOverride; m_restModeSecondsOverride = -1; } else { Theme themeRest = m_soundtrack.getThemeById(themeIdToUseForRestingTimeCalculation); if (themeRest != null) { #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("--- resting time is based on Theme "); sb.Append(themeRest.Name); Logger.Instance.Log(sb.ToString(), LogLevel.info); } } #endif millisTimerRest = GetRandomInt(themeRest.restSecondsMin, themeRest.restSecondsMax) * 1000; } else { #if !(PSAI_NOLOG) { if (LogLevel.warnings <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("--- resting time is based on Theme "); sb.Append(m_effectiveTheme.Name); sb.Append("(themeIdToUseForRestingTimeCalculation was not found: "); sb.Append(themeIdToUseForRestingTimeCalculation); sb.Append(" )"); Logger.Instance.Log(sb.ToString(), LogLevel.warnings); } } #endif millisTimerRest = GetRandomInt(m_effectiveTheme.restSecondsMin, m_effectiveTheme.restSecondsMax) * 1000; } } if (millisTimerRest > 0) { m_timeStampRestStart = GetTimestampMillisElapsedSinceInitialisation(); #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("...resting for "); sb.Append(millisTimerRest); sb.Append(" ms"); Logger.Instance.Log(sb.ToString(), LogLevel.info); } } #endif m_timerWakeUpFromRest.SetTimer(millisTimerRest, 0); } else { #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { Logger.Instance.Log("resting time is zero, starting again immediately.", LogLevel.info); } } #endif WakeUpFromRestHandler(); } } else { #if !(PSAI_NOLOG) { if (LogLevel.errors <= Logger.Instance.LogLevel) { Logger.Instance.Log("can't go to rest because Theme wasn't found!", LogLevel.errors); } } #endif } }
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; }
PsaiResult HandleNonInterruptingTriggerCall(Theme argTheme, float intensity, int musicDuration) { #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("HandleNonInterruptingTriggerCall() argTheme="); sb.Append(argTheme.Name); sb.Append(" intensity="); sb.Append(intensity); sb.Append(" musicDuration="); sb.Append(musicDuration); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } } #endif bool updateTqe = false; if (m_nonInterruptingTriggerOfHighestPriority.themeId != -1) { m_nonInterruptingTriggerOfHighestPriority.themeId = -1; updateTqe = true; /* #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("previouslyTriggeredTheme was null"); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } } #endif */ } else { // check if the trigger call differs from the last one if ( m_nonInterruptingTriggerOfHighestPriority.themeId != argTheme.id || m_nonInterruptingTriggerOfHighestPriority.startIntensity != intensity ) { #if !(PSAI_NOLOG) { /* if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("differing trigger -> update"); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } */ } #endif updateTqe = true; } } if (updateTqe) { m_nonInterruptingTriggerOfHighestPriority.themeId = argTheme.id; m_nonInterruptingTriggerOfHighestPriority.startIntensity = intensity; m_nonInterruptingTriggerOfHighestPriority.musicDuration = musicDuration; m_psaiStateIntended = PsaiState.playing; return PsaiResult.OK; } else { #if !(PSAI_NOLOG) if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("... no update necessary", LogLevel.debug); } #endif return PsaiResult.OK; } }
void InitiateTransitionToRestMode() { #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("InitiateTransitionToRestMode()", LogLevel.debug); } } #endif if (m_currentSegmentPlaying != null) { if ( m_currentSegmentPlaying.IsUsableAs(SegmentSuitability.end) ) { EnterRestMode(GetLastBasicMoodId(), getEffectiveThemeId()); } else if (CheckIfThereIsAPathToEndSegmentForEffectiveSegmentAndLogWarningIfThereIsnt() == false) { startFade(m_currentVoiceNumber, GetRemainingMillisecondsOfCurrentSegmentPlayback(), 0); EnterRestMode(GetLastBasicMoodId(), getEffectiveThemeId()); } else { WriteLogWarningIfThereIsNoDirectPathForEffectiveSnippetToEndSnippet(); PlaySegment(m_currentSegmentPlaying.nextSnippetToShortestEndSequence, false); m_psaiStateIntended = PsaiState.rest; } } }
internal PsaiResult StopMusic(bool immediately) { //boost::recursive_mutex::scoped_lock blockieren(m_pnxLogicMutex); #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("StopMusic("); sb.Append(immediately); sb.Append(") called"); Logger.Instance.Log(sb.ToString(), LogLevel.info); } } #endif if (m_paused) { setPaused(false); } // return immediately in menu mode if (m_psaiPlayMode == PsaiPlayMode.menuMode) { #if !(PSAI_NOLOG) if (LogLevel.warnings <= Logger.Instance.LogLevel) { Logger.Instance.Log("StopMusic() ignored: Menu Mode is active", LogLevel.warnings); } #endif return PsaiResult.commandIgnoredMenuModeActive; } // return immediately in cutscene mode if (m_psaiPlayModeIntended == PsaiPlayMode.cutScene) { #if !(PSAI_NOLOG) if (LogLevel.warnings <= Logger.Instance.LogLevel) { Logger.Instance.Log("StopMusic() ignored: Cutscene is active", LogLevel.warnings); } #endif return PsaiResult.commandIgnoredCutsceneActive; } if (m_initializationFailure) { #if !(PSAI_NOLOG) { Logger.Instance.Log(LOGMESSAGE_ABORTION_DUE_TO_INITIALIZATION_FAILURE, LogLevel.errors); } #endif return PsaiResult.initialization_error; } if (m_psaiStateIntended == PsaiState.silence && !immediately) { #if !(PSAI_NOLOG) { if (LogLevel.warnings <= Logger.Instance.LogLevel) { Logger.Instance.Log("StopMusic() ignored - psai is currently already transitioning to SILENCE mode", LogLevel.warnings); } } #endif return PsaiResult.commandIgnored; } m_returnToLastBasicMoodFlag = false; m_holdIntensity = false; ////////////////////////////////////////////////////////////////////////// switch (m_psaiState) { case PsaiState.playing: case PsaiState.silence: // SILENCE will be set if we are leaving the menu and were in SILENCE when we entered it { Segment effectiveSnippet = GetEffectiveSegment(); if (effectiveSnippet != null) { bool validPathToEndSegmentExists = false; if (!immediately) { validPathToEndSegmentExists = CheckIfThereIsAPathToEndSegmentForEffectiveSegmentAndLogWarningIfThereIsnt(); } if (immediately || !validPathToEndSegmentExists) { startFade(m_currentVoiceNumber, PSAI_FADEOUTMILLIS_STOPMUSIC, 0); EnterSilenceMode(); } else { #if !(PSAI_NOLOG) { WriteLogWarningIfThereIsNoDirectPathForEffectiveSnippetToEndSnippet(); } #endif if (m_nonInterruptingTriggerOfHighestPriority.themeId != -1) { m_nonInterruptingTriggerOfHighestPriority.themeId = -1; #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("cleared noninterrupting trigger", LogLevel.debug); } } #endif } m_psaiStateIntended = PsaiState.silence; } } } break; case PsaiState.rest: { EnterSilenceMode(); } break; } return PsaiResult.OK; }
void SegmentEndApproachingHandler() { //boost::recursive_mutex::scoped_lock block(m_pnxLogicMutex); #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("--- SegmentEndApproachingHandler() m_psaiStateIntended="); sb.Append(m_psaiStateIntended); sb.Append(" m_nonInterruptingTriggerOfHighestPriority.themeId="); sb.Append(m_nonInterruptingTriggerOfHighestPriority.themeId); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } } #endif // make sure we don't go to rest/sleep in case a non-interrupting trigger call // has been received if (m_nonInterruptingTriggerOfHighestPriority.themeId != -1) { m_psaiState = PsaiState.playing; m_psaiStateIntended = PsaiState.playing; } switch (m_psaiStateIntended) { case PsaiState.silence: { if (m_currentSegmentPlaying == null || m_currentSegmentPlaying.IsUsableAs(SegmentSuitability.end)) { EnterSilenceMode(); } else { PlaySegment(m_currentSegmentPlaying.nextSnippetToShortestEndSequence, false); } } break; case PsaiState.rest: { if (m_currentSegmentPlaying == null || m_currentSegmentPlaying.IsUsableAs(SegmentSuitability.end)) { if (m_psaiState != PsaiState.rest) // m_psaiState will be PsaiState.rest if we already called GoToRest using a fadeout. { EnterRestMode(GetLastBasicMoodId(), getEffectiveThemeId()); } } else { PlaySegment(m_currentSegmentPlaying.nextSnippetToShortestEndSequence, false); } } break; default: { if (m_returnToLastBasicMoodFlag) { #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { Logger.Instance.Log("returnToLastBasicMoodFlag was set", LogLevel.debug); } } #endif // no End-Segment is playing and there is a valid transition to End - go on with the ending sequence. if ( ((m_currentSegmentPlaying.SnippetTypeBitfield & (int)SegmentSuitability.end) == 0) && CheckIfThereIsAPathToEndSegmentForEffectiveSegmentAndLogWarningIfThereIsnt() == true ) { WriteLogWarningIfThereIsNoDirectPathForEffectiveSnippetToEndSnippet(); PlaySegment(m_currentSegmentPlaying.nextSnippetToShortestEndSequence, false); return; } else { PlayThemeNowOrAtEndOfCurrentSegment(GetLastBasicMoodId(), m_lastBasicMood.intensityAfterRest, m_lastBasicMood.musicDurationGeneral, false, false); m_returnToLastBasicMoodFlag = false; return; } } // do we have received any trigger-Calls while playing? Then use the one with the // highest priority if (m_psaiPlayMode == PsaiPlayMode.regular && m_nonInterruptingTriggerOfHighestPriority.themeId != -1) { Theme nonInterruptingTheme = m_soundtrack.getThemeById(m_nonInterruptingTriggerOfHighestPriority.themeId); if (m_currentSegmentPlaying.CheckIfAtLeastOneDirectTransitionOrLayeringIsPossible(m_soundtrack, nonInterruptingTheme.id)) { PlayThemeNowOrAtEndOfCurrentSegment(m_nonInterruptingTriggerOfHighestPriority.themeId, m_nonInterruptingTriggerOfHighestPriority.startIntensity, m_nonInterruptingTriggerOfHighestPriority.musicDuration, false, m_nonInterruptingTriggerOfHighestPriority.holdIntensity); m_nonInterruptingTriggerOfHighestPriority.themeId = -1; } else { if (m_currentSegmentPlaying.MapOfNextTransitionSegmentToTheme.ContainsKey(nonInterruptingTheme.id)) { #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("No direct transition exists from Segment "); sb.Append(m_currentSegmentPlaying.Name); sb.Append(" to any MIDDLE or BRIDGE Segment of Theme "); sb.Append(nonInterruptingTheme.Name); sb.Append(", psai is therefore playing an indirect transition via the shortest path of compatible Segments. The next one will be "); sb.Append(m_currentSegmentPlaying.MapOfNextTransitionSegmentToTheme[nonInterruptingTheme.id]); Logger.Instance.Log(sb.ToString(), LogLevel.info); } } #endif PlaySegment(m_currentSegmentPlaying.MapOfNextTransitionSegmentToTheme[nonInterruptingTheme.id], false); } else { #if !(PSAI_NOLOG) { if (LogLevel.warnings <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("Could not perform any transition from Segment "); sb.Append(m_currentSegmentPlaying.Name); sb.Append(" to Theme "); sb.Append(nonInterruptingTheme.Name); sb.Append(" as no direct or indirect path of compatible Segments exists. Psai is therefore switching Themes by crossfading."); Logger.Instance.Log(sb.ToString(), LogLevel.warnings); } } #endif PlayThemeNowOrAtEndOfCurrentSegment(m_nonInterruptingTriggerOfHighestPriority.themeId, m_nonInterruptingTriggerOfHighestPriority.startIntensity, m_nonInterruptingTriggerOfHighestPriority.musicDuration, true, m_nonInterruptingTriggerOfHighestPriority.holdIntensity); m_nonInterruptingTriggerOfHighestPriority.themeId = -1; } } } else { // business as usual: get the next snippet of the current theme float currentIntensity = getCurrentIntensity(); if (currentIntensity > 0.0f) { m_nonInterruptingTriggerOfHighestPriority.themeId = -1; PlaySegmentOfCurrentTheme(SegmentSuitability.middle); } else { IntensityZeroHandler(); } } } break; } }
/** internal function to initiate the playback of a theme. * @param themeId id of the theme to be played * @param intensity the desired intensity level [0.0f ... 1.0f] * @param immediately true: play instantly false: play at end of current snippet * @param SnippetType * @param recalculateIntensitySlope pass true if the intensity slope needs to be reinitialized to the full musical period as defined in the authoring software. * @param holdIntensity pass true if the intensity should be held on a constant level, false otherwise (default is false) * @return PsaiResult.OK ok, theme will be played * PSAI_INFO_TRIGGER_DENIED trigger was denied as a theme transition is not guaranteed due to missing snippet transitions, and error handling was set to DENY_TRIGGER * PSAI_ERR_ESSENTIAL_SNIPPET_MISSING a transition could not be achieved because there was no compatible follower to the current snippet, and error handling was set to ABORT or STOP_MUSIC * */ PsaiResult PlayThemeNowOrAtEndOfCurrentSegment(int themeId, float intensity, int musicDuration, bool immediately, bool holdIntensity) { #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { Theme theme = m_soundtrack.getThemeById(themeId); StringBuilder sb = new StringBuilder(); sb.Append("PlayThemeNowOrAtEndOfCurrentSegment() themeId="); sb.Append(themeId); if (theme == null) { sb.Append("THEME NOT FOUND!"); } else { sb.Append(" ["); sb.Append(theme.Name); sb.Append("] themeType="); sb.Append(theme.themeType); } sb.Append( " intensity="); sb.Append(intensity); sb.Append(" immediately="); sb.Append(immediately); sb.Append(" holdIntensity="); sb.Append(holdIntensity); sb.Append(" musicDuration="); sb.Append(musicDuration); sb.Append(" m_currentSegmentPlaying="); sb.Append(m_currentSegmentPlaying); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } } #endif SetCurrentIntensityAndMusicDuration(intensity, musicDuration, true); m_psaiStateIntended = PsaiState.playing; m_heldIntensity = intensity; // if we're interrupting rest mode, kill the rest wake up timer if (m_psaiState == PsaiState.rest) { m_timerWakeUpFromRest.Stop(); } // choice of Segment Suitability: // * Use Start Segments: // 1. when playing out of silence // 2. if a pure END-Segment is currently playing // * Use Middle Segments when staying within the current Theme. // * Use Bridge or Middle Segments when transitioning to another Theme. m_targetSegmentSuitabilitiesRequested = (int)SegmentSuitability.start; if (m_psaiState == PsaiState.playing) { if (m_currentSegmentPlaying != null) { if (m_currentSegmentPlaying.IsUsableOnlyAs(SegmentSuitability.end)) { m_targetSegmentSuitabilitiesRequested = (int)SegmentSuitability.start; } else { if (getEffectiveThemeId() == themeId) { // we stay within the same Theme, so choose a MIDDLE Segment // NOTE: this may only happen if we return from the Menu or a CutScene to the same Theme. Otherwise, // PlayThemeNowOrAtEndOfCurrentSegment should not be called explicitly m_targetSegmentSuitabilitiesRequested = (int)SegmentSuitability.middle; } else { // upon Theme changes we play BRIDGE or MIDDLE Segments m_targetSegmentSuitabilitiesRequested = SNIPPET_TYPE_MIDDLE_OR_BRIDGE; } } } } m_effectiveTheme = m_soundtrack.getThemeById(themeId); Segment targetSnippet; if ((m_targetSegmentSuitabilitiesRequested & (int)SegmentSuitability.start) > 0 || GetEffectiveSegment() == null) { targetSnippet = GetBestStartSegmentForTheme(themeId, intensity); } else { targetSnippet = GetBestCompatibleSegment(GetEffectiveSegment(), themeId, intensity, m_targetSegmentSuitabilitiesRequested); } ////////////////////////////////////////////////////////////////////////// // no compatible Segment could be found ! ////////////////////////////////////////////////////////////////////////// if (targetSnippet == null) { #if !(PSAI_NOLOG) { Logger.Instance.Log("essential Segment could not be found! Trying to substitute...", LogLevel.errors); } #endif targetSnippet = substituteSegment(themeId); if (targetSnippet == null) { #if !(PSAI_NOLOG) { Logger.Instance.Log("failed to substitute Segment. Stopping music.", LogLevel.errors); } #endif StopMusic(true); return PsaiResult.essential_segment_missing; } } // //////////////////////////////////////////////// // at this point, the target Segment has to be set! (internal error otherwise) // //////////////////////////////////////////////// m_holdIntensity = holdIntensity; // starting a Theme immediately when any other Theme is already playing should result in // fading out that other Theme. if (immediately && GetEffectiveSegment() != null) { m_scheduleFadeoutUponSnippetPlayback = true; } if (targetSnippet != null) { return PlaySegment(targetSnippet, immediately); } else { #if !(PSAI_NOLOG) { Logger.Instance.Log("fatal internal error! entered code section in PlayTheme that is supposed to be unreachable!", LogLevel.errors); } #endif return PsaiResult.internal_error; } }
/* 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 }
/** basic internal function for setting the first theme that will be played as soon as the * current theme's intensity has dropped (or has been set) to zero. This method behaves as a push operation, so the parameter will be * the first theme on the theme queue stack. * @param clearThemeQueue pass true to clear the themeQueue, false to enqueue the followingTheme in the themeQueue * @param restTimeMillis if the theme should start in rest mode, pass the resting millis. 0 otherwise. * @param playMode the playMode that will be entered when the theme is popped. * @param holdIntensity true for holding the intensity at a constant level */ bool pushThemeToThemeQueue(int themeId, float intensity, int musicDuration, bool clearThemeQueue, int restTimeMillis, PsaiPlayMode playMode, bool holdIntensity) { //boost::recursive_mutex::scoped_lock block(m_pnxLogicMutex); #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("setting the Following Theme to "); sb.Append(themeId); if (LogLevel.debug <= Logger.Instance.LogLevel) { sb.Append(" intensity= "); sb.Append(intensity); sb.Append(" clearThemeQueue="); sb.Append(clearThemeQueue); sb.Append(" playmode="); sb.Append(playMode); sb.Append(" holdIntensity="); sb.Append(holdIntensity); sb.Append(" musicDuration="); sb.Append(musicDuration); } Logger.Instance.Log(sb.ToString(), LogLevel.info); } } #endif if (clearThemeQueue) { m_themeQueue.Clear(); } Theme theme = m_soundtrack.getThemeById(themeId); if (theme != null) { ThemeQueueEntry newEntry = new ThemeQueueEntry(); newEntry.themeId = themeId; newEntry.startIntensity = intensity; newEntry.musicDuration = musicDuration; newEntry.restTimeMillis = restTimeMillis; newEntry.playmode = playMode; newEntry.holdIntensity = holdIntensity; #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append(" m_themeQueue.size()="); sb.Append(m_themeQueue.Count); for (int i=0; i<m_themeQueue.Count; i++) { ThemeQueueEntry tmpEntry = m_themeQueue[i]; sb.Append(" ["); sb.Append(i); sb.Append("] themeId="); sb.Append(tmpEntry.themeId); sb.Append(" startIntensity="); sb.Append(tmpEntry.startIntensity); } Logger.Instance.Log(sb.ToString(), LogLevel.debug); } } #endif m_themeQueue.Insert(0, newEntry); m_psaiStateIntended = PsaiState.playing; // in case IntensityZeroHandler() had already been called, we need to reset the psaiStateIntended here return true; } else { return false; } }
internal PsaiResult TriggerMusicTheme(Theme argTheme, float argIntensity, int argMusicDuration) { if (m_initializationFailure) { #if !(PSAI_NOLOG) { if (LogLevel.errors <= Logger.Instance.LogLevel) { Logger.Instance.Log(LOGMESSAGE_ABORTION_DUE_TO_INITIALIZATION_FAILURE, LogLevel.errors); } } #endif return PsaiResult.initialization_error; } ////////////////////////////////////////////////////////////////////////// if (m_paused) { setPaused(false); } Segment effectiveSegment = GetEffectiveSegment(); // special treatment for Highlights if (argTheme.themeType == ThemeType.highlightLayer) { if (CheckIfAnyThemeIsCurrentlyPlaying() == true) { if (m_effectiveTheme != null && effectiveSegment != null && effectiveSegment.CheckIfAtLeastOneDirectTransitionOrLayeringIsPossible(m_soundtrack, argTheme.id) == false) { #if !(PSAI_NOLOG) { Logger.Instance.Log(LOGMESSAGE_TRIGGER_DENIED, LogLevel.warnings); } #endif return PsaiResult.triggerDenied; }; } return startHighlight(argTheme); } // return immediately in menu mode if (m_psaiPlayMode == PsaiPlayMode.menuMode) { #if !(PSAI_NOLOG) if (LogLevel.warnings <= Logger.Instance.LogLevel) { Logger.Instance.Log("TriggerMusicTheme() ignored: Menu Mode is active", LogLevel.warnings); } #endif return PsaiResult.commandIgnoredMenuModeActive; } else if (m_psaiPlayModeIntended == PsaiPlayMode.cutScene) { #if !(PSAI_NOLOG) if (LogLevel.warnings <= Logger.Instance.LogLevel) { Logger.Instance.Log("TriggerMusicTheme() ignored: Cutscene is active", LogLevel.warnings); } #endif return PsaiResult.commandIgnoredCutsceneActive; } else if (m_psaiPlayMode == PsaiPlayMode.cutScene && m_psaiStateIntended == PsaiState.silence && m_currentSegmentPlaying != null) { #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { Logger.Instance.Log("special case: Cutscene Theme is still playing, continuing with theme " + argTheme.Name, LogLevel.info); } } #endif m_psaiState = PsaiState.playing; m_psaiStateIntended = PsaiState.playing; return PlayThemeNowOrAtEndOfCurrentSegment(argTheme.id, argIntensity, argMusicDuration, true, false); } ////////////////////////////////////////////////////////////////////////// // regular Mode ////////////////////////////////////////////////////////////////////////// // if we trigger a BasicMood shortly after ReturnToBasicMood(by End) has been called, we don't want to cancel the // return, but instead switch the Last Basic Mood to return to. In all other cases, cancel the Return process. if (m_returnToLastBasicMoodFlag) { if (argTheme.themeType != ThemeType.basicMood) { m_returnToLastBasicMoodFlag = false; } } if (argTheme.themeType == ThemeType.basicMood) { SetThemeAsLastBasicMood(argTheme); } // always clear the Theme Queue, as the most recent trigger call is the one that counts. removeFirstFollowingThemeQueueEntry(); // nothing is playing -> play immediately if (effectiveSegment == null || m_psaiState == PsaiState.silence || m_psaiState == PsaiState.rest) { return PlayThemeNowOrAtEndOfCurrentSegment(argTheme.id, argIntensity, argMusicDuration, true, false); } // special case: StopMusic(by End Segment) is in progress, and a Theme of lower or same priority is triggered // -> conduct a seamless transition. if (m_psaiStateIntended == PsaiState.silence && effectiveSegment != null) { Theme effectiveTheme1 = m_soundtrack.getThemeById(effectiveSegment.ThemeId); ThemeInterruptionBehavior tib = Theme.GetThemeInterruptionBehavior(effectiveTheme1.themeType, argTheme.themeType); if (tib == ThemeInterruptionBehavior.at_end_of_current_snippet || tib == ThemeInterruptionBehavior.never) { m_psaiStateIntended = PsaiState.playing; return PlayThemeNowOrAtEndOfCurrentSegment(argTheme.id, argIntensity, argMusicDuration, false, false); } } // the effective Theme was triggered again? if (effectiveSegment.ThemeId == argTheme.id) { #if !(PSAI_NOLOG) { /* if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("theme "); sb.Append(argTheme.Name); sb.Append(" is already playing and the SegmentEndApproaching timer is pending. Updating intensity to "); sb.Append(argIntensity); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } */ } #endif m_nonInterruptingTriggerOfHighestPriority.themeId = -1; // clear any non-interrupting trigger call we may have received recently! SetCurrentIntensityAndMusicDuration(argIntensity, argMusicDuration, true); m_psaiStateIntended = PsaiState.playing; return PsaiResult.OK; } Theme effectiveTheme = m_soundtrack.getThemeById(effectiveSegment.ThemeId); ThemeInterruptionBehavior themeInteruptionBehavior = Theme.GetThemeInterruptionBehavior(effectiveTheme.themeType, argTheme.themeType); switch (themeInteruptionBehavior) { case ThemeInterruptionBehavior.immediately: { // don't push the interrupted Theme on the stack if GoToRest() or StopMusic() has just been called. if (argTheme.themeType == ThemeType.shock && m_psaiStateIntended == PsaiState.playing) { PushEffectiveThemeToThemeQueue(PsaiPlayMode.regular); } else { m_nonInterruptingTriggerOfHighestPriority.themeId = -1; } return PlayThemeNowOrAtEndOfCurrentSegment(argTheme.id, argIntensity, argMusicDuration, true, false); } //break; case ThemeInterruptionBehavior.at_end_of_current_snippet: { return HandleNonInterruptingTriggerCall(argTheme, argIntensity, argMusicDuration); } //break; case ThemeInterruptionBehavior.never: { if (argTheme.themeType != ThemeType.basicMood) { pushThemeToThemeQueue(argTheme.id, argIntensity, argMusicDuration, false, 0, PsaiPlayMode.regular, false); #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("Theme "); sb.Append(argTheme.Name); sb.Append(" has been queued for direct playback after the intensity of the current Theme has dropped to zero."); Logger.Instance.Log(sb.ToString(), LogLevel.info); } } #endif } return PsaiResult.OK; } //break; } #if !(PSAI_NOLOG) { if (LogLevel.errors <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("INTERNAL ERROR: end of TriggerMusicTheme() reached without returning a proper returnCode. "); sb.Append("argThemeId="); sb.Append(argTheme.id); sb.Append(" m_currentTheme="); sb.Append(m_effectiveTheme); if (m_effectiveTheme != null) { sb.Append(" m_currentTheme id="); sb.Append(m_effectiveTheme.id); sb.Append(" m_currentTheme themeType="); sb.Append(m_effectiveTheme.themeType); } Logger.Instance.Log(sb.ToString(), LogLevel.errors); } } #endif return PsaiResult.internal_error; }
internal PsaiResult ReturnToLastBasicMood(bool immediately) { #if !(PSAI_NOLOG) { if (LogLevel.info <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("ReturnToBasicMood("); sb.Append(immediately); sb.Append(") m_psaiState="); sb.Append(m_psaiState); sb.Append(" m_currentSnippetTypesRequested="); sb.Append(Segment.GetStringFromSegmentSuitabilities(m_currentSnippetTypeRequested)); Logger.Instance.Log(sb.ToString(), LogLevel.info); } } #endif if (m_initializationFailure) { return PsaiResult.initialization_error; } ////////////////////////////////////////////////////////////////////////// if (m_paused) { setPaused(false); } if (m_psaiPlayModeIntended == PsaiPlayMode.regular) { switch(m_psaiState) { case PsaiState.playing: { m_themeQueue.Clear(); m_holdIntensity = false; m_nonInterruptingTriggerOfHighestPriority.themeId = -1; if (m_currentSegmentPlaying != null && m_effectiveTheme.themeType != ThemeType.basicMood) { bool validPathToEndSnippetExists = false; if (!immediately) { validPathToEndSnippetExists = CheckIfThereIsAPathToEndSegmentForEffectiveSegmentAndLogWarningIfThereIsnt(); } if (immediately || !validPathToEndSnippetExists) { PlayThemeNowOrAtEndOfCurrentSegment(GetLastBasicMoodId(), m_lastBasicMood.intensityAfterRest, m_lastBasicMood.musicDurationGeneral, true, false); } else { m_psaiStateIntended = PsaiState.playing; // in case we are interrupting a transition to SILENCE after StopMusic(0) was called m_returnToLastBasicMoodFlag = true; } return PsaiResult.OK; } else { #if !(PSAI_NOLOG) if (LogLevel.warnings <= Logger.Instance.LogLevel) { Logger.Instance.Log("ReturnToLastBasicMood() ignored: base theme is already playing", LogLevel.warnings); } #endif return PsaiResult.commandIgnored; } } //break; case PsaiState.rest: case PsaiState.silence: { PlayThemeNowOrAtEndOfCurrentSegment(GetLastBasicMoodId(), m_lastBasicMood.intensityAfterRest, m_lastBasicMood.musicDurationGeneral, true, false); return PsaiResult.OK; } //break; default: { #if !(PSAI_NOLOG) { if (LogLevel.debug <= Logger.Instance.LogLevel) { StringBuilder sb = new StringBuilder(); sb.Append("INTERNAL ERROR: unconsidered psaiState in ReturnToLastBasicMood()! m_psaiState="); sb.Append(m_psaiState); Logger.Instance.Log(sb.ToString(), LogLevel.debug); } } #endif return PsaiResult.internal_error; } } } else { if (m_psaiPlayModeIntended == PsaiPlayMode.menuMode) { #if !(PSAI_NOLOG) if (LogLevel.warnings <= Logger.Instance.LogLevel) { Logger.Instance.Log("ReturnToLastBasicMood() ignored: MenuMode is active. Call MenuModeLeave() first.", LogLevel.warnings); } #endif return PsaiResult.commandIgnoredMenuModeActive; } else if (m_psaiPlayModeIntended == PsaiPlayMode.cutScene) { #if !(PSAI_NOLOG) if (LogLevel.warnings <= Logger.Instance.LogLevel) { Logger.Instance.Log("ReturnToLastBasicMood() ignored: CutScene is active. Call CutsceneLeave() first.", LogLevel.warnings); } #endif return PsaiResult.commandIgnoredCutsceneActive; } } return PsaiResult.internal_error; // should never be reached }