Skip to content

Commit

Permalink
Add musical arrangement and encapsulate pause/resume logic within the…
Browse files Browse the repository at this point in the history
… Engine class
  • Loading branch information
justindriggers committed Jan 16, 2024
1 parent fe09d13 commit a13c5c7
Show file tree
Hide file tree
Showing 35 changed files with 748 additions and 90 deletions.
2 changes: 2 additions & 0 deletions alfredo/src/platform/MacAudioEngineFileLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace linguine::alfredo {
class MacAudioEngineFileLoader : public audio::AudioEngineFileLoader {
public:
NSURL* getUrlForEffect(EffectType effectType) override;

NSURL* getUrlForSong(SongType songType) override;
};

} // namespace linguine::alfredo
27 changes: 25 additions & 2 deletions alfredo/src/platform/MacAudioEngineFileLoader.mm
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
NSString* path;

switch (effectType) {
case Pop:
case EffectType::Pop:
path = @"Balloon Pop 1.wav";
break;
case Select:
case EffectType::Select:
path = @"Select 1.wav";
break;
}
Expand All @@ -22,4 +22,27 @@
return nil;
}

NSURL* MacAudioEngineFileLoader::getUrlForSong(SongType songType) {
NSString* path;

switch (songType) {
case SongType::Theme:
path = @"aegis4-16bit-44_1khz.wav";
break;
case SongType::Title:
path = @"aegis4_title-16bit-44_1khz.wav";
break;
case SongType::GameOver:
path = @"aegis4_gameover-16bit-44_1khz.wav";
break;
}

if (path) {
return [NSURL fileURLWithPath:path
isDirectory:false];
}

return nil;
}

} // namespace linguine::alfredo
Binary file added assets/audio/aegis4-16bit-44_1khz.wav
Binary file not shown.
Binary file added assets/audio/aegis4_gameover-16bit-44_1khz.wav
Binary file not shown.
Binary file added assets/audio/aegis4_title-16bit-44_1khz.wav
Binary file not shown.
31 changes: 30 additions & 1 deletion audio/audioengine/include/AudioEngineAudioManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,48 @@ class AudioEngineAudioManager : public AudioManager {

~AudioEngineAudioManager() override;

void poll() override {};

void play(EffectType effectType) override;

void play(SongType songType, Mode mode) override;

void pause() override;

void resume() override;

private:
static constexpr uint8_t _maxChannels = 32;
static constexpr uint8_t _maxEffectChannels = 8;

std::unique_ptr<AudioEngineFileLoader> _fileLoader;
AVAudioEngine* _audioEngine;
NSMutableArray<AVAudioPlayerNode*>* _playerNodes;
AVAudioFormat* _inputFormat;

std::array<AVAudioPlayerNode*, 2> _songNodes;
int _currentSongNode = 0;
int64_t _lastSongStartSample = 0;
int64_t _generation = 0;

std::unordered_map<EffectType, AVAudioPCMBuffer*> _effectBuffers;
std::unordered_map<SongType, AVAudioPCMBuffer*> _songBuffers;

std::queue<AVAudioPlayerNode*> _nodePool;
std::mutex _poolMutex;

void initSongNodes();

void initEffectNodes();

void loadBuffer(EffectType effectType);

void loadBuffer(SongType songType);

AVAudioPlayerNode* getPlayerNode();

AVAudioPlayerNode* getNextSongNode();

void loop(SongType songType, int64_t generation);
};

} // namespace linguine::audio
28 changes: 22 additions & 6 deletions audio/audioengine/include/AudioEngineFileLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,35 @@
#import <AVFoundation/AVAudioFile.h>

#include <audio/EffectType.h>
#include <audio/SongType.h>

namespace linguine::audio {

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

AVAudioFile* getAudioFileForEffect(EffectType effectType);
AVAudioFile* getAudioFileForEffect(EffectType effectType);

virtual NSURL* getUrlForEffect(EffectType effectType) = 0;
AVAudioFile* getAudioFileForSong(SongType songType);

private:
std::unordered_map<EffectType, AVAudioFile*> _loadedFiles;
[[nodiscard]] int64_t getSongLoopPoint(SongType songType) const {
return _songLoopPoints.at(songType);
}

virtual NSURL* getUrlForEffect(EffectType effectType) = 0;

virtual NSURL* getUrlForSong(SongType songType) = 0;

private:
std::unordered_map<EffectType, AVAudioFile*> _loadedEffects;
std::unordered_map<SongType, AVAudioFile*> _loadedSongs;

const std::unordered_map<SongType, int64_t> _songLoopPoints = {
{ SongType::Title, 1058400 },
{ SongType::Theme, 2646000 },
{ SongType::GameOver, 529200 }
};
};

} // namespace linguine::audio
178 changes: 160 additions & 18 deletions audio/audioengine/src/AudioEngineAudioManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,29 @@
_audioEngine([[AVAudioEngine alloc] init]),
_playerNodes([[NSMutableArray alloc] init]) {
auto outputFormat = [_audioEngine.outputNode inputFormatForBus:0];
auto inputFormat = [[AVAudioFormat alloc] initWithCommonFormat:outputFormat.commonFormat
_inputFormat = [[AVAudioFormat alloc] initWithCommonFormat:outputFormat.commonFormat
sampleRate:outputFormat.sampleRate
channels:1
channels:2
interleaved:outputFormat.isInterleaved];

[_audioEngine connect:_audioEngine.mainMixerNode
to:_audioEngine.outputNode
format:outputFormat];

for (auto i = 0; i < _maxChannels; ++i) {
auto playerNode = [[AVAudioPlayerNode alloc] init];
[_playerNodes addObject:playerNode];

[_audioEngine attachNode:playerNode];
[_audioEngine connect:playerNode
to:_audioEngine.mainMixerNode
format:inputFormat];

_nodePool.push(playerNode);
}
initSongNodes();
initEffectNodes();

NSError* error;
if (![_audioEngine startAndReturnError:&error]) {
NSLog(@"%@", error.localizedDescription);
return;
}

for (AVAudioPlayerNode* playerNode in _playerNodes) {
[playerNode play];
}
loadBuffer(EffectType::Pop);
loadBuffer(EffectType::Select);
loadBuffer(SongType::Theme);
loadBuffer(SongType::Title);
loadBuffer(SongType::GameOver);
}

AudioEngineAudioManager::~AudioEngineAudioManager() {
Expand All @@ -53,10 +46,13 @@
auto playerNode = getPlayerNode();

if (playerNode) {
auto file = _fileLoader->getAudioFileForEffect(effectType);
if (!playerNode.isPlaying) {
[playerNode play];
}

[playerNode scheduleFile:file
[playerNode scheduleBuffer:_effectBuffers[effectType]
atTime:nil
options:AVAudioPlayerNodeBufferInterrupts
completionCallbackType:AVAudioPlayerNodeCompletionDataPlayedBack
completionHandler:^(AVAudioPlayerNodeCompletionCallbackType callbackType) {
std::unique_lock<std::mutex> lock(_poolMutex);
Expand All @@ -65,6 +61,126 @@
}
}

void AudioEngineAudioManager::play(SongType songType, Mode mode) {
auto generation = ++_generation;

for (auto songNode : _songNodes) {
if (songNode.isPlaying) {
[songNode stop];
}

[songNode play];
}

auto songNode = getNextSongNode();

auto buffer = _songBuffers[songType];

_lastSongStartSample = 0;
[songNode scheduleBuffer:buffer
atTime:[AVAudioTime timeWithSampleTime:_lastSongStartSample atRate:buffer.format.sampleRate]
options:0
completionCallbackType:AVAudioPlayerNodeCompletionDataPlayedBack
completionHandler:^(AVAudioPlayerNodeCompletionCallbackType callbackType) {
if (generation == _generation && mode == Mode::Repeat) {
loop(songType, generation);
}
}];

auto repeatSongNode = getNextSongNode();

if (mode == Mode::Repeat) {
_lastSongStartSample += _fileLoader->getSongLoopPoint(songType);
[repeatSongNode scheduleBuffer:buffer
atTime:[AVAudioTime timeWithSampleTime:_lastSongStartSample atRate:buffer.format.sampleRate]
options:0
completionCallbackType:AVAudioPlayerNodeCompletionDataPlayedBack
completionHandler:^(AVAudioPlayerNodeCompletionCallbackType callbackType) {
if (generation == _generation) {
loop(songType, generation);
}
}];
}
}

void AudioEngineAudioManager::pause() {
for (AVAudioPlayerNode* playerNode in _playerNodes) {
[playerNode pause];
}

[_audioEngine pause];
}

void AudioEngineAudioManager::resume() {
NSError* error;
if (![_audioEngine startAndReturnError:&error]) {
NSLog(@"%@", error.localizedDescription);
return;
}

for (AVAudioPlayerNode* playerNode in _playerNodes) {
[playerNode play];
}
}

void AudioEngineAudioManager::initSongNodes() {
for (auto& songNode : _songNodes) {
songNode = [[AVAudioPlayerNode alloc] init];
[_playerNodes addObject:songNode];

[_audioEngine attachNode:songNode];
[_audioEngine connect:songNode
to:_audioEngine.mainMixerNode
format:[[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100
channels:2]];
}
}

void AudioEngineAudioManager::initEffectNodes() {
for (auto i = 0; i < _maxEffectChannels; ++i) {
auto playerNode = [[AVAudioPlayerNode alloc] init];
[_playerNodes addObject:playerNode];

[_audioEngine attachNode:playerNode];
[_audioEngine connect:playerNode
to:_audioEngine.mainMixerNode
format:[[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100
channels:2]];

_nodePool.push(playerNode);
}
}

void AudioEngineAudioManager::loadBuffer(EffectType effectType) {
auto file = _fileLoader->getAudioFileForEffect(effectType);

auto buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:file.processingFormat
frameCapacity:file.length];

NSError* error;
if (![file readIntoBuffer:buffer error:&error]) {
NSLog(@"%@", error.localizedDescription);
return;
}

_effectBuffers[effectType] = buffer;
}

void AudioEngineAudioManager::loadBuffer(SongType songType) {
auto file = _fileLoader->getAudioFileForSong(songType);

auto buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:file.processingFormat
frameCapacity:file.length];

NSError* error;
if (![file readIntoBuffer:buffer error:&error]) {
NSLog(@"%@", error.localizedDescription);
return;
}

_songBuffers[songType] = buffer;
}

AVAudioPlayerNode* AudioEngineAudioManager::getPlayerNode() {
if (_nodePool.empty()) {
return nullptr;
Expand All @@ -78,4 +194,30 @@
return result;
}

AVAudioPlayerNode* AudioEngineAudioManager::getNextSongNode() {
if (_currentSongNode == 1) {
_currentSongNode = 0;
} else {
_currentSongNode = 1;
}

return _songNodes[_currentSongNode];
}

void AudioEngineAudioManager::loop(SongType songType, int64_t generation) {
auto songNode = getNextSongNode();

_lastSongStartSample += _fileLoader->getSongLoopPoint(songType);
auto buffer = _songBuffers[songType];
[songNode scheduleBuffer:buffer
atTime:[AVAudioTime timeWithSampleTime:_lastSongStartSample atRate:buffer.format.sampleRate]
options:0
completionCallbackType:AVAudioPlayerNodeCompletionDataPlayedBack
completionHandler:^(AVAudioPlayerNodeCompletionCallbackType callbackType) {
if (generation == _generation) {
loop(songType, generation);
}
}];
}

} // namespace linguine::audio

0 comments on commit a13c5c7

Please sign in to comment.