How to crossfade music

Started by eri0o, Sat 11/11/2017 20:09:34

Previous topic - Next topic

eri0o

Hello!

I need to crossfade between two music tracks. I think this isn't so unusual, so probably someone here has already done it.

an excerpt of my audio player code is below
Code: ags

AudioChannel *acMusic;
AudioClip * currentMusic;
bool stopped;
int globalMusicVol;
AudioClip * previousmusic ;


static void MusicPlayer::play(AudioClip * musicClip,  int position){ 
  stopped = false;

  if(previousmusic==musicClip){
    //don't play music if it's already playing
    return;
  }
  
  //stop previous music
  if(previousmusic != null){
    previousmusic.Stop();
  }
  
  previousmusic = musicClip;
  
  currentMusic = musicClip;
  if(position == 0){
    acMusic = musicClip.Play(eAudioPriorityHigh, eRepeat);
  } else {
    acMusic = musicClip.PlayFrom(position,eAudioPriorityHigh, eRepeat);
    
  }
  acMusic.Volume = globalMusicVol;
  updateAllVolumes();
}

static void MusicPlayer::playFromPreviousTime(AudioClip * musicClip, float crossfadeTime){ 
  if(crossfadeTime!= 0.0){
    
  } else {
    int previousPosition = acMusic.Position;
    MusicPlayer.play(musicClip, previousPosition);
  }
}


The function I am working is static void MusicPlayer::playFromPreviousTime(AudioClip * musicClip, float crossfadeTime).

I want to enable the possibility to crossfade between two songs that have the exact same length. How to do this? I only have 1 audio channel for music, and I am not using the ambient channel, would be possible to set the audioclip type to whichever isn't the last one?

Cassiebsg told me there is the possibility to use the following function too:
SetGameOption (OPT_CROSSFADEMUSIC, x); // where x= 0 (no), 1 (slow), 2 (slowish), 3 (medium) and 4 (fast)

But... I would like to have control over the crossfade time. Below is my full audio module code, it has dependency on the Tween Module.


MusicPlayer.ash
Spoiler
Code: ags

// new module header

#define MusicPlayer_CROSSFADETIME 0


struct MusicPlayer
{
  import static void play(AudioClip * musicClip,  int position = 0);
  import static void playFromPreviousTime(AudioClip * musicClip, float crossfadeTime = MusicPlayer_CROSSFADETIME);
  import static AudioClip * getCurrentMusic();
  import static void stop(AudioClip * musicClip);
  import static void setMusicPosition(int x, int y);
  import static void removeMusicPosition();
  import static void tweenMusicPosition(int x, int y,  float timing);
  import static void setGlobalVolume(int musicVolume);
  import static int  getGlobalVolume();
  import static void setVolume(int musicVolume);
  import static void tweenVolume(int musicVolume, float timing);
};

struct SFXPlayer
{
  import static void play(AudioClip * soundClip);
  import static void stop(AudioClip * soundClip);
  import static void setGlobalVolume(int soundVolume);
  import static int  getGlobalVolume();
  import static void setVolume(int soundVolume);
};

import void SPlayerSetGameVolume(int gameVolume);
import int SPlayerGetGameVolume();
import void SPlayerSetDefaults();

[close]

MusicPlayer.asc
Spoiler
Code: ags

// MusicPlayer variables
AudioChannel *acMusic;
AudioClip * currentMusic;
bool stopped;
int globalMusicVol;
int currentMusicVolue;

// SFXPlayer variables
AudioChannel *acSound;
int gloabalSoundVol;
int orignalMusicX;
int orignalMusicY;

function updateAllVolumes(){
  int i = 0;
  AudioChannel *ac;
   
  while (i < System.AudioChannelCount)
  {
    ac = System.AudioChannels[i];
   
    if (ac.PlayingClip != currentMusic)
    {
      ac.Volume = gloabalSoundVol;
    } else {
      ac.Volume = globalMusicVol;
    }
    i++;
  }  
}


AudioClip * previousmusic ;


static AudioClip * MusicPlayer::getCurrentMusic(){
  return currentMusic;  
}

static void MusicPlayer::play(AudioClip * musicClip,  int position){ 
  stopped = false;

  if(previousmusic==musicClip){
    //don't play music if it's already playing
    return;
  }
  
  //stop previous music
  if(previousmusic != null){
    previousmusic.Stop();
  }
  
  previousmusic = musicClip;
  
  currentMusic = musicClip;
  if(position == 0){
    acMusic = musicClip.Play(eAudioPriorityHigh, eRepeat);
  } else {
    acMusic = musicClip.PlayFrom(position,eAudioPriorityHigh, eRepeat);
    
  }
  acMusic.Volume = globalMusicVol;
  updateAllVolumes();
}


static void MusicPlayer::playFromPreviousTime(AudioClip * musicClip, float crossfadeTime){ 
  if(crossfadeTime!= 0.0){
    
  } else {
    int previousPosition = acMusic.Position;
    MusicPlayer.play(musicClip, previousPosition);
  }
}

static void MusicPlayer::setMusicPosition(int x, int y){
  acMusic.SetRoomLocation(x, y);
  orignalMusicX = x;
  orignalMusicY = y;
}


static void MusicPlayer::removeMusicPosition(){
  acMusic.SetRoomLocation(0, 0);
  orignalMusicX = 0;
  orignalMusicY = 0;
}

static void MusicPlayer::tweenMusicPosition(int x, int y,  float timing){
  if(x==0 && y==0){
    MusicPlayer.removeMusicPosition();
  }
  
  if(orignalMusicX ==0 && orignalMusicY==0){
    return;  
  }
  
  acMusic.TweenRoomLocation(timing, x, y, orignalMusicX, orignalMusicY);
  orignalMusicX = x;
  orignalMusicY = y;
}


static void MusicPlayer::stop(AudioClip * musicClip){
  stopped = true;
  previousmusic = null;
  musicClip.Stop();  
}

static void MusicPlayer::setVolume(int musicVolume){
  if(acMusic != null){
    currentMusicVolue = musicVolume;
    acMusic.Volume = FloatToInt(IntToFloat(currentMusicVolue) * IntToFloat(globalMusicVol)/100.0);
  }
}

static void MusicPlayer::tweenVolume(int musicVolume, float timing){
  if(acMusic != null){
    currentMusicVolue = musicVolume;
    acMusic.TweenVolume(timing, FloatToInt(IntToFloat(musicVolume) * IntToFloat(globalMusicVol)/100.0), eEaseLinearTween, eNoBlockTween);
  }
}

static void MusicPlayer::setGlobalVolume(int musicVolume){
  globalMusicVol = musicVolume;
  Game.SetAudioTypeVolume(eAudioTypeAmbientSound, globalMusicVol, eVolExistingAndFuture);
  Game.SetAudioTypeVolume(eAudioTypeMusic, globalMusicVol, eVolExistingAndFuture);
}

static int MusicPlayer::getGlobalVolume(){
  return globalMusicVol;  
}



static void SFXPlayer::play(AudioClip * soundClip){ 
  acSound = soundClip.Play(eAudioPriorityNormal, eOnce);
  acSound.Volume = gloabalSoundVol;
  updateAllVolumes();
}

static void SFXPlayer::stop(AudioClip * soundClip){
  soundClip.Stop();  
}

static void SFXPlayer::setGlobalVolume(int soundVolume){
  gloabalSoundVol = soundVolume;
  Game.SetAudioTypeVolume(eAudioTypeSound, gloabalSoundVol, eVolExistingAndFuture);
}

static void SFXPlayer::setVolume(int soundVolume){
  if(acSound != null){
    acSound.Volume = FloatToInt(IntToFloat(soundVolume) * IntToFloat(gloabalSoundVol)/100.0);
  }
}

static int SFXPlayer::getGlobalVolume(){
  return gloabalSoundVol;  
}

void  SPlayerSetGameVolume(int gameVolume){
  System.Volume = gameVolume;
}

int SPlayerGetGameVolume(){
  return System.Volume;
}

void SPlayerSetDefaults(){
  MusicPlayer.setGlobalVolume(70);
  SFXPlayer.setGlobalVolume(70);
  SPlayerSetGameVolume(70);
}

function game_start()
{
  SPlayerSetDefaults();
  sldAudio.Value = System.Volume;
  sldTestMainVolume.Value = System.Volume;
  sldTestMusicVolume.Value = globalMusicVol;
  sldTestSoundVolume.Value = gloabalSoundVol;
}
[close]

selmiak

old versions of the tween module didn't address the music channel volume, but newer versions of the tween module should help you tweening the music channel volume nonblockingly.

eri0o

Hey, using the tween module alone doesn't help me. I would still need to dynamically change the type of song to alocate to a different music channel to do crossfade, but I don't know how.

Crimson Wizard

Quote from: eri0o on Sun 12/11/2017 19:40:36
Hey, using the tween module alone doesn't help me. I would still need to dynamically change the type of song to alocate to a different music channel to do crossfade, but I don't know how.
AGS does not support changing audio types at runtime. You really need 2 music channels to do crossfade, and also to make sure you explicitly stop previous music when you do not want crossfade.

eri0o

I looked the code but still couldn't figure out. If I use high priority, will the first music be allocated in the channel for music and the second be allocated in a random channel? I looked the engine crossfade code but it uses a crossfade channel. I am asking because I provide volume controls, so I need to track the channels.

Monsieur OUXX

If you want a better control over the fading, have a look at how it's done here : http://www.adventuregamestudio.co.uk/forums/index.php?topic=48583.0
 

Crimson Wizard

#6
Quote from: eri0o on Mon 13/11/2017 08:37:18
I looked the code but still couldn't figure out. If I use high priority, will the first music be allocated in the channel for music and the second be allocated in a random channel?

It is not allocated in a random channel, it is allocated on any free channel from the ones you reserved for this type. For that reason you need to reserve at least 2 channels for music type.
BTW, for that reason I am not sure priority is much useful here (unless you have situational music VS background music case).

Normally you start one music and stop the previous one - to prevent them playing at the same time (since you have 2 music channels now). But when you need crossfade, you start another music without stopping previous, the new one will play on the second free channel, and the first channel will eventually be freed when the previous music finished volume decay and stopped.

eri0o

#7
QuoteFor that reason you need to reserve at least 2 channels for music type.

WHOA!!! THAT'S THE MAGIC!  I didn't knew it was possible. I thought it was hard coded or something.

If someone ever need to do this, here's how: In the AGS Editor, go in Explore Project, go in Audio -> Types -> Music, in the property MaxChannels, write the number you want (in my crossfade case, 2).

About the Crossfade, I still need to code, soon will post here. Thanks Monsieur OUXX, great module! :) (it's kind of magical how there's always a module in the forum that I can't find by searching but someone links in a topic :P)

Thank you Crimson Wizard!!!! :-D

SMF spam blocked by CleanTalk