diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7b9a25f..7697f51 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,7 +2,7 @@ SET(CMAKE_C_COMPILER arm-none-eabi-gcc) SET(CMAKE_CXX_COMPILER arm-none-eabi-g++) set_property(SOURCE engine/gba/sin_lut.s PROPERTY LANGUAGE C) -add_executable(${PROJECT_NAME}.elf main.cpp engine/sprites/sprite_manager.cpp engine/sprites/sprite_manager.h engine/gba/tonc_memmap.h engine/gba/tonc_core.h engine/gba/tonc_memdef.h engine/gba/tonc_types.h engine/sprites/sprite.cpp engine/sprites/sprite.h kul.h engine/palette/palette_manager.cpp engine/palette/palette_manager.h engine/palette/combined_palette.cpp engine/palette/combined_palette.h engine/allocator.cpp engine/allocator.h engine/gba/tonc_oam.h engine/gba/tonc_math.h engine/gba/sin_lut.s engine/Scene.cpp engine/Scene.h engine/sprites/sprite_builder.cpp engine/sprites/sprite_builder.h engine/sprites/affine_sprite.cpp engine/sprites/affine_sprite.h flying_stuff_scene.cpp flying_stuff_scene.h engine/gba_engine.cpp engine/gba_engine.h engine/background/text_stream.cpp engine/background/text_stream.h engine/background/background.cpp engine/background/background.h engine/background/text.h sample_start_scene.cpp sample_start_scene.h engine/effects/fade_out_scene.cpp engine/effects/fade_out_scene.h engine/gba/tonc_core_stub.h engine/effects/scene_effect.h lama.h sample_sound.h) +add_executable(${PROJECT_NAME}.elf main.cpp engine/sprites/sprite_manager.cpp engine/sprites/sprite_manager.h engine/gba/tonc_memmap.h engine/gba/tonc_core.h engine/gba/tonc_memdef.h engine/gba/tonc_types.h engine/sprites/sprite.cpp engine/sprites/sprite.h kul.h engine/palette/palette_manager.cpp engine/palette/palette_manager.h engine/palette/combined_palette.cpp engine/palette/combined_palette.h engine/allocator.cpp engine/allocator.h engine/gba/tonc_oam.h engine/gba/tonc_math.h engine/gba/sin_lut.s engine/Scene.cpp engine/Scene.h engine/sprites/sprite_builder.cpp engine/sprites/sprite_builder.h engine/sprites/affine_sprite.cpp engine/sprites/affine_sprite.h flying_stuff_scene.cpp flying_stuff_scene.h engine/gba_engine.cpp engine/gba_engine.h engine/background/text_stream.cpp engine/background/text_stream.h engine/background/background.cpp engine/background/background.h engine/background/text.h sample_start_scene.cpp sample_start_scene.h engine/effects/fade_out_scene.cpp engine/effects/fade_out_scene.h engine/gba/tonc_core_stub.h engine/effects/scene_effect.h lama.h sample_sound.h engine/sound.h engine/sound.cpp) add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD COMMAND ${CMAKE_OBJCOPY} -v -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.gba diff --git a/src/engine/gba_engine.cpp b/src/engine/gba_engine.cpp index acc78ba..bc66fe8 100644 --- a/src/engine/gba_engine.cpp +++ b/src/engine/gba_engine.cpp @@ -7,90 +7,80 @@ #include "gba_engine.h" #include "allocator.h" -int x = 0; +SoundControl* GBAEngine::activeChannelA; +SoundControl* GBAEngine::activeChannelB; + +void GBAEngine::vsync() { + while (REG_VCOUNT >= 160); + while (REG_VCOUNT < 160); +} + void GBAEngine::onVBlank() { - REG_IME = 0; + stopOnVBlank(); unsigned short tempInterruptState = REG_IF; if((REG_IF & INTERRUPT_VBLANK) == INTERRUPT_VBLANK) { - // if vblanks_remaning is zero: stop - //*sound_control &= ~(SOUND_B_RIGHT_CHANNEL | SOUND_B_LEFT_CHANNEL | SOUND_B_FIFO_RESET); - //*dma2_control = 0; - // else counter-- - // OR: to restart sound (channelA) + if(GBAEngine::activeChannelA && GBAEngine::activeChannelA->done()) { - - x++; - } - - //TextStream::instance() << x; - REG_IF = tempInterruptState; - REG_IME = 1; -} - -void GBAEngine::update() { - vsync(); - - if(sceneToTransitionTo) { - currentEffectForTransition->update(); - - if(currentEffectForTransition->isDone()) { - setScene(sceneToTransitionTo); + } + if(GBAEngine::activeChannelB) { + if(GBAEngine::activeChannelB->done()) { + GBAEngine::activeChannelB->disable(); + delete GBAEngine::activeChannelB; + } else { + GBAEngine::activeChannelB->step(); + } } } - spriteManager.render(); - u16 keys = readKeys(); - this->currentScene->tick(keys); -} - -void GBAEngine::transitionIntoScene(Scene *scene, SceneEffect* effect) { - sceneToTransitionTo = scene; - currentEffectForTransition = effect; - currentEffectForTransition->setSceneToAffect(this->currentScene); + REG_IF = tempInterruptState; + startOnVBlank(); } u16 GBAEngine::readKeys() { return ~REG_KEYS & KEY_ANY; } -SoundControl GBAEngine::channelAControl() { - return { - ®_DMA1CNT, - ®_DMA1SAD, - ®_DMA1DAD, - ®_FIFOA, - SDS_AR | SDS_AL | SDS_ARESET - }; +void GBAEngine::dequeueAllSounds() { + stopOnVBlank(); + + if(GBAEngine::activeChannelA) { + GBAEngine::activeChannelA->disable(); + } if(GBAEngine::activeChannelB) { + GBAEngine::activeChannelB->disable(); + } } -SoundControl GBAEngine::channelBControl() { - return { - ®_DMA2CNT, - ®_DMA2SAD, - ®_DMA2DAD, - ®_FIFOB, - SDS_BR | SDS_BL | SDS_BRESET - }; -} +void GBAEngine::enqueueSound(const s8 *data, int totalSamples, int sampleRate, SoundChannel channel) { + SoundControl* control; + stopOnVBlank(); + + if(channel == ChannelA) { // repeating bg music can be restarted + if(GBAEngine::activeChannelA) delete GBAEngine::activeChannelA; + control = SoundControl::channelAControl(); + GBAEngine::activeChannelA = control; + } else { // sound still playing, don't stop that + if(GBAEngine::activeChannelB) { + if(!GBAEngine::activeChannelB->done()) return; + delete GBAEngine::activeChannelB; + } + control = SoundControl::channelBControl(); + GBAEngine::activeChannelB = control; + } -void GBAEngine::queueSound(const s8 *data, int totalSamples, int sampleRate, SoundControl control) { REG_TM0CNT = 0; - *(control.DMAControl) = 0; // reset previous sound + control->disable(); - REG_SNDDSCNT |= control.ControlFlags; // output to both sides, reset fifo + REG_SNDDSCNT |= control->getControlFlags(); // output to both sides, reset fifo REG_SNDSTAT = SSTAT_ENABLE; // enable all sound - - *(control.DMASourceAddress) = (u32) data; // WARNING - these are pointers to the address! - *(control.DMADestinationAddress) = (u32) control.FiFoBuffer; - *(control.DMAControl) = DMA_DST_FIXED | DMA_REPEAT | DMA_32 | DMA_SYNC_TO_TIMER | DMA_ENABLE; - u16 ticksPerSample = CLOCK / sampleRate; // divide the clock (ticks/second) by the sample rate (samples/second) - REG_TM0D = OVERFLOW_16_BIT_VALUE - ticksPerSample; - // channel_a_vblanks_remaining = total_samples * ticks_per_sample * (1.0 / CYCLES_PER_BLANK); + control->accept(data, totalSamples, ticksPerSample); + startOnVBlank(); + control->enable(); + REG_TM0D = OVERFLOW_16_BIT_VALUE - ticksPerSample; REG_TM0CNT = TM_ENABLE | TM_FREQ_1; // enable timer - dma auto-syncs to this thanks to DMA_SYNC_TO_TIMER } @@ -98,16 +88,38 @@ GBAEngine::GBAEngine() { // setup screen control flags REG_DISPCNT = DCNT_MODE0 | DCNT_OBJ | DCNT_OBJ_1D | DCNT_BG0 | DCNT_BG1 | DCNT_BG2 | DCNT_BG3; - // setup interrupt control flags for vblank IRQing + // setup interrupt control flags for vblank IRQing (started only when sound played) REG_DISPSTAT |= DISPLAY_INTERRUPT_VBLANK_ENABLE; REG_IE |= INTERRUPT_VBLANK; *IRQ_CALLBACK = (u32) &GBAEngine::onVBlank; - //REG_IME = 1; REG_SNDDSCNT = 0; Allocator::free(); } +void GBAEngine::update() { + if(sceneToTransitionTo) { + dequeueAllSounds(); + currentEffectForTransition->update(); + + if(currentEffectForTransition->isDone()) { + setScene(sceneToTransitionTo); + } + } + + u16 keys = readKeys(); + this->currentScene->tick(keys); + + vsync(); + spriteManager.render(); +} + +void GBAEngine::transitionIntoScene(Scene *scene, SceneEffect* effect) { + sceneToTransitionTo = scene; + currentEffectForTransition = effect; + currentEffectForTransition->setSceneToAffect(this->currentScene); +} + void GBAEngine::cleanupPreviousScene() { delete currentScene; sceneToTransitionTo = nullptr; diff --git a/src/engine/gba_engine.h b/src/engine/gba_engine.h index d9cf89b..ce91fb9 100644 --- a/src/engine/gba_engine.h +++ b/src/engine/gba_engine.h @@ -11,27 +11,7 @@ #include #include #include "Scene.h" - -#define CLOCK 16777216 -#define CYCLES_PER_BLANK 280806 -#define OVERFLOW_16_BIT_VALUE 65536 -#define DISPLAY_INTERRUPT_VBLANK_ENABLE 0x08 -#define INTERRUPT_VBLANK 0x1 -#define DMA_SYNC_TO_TIMER 0x30000000 - -#define IRQ_CALLBACK ((volatile unsigned int*) 0x3007FFC) - -enum SoundChannel { - ChannelA, ChannelB -}; - -struct SoundControl { - vu32* DMAControl; // ex. ®_DMA1CNT - vu32* DMASourceAddress; // ex. ®_DMA1SAD - vu32* DMADestinationAddress; // ex. ®_DMA1DAD - vu32* FiFoBuffer; // ex. ®_FIFOA - u16 ControlFlags; -}; +#include "sound.h" class GBAEngine { private: @@ -42,18 +22,15 @@ private: SpriteManager spriteManager; - void vsync() { - while (REG_VCOUNT >= 160); - while (REG_VCOUNT < 160); - } - SoundControl channelAControl(); - SoundControl channelBControl(); - SoundControl soundControl(SoundChannel channel) { - return channel == ChannelA ? channelAControl() : channelBControl(); - }; + static SoundControl* activeChannelA; + static SoundControl* activeChannelB; + void vsync(); void cleanupPreviousScene(); - void queueSound(const s8* data, int totalSamples, int sampleRate, SoundControl control); + void enqueueSound(const s8 *data, int totalSamples, int sampleRate, SoundChannel channel); + + static void startOnVBlank() { REG_IME = 1; } + static void stopOnVBlank() { REG_IME = 0; } static void onVBlank(); public: @@ -62,11 +39,12 @@ public: void setScene(Scene* scene); void transitionIntoScene(Scene* scene, SceneEffect* effect); - void queueMusic(const s8* data, int totalSamples) { - queueSound(data, totalSamples, 16000, soundControl(ChannelA)); + void dequeueAllSounds(); + void enqueueMusic(const s8 *data, int totalSamples) { + enqueueSound(data, totalSamples, 16000, ChannelA); } - void queueSound(const s8* data, int totalSamples) { - queueSound(data, totalSamples, 16000, soundControl(ChannelB)); + void enqueueSound(const s8 *data, int totalSamples) { + enqueueSound(data, totalSamples, 16000, ChannelB); } u16 readKeys(); diff --git a/src/engine/sound.cpp b/src/engine/sound.cpp new file mode 100644 index 0000000..ee723aa --- /dev/null +++ b/src/engine/sound.cpp @@ -0,0 +1,32 @@ +// +// Created by Wouter Groeneveld on 07/08/18. +// + +#include +#include "sound.h" + +void SoundControl::accept(const void *data, int totalSamples, int ticksPerSample) { + *DMASourceAddress = (u32) data; + *DMADestinationAddress = (u32) FiFoBuffer; + vblanksTotal = vblanksRemaning = totalSamples * ticksPerSample * (1.0 / CYCLES_PER_BLANK); +}; + +SoundControl* SoundControl::channelAControl() { + return new SoundControl{ + ®_DMA1CNT, + ®_DMA1SAD, + ®_DMA1DAD, + ®_FIFOA, + SDS_AR | SDS_AL | SDS_ARESET + }; +} + +SoundControl* SoundControl::channelBControl() { + return new SoundControl{ + ®_DMA2CNT, + ®_DMA2SAD, + ®_DMA2DAD, + ®_FIFOB, + SDS_BR | SDS_BL | SDS_BRESET + }; +} diff --git a/src/engine/sound.h b/src/engine/sound.h new file mode 100644 index 0000000..88cd634 --- /dev/null +++ b/src/engine/sound.h @@ -0,0 +1,64 @@ +// +// Created by Wouter Groeneveld on 07/08/18. +// + +#ifndef GBA_SPRITE_ENGINE_SOUND_H +#define GBA_SPRITE_ENGINE_SOUND_H + +#include +#include +#include +#include + +#define CLOCK 16777216 +#define CYCLES_PER_BLANK 280806 +#define OVERFLOW_16_BIT_VALUE 65536 +#define DISPLAY_INTERRUPT_VBLANK_ENABLE 0x08 +#define INTERRUPT_VBLANK 0x1 +#define DMA_SYNC_TO_TIMER 0x30000000 + +#define IRQ_CALLBACK ((volatile unsigned int*) 0x3007FFC) + +enum SoundChannel { + ChannelA, ChannelB +}; + +class SoundControl { +private: + vu32* DMAControl; // ex. ®_DMA1CNT + vu32* DMASourceAddress; // ex. ®_DMA1SAD + vu32* DMADestinationAddress; // ex. ®_DMA1DAD + vu32* FiFoBuffer; // ex. ®_FIFOA + u16 controlFlags; + u32 vblanksRemaning; // updated each vblank, counts down to 0 + u32 vblanksTotal; // calculated once when enqueueing + +public: + SoundControl(vu32* dma, vu32* src, vu32* dest, vu32* fifo, u16 flags) + : DMAControl(dma), DMASourceAddress(src), DMADestinationAddress(dest), FiFoBuffer(fifo), controlFlags(flags), vblanksRemaning(0), vblanksTotal(0) {} + + u16 getControlFlags() { return controlFlags; } + u32 getVBlanksRemaning() { return vblanksRemaning; } + void step() { vblanksRemaning--; } + bool done() { return vblanksRemaning <= 0; } + u32 getVBlanksTotal() { return vblanksTotal; } + + void disable() { + *(DMAControl) = 0; + vblanksRemaning = 0; + REG_SNDDSCNT &= ~(controlFlags); + }; + void enable() { + *DMAControl = DMA_DST_FIXED | DMA_REPEAT | DMA_32 | DMA_SYNC_TO_TIMER | DMA_ENABLE; + }; + void accept(const void* data, int totalSamples, int ticksPerSample); + + static SoundControl* channelAControl(); + static SoundControl* channelBControl(); + + static SoundControl* soundControl(SoundChannel channel) { + return channel == ChannelA ? channelAControl() : channelBControl(); + }; +}; + +#endif //GBA_SPRITE_ENGINE_SOUND_H diff --git a/src/main.cpp b/src/main.cpp index db96ad3..ab5791f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,9 +5,7 @@ #include #include -#include "sample_sound.h" #include "sample_start_scene.h" -#include "flying_stuff_scene.h" /** * shared palette extracted from grit @@ -21,8 +19,6 @@ int main() { startScene->setEngineForSceneSwitching(engine); engine->setScene(startScene); - engine->queueSound(zelda_secret_16K_mono, zelda_secret_16K_mono_bytes); - while (true) { engine->update(); engine->delay(1000); diff --git a/src/sample_start_scene.cpp b/src/sample_start_scene.cpp index 9d3c8bb..cbff0b1 100644 --- a/src/sample_start_scene.cpp +++ b/src/sample_start_scene.cpp @@ -10,8 +10,8 @@ #include "sample_start_scene.h" #include "flying_stuff_scene.h" -#include "kul.h" #include "lama.h" +#include "sample_sound.h" std::vector SampleStartScene::backgrounds() { return {}; @@ -34,6 +34,7 @@ void SampleStartScene::load() { .buildPtr(); TextStream::instance().setText("PRESS START", 3, 8); + engine->enqueueSound(zelda_secret_16K_mono, zelda_secret_16K_mono_bytes); } void SampleStartScene::tick(u16 keys) {