diff --git a/CMakeLists.txt b/CMakeLists.txt index a7e8b66..bab2b90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,26 +41,29 @@ set(POMME_SOURCES $<$:${POMME_SRCDIR}/Platform/Windows/PommeWindows.h> ) -if (NOT(POMME_NO_SOUND)) +if (NOT(POMME_NO_SOUND_FORMATS)) list(APPEND POMME_SOURCES - ${POMME_SRCDIR}/Sound/AIFF.cpp - ${POMME_SRCDIR}/Sound/IMA4.cpp - ${POMME_SRCDIR}/Sound/MACE.cpp - ${POMME_SRCDIR}/Sound/SoundUtilities.cpp - ${POMME_SRCDIR}/Sound/xlaw.cpp + ${POMME_SRCDIR}/SoundFormats/AIFF.cpp + ${POMME_SRCDIR}/SoundFormats/IMA4.cpp + ${POMME_SRCDIR}/SoundFormats/MACE.cpp + ${POMME_SRCDIR}/SoundFormats/Midi.cpp + ${POMME_SRCDIR}/SoundFormats/SoundFormats.cpp + ${POMME_SRCDIR}/SoundFormats/xlaw.cpp ) else() - add_compile_definitions(POMME_NO_SOUND) + add_compile_definitions(POMME_NO_SOUND_FORMATS) endif() -if (NOT(POMME_NO_SOUND_PLAYBACK)) +if (NOT(POMME_NO_SOUND_MIXER)) list(APPEND POMME_SOURCES - ${POMME_SRCDIR}/Sound/cmixer.cpp - ${POMME_SRCDIR}/Sound/cmixer.h - ${POMME_SRCDIR}/Sound/SoundManager.cpp - ) + ${POMME_SRCDIR}/SoundMixer/ChannelImpl.cpp + ${POMME_SRCDIR}/SoundMixer/ChannelImpl.h + ${POMME_SRCDIR}/SoundMixer/cmixer.cpp + ${POMME_SRCDIR}/SoundMixer/cmixer.h + ${POMME_SRCDIR}/SoundMixer/SoundManager.cpp + ) else() - add_compile_definitions(POMME_NO_SOUND_PLAYBACK) + add_compile_definitions(POMME_NO_SOUND_MIXER) endif() if (NOT(POMME_NO_GRAPHICS)) diff --git a/src/Pomme.cpp b/src/Pomme.cpp index b0153f7..c6dfc53 100644 --- a/src/Pomme.cpp +++ b/src/Pomme.cpp @@ -53,8 +53,12 @@ void Pomme::Init() Pomme::Graphics::Init(); #endif -#ifndef POMME_NO_SOUND_PLAYBACK - Pomme::Sound::Init(); +#ifndef POMME_NO_SOUND_FORMATS + Pomme::Sound::InitMidiFrequencyTable(); +#endif + +#ifndef POMME_NO_SOUND_MIXER + Pomme::Sound::InitMixer(); #endif #ifndef POMME_NO_INPUT @@ -64,7 +68,7 @@ void Pomme::Init() void Pomme::Shutdown() { -#ifndef POMME_NO_SOUND_PLAYBACK - Pomme::Sound::Shutdown(); +#ifndef POMME_NO_SOUND_MIXER + Pomme::Sound::ShutdownMixer(); #endif } diff --git a/src/PommeSound.h b/src/PommeSound.h index 4d46a55..bd61a89 100644 --- a/src/PommeSound.h +++ b/src/PommeSound.h @@ -6,13 +6,16 @@ #include #include #include -#include "Sound/cmixer.h" namespace Pomme::Sound { - void Init(); + void InitMidiFrequencyTable(); - void Shutdown(); + void InitMixer(); + void ShutdownMixer(); + + double GetMidiNoteFrequency(int note); + std::string GetMidiNoteName(int note); struct SampledSoundInfo { diff --git a/src/Sound/AIFF.cpp b/src/SoundFormats/AIFF.cpp similarity index 100% rename from src/Sound/AIFF.cpp rename to src/SoundFormats/AIFF.cpp diff --git a/src/Sound/IMA4.cpp b/src/SoundFormats/IMA4.cpp similarity index 100% rename from src/Sound/IMA4.cpp rename to src/SoundFormats/IMA4.cpp diff --git a/src/Sound/MACE.cpp b/src/SoundFormats/MACE.cpp similarity index 100% rename from src/Sound/MACE.cpp rename to src/SoundFormats/MACE.cpp diff --git a/src/SoundFormats/Midi.cpp b/src/SoundFormats/Midi.cpp new file mode 100644 index 0000000..280690a --- /dev/null +++ b/src/SoundFormats/Midi.cpp @@ -0,0 +1,55 @@ +#include "PommeSound.h" +#include + +constexpr int kNumMidiNotes = 128; + +static double gMidiNoteFrequencies[kNumMidiNotes]; + +void Pomme::Sound::InitMidiFrequencyTable() +{ + // powers of twelfth root of two + double gamme[12]; + gamme[0] = 1.0; + for (int i = 1; i < 12; i++) + { + gamme[i] = gamme[i - 1] * 1.059630943592952646; + } + + for (int i = 0; i < 128; i++) + { + int octave = 1 + (i + 3) / 12; // A440 and middle C are in octave 7 + int semitone = (i + 3) % 12; // halfsteps up from A in current octave + + float freq; + if (octave < 7) + freq = gamme[semitone] * 440.0 / (1 << (7 - octave)); // 440/(2**octaveDiff) + else + freq = gamme[semitone] * 440.0 * (1 << (octave - 7)); // 440*(2**octaveDiff) + + gMidiNoteFrequencies[i] = freq; + } +} + +double Pomme::Sound::GetMidiNoteFrequency(int note) +{ + if (note < 0 || note >= kNumMidiNotes) + return 440.0; + else + return gMidiNoteFrequencies[note]; +} + +// Note: these names are according to IM:S:2-43. +// These names won't match real-world names. +// E.g. for note 67 (A 440Hz), this will return "A6", whereas the real-world +// convention for that note is "A4". +std::string Pomme::Sound::GetMidiNoteName(int note) +{ + static const char* gamme[12] = {"A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"}; + + int octave = 1 + (note + 3) / 12; + int semitonesFromA = (note + 3) % 12; + + std::stringstream ss; + ss << gamme[semitonesFromA] << octave; + return ss.str(); +} diff --git a/src/Sound/SoundUtilities.cpp b/src/SoundFormats/SoundFormats.cpp similarity index 100% rename from src/Sound/SoundUtilities.cpp rename to src/SoundFormats/SoundFormats.cpp diff --git a/src/Sound/xlaw.cpp b/src/SoundFormats/xlaw.cpp similarity index 100% rename from src/Sound/xlaw.cpp rename to src/SoundFormats/xlaw.cpp diff --git a/src/SoundMixer/ChannelImpl.cpp b/src/SoundMixer/ChannelImpl.cpp new file mode 100644 index 0000000..e4a4185 --- /dev/null +++ b/src/SoundMixer/ChannelImpl.cpp @@ -0,0 +1,126 @@ +#include "PommeSound.h" +#include "SoundMixer/ChannelImpl.h" +#include + +namespace Pomme::Sound +{ + extern ChannelImpl* gHeadChan; + extern int gNumManagedChans; +} + +ChannelImpl::ChannelImpl(SndChannelPtr _macChannel, bool transferMacChannelOwnership) + : macChannel(_macChannel) + , macChannelStructAllocatedByPomme(transferMacChannelOwnership) + , source() + , pan(0.0) + , gain(1.0) + , baseNote(kMiddleC) + , playbackNote(kMiddleC) + , pitchMult(1.0) + , loop(false) + , interpolate(false) +{ + macChannel->channelImpl = (Ptr) this; + + Link(); // Link chan into our list of managed chans +} + +ChannelImpl::~ChannelImpl() +{ + Unlink(); + + macChannel->channelImpl = nullptr; + + if (macChannelStructAllocatedByPomme) + { + delete macChannel; + } +} + +void ChannelImpl::Recycle() +{ + source.Clear(); +} + +void ChannelImpl::SetInitializationParameters(long initBits) +{ + interpolate = !(initBits & initNoInterp); + source.SetInterpolation(interpolate); +} + +void ChannelImpl::ApplyParametersToSource(uint32_t mask, bool evenIfInactive) +{ + if (!evenIfInactive && !source.active) + { + return; + } + + // Pitch + if (mask & kApplyParameters_Pitch) + { + double baseFreq = Pomme::Sound::GetMidiNoteFrequency(baseNote); + double playbackFreq = Pomme::Sound::GetMidiNoteFrequency(playbackNote); + source.SetPitch(pitchMult * playbackFreq / baseFreq); + } + + // Pan and gain + if (mask & kApplyParameters_PanAndGain) + { + source.SetPan(pan); + source.SetGain(gain); + } + + // Interpolation + if (mask & kApplyParameters_Interpolation) + { + source.SetInterpolation(interpolate); + } + + // Interpolation + if (mask & kApplyParameters_Loop) + { + source.SetLoop(loop); + } +} + +void ChannelImpl::Link() +{ + if (!Pomme::Sound::gHeadChan) + { + SetNext(nullptr); + } + else + { + assert(nullptr == Pomme::Sound::gHeadChan->GetPrev()); + Pomme::Sound::gHeadChan->SetPrev(this); + SetNext(Pomme::Sound::gHeadChan); + } + + Pomme::Sound::gHeadChan = this; + SetPrev(nullptr); + + Pomme::Sound::gNumManagedChans++; +} + +void ChannelImpl::Unlink() +{ + if (Pomme::Sound::gHeadChan == this) + { + Pomme::Sound::gHeadChan = GetNext(); + } + + if (nullptr != GetPrev()) + { + GetPrev()->SetNext(GetNext()); + } + + if (nullptr != GetNext()) + { + GetNext()->SetPrev(GetPrev()); + } + + SetPrev(nullptr); + SetNext(nullptr); + + Pomme::Sound::gNumManagedChans--; +} diff --git a/src/SoundMixer/ChannelImpl.h b/src/SoundMixer/ChannelImpl.h new file mode 100644 index 0000000..fa247e8 --- /dev/null +++ b/src/SoundMixer/ChannelImpl.h @@ -0,0 +1,73 @@ +#pragma once + +#include "Pomme.h" +#include "SoundMixer/cmixer.h" + +enum ApplyParametersMask +{ + kApplyParameters_PanAndGain = 1 << 0, + kApplyParameters_Pitch = 1 << 1, + kApplyParameters_Loop = 1 << 2, + kApplyParameters_Interpolation = 1 << 3, + kApplyParameters_All = 0xFFFFFFFF +}; + +struct ChannelImpl +{ +private: + ChannelImpl* prev; + ChannelImpl* next; + +public: + // Pointer to application-facing interface + SndChannelPtr macChannel; + + bool macChannelStructAllocatedByPomme; + cmixer::WavStream source; + + // Parameters coming from Mac sound commands, passed back to cmixer source + double pan; + double gain; + Byte baseNote; + Byte playbackNote; + double pitchMult; + bool loop; + bool interpolate; + + bool temporaryPause = false; + + ChannelImpl(SndChannelPtr _macChannel, bool transferMacChannelOwnership); + + ~ChannelImpl(); + + void Recycle(); + + void SetInitializationParameters(long initBits); + + void ApplyParametersToSource(uint32_t mask, bool evenIfInactive = false); + + inline ChannelImpl* GetPrev() const + { + return prev; + } + + inline ChannelImpl* GetNext() const + { + return next; + } + + inline void SetPrev(ChannelImpl* newPrev) + { + prev = newPrev; + } + + inline void SetNext(ChannelImpl* newNext) + { + next = newNext; + macChannel->nextChan = newNext ? newNext->macChannel : nullptr; + } + + void Link(); + + void Unlink(); +}; diff --git a/src/Sound/SoundManager.cpp b/src/SoundMixer/SoundManager.cpp similarity index 68% rename from src/Sound/SoundManager.cpp rename to src/SoundMixer/SoundManager.cpp index 938c60d..29387f0 100644 --- a/src/Sound/SoundManager.cpp +++ b/src/SoundMixer/SoundManager.cpp @@ -1,7 +1,8 @@ #include "Pomme.h" #include "PommeFiles.h" -#include "cmixer.h" #include "PommeSound.h" +#include "SoundMixer/ChannelImpl.h" +#include "SoundMixer/cmixer.h" #include "Utilities/bigendianstreams.h" #include "Utilities/IEEEExtended.h" #include "Utilities/memstream.h" @@ -15,184 +16,14 @@ #define LOG POMME_GENLOG(POMME_DEBUG_SOUND, "SOUN") #define LOG_NOPREFIX POMME_GENLOG_NOPREFIX(POMME_DEBUG_SOUND) -static struct ChannelImpl* headChan = nullptr; -static int nManagedChans = 0; -static double midiNoteFrequencies[128]; - //----------------------------------------------------------------------------- // Internal channel info -enum ApplyParametersMask +namespace Pomme::Sound { - kApplyParameters_PanAndGain = 1 << 0, - kApplyParameters_Pitch = 1 << 1, - kApplyParameters_Loop = 1 << 2, - kApplyParameters_Interpolation = 1 << 3, - kApplyParameters_All = 0xFFFFFFFF -}; - -struct ChannelImpl -{ -private: - ChannelImpl* prev; - ChannelImpl* next; - -public: - // Pointer to application-facing interface - SndChannelPtr macChannel; - - bool macChannelStructAllocatedByPomme; - cmixer::WavStream source; - - // Parameters coming from Mac sound commands, passed back to cmixer source - double pan; - double gain; - Byte baseNote; - Byte playbackNote; - double pitchMult; - bool loop; - bool interpolate; - - bool temporaryPause = false; - - ChannelImpl(SndChannelPtr _macChannel, bool transferMacChannelOwnership) - : macChannel(_macChannel) - , macChannelStructAllocatedByPomme(transferMacChannelOwnership) - , source() - , pan(0.0) - , gain(1.0) - , baseNote(kMiddleC) - , playbackNote(kMiddleC) - , pitchMult(1.0) - , loop(false) - , interpolate(false) - { - macChannel->channelImpl = (Ptr) this; - - Link(); // Link chan into our list of managed chans - } - - ~ChannelImpl() - { - Unlink(); - - macChannel->channelImpl = nullptr; - - if (macChannelStructAllocatedByPomme) - { - delete macChannel; - } - } - - void Recycle() - { - source.Clear(); - } - - void SetInitializationParameters(long initBits) - { - interpolate = !(initBits & initNoInterp); - source.SetInterpolation(interpolate); - } - - void ApplyParametersToSource(uint32_t mask, bool evenIfInactive = false) - { - if (!evenIfInactive && !source.active) - { - return; - } - - // Pitch - if (mask & kApplyParameters_Pitch) - { - double baseFreq = midiNoteFrequencies[baseNote]; - double playbackFreq = midiNoteFrequencies[playbackNote]; - source.SetPitch(pitchMult * playbackFreq / baseFreq); - } - - // Pan and gain - if (mask & kApplyParameters_PanAndGain) - { - source.SetPan(pan); - source.SetGain(gain); - } - - // Interpolation - if (mask & kApplyParameters_Interpolation) - { - source.SetInterpolation(interpolate); - } - - // Interpolation - if (mask & kApplyParameters_Loop) - { - source.SetLoop(loop); - } - } - - ChannelImpl* GetPrev() const - { - return prev; - } - - ChannelImpl* GetNext() const - { - return next; - } - - void SetPrev(ChannelImpl* newPrev) - { - prev = newPrev; - } - - void SetNext(ChannelImpl* newNext) - { - next = newNext; - macChannel->nextChan = newNext ? newNext->macChannel : nullptr; - } - - void Link() - { - if (!headChan) - { - SetNext(nullptr); - } - else - { - assert(nullptr == headChan->GetPrev()); - headChan->SetPrev(this); - SetNext(headChan); - } - - headChan = this; - SetPrev(nullptr); - - nManagedChans++; - } - - void Unlink() - { - if (headChan == this) - { - headChan = GetNext(); - } - - if (nullptr != GetPrev()) - { - GetPrev()->SetNext(GetNext()); - } - - if (nullptr != GetNext()) - { - GetNext()->SetPrev(GetPrev()); - } - - SetPrev(nullptr); - SetNext(nullptr); - - nManagedChans--; - } -}; + struct ChannelImpl* gHeadChan = nullptr; + int gNumManagedChans = 0; +} //----------------------------------------------------------------------------- // Internal utilities @@ -202,47 +33,6 @@ static inline ChannelImpl& GetChannelImpl(SndChannelPtr chan) return *(ChannelImpl*) chan->channelImpl; } -//----------------------------------------------------------------------------- -// MIDI note utilities - -// Note: these names are according to IM:S:2-43. -// These names won't match real-world names. -// E.g. for note 67 (A 440Hz), this will return "A6", whereas the real-world -// convention for that note is "A4". -static std::string GetMidiNoteName(int i) -{ - static const char* gamme[12] = {"A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"}; - - int octave = 1 + (i + 3) / 12; - int semitonesFromA = (i + 3) % 12; - - std::stringstream ss; - ss << gamme[semitonesFromA] << octave; - return ss.str(); -} - -static void InitMidiFrequencyTable() -{ - // powers of twelfth root of two - double gamme[12]; - gamme[0] = 1.0; - for (int i = 1; i < 12; i++) - { - gamme[i] = gamme[i - 1] * 1.059630943592952646; - } - - for (int i = 0; i < 128; i++) - { - int octave = 1 + (i + 3) / 12; // A440 and middle C are in octave 7 - int semitone = (i + 3) % 12; // halfsteps up from A in current octave - if (octave < 7) - midiNoteFrequencies[i] = gamme[semitone] * 440.0 / (1 << (7 - octave)); // 440/(2**octaveDiff) - else - midiNoteFrequencies[i] = gamme[semitone] * 440.0 * (1 << (octave - 7)); // 440*(2**octaveDiff) - //LOG << i << "\t" << GetMidiNoteName(i) << "\t" << midiNoteFrequencies[i] << "\n"; - } -} - //----------------------------------------------------------------------------- // Sound Manager @@ -297,7 +87,8 @@ OSErr SndNewChannel(SndChannelPtr* macChanPtr, short synth, long init, SndCallBa //--------------------------- // Done - LOG << "New channel created, init = $" << std::hex << init << std::dec << ", total managed channels = " << nManagedChans << "\n"; + LOG << "New channel created, init = $" << std::hex << init << std::dec + << ", total managed channels = " << Pomme::Sound::gNumManagedChans << "\n"; return noErr; } @@ -440,7 +231,8 @@ OSErr SndDoImmediate(SndChannelPtr chan, const SndCommand* cmd) } case freqCmd: - LOG << "freqCmd " << cmd->param2 << " " << GetMidiNoteName(cmd->param2) << " " << midiNoteFrequencies[cmd->param2] << "\n"; + LOG << "freqCmd " << cmd->param2 << " " + << Pomme::Sound::GetMidiNoteName(cmd->param2) << " " << Pomme::Sound::GetMidiNoteFrequency(cmd->param2) << "\n"; impl.playbackNote = Byte(cmd->param2); impl.ApplyParametersToSource(kApplyParameters_Pitch); break; @@ -598,7 +390,7 @@ NumVersion SndSoundManagerVersion() void Pomme_PauseAllChannels(Boolean pause) { - for (auto* chan = headChan; chan; chan = chan->GetNext()) + for (auto* chan = Pomme::Sound::gHeadChan; chan; chan = chan->GetNext()) { auto& source = chan->source; if (pause && source.state == cmixer::CM_STATE_PLAYING && !chan->temporaryPause) @@ -617,17 +409,16 @@ void Pomme_PauseAllChannels(Boolean pause) //----------------------------------------------------------------------------- // Init Sound Manager -void Pomme::Sound::Init() +void Pomme::Sound::InitMixer() { - InitMidiFrequencyTable(); cmixer::InitWithSDL(); } -void Pomme::Sound::Shutdown() +void Pomme::Sound::ShutdownMixer() { cmixer::ShutdownWithSDL(); - while (headChan) + while (Pomme::Sound::gHeadChan) { - SndDisposeChannel(headChan->macChannel, true); + SndDisposeChannel(Pomme::Sound::gHeadChan->macChannel, true); } } diff --git a/src/Sound/cmixer.cpp b/src/SoundMixer/cmixer.cpp similarity index 100% rename from src/Sound/cmixer.cpp rename to src/SoundMixer/cmixer.cpp diff --git a/src/Sound/cmixer.h b/src/SoundMixer/cmixer.h similarity index 100% rename from src/Sound/cmixer.h rename to src/SoundMixer/cmixer.h