/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "ags/shared/core/platform.h" #include "ags/engine/media/audio/audio.h" #include "ags/shared/ac/audio_clip_type.h" #include "ags/shared/ac/game_setup_struct.h" #include "ags/engine/ac/dynobj/cc_audio_clip.h" #include "ags/engine/ac/dynobj/cc_audio_channel.h" #include "ags/engine/ac/dynobj/dynobj_manager.h" #include "ags/engine/ac/game_state.h" #include "ags/engine/script/script_runtime.h" #include "ags/engine/ac/audio_channel.h" #include "ags/engine/ac/audio_clip.h" #include "ags/engine/ac/game_setup.h" #include "ags/engine/ac/path_helper.h" #include "ags/engine/media/audio/sound.h" #include "ags/engine/debugging/debug_log.h" #include "ags/engine/debugging/debugger.h" #include "ags/engine/platform/base/sys_main.h" #include "ags/shared/ac/common.h" #include "ags/engine/ac/file.h" #include "ags/engine/ac/global_audio.h" #include "ags/shared/util/stream.h" #include "ags/shared/core/asset_manager.h" #include "ags/engine/ac/timer.h" #include "ags/engine/main/game_run.h" #include "ags/globals.h" #include "ags/ags.h" namespace AGS3 { using namespace AGS::Shared; using namespace AGS::Engine; //----------------------- //sound channel management; all access goes through here, which can't be done without a lock SOUNDCLIP *AudioChans::GetChannel(int index) { return _GP(audioChannels)[index]; } SOUNDCLIP *AudioChans::GetChannelIfPlaying(int index) { auto *ch = _GP(audioChannels)[index]; return (ch != nullptr && ch->is_playing()) ? ch : nullptr; } SOUNDCLIP *AudioChans::SetChannel(int index, SOUNDCLIP *ch) { SoundClipWaveBase *wavClip = dynamic_cast(ch); if (wavClip) { switch (index) { case SCHAN_SPEECH: wavClip->setType(Audio::Mixer::kSpeechSoundType); break; case SCHAN_MUSIC: wavClip->setType(Audio::Mixer::kMusicSoundType); break; default: wavClip->setType(Audio::Mixer::kSFXSoundType); break; } } // TODO: store clips in smart pointers if (_GP(audioChannels)[index] != nullptr && _GP(audioChannels)[index] == ch) Debug::Printf(kDbgMsg_Warn, "WARNING: channel %d - same clip assigned", index); else if (_GP(audioChannels)[index] != nullptr && ch != nullptr) Debug::Printf(kDbgMsg_Warn, "WARNING: channel %d - clip overwritten", index); _GP(audioChannels)[index] = ch; return ch; } SOUNDCLIP *AudioChans::MoveChannel(int to, int from) { auto from_ch = _GP(audioChannels)[from]; _GP(audioChannels)[from] = nullptr; return SetChannel(to, from_ch); } void calculate_reserved_channel_count() { int reservedChannels = 0; for (size_t i = 0; i < _GP(game).audioClipTypes.size(); i++) { reservedChannels += _GP(game).audioClipTypes[i].reservedChannels; } _G(reserved_channel_count) = reservedChannels; } void update_clip_default_volume(ScriptAudioClip *audioClip) { if (_GP(play).default_audio_type_volumes[audioClip->type] >= 0) { audioClip->defaultVolume = _GP(play).default_audio_type_volumes[audioClip->type]; } } void start_fading_in_new_track_if_applicable(int fadeInChannel, ScriptAudioClip *newSound) { int crossfadeSpeed = _GP(game).audioClipTypes[newSound->type].crossfadeSpeed; if (crossfadeSpeed > 0) { update_clip_default_volume(newSound); _GP(play).crossfade_in_volume_per_step = crossfadeSpeed; _GP(play).crossfade_final_volume_in = newSound->defaultVolume; _GP(play).crossfading_in_channel = fadeInChannel; } } static void move_track_to_crossfade_channel(int currentChannel, int crossfadeSpeed, int fadeInChannel, ScriptAudioClip *newSound) { stop_and_destroy_channel(SPECIAL_CROSSFADE_CHANNEL); auto *cfade_clip = AudioChans::MoveChannel(SPECIAL_CROSSFADE_CHANNEL, currentChannel); if (!cfade_clip) return; _GP(play).crossfading_out_channel = SPECIAL_CROSSFADE_CHANNEL; _GP(play).crossfade_step = 0; _GP(play).crossfade_initial_volume_out = cfade_clip->get_volume100(); _GP(play).crossfade_out_volume_per_step = crossfadeSpeed; _GP(play).crossfading_in_channel = fadeInChannel; if (newSound != nullptr) { start_fading_in_new_track_if_applicable(fadeInChannel, newSound); } } // NOTE: this function assumes one of the user channels void stop_or_fade_out_channel(int fadeOutChannel, int fadeInChannel, ScriptAudioClip *newSound) { ScriptAudioClip *sourceClip = AudioChannel_GetPlayingClip(&_G(scrAudioChannel)[fadeOutChannel]); if ((_GP(play).fast_forward == 0) && // don't crossfade if skipping a cutscene (sourceClip != nullptr) && (_GP(game).audioClipTypes[sourceClip->type].crossfadeSpeed > 0)) { move_track_to_crossfade_channel(fadeOutChannel, _GP(game).audioClipTypes[sourceClip->type].crossfadeSpeed, fadeInChannel, newSound); } else { stop_and_destroy_channel(fadeOutChannel); } } static int find_free_audio_channel(ScriptAudioClip *clip, int priority, bool interruptEqualPriority, bool for_queue = true) { int lowestPrioritySoFar = 9999999; int lowestPriorityID = -1; int channelToUse = -1; if (!interruptEqualPriority) priority--; int startAtChannel = _G(reserved_channel_count); int endBeforeChannel = _GP(game).numGameChannels; if (_GP(game).audioClipTypes[clip->type].reservedChannels > 0) { startAtChannel = 0; for (int i = 0; i < clip->type; i++) { startAtChannel += MIN(MAX_SOUND_CHANNELS, _GP(game).audioClipTypes[i].reservedChannels); } // NOTE: we allow to place sound on a crossfade channel for backward compatibility, // but ONLY for the case of audio type with reserved channels (weird quirk). endBeforeChannel = MIN(_GP(game).numCompatGameChannels, startAtChannel + _GP(game).audioClipTypes[clip->type].reservedChannels); } for (int i = startAtChannel; i < endBeforeChannel; i++) { auto *ch = AudioChans::GetChannelIfPlaying(i); if (ch == nullptr) { channelToUse = i; stop_and_destroy_channel(i); break; } if ((ch->_priority < lowestPrioritySoFar) && (ch->_sourceClipType == clip->type)) { lowestPrioritySoFar = ch->_priority; lowestPriorityID = i; } // NOTE: This is a "hack" for starting queued clips; // since having a new audio system (3.6.0 onwards), the audio timing // changed a little, and queued sounds have to start bit earlier // if we want them to sound seamless with the previous clips. // TODO: investigate better solutions? may require reimplementation of the sound queue. if (for_queue && (ch->_sourceClipType == clip->type)) { // try to start queued sounds 1 frame earlier const float trigger_pos = (1000.f / _G(frames_per_second)) * 1.f; if (ch->get_pos_ms() >= (ch->get_length_ms() - trigger_pos)) { lowestPrioritySoFar = priority; lowestPriorityID = i; } } } if ((channelToUse < 0) && (lowestPriorityID >= 0) && (lowestPrioritySoFar <= priority)) { stop_or_fade_out_channel(lowestPriorityID, lowestPriorityID, clip); channelToUse = lowestPriorityID; } else if ((channelToUse >= 0) && (_GP(play).crossfading_in_channel < 1)) { start_fading_in_new_track_if_applicable(channelToUse, clip); } return channelToUse; } bool is_audiotype_allowed_to_play(AudioFileType /*type*/) { // TODO: this is a remnant of an old audio logic, think this function over return _GP(usetup).audio_enabled; } SOUNDCLIP *load_sound_clip(ScriptAudioClip *audioClip, bool repeat) { if (!is_audiotype_allowed_to_play((AudioFileType)audioClip->fileType)) { return nullptr; } update_clip_default_volume(audioClip); SOUNDCLIP *soundClip = nullptr; AssetPath asset_name = get_audio_clip_assetpath(audioClip->bundlingType, audioClip->fileName); switch (audioClip->fileType) { case eAudioFileOGG: soundClip = my_load_static_ogg(asset_name, repeat); break; case eAudioFileMP3: soundClip = my_load_static_mp3(asset_name, repeat); break; case eAudioFileWAV: case eAudioFileVOC: soundClip = my_load_wave(asset_name, repeat); break; case eAudioFileMIDI: soundClip = my_load_midi(asset_name, repeat); break; case eAudioFileMOD: soundClip = my_load_mod(asset_name, repeat); break; default: quitprintf("AudioClip.Play: invalid audio file type encountered: %d", audioClip->fileType); } if (soundClip != nullptr) { soundClip->set_volume100(audioClip->defaultVolume); soundClip->_sourceClipID = audioClip->id; soundClip->_sourceClipType = audioClip->type; } return soundClip; } static void audio_update_polled_stuff() { /////////////////////////////////////////////////////////////////////////// // Do crossfade _GP(play).crossfade_step++; if (_GP(play).crossfading_out_channel > 0 && !AudioChans::GetChannelIfPlaying(_GP(play).crossfading_out_channel)) _GP(play).crossfading_out_channel = 0; if (_GP(play).crossfading_out_channel > 0) { SOUNDCLIP *ch = AudioChans::GetChannel(_GP(play).crossfading_out_channel); int newVolume = ch ? ch->get_volume100() - _GP(play).crossfade_out_volume_per_step : 0; if (newVolume > 0) { ch->set_volume100(newVolume); } else { stop_and_destroy_channel(_GP(play).crossfading_out_channel); _GP(play).crossfading_out_channel = 0; } } if (_GP(play).crossfading_in_channel > 0 && !AudioChans::GetChannelIfPlaying(_GP(play).crossfading_in_channel)) _GP(play).crossfading_in_channel = 0; if (_GP(play).crossfading_in_channel > 0) { SOUNDCLIP *ch = AudioChans::GetChannel(_GP(play).crossfading_in_channel); int newVolume = ch ? ch->get_volume100() + _GP(play).crossfade_in_volume_per_step : 0; if (newVolume > _GP(play).crossfade_final_volume_in) { newVolume = _GP(play).crossfade_final_volume_in; } ch->set_volume100(newVolume); if (newVolume >= _GP(play).crossfade_final_volume_in) { _GP(play).crossfading_in_channel = 0; } } /////////////////////////////////////////////////////////////////////////// // Do audio queue if (_GP(play).new_music_queue_size > 0) { for (int i = 0; i < _GP(play).new_music_queue_size; i++) { ScriptAudioClip *clip = &_GP(game).audioClips[_GP(play).new_music_queue[i].audioClipIndex]; int channel = find_free_audio_channel(clip, clip->defaultPriority, false, true); if (channel >= 0) { QueuedAudioItem itemToPlay = _GP(play).new_music_queue[i]; _GP(play).new_music_queue_size--; for (int j = i; j < _GP(play).new_music_queue_size; j++) { _GP(play).new_music_queue[j] = _GP(play).new_music_queue[j + 1]; } play_audio_clip_on_channel(channel, clip, itemToPlay.priority, itemToPlay.repeat, 0, itemToPlay.cachedClip); i--; } } } /////////////////////////////////////////////////////////////////////////// // Do non-blocking voice speech // NOTE: there's only one speech channel, therefore it's either blocking // or non-blocking at any given time. If it's changed, we'd need to keep // record of every channel, or keep a count of active channels. if (_GP(play).IsNonBlockingVoiceSpeech()) { if (!AudioChans::ChannelIsPlaying(SCHAN_SPEECH)) { stop_voice_nonblocking(); } } } // Applies a volume drop modifier to the clip, in accordance to its audio type static void apply_volume_drop_to_clip(SOUNDCLIP *clip) { int audiotype = clip->_sourceClipType; clip->apply_volume_modifier(-(_GP(game).audioClipTypes[audiotype].volume_reduction_while_speech_playing * 255 / 100)); } static void queue_audio_clip_to_play(ScriptAudioClip *clip, int priority, int repeat) { if (_GP(play).new_music_queue_size >= MAX_QUEUED_MUSIC) { debug_script_log("Too many queued music, cannot add %s", clip->scriptName.GetCStr()); return; } SOUNDCLIP *cachedClip = load_sound_clip(clip, (repeat != 0)); if (cachedClip != nullptr) { _GP(play).new_music_queue[_GP(play).new_music_queue_size].audioClipIndex = clip->id; _GP(play).new_music_queue[_GP(play).new_music_queue_size].priority = priority; _GP(play).new_music_queue[_GP(play).new_music_queue_size].repeat = (repeat != 0); _GP(play).new_music_queue[_GP(play).new_music_queue_size].cachedClip = cachedClip; _GP(play).new_music_queue_size++; } } ScriptAudioChannel *play_audio_clip_on_channel(int channel, ScriptAudioClip *clip, int priority, int repeat, int fromOffset, SOUNDCLIP *soundfx) { if (soundfx == nullptr) { soundfx = load_sound_clip(clip, (repeat) ? true : false); } if (soundfx == nullptr) { debug_script_log("AudioClip.Play: unable to load sound file"); if (_GP(play).crossfading_in_channel == channel) { _GP(play).crossfading_in_channel = 0; } return nullptr; } soundfx->_priority = priority; if (_GP(play).crossfading_in_channel == channel) { soundfx->set_volume100(0); } // Mute the audio clip if fast-forwarding the cutscene if (_GP(play).fast_forward) { soundfx->set_mute(true); // CHECKME!! // [IKM] According to the 3.2.1 logic the clip will restore // its value after cutscene, but only if originalVolAsPercentage // is not zeroed. Something I am not sure about: why does it // disable the clip under condition that there's more than one // channel for this audio type? It does not even check if // anything of this type is currently playing. if (_GP(game).audioClipTypes[clip->type].reservedChannels != 1) soundfx->set_volume100(0); } if (soundfx->play_from(fromOffset) == 0) { // not assigned to a channel, so clean up manually. delete soundfx; soundfx = nullptr; debug_script_log("AudioClip.Play: failed to play sound file"); return nullptr; } // Apply volume drop if any speech voice-over is currently playing // NOTE: there is a confusing logic in sound clip classes, that they do not use // any modifiers when begin playing, therefore we must apply this only after // playback was started. if (!_GP(play).fast_forward && _GP(play).speech_has_voice) apply_volume_drop_to_clip(soundfx); AudioChans::SetChannel(channel, soundfx); return &_G(scrAudioChannel)[channel]; } void remove_clips_of_type_from_queue(int audioType) { int aa; for (aa = 0; aa < _GP(play).new_music_queue_size; aa++) { ScriptAudioClip *clip = &_GP(game).audioClips[_GP(play).new_music_queue[aa].audioClipIndex]; if ((audioType == SCR_NO_VALUE) || (clip->type == audioType)) { _GP(play).new_music_queue_size--; for (int bb = aa; bb < _GP(play).new_music_queue_size; bb++) _GP(play).new_music_queue[bb] = _GP(play).new_music_queue[bb + 1]; aa--; } } } void update_queued_clips_volume(int audioType, int new_vol) { for (int i = 0; i < _GP(play).new_music_queue_size; ++i) { // NOTE: if clip is uncached, the volume will be set from defaults when it is loaded SOUNDCLIP *sndclip = _GP(play).new_music_queue[i].cachedClip; if (sndclip) { ScriptAudioClip *clip = &_GP(game).audioClips[_GP(play).new_music_queue[i].audioClipIndex]; if (clip->type == audioType) sndclip->set_volume100(new_vol); } } } ScriptAudioChannel *play_audio_clip(ScriptAudioClip *clip, int priority, int repeat, int fromOffset, bool queueIfNoChannel) { if (!queueIfNoChannel) remove_clips_of_type_from_queue(clip->type); if (priority == SCR_NO_VALUE) priority = clip->defaultPriority; if (repeat == SCR_NO_VALUE) repeat = clip->defaultRepeat; int channel = find_free_audio_channel(clip, priority, !queueIfNoChannel, queueIfNoChannel); if (channel < 0) { if (queueIfNoChannel) queue_audio_clip_to_play(clip, priority, repeat); else debug_script_log("AudioClip.Play: no channels available to interrupt PRI:%d TYPE:%d", priority, clip->type); return nullptr; } return play_audio_clip_on_channel(channel, clip, priority, repeat, fromOffset); } ScriptAudioChannel *play_audio_clip_by_index(int audioClipIndex) { if ((audioClipIndex >= 0) && ((size_t)audioClipIndex < _GP(game).audioClips.size())) return AudioClip_Play(&_GP(game).audioClips[audioClipIndex], SCR_NO_VALUE, SCR_NO_VALUE); else return nullptr; } void stop_and_destroy_channel_ex(int chid, bool resetLegacyMusicSettings) { if ((chid < 0) || (chid >= TOTAL_AUDIO_CHANNELS)) quit("!StopChannel: invalid channel ID"); SOUNDCLIP *ch = AudioChans::GetChannel(chid); if (ch != nullptr) { delete ch; AudioChans::SetChannel(chid, nullptr); ch = nullptr; } if (_GP(play).crossfading_in_channel == chid) _GP(play).crossfading_in_channel = 0; if (_GP(play).crossfading_out_channel == chid) _GP(play).crossfading_out_channel = 0; // don't update '_G(crossFading)' here as it is updated in all the cross-fading functions. // destroyed an ambient sound channel if (chid < _GP(game).numGameChannels) { if (_GP(ambient)[chid].channel > 0) _GP(ambient)[chid].channel = 0; } if ((chid == SCHAN_MUSIC) && (resetLegacyMusicSettings)) { _GP(play).cur_music_number = -1; _G(current_music_type) = 0; } } void stop_and_destroy_channel(int chid) { stop_and_destroy_channel_ex(chid, true); } void export_missing_audiochans() { for (int i = 0; i < _GP(game).numCompatGameChannels; ++i) { int h = ccGetObjectHandleFromAddress(&_G(scrAudioChannel)[i]); if (h <= 0) ccRegisterManagedObject(&_G(scrAudioChannel)[i], &_GP(ccDynamicAudio)); } } // ***** BACKWARDS COMPATIBILITY WITH OLD AUDIO SYSTEM ***** // int get_old_style_number_for_sound(int sound_number) { // In the legacy audio system treat sound_number as an old style number if (_GP(game).IsLegacyAudioSystem()) { return sound_number; } // Treat sound number as a real clip index if (sound_number >= 0) { int old_style_number = 0; if (sscanf(_GP(game).audioClips[sound_number].scriptName.GetCStr(), "aSound%d", &old_style_number) == 1) return old_style_number; } return 0; } SOUNDCLIP *load_sound_clip_from_old_style_number(bool isMusic, int indexNumber, bool repeat) { ScriptAudioClip *audioClip = GetAudioClipForOldStyleNumber(_GP(game), isMusic, indexNumber); if (audioClip != nullptr) { return load_sound_clip(audioClip, repeat); } return nullptr; } //============================================================================= int get_volume_adjusted_for_distance(int volume, int sndX, int sndY, int sndMaxDist) { int distx = _G(playerchar)->x - sndX; int disty = _G(playerchar)->y - sndY; // it uses Allegro's "fix" sqrt without the :: int dist = (int)::sqrt((double)(distx * distx + disty * disty)); // if they're quite close, full volume int wantvol = volume; if (dist >= AMBIENCE_FULL_DIST) { // get the relative volume wantvol = ((dist - AMBIENCE_FULL_DIST) * volume) / sndMaxDist; // closer is louder wantvol = volume - wantvol; } return wantvol; } void update_directional_sound_vol() { for (int chnum = NUM_SPEECH_CHANS; chnum < _GP(game).numGameChannels; chnum++) { auto *ch = AudioChans::GetChannelIfPlaying(chnum); if ((ch != nullptr) && (ch->_xSource >= 0)) { ch->apply_directional_modifier( get_volume_adjusted_for_distance(ch->get_volume255(), ch->_xSource, ch->_ySource, ch->_maximumPossibleDistanceAway) - ch->get_volume255()); } } } void update_ambient_sound_vol() { for (int chan = NUM_SPEECH_CHANS; chan < _GP(game).numGameChannels; chan++) { AmbientSound *thisSound = &_GP(ambient)[chan]; if (thisSound->channel == 0) continue; int sourceVolume = thisSound->vol; if (_GP(play).speech_has_voice) { // Negative value means set exactly; positive means drop that amount if (_GP(play).speech_music_drop < 0) sourceVolume = -_GP(play).speech_music_drop; else sourceVolume -= _GP(play).speech_music_drop; if (sourceVolume < 0) sourceVolume = 0; if (sourceVolume > 255) sourceVolume = 255; } // Adjust ambient volume so it maxes out at overall sound volume int ambientvol = (sourceVolume * _GP(play).sound_volume) / 255; int wantvol; if ((thisSound->x == 0) && (thisSound->y == 0)) { wantvol = ambientvol; } else { wantvol = get_volume_adjusted_for_distance(ambientvol, thisSound->x, thisSound->y, thisSound->maxdist); } auto *ch = AudioChans::GetChannelIfPlaying(thisSound->channel); if (ch) ch->set_volume255(wantvol); } } SOUNDCLIP *load_sound_and_play(ScriptAudioClip *aclip, bool repeat) { SOUNDCLIP *soundfx = load_sound_clip(aclip, repeat); if (!soundfx) { return nullptr; } if (soundfx->play() == 0) { // not assigned to a channel, so clean up manually. delete soundfx; return nullptr; } return soundfx; } void stop_all_sound_and_music() { stopmusic(); stop_voice_nonblocking(); // make sure it doesn't start crossfading when it comes back _G(crossFading) = 0; // any ambient sound will be aborted for (int i = 0; i < TOTAL_AUDIO_CHANNELS; ++i) stop_and_destroy_channel(i); } void shutdown_sound() { stop_all_sound_and_music(); // game logic #if !AGS_PLATFORM_SCUMMVM audio_core_shutdown(); // audio core system #endif sys_audio_shutdown(); // backend _GP(usetup).audio_enabled = false; } // the sound will only be played if there is a free channel or // it has a priority >= an existing sound to override static int play_sound_priority(int val1, int priority) { int lowest_pri = 9999, lowest_pri_id = -1; // find a free channel to play it on for (int i = SCHAN_NORMAL; i < _GP(game).numGameChannels; i++) { auto *ch = AudioChans::GetChannelIfPlaying(i); if (val1 < 0) { // Playing sound -1 means iterate through and stop all sound if (ch) stop_and_destroy_channel(i); } else if (ch == nullptr || !ch->is_playing()) { // PlaySoundEx will destroy the previous channel value. const int usechan = PlaySoundEx(val1, i); if (usechan >= 0) { // channel will hold a different clip here assert(usechan == i); auto *chan = AudioChans::GetChannel(usechan); if (chan) chan->_priority = priority; } return usechan; } else if (ch->_priority < lowest_pri) { lowest_pri = ch->_priority; lowest_pri_id = i; } } if (val1 < 0) return -1; // no free channels, see if we have a high enough priority // to override one if (priority >= lowest_pri) { const int usechan = PlaySoundEx(val1, lowest_pri_id); if (usechan >= 0) { assert(usechan == lowest_pri_id); auto *ch = AudioChans::GetChannel(usechan); if (ch) ch->_priority = priority; return usechan; } } return -1; } int play_sound(int val1) { return play_sound_priority(val1, 10); } //============================================================================= void cancel_scheduled_music_update() { _G(music_update_scheduled) = false; } void schedule_music_update_at(AGS_Clock::time_point at) { _G(music_update_scheduled) = true; _G(music_update_at) = at; } void postpone_scheduled_music_update_by(std::chrono::milliseconds duration) { if (!_G(music_update_scheduled)) { return; } _G(music_update_at) += duration; } void process_scheduled_music_update() { if (!_G(music_update_scheduled)) { return; } if (_G(music_update_at) > AGS_Clock::now()) { return; } cancel_scheduled_music_update(); update_music_volume(); apply_volume_drop_modifier(false); update_ambient_sound_vol(); } // end scheduled music update functions //============================================================================= void clear_music_cache() { if (_G(cachedQueuedMusic) != nullptr) { delete _G(cachedQueuedMusic); _G(cachedQueuedMusic) = nullptr; } } static void play_new_music(int mnum, SOUNDCLIP *music); void play_next_queued() { // check if there's a queued one to play if (_GP(play).music_queue_size > 0) { int tuneToPlay = _GP(play).music_queue[0]; if (tuneToPlay >= QUEUED_MUSIC_REPEAT) { // Loop it! _GP(play).music_repeat++; play_new_music(tuneToPlay - QUEUED_MUSIC_REPEAT, _G(cachedQueuedMusic)); _GP(play).music_repeat--; } else { // Don't loop it! int repeatWas = _GP(play).music_repeat; _GP(play).music_repeat = 0; play_new_music(tuneToPlay, _G(cachedQueuedMusic)); _GP(play).music_repeat = repeatWas; } // don't free the memory, as it has been transferred onto the // main music channel _G(cachedQueuedMusic) = nullptr; _GP(play).music_queue_size--; for (int i = 0; i < _GP(play).music_queue_size; i++) _GP(play).music_queue[i] = _GP(play).music_queue[i + 1]; if (_GP(play).music_queue_size > 0) _G(cachedQueuedMusic) = load_music_from_disk(_GP(play).music_queue[0], 0); } } int calculate_max_volume() { // quieter so that sounds can be heard better int newvol = _GP(play).music_master_volume + ((int)_GP(thisroom).Options.MusicVolume) * LegacyRoomVolumeFactor; if (newvol > 255) newvol = 255; if (newvol < 0) newvol = 0; if (_GP(play).fast_forward) newvol = 0; return newvol; } // add/remove the volume drop to the audio channels while speech is playing void apply_volume_drop_modifier(bool applyModifier) { for (int i = NUM_SPEECH_CHANS; i < _GP(game).numGameChannels; i++) { auto *ch = AudioChans::GetChannelIfPlaying(i); if (ch && ch->_sourceClipID >= 0) { if (applyModifier) apply_volume_drop_to_clip(ch); else ch->apply_volume_modifier(0); // reset modifier } } } // Checks if speech voice-over is currently playing, and reapply volume drop to all other active clips void update_volume_drop_if_voiceover() { apply_volume_drop_modifier(_GP(play).speech_has_voice); } // Sync logical game channels with the audio backend: // startup new assigned clips, apply changed parameters. void sync_audio_playback() { for (int i = 0; i < TOTAL_AUDIO_CHANNELS; ++i) { // update the playing channels, and dispose the finished / invalid ones auto *ch = AudioChans::GetChannelIfPlaying(i); if (ch && !ch->update()) { AudioChans::SetChannel(i, nullptr); delete ch; } } } // Update the music, and advance the crossfade on a step // (this should only be called once per game loop) void update_audio_system_on_game_loop() { update_polled_stuff(); // Sync logical game channels with the audio backend // NOTE: we update twice, first time here - because we need to know // which clips are still playing before updating the sound transitions // and queues, then second time later - because we need to apply any // changes to channels / parameters. // TODO: investigate options for optimizing this. sync_audio_playback(); process_scheduled_music_update(); audio_update_polled_stuff(); if (_G(crossFading)) { _G(crossFadeStep)++; update_music_volume(); } // Check if the current music has finished playing if ((_GP(play).cur_music_number >= 0) && (_GP(play).fast_forward == 0)) { if (IsMusicPlaying() == 0) { // The current music has finished _GP(play).cur_music_number = -1; play_next_queued(); } else if ((_GP(game).options[OPT_CROSSFADEMUSIC] > 0) && (_GP(play).music_queue_size > 0) && (!_G(crossFading))) { // want to crossfade, and new tune in the queue auto *ch = AudioChans::GetChannel(SCHAN_MUSIC); if (ch) { int curpos = ch->get_pos_ms(); int muslen = ch->get_length_ms(); if ((curpos > 0) && (muslen > 0)) { // we want to crossfade, and we know how far through // the tune we are int takesSteps = calculate_max_volume() / _GP(game).options[OPT_CROSSFADEMUSIC]; int takesMs = ::lround(takesSteps * 1000.0f / get_game_fps()); if (curpos >= muslen - takesMs) play_next_queued(); } } } } if (_G(loopcounter) % 5 == 0) { // TODO: investigate why we do this each 5 frames? update_ambient_sound_vol(); update_directional_sound_vol(); } // Sync logical game channels with the audio backend again sync_audio_playback(); } void stopmusic() { if (_G(crossFading) > 0) { // stop in the middle of a new track fading in // Abort the new track, and let the old one finish fading out stop_and_destroy_channel(_G(crossFading)); _G(crossFading) = -1; } else if (_G(crossFading) < 0) { // the music is already fading out if (_GP(game).options[OPT_CROSSFADEMUSIC] <= 0) { // If they have since disabled crossfading, stop the fadeout stop_and_destroy_channel(SCHAN_MUSIC); _G(crossFading) = 0; _G(crossFadeStep) = 0; update_music_volume(); } } else if ((_GP(game).options[OPT_CROSSFADEMUSIC] > 0) && (AudioChans::GetChannelIfPlaying(SCHAN_MUSIC) != nullptr) && (_G(current_music_type) != 0) && (_G(current_music_type) != MUS_MIDI) && (_G(current_music_type) != MUS_MOD)) { _G(crossFading) = -1; _G(crossFadeStep) = 0; _G(crossFadeVolumePerStep) = _GP(game).options[OPT_CROSSFADEMUSIC]; _G(crossFadeVolumeAtStart) = calculate_max_volume(); } else stop_and_destroy_channel(SCHAN_MUSIC); _GP(play).cur_music_number = -1; _G(current_music_type) = 0; } void update_music_volume() { if ((_G(current_music_type)) || (_G(crossFading) < 0)) { // targetVol is the maximum volume we're fading in to // newvol is the starting volume that we faded out from int targetVol = calculate_max_volume(); int newvol; if (_G(crossFading)) newvol = _G(crossFadeVolumeAtStart); else newvol = targetVol; // fading out old track, target volume is silence if (_G(crossFading) < 0) targetVol = 0; if (_G(crossFading)) { int curvol = _G(crossFadeVolumePerStep) * _G(crossFadeStep); if ((curvol > targetVol) && (curvol > newvol)) { // it has fully faded to the new track newvol = targetVol; stop_and_destroy_channel_ex(SCHAN_MUSIC, false); if (_G(crossFading) > 0) { AudioChans::MoveChannel(SCHAN_MUSIC, _G(crossFading)); } _G(crossFading) = 0; } else { if (_G(crossFading) > 0) { auto *ch = AudioChans::GetChannel(_G(crossFading)); if (ch) ch->set_volume255((curvol > targetVol) ? targetVol : curvol); } newvol -= curvol; if (newvol < 0) newvol = 0; } } auto *ch = AudioChans::GetChannel(SCHAN_MUSIC); if (ch) ch->set_volume255(newvol); } } // Ensures crossfader is stable after loading (or failing to load) new music // NOTE: part of the legacy audio logic void post_new_music_check() { if ((_G(crossFading) > 0) && (AudioChans::GetChannel(_G(crossFading)) == nullptr)) { _G(crossFading) = 0; // Was fading out but then they played invalid music, continue to fade out if (AudioChans::GetChannel(SCHAN_MUSIC) != nullptr) _G(crossFading) = -1; } } int prepare_for_new_music() { int useChannel = SCHAN_MUSIC; if ((_GP(game).options[OPT_CROSSFADEMUSIC] > 0) && (AudioChans::GetChannelIfPlaying(SCHAN_MUSIC) != nullptr) && (_G(current_music_type) != MUS_MIDI) && (_G(current_music_type) != MUS_MOD)) { if (_G(crossFading) > 0) { // It's still crossfading to the previous track stop_and_destroy_channel_ex(SCHAN_MUSIC, false); AudioChans::MoveChannel(SCHAN_MUSIC, _G(crossFading)); _G(crossFading) = 0; update_music_volume(); } else if (_G(crossFading) < 0) { // an old track is still fading out, no new music yet // Do nothing, and keep the current crossfade step } else { // start crossfading _G(crossFadeStep) = 0; _G(crossFadeVolumePerStep) = _GP(game).options[OPT_CROSSFADEMUSIC]; _G(crossFadeVolumeAtStart) = calculate_max_volume(); } useChannel = SPECIAL_CROSSFADE_CHANNEL; _G(crossFading) = useChannel; } else { // crossfading is now turned off stopmusic(); // ensure that any traces of old tunes fading are eliminated // (otherwise the new track will be faded out) _G(crossFading) = 0; } // Just make sure, because it will be overwritten in a sec if (AudioChans::GetChannel(useChannel) != nullptr) stop_and_destroy_channel(useChannel); return useChannel; } ScriptAudioClip *get_audio_clip_for_music(int mnum) { if (mnum >= QUEUED_MUSIC_REPEAT) mnum -= QUEUED_MUSIC_REPEAT; return GetAudioClipForOldStyleNumber(_GP(game), true, mnum); } SOUNDCLIP *load_music_from_disk(int mnum, bool doRepeat) { if (mnum >= QUEUED_MUSIC_REPEAT) { mnum -= QUEUED_MUSIC_REPEAT; doRepeat = true; } SOUNDCLIP *loaded = load_sound_clip_from_old_style_number(true, mnum, doRepeat); if ((loaded == nullptr) && (mnum > 0)) { debug_script_warn("Music %d not found", mnum); debug_script_log("FAILED to load music %d", mnum); } return loaded; } static void play_new_music(int mnum, SOUNDCLIP *music) { if (_G(debug_flags) & DBG_NOMUSIC) return; if ((_GP(play).cur_music_number == mnum) && (music == nullptr)) { debug_script_log("PlayMusic %d but already playing", mnum); return; // don't play the music if it's already playing } ScriptAudioClip *aclip = get_audio_clip_for_music(mnum); if (aclip && !is_audiotype_allowed_to_play((AudioFileType)aclip->fileType)) return; int useChannel = SCHAN_MUSIC; debug_script_log("Playing music %d", mnum); if (mnum < 0) { stopmusic(); return; } if (_GP(play).fast_forward) { // while skipping cutscene, don't change the music _GP(play).end_cutscene_music = mnum; return; } useChannel = prepare_for_new_music(); _GP(play).cur_music_number = mnum; _G(current_music_type) = 0; _GP(play).current_music_repeating = _GP(play).music_repeat; // now that all the previous music is unloaded, load in the new one SOUNDCLIP *new_clip; if (music != nullptr) new_clip = music; else new_clip = load_music_from_disk(mnum, (_GP(play).music_repeat > 0)); auto *ch = AudioChans::SetChannel(useChannel, new_clip); if (ch != nullptr) { if (!ch->play()) { // previous behavior was to set channel[] to null on error, so continue to do that here. delete ch; ch = nullptr; AudioChans::SetChannel(useChannel, nullptr); } else _G(current_music_type) = ch->get_sound_type(); } post_new_music_check(); update_music_volume(); } void newmusic(int mnum) { play_new_music(mnum, nullptr); } } // namespace AGS3