Interactive Multi-State Engine

 

Key Features:

  • Done with FMOD API, diagram shows class dependency and interactive class state structure
  • State containers and segments can be set by order, loop, and randomness
  • Interactive music class is an open nested loop style, no buffering
  • Uses two interchangeable  sub music channels sharing the states by cross-fade
  • Cross-fades occur by means of embedded thread objects
  • SFML library is being used for press key options to send commands

 

Improvement Areas:

  • A thread work might be used for “SegmentIndexToPlayGenerator” if the system is very busy
  • General refactoring

 

Download Link:

 

 

 

AudioEngine.h

// AudioEngine.h

#pragma once
#include <iostream>
#include <fmod.hpp>
#include <fmod_errors.h>
#include <string>
#include <Windows.h>
#include <unordered_map>
#include <thread>
#include <chrono>

class AudioEngine
{
public:
    AudioEngine();
    AudioEngine(const AudioEngine&) = delete;
    ~AudioEngine();

    AudioEngine &operator=(const AudioEngine&) = delete;

    void SetMasterChannelVolume(const float _volume);
    void SetMusicChannelVolume(const float _volume);

    void Load(const int _fileID, const std::string & _path, const std::string & _name);
    void LoadFromResource(const int _resourceID, LPCTSTR _resourceType, const std::string & _name);
    void ShrinkAudioEngineMaps();

    int MusicChannelActivity();
    void AssignChannel(); 
    FMOD::Channel* GetActiveChannel();
    
    void CrossFade(FMOD::Channel* _channelIn, FMOD::Channel* _channelOut, const int _millisec);
    void PlayMusic(const int _fileID);
    void Stop();
    void Update();

private:
    //Declaring system object
    FMOD::System* ptrSystem;

    //Declaring master and music group channels
    FMOD::ChannelGroup* ptrMasterGroupChannel;
    FMOD::ChannelGroup* ptrMusicGroupChannel;

    //Declaring music sub channels to be used continuously
    FMOD::Channel* ptrMusicChannel1;
    FMOD::Channel* ptrMusicChannel2;

    //Pointer to channels for assigning them
    FMOD::Channel** pptrChannel;
    
    //For checking if interactive sub channel is playing or not
    bool m_ch1_Activity;
    bool m_ch2_Activity;

    //Declaring map for audio files
    std::unordered_map<int, FMOD::Sound*> MusicMap;

    //Creating ID-name match map for file information
    std::unordered_map<int, std::string> NameIDMap;
};

 

AudioEngine.cpp

// AudioEngine.cpp

#pragma once
#include "AudioEngine.h"

AudioEngine::AudioEngine() :
    ptrSystem(nullptr),
    ptrMasterGroupChannel(nullptr),
    ptrMusicGroupChannel(nullptr),
    ptrMusicChannel1(nullptr),
    ptrMusicChannel2(nullptr),
    pptrChannel(nullptr),
    m_ch1_Activity(false),
    m_ch2_Activity(false)
{	
    //Initialising the system
    FMOD::System_Create(&ptrSystem);
    ptrSystem->init(100, FMOD_INIT_NORMAL, nullptr);

    //Creating group channels
    ptrSystem->createChannelGroup("Master", &ptrMasterGroupChannel);
    ptrSystem->createChannelGroup("Music", &ptrMusicGroupChannel);

    //Assigning music group channel to the master group channel
    ptrMasterGroupChannel->addGroup(ptrMusicGroupChannel); //Music->Master
}

////////////////////////////////////////////////////////////////////////
AudioEngine::~AudioEngine()
{
    //Releasing sounds and clear the containers
    for (auto & sound_file : MusicMap)
    {
        sound_file.second->release();
    }
    MusicMap.clear();

    //Releasing the root of the system
    ptrSystem->release();
}

////////////////////////////////////////////////////////////////////////
void AudioEngine::SetMasterChannelVolume
(
    const float _volume
)
{
    ptrMasterGroupChannel->setVolume(_volume);
}

////////////////////////////////////////////////////////////////////////
void AudioEngine::SetMusicChannelVolume
(
    const float _volume
)
{
    ptrMusicGroupChannel->setVolume(_volume);
}

////////////////////////////////////////////////////////////////////////
void AudioEngine::Load
(
    const int _fileID,
    const std::string & _path,
    const std::string & _name
)
{
    FMOD::Sound* ptrSound;

    ptrSystem->createSound(_path.c_str(), FMOD_DEFAULT, nullptr, &ptrSound);
    //Or ptrSystem->createStream(path.c_str(), FMOD_DEFAULT, nullptr, &ptrSound);
    //It depends on the situation, streaming is better for big size audio files

    //Checking if the file does exist or not against any typing error
    unsigned int length = 0;
    ptrSound->getLength(&length, FMOD_TIMEUNIT_MS);
    if (length == 0)
    {
        std::cout << "Serious problem occured at loading stage..." << "\n";
        std::cout << "Please check this sound file, it does not exist:" << "\n";
        std::cout << _path << "\n";
        std::cout << "" << "\n";
        ptrSound->release();
        throw;
    }
    //Adding audio objects and setting their names
    MusicMap.emplace(_fileID, ptrSound);
    NameIDMap.emplace(_fileID, _name);
}

////////////////////////////////////////////////////////////////////////
void AudioEngine::LoadFromResource
(
    const int _resourceID,
    LPCTSTR _resourceType,
    const std::string & _name
)
{
    HRSRC rsrc = FindResource(NULL, MAKEINTRESOURCE(_resourceID), _resourceType);
    HGLOBAL handle = LoadResource(NULL, rsrc);

    DWORD audio_size = SizeofResource(NULL, rsrc);
    LPVOID audio_data = LockResource(handle);

    FMOD_CREATESOUNDEXINFO AudioInfo;
    memset(&AudioInfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
    AudioInfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);
    AudioInfo.length = static_cast<unsigned int>(audio_size);

    FMOD::Sound* ptrResource;
    ptrSystem->createSound(static_cast<const char*>(audio_data),
        FMOD_OPENMEMORY, &AudioInfo, &ptrResource);

    //Adding loaded resource file and setting their names
    MusicMap.emplace(_resourceID, ptrResource);
    NameIDMap.emplace(_resourceID, _name);
}

////////////////////////////////////////////////////////////////////////
void AudioEngine::ShrinkAudioEngineMaps
(
)
{
    //For potentially less memory consumption
    std::unordered_map<int, FMOD::Sound*>(MusicMap).swap(MusicMap);
    std::unordered_map<int, std::string>(NameIDMap).swap(NameIDMap);
}

////////////////////////////////////////////////////////////////////////
int AudioEngine::MusicChannelActivity
(
)
{
    ptrMusicChannel1->isPlaying(&m_ch1_Activity);
    ptrMusicChannel2->isPlaying(&m_ch2_Activity);

    //Both channels are active -> return 1 
    //None of them is active -> return 2 
    //Only one channel is active -> return 3

    if (m_ch1_Activity && m_ch2_Activity)
    {
        return 1;
    }		
    else if (!m_ch1_Activity && !m_ch2_Activity)
    {
        return 2;
    }
    else
    {
        return 3;
    }
}

////////////////////////////////////////////////////////////////////////
void AudioEngine::AssignChannel
(
)
{
    ptrMusicChannel1->isPlaying(&m_ch1_Activity);
    ptrMusicChannel2->isPlaying(&m_ch2_Activity);
    
    if ((!m_ch1_Activity && !m_ch2_Activity) || (!m_ch1_Activity && m_ch2_Activity))
    {
        //If channels are empty or the second one is active, take the first one 
        pptrChannel = &ptrMusicChannel1;
    }
    else if (m_ch1_Activity && !m_ch2_Activity)
    {
        //If the first one is active, take the second one
        pptrChannel = &ptrMusicChannel2;
    }
}

////////////////////////////////////////////////////////////////////////
FMOD::Channel* AudioEngine::GetActiveChannel()
{
    return *pptrChannel;
}

////////////////////////////////////////////////////////////////////////
void AudioEngine::CrossFade
(
    FMOD::Channel* _channelIn, 
    FMOD::Channel* _channelOut, 
    const int _millisec
)
{
    //channel_in is the pasive channel which will play soon, it will fade in
    //channel_out is the active channel which is currently playing, it will fade away 
    float channelVolumeIn = 0.0f;
    float channelVolumeOut = 1.0f;
    _channelIn->setVolume(0.0f);
    _channelOut->setVolume(1.0f);

    while (channelVolumeIn < 1.0f && channelVolumeOut > 0.0f)
    {
        //1-(x^2.5) crossfade calculation with 0.15 base value, pow(0.15f, 2.5f) 
        //Increment-decrement value is approximately 0.0087 per decided millisecs
        channelVolumeIn += 0.0087f;
        channelVolumeOut -= 0.0087f;
        _channelIn->setVolume(channelVolumeIn);
        _channelOut->setVolume(channelVolumeOut);
        std::this_thread::sleep_for(std::chrono::milliseconds(_millisec));

        if (channelVolumeOut < 0.01f && channelVolumeIn > 0.99f)
        {
            _channelOut->setVolume(0.0f); //For precision
            _channelIn->setVolume(1.0f);
            _channelOut->stop();
            break;
        }
        _channelIn->getVolume(&channelVolumeIn);
        _channelOut->getVolume(&channelVolumeOut);
    }
}

////////////////////////////////////////////////////////////////////////
void AudioEngine::PlayMusic
(
    const int _fileID
)
{
    auto musicFile = MusicMap.find(_fileID);

    ptrSystem->playSound(musicFile->second, ptrMusicGroupChannel, false, pptrChannel);

    //Progressing the cross-fade if there is a new play call
    if (MusicChannelActivity() == 1)
    {
        if (pptrChannel == &ptrMusicChannel1)
        {
            std::thread crossState(&AudioEngine::CrossFade, this, 
                ptrMusicChannel1, ptrMusicChannel2, 15);
            crossState.detach();
        }
        else if (pptrChannel == &ptrMusicChannel2)
        {
            std::thread crossState(&AudioEngine::CrossFade, this, 
                ptrMusicChannel2, ptrMusicChannel1, 15);
            crossState.detach();
        }
    }
    //Segment information
    std::cout << NameIDMap.find(_fileID)->second << "\n";
}

////////////////////////////////////////////////////////////////////////
void AudioEngine::Stop
(
)
{
    float channelVolume;
    ptrMusicGroupChannel->getVolume(&channelVolume);
    
    if (channelVolume < 0.02f)
    {
        //Minimum base value against a locked loop
        channelVolume = 0.02f;
    }
        
    while (channelVolume > 0.0f)
    {
        //1-(x^2.5) crossfade calculation with 0.15 base value, pow(0.15f, 2.5f) 
        //Increment-decrement value is approximately 0.0087 per 15 millisecs
        channelVolume -= 0.0087f;
        ptrMusicGroupChannel->setVolume(channelVolume);
        std::this_thread::sleep_for(std::chrono::milliseconds(15));

        if (channelVolume < 0.01f)
        {
            ptrMusicGroupChannel->setVolume(0.0f);  //For precision
            ptrMusicChannel1->stop();
            ptrMusicChannel2->stop();
            break;
        }
        ptrMusicGroupChannel->getVolume(&channelVolume);
    }
    ptrMusicGroupChannel->setVolume(1.0f);
}

////////////////////////////////////////////////////////////////////////
void AudioEngine::Update
(
)
{
    ptrSystem->update();
}

 

Music.h

// Music.h

#pragma once
#include "AudioEngine.h"

class Music 
{
public:
    virtual ~Music() = default;

    virtual void Play(AudioEngine & _obj, const int _fileID) = 0;
    virtual void CallToPlay(AudioEngine & _obj, const int _fileID) = 0;
    virtual void UpdateMusic(AudioEngine & _obj) = 0;
};

 

Interactive.h

// Interactive.h

#pragma once
#include "Music.h"
#include <vector>
#include <list>
#include <random>
#include <thread>

class InteractiveState final : public Music
{
public:
    InteractiveState();
    ~InteractiveState() = default;

    void SetMaxStateLoop(const int _maxStateLoopAmount = 4);
    void CreateContainer(const bool _randomContainer = false, 
                         const int _containerLoopAmount = 1);
    void SetSegmentData(const int _segmentID, 
                        const int _containerIndex, 
                        const int _segmentLoopAmount,
                        const int _segmentDuration);
    void SetContainerSegmentOrder(const int _containerIndex);

    void ShrinkInteractiveContainers();

    void SegmentIndexToPlayGenerator(const int _containerIndex);

    void Play(AudioEngine & _obj, const int _fileID = 0) override;
    void CallToPlay(AudioEngine & _obj, const int _fileID = 0) override;
    void UpdateMusic(AudioEngine & _obj) override;

    void DefaultValues();

private:   
    //State members
    int m_maxStateLoopAmount;
    int m_maxContainerIndex;
 
    //Segment (music) members	
    int m_segmentLoopAmount;
    unsigned int m_segmentDuration;

    //Play state members in active usage  	
    bool m_playAction; 
    int m_currentStateLoop;
    int m_currentContainerIndex;
    int m_currentContainerLoop;
    int m_currentSegmentLoop;
    unsigned int m_currentSegmentPosition;

    //Creating segment music data for each container's segment data store
    struct SegmentData
    {
        int m_segmentID = 0;
        int m_segmentLoopAmount = 0;
        unsigned int m_segmentDuration = 0;
    };

    //Creating container store for states which will be triggered in game scenario
    struct ContainerData
    {
        bool b_randomContainer = false;
        int m_containerLoopAmount = 0;
        std::vector<SegmentData> Segments;
        std::list<int> SegmentIndicesToPlay;
    };
    std::vector<ContainerData> Containers;	
};

 

Interactive.cpp

// Interactive.cpp

#pragma once
#include "Interactive.h"

InteractiveState::InteractiveState() :
    m_maxStateLoopAmount(0),
    m_maxContainerIndex(0),
    m_segmentLoopAmount(0),
    m_segmentDuration(0),
    m_playAction(false),
    m_currentStateLoop(0),
    m_currentContainerIndex(0),
    m_currentContainerLoop(0),
    m_currentSegmentLoop(0),
    m_currentSegmentPosition(0)
{
}

////////////////////////////////////////////////////////////////////////
void InteractiveState::SetMaxStateLoop
(
    const int _maxStateLoopAmount
)
{
    //State max loop amount should be bigger than 0
    m_maxStateLoopAmount = _maxStateLoopAmount;
}

////////////////////////////////////////////////////////////////////////
void InteractiveState::CreateContainer
(
    const bool _randomContainer,
    const int _containerLoopAmount
)
{
    //Containers play in order
    //In random type container, segments will play in random
    auto containerData = ContainerData();
    containerData.b_randomContainer = _randomContainer;
    containerData.m_containerLoopAmount = _containerLoopAmount;

    Containers.push_back(containerData);

    //Setting interactive state size at the same time
    m_maxContainerIndex = Containers.size();
}

////////////////////////////////////////////////////////////////////////
void InteractiveState::SetSegmentData
(
    const int _segmentID,
    const int _containerIndex,
    const int _segmentLoopAmount,
    const int _segmentDuration
)
{
    auto segmentData = SegmentData();
    segmentData.m_segmentID = _segmentID;
    segmentData.m_segmentLoopAmount = _segmentLoopAmount;
    segmentData.m_segmentDuration = _segmentDuration;

    Containers[_containerIndex].Segments.push_back(segmentData);
}

////////////////////////////////////////////////////////////////////////
void InteractiveState::SetContainerSegmentOrder
(
    const int _containerIndex
)
{
    SegmentIndexToPlayGenerator(_containerIndex);
}

////////////////////////////////////////////////////////////////////////
void InteractiveState::ShrinkInteractiveContainers
(
)
{
    //For potentially less memory consumption
    for (auto & containerData : Containers)
    {
        containerData.Segments.shrink_to_fit();
    }

    Containers.shrink_to_fit();
}

////////////////////////////////////////////////////////////////////////
void InteractiveState::SegmentIndexToPlayGenerator
(
    const int _containerIndex
)
{
    const bool isRandom = Containers[_containerIndex].b_randomContainer;
    const int totalSegments = Containers[_containerIndex].Segments.size();
    auto & segmentIndicesToPlay = Containers[_containerIndex].SegmentIndicesToPlay;

    segmentIndicesToPlay.clear();

    if (isRandom)
    {
        //Create a vector of indices from 0 to totalSegments - 1
        std::vector<int> indices(totalSegments);
        for (int i = 0; i < totalSegments; ++i)
        {
            indices[i] = i;
        }

        //Shuffle the vector using a random engine
        unsigned int seed = static_cast<unsigned int>(time(0));
        std::mt19937 engine(seed);
        std::shuffle(indices.begin(), indices.end(), engine);

        //Copy this temporary vector into the segment indices list
        for (const int i : indices)
        {
            segmentIndicesToPlay.push_back(i);
        }
    }
    else
    {
        // If not random, just insert the indices in order
        for (int i = 0; i < totalSegments; ++i)
        {
            segmentIndicesToPlay.push_back(i);
        }
    }
}

////////////////////////////////////////////////////////////////////////
void InteractiveState::Play
(
    AudioEngine & _obj, 
    const int _fileID
)
{
    //Checking current state loop number
    if (m_currentStateLoop < m_maxStateLoopAmount)
    {
        //Checking current container
        if (m_currentContainerIndex < m_maxContainerIndex)
        {	
            //Playing container loops
            auto & activeContainer = Containers[m_currentContainerIndex];
            
            if (m_currentContainerLoop < activeContainer.m_containerLoopAmount)
            {			
                //Extracting segment info and playing the segment	
                auto & segmentIndicesToPlay = activeContainer.SegmentIndicesToPlay;

                if (m_playAction)  //m_playAction works as a valve !!!
                {													
                    const int segmentIndex = segmentIndicesToPlay.front();
                    const auto & segmentData = activeContainer.Segments[segmentIndex];

                    m_segmentLoopAmount = segmentData.m_segmentLoopAmount;
                    m_segmentDuration = segmentData.m_segmentDuration;
                    m_currentSegmentLoop++;
                    m_playAction = false;  //valve is close

                    _obj.PlayMusic(segmentData.m_segmentID);					
                }

                //Just before segment duration to be completed, it will go inside earlier
                //Then, thread "sleep for" will wait for the time difference
                //Let's make the threshold 100 ms
                _obj.GetActiveChannel()->getPosition(&m_currentSegmentPosition, FMOD_TIMEUNIT_MS);

                if (m_currentSegmentPosition >= m_segmentDuration - 100)
                {
                    //If position is higher than m_segmentDuration somehow, there will be delay
                    //Programme will continue to run for stability by accepting delay if it is case	  
                    if (m_currentSegmentPosition < m_segmentDuration)
                    {
                        std::this_thread::sleep_for
                        (
                            std::chrono::milliseconds(m_segmentDuration - m_currentSegmentPosition)
                        );
                    }
                    
                    m_playAction = true;  //valve is open

                    //If the current segment's job is done, go to the next segment to play
                    if (m_currentSegmentLoop >= m_segmentLoopAmount)
                    {
                        segmentIndicesToPlay.pop_front(); 
                        m_currentSegmentLoop = 0;
                    }

                    //Going to next container loop when segment plays are completed
                    if (segmentIndicesToPlay.empty())
                    {
                        m_currentContainerLoop++;
                    }
                }
            }
            else
            {
                //Setting segment indices to play for the current container after it's turn is done
                SegmentIndexToPlayGenerator(m_currentContainerIndex);

                //If segment numbers are big enough, we can use thread to calculate indices
                /*		
                {
                    const int containerIndex = m_currentContainerIndex; // Against index change during the execution
                    std::thread playOrder(&InteractiveState::SegmentIndexToPlayGenerator, this, containerIndex);
                    playOrder.detach();
                }
                */
                                                
                //Move to next container in its state after previous container finished its all loops
                m_currentContainerIndex++;
                m_currentContainerLoop = 0;
            }
        }
        else
        {
            m_currentStateLoop++;
            m_currentContainerIndex = 0; //Back to the first container
        }
    }
    else
    {
        m_currentStateLoop = 0;
        _obj.Stop();
        std::cout << "- Current state play loops are completed" << "\n";
    }
}

////////////////////////////////////////////////////////////////////////
void InteractiveState::CallToPlay
(
    AudioEngine & _obj, 
    const int _fileID
)
{
    //If both channels are active, refuse for the call 
    if (_obj.MusicChannelActivity() != 1)
    {
        //First play for new coming state 
        _obj.AssignChannel();
        m_playAction = true;
        Play(_obj);
    }
}

////////////////////////////////////////////////////////////////////////
void InteractiveState::UpdateMusic
(
    AudioEngine & _obj
)
{
    Play(_obj, 0);
}

////////////////////////////////////////////////////////////////////////
void InteractiveState::DefaultValues
(
)
{
    m_playAction = false;
    m_currentStateLoop = 0;
    m_currentContainerIndex = 0;
    m_currentContainerLoop = 0;
    m_currentSegmentLoop = 0;
    m_currentSegmentPosition = 0;
}

 

Background.h

// Background.h

#pragma once
#include "Music.h"

class Background final : public Music
{
public:
    Background() = default;
    Background(const Background&) = delete;
    ~Background() = default;

    Background &operator=(const Background&) = delete;

    void Play(AudioEngine & _obj, const int _fileID) override;
    void CallToPlay(AudioEngine & _obj, const int _fileID) override;
    void UpdateMusic(AudioEngine & _obj) override;
};

 

Background.cpp

// Background.cpp

#pragma once
#include "Background.h"

void Background::Play(AudioEngine & _obj, const int _fileID)
{
    _obj.AssignChannel();
    _obj.PlayMusic(_fileID);
}

void Background::CallToPlay(AudioEngine & _obj, const int _fileID)
{
    //If both channels are active, refuse for the call 
    if (_obj.MusicChannelActivity() != 1)
    {
        Play(_obj, _fileID);
    }
}

void Background::UpdateMusic(AudioEngine & _obj)
{
}

 

Main.cpp

// Main.cpp

#pragma once
#include "Interactive.h"
#include "Background.h"
#include <SFML/Window.hpp>

int main()
{
    //Creating main system object dealing with system, channels, load, etc.
    AudioEngine audio;

    //Initialising channel group volumes
    audio.SetMasterChannelVolume(1.0f);
    audio.SetMusicChannelVolume(1.0f);

    /*Loading interactive music files example: if their paths were used
    audio.Load(201, R"(C:\Data\battle1.mp3)", "Battle State -> Segment 1");
    Segment ID, Container index, Segment index, Segment loop, Segment duration:
    battle.setSegment(201, 0, 0, 1, 4000);*/

    //Loading interactive music files from resource
    //Setting Resource ID, Resource type, File name
    audio.LoadFromResource(201, "MP3", "- Battle State -> Segment 1");
    audio.LoadFromResource(202, "MP3", "- Battle State -> Segment 2");
    audio.LoadFromResource(203, "MP3", "- Battle State -> Segment 3");
    audio.LoadFromResource(204, "MP3", "- Battle State -> Segment 4");
    audio.LoadFromResource(205, "MP3", "- Tension State -> Segment 1");
    audio.LoadFromResource(206, "MP3", "- Tension State -> Segment 2");
    audio.LoadFromResource(207, "MP3", "- Tension State -> Segment 3");
    audio.LoadFromResource(208, "MP3", "- Tension State -> Segment 4");
    audio.LoadFromResource(209, "MP3", "- Background Ambience -> Peaceful track");

    //Creating background music state
    Background background;

    //Creating interactive music states
    InteractiveState battle;
    InteractiveState tension;

    //Pointer to music subclass objects
    Music *ptrMusic = nullptr;

    //SETTING INTERACTIVE STATE OBJECTS
    //State max loop amount (default = 4)
    battle.SetMaxStateLoop();
    tension.SetMaxStateLoop();
    
    //Deciding how many containers would be in the interactive state
    //Container type (default = false), Container loop amount (default = 1)
    battle.CreateContainer();
    battle.CreateContainer(true); //Segments will play in random order

    tension.CreateContainer();
    tension.CreateContainer(true);
    
    //Assigning the segments into the containers with relevant data
    //Segment ID, Container index, Segment loop, Segment duration(ms)
    battle.SetSegmentData(201, 0, 1, 4000);
    battle.SetSegmentData(202, 1, 1, 4000);
    battle.SetSegmentData(203, 1, 2, 4000);
    battle.SetSegmentData(204, 1, 2, 4000);

    tension.SetSegmentData(205, 0, 1, 4000); 
    tension.SetSegmentData(206, 0, 2, 4000);
    tension.SetSegmentData(207, 1, 1, 4000);
    tension.SetSegmentData(208, 1, 1, 4000);

    //Setting segment play order in the containers (random or not)
    //Container index
    battle.SetContainerSegmentOrder(0);
    battle.SetContainerSegmentOrder(1);

    tension.SetContainerSegmentOrder(0);
    tension.SetContainerSegmentOrder(1);

    //Shrink engine maps and state containers (Optional)
    audio.ShrinkAudioEngineMaps();
    battle.ShrinkInteractiveContainers();
    tension.ShrinkInteractiveContainers();

    //======================================================================//
    std::cout << "Multistate Interactive Music Demonstration" << "\n";
    std::cout << "Music and programming: Hakan Yurdakul, copyright(c) 2025" << "\n";
    std::cout << "" << "\n";
    std::cout << "Press 1 to play Background Ambience" << "\n";
    std::cout << "Press 2 to play Tension State (interactive)" << "\n";
    std::cout << "Press 3 to play Battle State (interactive)" << "\n";
    std::cout << "" << "\n";
    std::cout << "Press SPACE to stop playing music" << "\n";
    std::cout << "Press ESCAPE to exit" << "\n";
    std::cout << "" << "\n";
    //======================================================================//

    //For keyword commands; it works as a valve
    std::string last_command = "";

    //START OF THE LOOP
    while (true)
    {
        //Keyboard Commands
        //======================================================================//
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Num1))
        {
            if (last_command != "Background" && audio.MusicChannelActivity() != 1)
            {
                last_command = "Background";
                ptrMusic = &background;
                background.Play(audio, 209);
            }
        }
        else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Num2))
        {
            if (last_command != "Tension" && audio.MusicChannelActivity() != 1)
            {
                last_command = "Tension";
                ptrMusic = &tension;
                tension.CallToPlay(audio);
            }
        }
        else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Num3))
        {
            if (last_command != "Battle" && audio.MusicChannelActivity() != 1)
            {
                last_command = "Battle";
                ptrMusic = &battle;
                battle.CallToPlay(audio);
            }
        }
        else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Space))
        {
            if (last_command != "Stop" && audio.MusicChannelActivity() != 2)
            {
                last_command = "Stop";
                ptrMusic = nullptr;
                audio.Stop();
                battle.DefaultValues();
                tension.DefaultValues();
                std::cout << "- Stopped..." << "\n";
            }
        }
        else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))
        {
            std::cout << "" << "\n";
            std::cout << "Thanks for listening..." << "\n";
            std::cout << "Preparing to exit..." << "\n";
            ptrMusic = nullptr;
            audio.Stop();
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
            break;
        }
        //======================================================================//

        //Checking and updating continously
        if (ptrMusic != nullptr)
        {
            ptrMusic->UpdateMusic(audio);
        }			
        audio.Update();

        //If nothing plays, release the pointer and command key (if assigned) 
        if (audio.MusicChannelActivity() == 2)
        {
            ptrMusic = nullptr;
            last_command = "";
        }	
    }
    //END OF THE LOOP

    return 0;
}