/* ----------------------------------------------------------------------------- This source file is part of OpenSpace3D For the latest info, see http://www.openspace3d.com Copyright (c) 2016 I-maginer This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA, or go to http://www.gnu.org/copyleft/lesser.txt ----------------------------------------------------------------------------- */ fun audioGetEffectByName(name)= if (!strcmpi name "REVERB") then AUDIO_EFFECT_REVERB else if (!strcmpi name "CHORUS") then AUDIO_EFFECT_CHORUS else if (!strcmpi name "DISTORTION") then AUDIO_EFFECT_DISTORTION else if (!strcmpi name "ECHO") then AUDIO_EFFECT_ECHO else if (!strcmpi name "FLANGER") then AUDIO_EFFECT_FLANGER else if (!strcmpi name "RING_MODULATOR") then AUDIO_EFFECT_FLANGER else if (!strcmpi name "COMPRESSOR") then AUDIO_EFFECT_COMPRESSOR else if (!strcmpi name "EQUALIZER") then AUDIO_EFFECT_EQUALIZER else AUDIO_EFFECT_NONE;; fun audioGetAvailableEffects()= "NONE"::"REVERB"::"CHORUS"::"DISTORTION"::"ECHO"::"FLANGER"::"RING_MODULATOR"::"COMPRESSOR"::"EQUALIZER"::nil;; fun audioGetAvailablePresets(name)= if (!strcmpi name "REVERB") then "Generic"::"Padded Cell"::"Room"::"Bath Room"::"Living Room"::"Stone Room"::"Auditorium"::"Concert Hall"::"Cave"::"Arena"::"Hangar"::"Carpeted Hallway"::"Hallway"::"Stone Corridor"::"Alley"::"Forest"::"City"::"Mountains"::"Quarry"::"Plain"::"Parking Lot"::"Sewer Pipe"::"Under Water"::"Drugged"::"Dizzy"::"Psychotic"::nil else nil;; /// AudioPlayer : player that can use both cAudio and FFmpeg as backends transparently /// struct AudioPlayer = [ APL_sName : S, APL_sSoundPath : S, APL_asSound : ObjAudio, APL_mediaPlayer : ObjMediaPlayer, APL_bIsPlaying : I, APL_bLoop : I, APL_bEnableEffects : I, APL_iVolume : I, APL_fPitch : F, APL_iSampleRate : I, APL_iAudioFormat : I, APL_3dSource : SO3_OBJECT, APL_fAttenuation : F, APL_tCone : [I I I], APL_iDebounceTick : I, APL_stopCallback : fun [AudioPlayer] I ]mkAudioPlayer;; fun audioPlayerCreate(name, loop, enableEffects, volume, pitch, stopCallback) = mkAudioPlayer [name nil nil nil 0 loop enableEffects volume (maxf 0.001 (absf pitch)) 44100 MP_AUDIO_16BIT_STEREO nil 0.0 [0 0 0] 0 stopCallback];; fun audioPlayerCreate3d(name, loop, enableEffects, volume, pitch, stopCallback, object, attenuation, cone) = mkAudioPlayer [name nil nil nil 0 loop enableEffects volume (maxf 0.001 (absf pitch)) 44100 MP_AUDIO_16BIT_MONO object attenuation cone 0 stopCallback];; fun audioPlayerSet3dSource(sndstr, object) = set sndstr.APL_3dSource = object;; fun audioPlayerSetAttenuation(sndstr, attenuation) = set sndstr.APL_fAttenuation = attenuation;; fun audioPlayerSetCone(sndstr, cone) = set sndstr.APL_tCone = cone;; fun audioPlayerInternalStopCb(obj, sndstr) = set sndstr.APL_bIsPlaying = 0; if (sndstr.APL_stopCallback == nil) then nil else exec sndstr.APL_stopCallback with [sndstr];; /** \brief If the sound is a 3D sound, update its position according to the position of its source 3D object. * When using FFmpeg, this function fills the cAudio buffer with raw audio data from FFmpeg. * It should be called in pre-render callbacks. * \param sndstr AudioPlayer instance. * \return Nil if update wasn't performed (audio not playing / not using FFmpeg / other failure). */ fun audioPlayerUpdate(sndstr) = if !sndstr.APL_bIsPlaying then nil else if (sndstr.APL_3dSource == nil) then nil else let SO3ObjectGetGlobalPosition sndstr.APL_3dSource -> pos in let SO3ObjectGetDerivedDirectionAxis sndstr.APL_3dSource [0.0 0.0 1.0] -> dir in _AudioSetPosition sndstr.APL_asSound pos dir; if (sndstr.APL_mediaPlayer == nil) then nil else _AudioFillBuffer sndstr.APL_asSound _GETmediaPlayerAudio sndstr.APL_mediaPlayer;; fun audioPlayerUpdateBuffer(sndstr, buff) = if !sndstr.APL_bIsPlaying then nil else if (sndstr.APL_3dSource == nil) then nil else let SO3ObjectGetGlobalPosition sndstr.APL_3dSource -> pos in let SO3ObjectGetDerivedDirectionAxis sndstr.APL_3dSource [0.0 0.0 1.0] -> dir in _AudioSetPosition sndstr.APL_asSound pos dir; _AudioFillBuffer sndstr.APL_asSound buff;; fun audioPlayerInternalStop(sndstr) = _AudioStop sndstr.APL_asSound; set sndstr.APL_bIsPlaying = 0; if (sndstr.APL_mediaPlayer == nil) then nil else _STOPmediaPlayer sndstr.APL_mediaPlayer; 0;; fun audioPlayerStop(sndstr) = audioPlayerInternalStop(sndstr); // Force end event on user stop for MediaPlayer if (sndstr.APL_mediaPlayer == nil) then nil else audioPlayerInternalStopCb nil sndstr; 0;; fun audioPlayerDelete(sndstr) = if (sndstr.APL_asSound == nil) then nil else ( _DSAudio sndstr.APL_asSound; set sndstr.APL_asSound = nil; ); if (sndstr.APL_mediaPlayer == nil) then nil else ( _DSmediaPlayer sndstr.APL_mediaPlayer; set sndstr.APL_mediaPlayer = nil; ); set sndstr.APL_bIsPlaying = 0; 0;; fun audioPlayerPlay(sndstr) = if (sndstr.APL_bIsPlaying) then nil else ( if (sndstr.APL_mediaPlayer == nil) then nil else _PLAYmediaPlayer sndstr.APL_mediaPlayer; if (sndstr.APL_3dSource != nil) then ( let SO3ObjectGetGlobalPosition sndstr.APL_3dSource -> pos in let SO3ObjectGetDerivedDirectionAxis sndstr.APL_3dSource [0.0 0.0 1.0] -> dir in if ((_AudioIsPlaying sndstr.APL_asSound) && ((_tickcount - sndstr.APL_iDebounceTick) < 60)) then nil else ( _AudioSetPosition sndstr.APL_asSound pos dir; set sndstr.APL_iDebounceTick = _tickcount; // Never set cAudio to loop if we're using MediaPlayer _AudioPlay3d sndstr.APL_asSound sndstr.APL_fAttenuation (sndstr.APL_bLoop && (sndstr.APL_mediaPlayer == nil)); set sndstr.APL_bIsPlaying = 1; ) ) else ( _AudioPlay sndstr.APL_asSound (sndstr.APL_bLoop && (sndstr.APL_mediaPlayer == nil)); set sndstr.APL_bIsPlaying = 1; ); ); 0;; fun audioPlayerPause(sndstr)= if (sndstr.APL_mediaPlayer != nil) then ( // Stop if pause fails (could be the case with livestreams) if ((_PAUSEmediaPlayer sndstr.APL_mediaPlayer) != nil) then ( _AudioPause sndstr.APL_asSound; 0; ) else ( audioPlayerInternalStop sndstr; 0; ); ) else ( _AudioPause sndstr.APL_asSound; 0; ); set sndstr.APL_bIsPlaying = 0; 0;; fun audioPlayerGetLength(sndstr)= if (sndstr.APL_mediaPlayer != nil) then ( let _GETmediaPlayerLength sndstr.APL_mediaPlayer -> ms in (itof ms) /. 1000.0; ) else ( _AudioGetTotalTime sndstr.APL_asSound; );; fun audioPlayerGetCurrentTime(sndstr)= if (sndstr.APL_mediaPlayer != nil) then ( let _GETmediaPlayerTime sndstr.APL_mediaPlayer -> ms in (itof ms) /. 1000.0; ) else ( _AudioGetCurrentTime sndstr.APL_asSound; );; fun openSoundMediaPlayer(sndstr, path) = audioPlayerInternalStop sndstr; // Create a new MediaPlayer on first run, or if the previous sound file was read by cAudio if (sndstr.APL_mediaPlayer != nil) then nil else ( set sndstr.APL_mediaPlayer = _CRmediaPlayer _channel; _SETmediaPlayerAudioFormat sndstr.APL_mediaPlayer sndstr.APL_iAudioFormat sndstr.APL_iSampleRate; _SETmediaPlayerLoop sndstr.APL_mediaPlayer sndstr.APL_bLoop; _CBmediaPlayerEnd sndstr.APL_mediaPlayer @audioPlayerInternalStopCb sndstr; ); if (strIsUrl path) then _OPENmediaPlayerUrl sndstr.APL_mediaPlayer path else _OPENmediaPlayerFile sndstr.APL_mediaPlayer (_checkpack path); if !(_GETmediaPlayerHasAudio sndstr.APL_mediaPlayer) then ( _DSmediaPlayer sndstr.APL_mediaPlayer; set sndstr.APL_mediaPlayer = nil; nil; ) else ( _DSAudio sndstr.APL_asSound; set sndstr.APL_asSound = _CRAudioStream _channel sndstr.APL_sName sndstr.APL_iSampleRate sndstr.APL_iAudioFormat; _AudioSetVolume sndstr.APL_asSound sndstr.APL_iVolume; _AudioSetPitch sndstr.APL_asSound sndstr.APL_fPitch; _AudioEnableSoundEffect sndstr.APL_asSound sndstr.APL_bEnableEffects; );; fun openSoundCaudio(sndstr, path)= // When reading from a path, we don't need a MediaPlayer. Delete it if we have one around, as well as the sound instance. audioPlayerDelete sndstr; set sndstr.APL_asSound = _CRAudio _channel sndstr.APL_sName (_checkpack path) 1; if sndstr.APL_asSound == nil then nil else if (_CBAudioEnd sndstr.APL_asSound @audioPlayerInternalStopCb sndstr) == nil then nil else if (_AudioSetVolume sndstr.APL_asSound sndstr.APL_iVolume) == nil then nil else if (_AudioSetPitch sndstr.APL_asSound sndstr.APL_fPitch) == nil then nil else _AudioEnableSoundEffect sndstr.APL_asSound sndstr.APL_bEnableEffects;; fun audioPlayerSetVolume(sndstr, vol) = if (sndstr.APL_asSound == nil) then nil else _AudioSetVolume sndstr.APL_asSound (abs vol);; fun audioPlayerSetPitch(sndstr, pitch) = set sndstr.APL_fPitch = maxf 0.001 (absf pitch); if (sndstr.APL_asSound == nil) then nil else _AudioSetPitch sndstr.APL_asSound sndstr.APL_fPitch;; /** \brief Seek relative to the current position in the sound. * Note that seeking is not always supported by audio sources. * \param offset Amount of time in seconds, relative to the current time. * \return Nil on failure. */ fun audioPlayerSeek(sndstr, offset) = if (sndstr.APL_mediaPlayer != nil) then ( if !(_GETmediaPlayerIsSeekable sndstr.APL_mediaPlayer) then nil else ( let ftoi (offset *. 1000.0) -> timeDiff in let _GETmediaPlayerTime sndstr.APL_mediaPlayer -> now in _SEEKmediaPlayer sndstr.APL_mediaPlayer (now + timeDiff); // Flush internal audio buffer _AudioStop sndstr.APL_asSound; if ((_GETmediaPlayerState sndstr.APL_mediaPlayer) != MP_STATE_PLAYING) then nil else _AudioPlay sndstr.APL_asSound 0; ); ) else if (sndstr.APL_asSound != nil) then _AudioSeek sndstr.APL_asSound offset 1 else nil;; fun audioPlayerEnableEffect(sndstr) = _AudioEnableSoundEffect sndstr.APL_asSound 1;; fun audioPlayerDisableEffect(sndstr) = _AudioEnableSoundEffect sndstr.APL_asSound 0;; fun audioPlayerOpenSound(sndstr, newSound) = if (newSound == nil) then nil else ( // Try to open the sound with cAudio, if it fails try with FFmpeg // When no sound card this always fail and create a media player set sndstr.APL_sSoundPath = newSound; let getFileExt newSound -> ext in if (!strIsUrl newSound) && ((!strcmpi "wav" ext) || (!strcmpi "ogg" ext) || (!strcmpi "mp3" ext)) then openSoundCaudio sndstr newSound else openSoundMediaPlayer sndstr newSound; let sndstr.APL_bIsPlaying -> wasPlaying in if (sndstr.APL_asSound != nil) then ( // Do additional setup for 3d sounds if (sndstr.APL_3dSource != nil) then let sndstr.APL_tCone -> [innerangle outerangle outervol] in _AudioSetCone sndstr.APL_asSound innerangle outerangle outervol else 0; if wasPlaying then audioPlayerPlay sndstr else 0 ) else nil; );; fun audioPlayerOpenBuffered(sndstr, samplerate, format) = audioPlayerDelete sndstr; set sndstr.APL_iSampleRate = samplerate; set sndstr.APL_iAudioFormat = format; set sndstr.APL_asSound = _CRAudioStream _channel sndstr.APL_sName sndstr.APL_iSampleRate sndstr.APL_iAudioFormat; if sndstr.APL_asSound == nil then nil else if (_CBAudioEnd sndstr.APL_asSound @audioPlayerInternalStopCb sndstr) == nil then nil else if (_AudioSetVolume sndstr.APL_asSound sndstr.APL_iVolume) == nil then nil else if (_AudioSetPitch sndstr.APL_asSound sndstr.APL_fPitch) == nil then nil else _AudioEnableSoundEffect sndstr.APL_asSound sndstr.APL_bEnableEffects; let sndstr.APL_bIsPlaying -> wasPlaying in if (sndstr.APL_asSound != nil) then ( // Do additional setup for 3d sounds if (sndstr.APL_3dSource != nil) then let sndstr.APL_tCone -> [innerangle outerangle outervol] in _AudioSetCone sndstr.APL_asSound innerangle outerangle outervol else 0; if wasPlaying then audioPlayerPlay sndstr else 0 ) else nil;; fun audioPlayerGetCurrentSound(sndstr) = sndstr.APL_sSoundPath;;