Initial commit

This commit is contained in:
Gericom
2025-11-22 17:21:45 +01:00
commit 5d6f67c612
517 changed files with 63025 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

7
arm9/gfx/backIcon.grit Normal file
View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/backIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/bannerListIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

View File

@@ -0,0 +1,5 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

View File

@@ -0,0 +1,18 @@
# tile format
-gt
# tile reduction by tiles, palette and hflip/vflip
-mRtpf
-ma2
# graphics bit depth is 4 (16 color)
-gB4
-p
-pn16
# map layout standard bg format
-mLs
#-gzl
#-mzl

BIN
arm9/gfx/bottomSheetBg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

View File

@@ -0,0 +1,3 @@
-gb
-gB8
-p!

BIN
arm9/gfx/carouselMask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

5
arm9/gfx/chipFilled.grit Normal file
View File

@@ -0,0 +1,5 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4

BIN
arm9/gfx/chipFilled.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/coverflowIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

View File

@@ -0,0 +1,3 @@
-gb
-gB8
-p

BIN
arm9/gfx/folderCover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

7
arm9/gfx/gamesIcon.grit Normal file
View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/gamesIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

7
arm9/gfx/hGridIcon.grit Normal file
View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/hGridIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

7
arm9/gfx/heartIcon.grit Normal file
View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/heartIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

View File

@@ -0,0 +1,3 @@
-gb
-gB8
-p!

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 B

7
arm9/gfx/iconCell.grit Normal file
View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/iconCell.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

5
arm9/gfx/iconCell2.grit Normal file
View File

@@ -0,0 +1,5 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4

BIN
arm9/gfx/iconCell2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

5
arm9/gfx/iconCell3.grit Normal file
View File

@@ -0,0 +1,5 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4

BIN
arm9/gfx/iconCell3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/largeFileIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

7
arm9/gfx/listIcon.grit Normal file
View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/listIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

13
arm9/gfx/mainBg.grit Normal file
View File

@@ -0,0 +1,13 @@
# tile format
-gt
# tile reduction
-mR8
# graphics bit depth is 8 (256 color)
-gB8
-p!
# map layout standard bg format
-mLs

BIN
arm9/gfx/mainBg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

7
arm9/gfx/moviesIcon.grit Normal file
View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/moviesIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

7
arm9/gfx/musicIcon.grit Normal file
View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/musicIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/picturesIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

7
arm9/gfx/recentIcon.grit Normal file
View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/recentIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

18
arm9/gfx/scrim.grit Normal file
View File

@@ -0,0 +1,18 @@
# tile format
-gt
# tile reduction by tiles, palette and hflip/vflip
-mRtpf
-mp1
# graphics bit depth is 4 (16 color)
-gB4
-p
-pn16
# map layout standard bg format
-mLs
#-gzl
#-mzl

BIN
arm9/gfx/scrim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/settingsIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/smallHeartIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

6
arm9/gfx/splashTop.grit Normal file
View File

@@ -0,0 +1,6 @@
-gt
-gB8
-mRtpf
-mp0
-p
-mLs

BIN
arm9/gfx/splashTop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,3 @@
-gb
-gB8
-p

BIN
arm9/gfx/unknownCover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/unknownIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

7
arm9/gfx/vGridIcon.grit Normal file
View File

@@ -0,0 +1,7 @@
# tile format
-gt
# graphics bit depth is 4 (16 color)
-gB4
-p!

BIN
arm9/gfx/vGridIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

519
arm9/source/App.cpp Normal file
View File

@@ -0,0 +1,519 @@
#include "common.h"
#include <algorithm>
#include <libtwl/mem/memVram.h>
#include <libtwl/gfx/gfx.h>
#include <libtwl/gfx/gfxOam.h>
#include <libtwl/gfx/gfxPalette.h>
#include <libtwl/gfx/gfxBackground.h>
#include <libtwl/gfx/gfxStatus.h>
#include <libtwl/gfx/gfx3d.h>
#include <libtwl/gfx/gfx3dCmd.h>
#include <libtwl/sys/sysPower.h>
#include "animation/Animator.h"
#include "gui/materialDesign.h"
#include "themes/material/MaterialColorSchemeFactory.h"
#include "core/math/ColorConverter.h"
#include "core/math/RgbMixer.h"
#include "gui/GraphicsContext.h"
#include "romBrowser/views/ChipView.h"
#include "picoLoaderBootstrap.h"
#include "romBrowser/DisplayMode/RomBrowserDisplayModeFactory.h"
#include "romBrowser/Theme/Material/MaterialThemeFileIconFactory.h"
#include "romBrowser/views/NdsGameDetailsBottomSheetView.h"
#include "romBrowser/views/DisplaySettingsBottomSheetView.h"
#include "bgm/AudioStreamPlayer.h"
#include "bgm/BgmService.h"
#include "themes/ThemeInfoFactory.h"
#include "themes/ThemeFactory.h"
#include "gui/Gx.h"
#include "splashTop.h"
#include "App.h"
#define SPLASH_FRAMES 44
App::App(IAppSettingsService& appSettingsService, IBgmService& bgmService)
: _mainObjPltt(GFX_PLTT_OBJ_MAIN)
, _mainObjVram(GFX_OBJ_MAIN)
, _mainObjDialogVram(GFX_OBJ_MAIN, 128 * 1024)
, _subObjVram(GFX_OBJ_SUB)
, _textureVram((vu16*)0x06860000)
, _texturePaletteVram((vu16*)0x6880000)
, _mainVramContext(nullptr, &_mainObjVram, &_textureVram, &_texturePaletteVram)
, _subVramContext(nullptr, &_subObjVram, nullptr, nullptr)
, _appSettingsService(appSettingsService)
, _bgmService(bgmService)
, _inputProvider(&_inputSource)
, _inputRepeater(&_inputProvider,
InputKey::DpadLeft | InputKey::DpadRight | InputKey::DpadUp | InputKey::DpadDown,
25, 8)
, _romBrowserController(&appSettingsService, &_ioTaskQueue, &_bgTaskQueue)
, _displaySettingsBottomSheetViewModel(&_romBrowserController)
, _romBrowserBottomScreenViewModel(&_romBrowserController)
, _dialogPresenter(&_focusManager, &_mainObjDialogVram) { }
void App::InitVramMapping() const
{
mem_setVramAMapping(MEM_VRAM_AB_TEX_SLOT_1);
mem_setVramBMapping(MEM_VRAM_AB_MAIN_OBJ_00000);
mem_setVramCMapping(MEM_VRAM_C_SUB_BG_00000);
mem_setVramDMapping(MEM_VRAM_D_TEX_SLOT_0);
mem_setVramEMapping(MEM_VRAM_E_TEX_PLTT_SLOT_0123);
mem_setVramFMapping(MEM_VRAM_FG_MAIN_BG_00000);
mem_setVramGMapping(MEM_VRAM_FG_MAIN_BG_04000);
mem_setVramHMapping(MEM_VRAM_H_SUB_BG_EXT_PLTT_SLOT_0123);
mem_setVramIMapping(MEM_VRAM_I_SUB_OBJ_00000);
}
void App::DisplaySplashScreen() const
{
dma_ntrCopy32(3, splashTopTiles, GFX_BG_SUB, splashTopTilesLen);
dma_ntrCopy32(3, splashTopMap, (u8*)GFX_BG_SUB + 0x3000, splashTopMapLen);
mem_setVramHMapping(MEM_VRAM_H_LCDC);
dma_ntrCopy32(3, splashTopPal, (void*)0x0689A000, splashTopPalLen);
mem_setVramHMapping(MEM_VRAM_H_SUB_BG_EXT_PLTT_SLOT_0123);
VBlank::Wait();
sys_setMainEngineToBottomScreen();
REG_DISPCNT_SUB = 0x40211015;
REG_BG1HOFS_SUB = 0;
REG_BG1VOFS_SUB = 0;
REG_BG1CNT_SUB = 0x0680;
REG_DISPCNT_SUB |= 1 << 9;
REG_BLDCNT_SUB = 0x3D42;
REG_BLDALPHA_SUB = 0x10;
REG_MASTER_BRIGHT_SUB = 0;
}
void App::LoadTheme()
{
ThemeInfoFactory themeInfoFactory;
auto themeInfo = themeInfoFactory.CreateFromThemeFolder(_appSettingsService.GetAppSettings().theme);
if (!themeInfo)
{
LOG_DEBUG("Failed to load theme '%s'. Using fallback theme.\n", _appSettingsService.GetAppSettings().theme.GetString());
themeInfo = themeInfoFactory.CreateFallbackTheme();
}
_theme = ThemeFactory().CreateFromThemeInfo(themeInfo.get());
themeInfo.reset();
_theme->LoadRomBrowserResources(_mainVramContext, _subVramContext);
_topBackground = _theme->CreateRomBrowserTopBackground();
_topBackground->LoadResources(*_theme, _subVramContext);
_bottomBackground = _theme->CreateRomBrowserBottomBackground();
_bottomBackground->LoadResources(*_theme, _mainVramContext);
_materialThemeFileIconFactory = std::make_unique<MaterialThemeFileIconFactory>(
&_theme->GetMaterialColorScheme(), _theme->GetFontRepository());
}
void App::VCountIrq()
{
_mainObjPltt.VCount();
}
void App::Run()
{
InitVramMapping();
DisplaySplashScreen();
gx_init();
_chipViewVram = ChipView::UploadGraphics(_mainObjVram);
_iconButtonViewVram = IconButton2DView::UploadGraphics(_mainObjVram);
mem_setVramEMapping(MEM_VRAM_E_LCDC);
_rgb6Palette.UploadGraphics(_mainVramContext);
mem_setVramEMapping(MEM_VRAM_E_TEX_PLTT_SLOT_0123);
_dialogPresenter.InitVram();
LoadTheme();
_ioTaskQueue.StartThread(1, _ioTaskThreadStack, sizeof(_ioTaskThreadStack));
_bgTaskQueue.StartThread(2, _bgTaskThreadStack, sizeof(_bgTaskThreadStack));
StoreVramState(_vramStateBeforeMakeBottomScreenView);
_romBrowserBottomScreenView = std::make_unique<RomBrowserBottomScreenView>(
&_romBrowserBottomScreenViewModel,
RomBrowserDisplayModeFactory().GetRomBrowserDisplayMode(
_romBrowserController.GetRomBrowserDisplaySettings().layout),
_materialThemeFileIconFactory.get(),
_theme->GetRomBrowserViewFactory(),
&_vblankTextureLoader);
_romBrowserBottomScreenView->InitVram(_mainVramContext);
StoreVramState(_vramStateAfterMakeBottomScreenView);
const auto& materialColorScheme = _theme->GetMaterialColorScheme();
auto scrimBlendColor = Rgb<8, 8, 8>(
materialColorScheme.inverseOnSurface.r + (materialColorScheme.scrim.r - materialColorScheme.inverseOnSurface.r) * 5 / 16,
materialColorScheme.inverseOnSurface.g + (materialColorScheme.scrim.g - materialColorScheme.inverseOnSurface.g) * 5 / 16,
materialColorScheme.inverseOnSurface.b + (materialColorScheme.scrim.b - materialColorScheme.inverseOnSurface.b) * 5 / 16);
RgbMixer::MakeGradientPalette((u16*)GFX_PLTT_BG_MAIN, scrimBlendColor, materialColorScheme.GetColor(md::sys::color::surfaceContainerLow));
GFX_PLTT_BG_MAIN[0] = ColorConverter::ToGBGR565(materialColorScheme.inverseOnSurface);
GFX_PLTT_BG_MAIN[31] = ColorConverter::ToGBGR565(materialColorScheme.scrim);
REG_DISPCNT = 0x211F1B;
REG_BG0HOFS = 0;
REG_BG0VOFS = 0;
REG_BG0CNT = 3;
Gx::MtxMode(GX_MTX_MODE_PROJECTION);
mtx43_t orthoMtx =
{
2048, 0, 0,
0, -21845, 0,
0, 0, 4096 >> 5,
-4096, 4096, 0
};
Gx::MtxLoad43(&orthoMtx);
_vcountIrqStarted = false;
rtos_disableIrqMask(RTOS_IRQ_VCOUNT);
rtos_setIrqFunc(RTOS_IRQ_VCOUNT, [] (u32 mask) { ((App*)gProcessManager.GetRunningProcess())->VCountIrq(); });
LOG_DEBUG("Amount of main obj vram used: %d\n", _mainObjVram.GetState());
_ioTaskQueue.Enqueue([this] (const vu8& cancelRequested)
{
_bgmService.StartBgmFromConfig();
return TaskResult<void>::Completed();
});
_fadeAnimator = Animator(16, 0, 16, &md::sys::motion::easing::linear);
MainLoop();
_bgmService.StopBgm();
rtos_disableIrqMask(RTOS_IRQ_VCOUNT);
rtos_setIrqFunc(RTOS_IRQ_VCOUNT, nullptr);
}
void App::MainLoop()
{
bool fadeIn = true;
int fadeWaitFrames = SPLASH_FRAMES;
while (true)
{
Update();
Draw();
VBlank::Wait();
VBlank();
if (_exit)
{
bool fadeComplete = _fadeAnimator.Update();
REG_MASTER_BRIGHT = 0x4000 | _fadeAnimator.GetValue();
REG_MASTER_BRIGHT_SUB = 0x4000 | _fadeAnimator.GetValue();
if (fadeComplete)
{
break;
}
}
else if (fadeIn)
{
if (fadeWaitFrames)
{
fadeWaitFrames--;
REG_BLDALPHA_SUB = 16;
REG_MASTER_BRIGHT = 0x4010;
}
else
{
bool fadeComplete = _fadeAnimator.Update();
if (fadeComplete)
{
fadeIn = false;
REG_BLDCNT_SUB = 0;
REG_DISPCNT_SUB &= ~(1 << 9);
REG_MASTER_BRIGHT = 0;
}
else
{
int fade = _fadeAnimator.GetValue();
REG_BLDALPHA_SUB = ((16 - fade) << 8) | fade;
REG_MASTER_BRIGHT = 0x4000 | fade;
}
}
}
}
}
void App::Exit()
{
_fadeAnimator.Goto(16, 16, &md::sys::motion::easing::linear);
_exit = true;
}
void App::HandleTrigger(RomBrowserStateTrigger trigger, RomBrowserState newState)
{
switch (trigger)
{
case RomBrowserStateTrigger::None:
case RomBrowserStateTrigger::Launch:
{
break;
}
case RomBrowserStateTrigger::ShowGameInfo:
{
HandleShowGameInfoTrigger();
break;
}
case RomBrowserStateTrigger::HideGameInfo:
{
HandleHideGameInfoTrigger();
break;
}
case RomBrowserStateTrigger::ShowDisplaySettings:
{
HandleShowDisplaySettingsTrigger();
break;
}
case RomBrowserStateTrigger::HideDisplaySettings:
{
HandleHideDisplaySettingsTrigger();
break;
}
case RomBrowserStateTrigger::Navigate:
{
HandleNavigateTrigger();
break;
}
case RomBrowserStateTrigger::FolderLoadDone:
{
HandleFolderLoadDoneTrigger();
break;
}
case RomBrowserStateTrigger::ChangeDisplayMode:
{
_changeDisplayMode = true;
break;
}
}
}
void App::HandleShowGameInfoTrigger()
{
auto gameInfoDialog = std::make_unique<NdsGameDetailsBottomSheetView>(
&_romBrowserController, &_theme->GetMaterialColorScheme(), _theme->GetFontRepository());
gameInfoDialog->SetGraphics(_chipViewVram);
_dialogPresenter.ShowDialog(std::move(gameInfoDialog));
}
void App::HandleHideGameInfoTrigger()
{
_dialogPresenter.CloseDialog();
if (!_dialogPresenter.GetOldFocus())
_romBrowserBottomScreenView->Focus(_focusManager);
}
void App::HandleShowDisplaySettingsTrigger()
{
auto displaySettingsDialog = std::make_unique<DisplaySettingsBottomSheetView>(
&_displaySettingsBottomSheetViewModel, &_theme->GetMaterialColorScheme(), _theme->GetFontRepository());
displaySettingsDialog->SetGraphics(_iconButtonViewVram);
_dialogPresenter.ShowDialog(std::move(displaySettingsDialog));
}
void App::HandleHideDisplaySettingsTrigger()
{
_dialogPresenter.CloseDialog();
if (!_dialogPresenter.GetOldFocus())
_romBrowserBottomScreenView->Focus(_focusManager);
}
void App::HandleNavigateTrigger()
{
if (!_romBrowserBottomScreenView->IsAppBarFocused(_focusManager))
_focusManager.Unfocus();
}
void App::HandleFolderLoadDoneTrigger()
{
_romBrowserTopScreenView.reset();
RestoreVramState(_vramStateAfterMakeBottomScreenView);
auto displayMode = RomBrowserDisplayModeFactory().GetRomBrowserDisplayMode(
_romBrowserController.GetRomBrowserDisplaySettings().layout);
_romBrowserTopScreenView = std::make_unique<RomBrowserTopScreenView>(
_romBrowserController.GetRomBrowserViewModel(),
displayMode,
_materialThemeFileIconFactory.get(),
_theme->GetRomBrowserViewFactory());
_romBrowserTopScreenView->InitVram(_subVramContext);
_romBrowserBottomScreenView->RomBrowserViewModelInvalidated(_mainVramContext);
if (!_focusManager.GetCurrentFocus())
_romBrowserBottomScreenView->Focus(_focusManager);
}
void App::HandleChangeDisplayModeTrigger(RomBrowserState newState)
{
_dialogPresenter.ClearOldFocus();
RestoreVramState(_vramStateBeforeMakeBottomScreenView);
auto displayMode = RomBrowserDisplayModeFactory().GetRomBrowserDisplayMode(
_romBrowserController.GetRomBrowserDisplaySettings().layout);
_romBrowserBottomScreenView = std::make_unique<RomBrowserBottomScreenView>(
&_romBrowserBottomScreenViewModel,
displayMode,
_materialThemeFileIconFactory.get(),
_theme->GetRomBrowserViewFactory(),
&_vblankTextureLoader);
_romBrowserBottomScreenView->InitVram(_mainVramContext);
StoreVramState(_vramStateAfterMakeBottomScreenView);
_romBrowserTopScreenView = std::make_unique<RomBrowserTopScreenView>(
_romBrowserController.GetRomBrowserViewModel(),
displayMode,
_materialThemeFileIconFactory.get(),
_theme->GetRomBrowserViewFactory());
_romBrowserTopScreenView->InitVram(_subVramContext);
_romBrowserBottomScreenView->RomBrowserViewModelInvalidated(_mainVramContext);
if (newState == RomBrowserState::Browser)
_romBrowserBottomScreenView->Focus(_focusManager);
}
bool App::IsRomBrowserVisible() const
{
const auto& stateMachine = _romBrowserController.GetStateMachine();
auto curState = stateMachine.GetCurrentState();
return curState == RomBrowserState::Browser
|| curState == RomBrowserState::GameInfo
|| curState == RomBrowserState::DisplaySettings
|| curState == RomBrowserState::Launching;
}
void App::Update()
{
const auto& stateMachine = _romBrowserController.GetStateMachine();
_romBrowserController.Update();
auto curState = stateMachine.GetCurrentState();
if (_changeDisplayMode)
{
HandleChangeDisplayModeTrigger(curState);
_changeDisplayMode = false;
}
if (stateMachine.HasStateChanged())
{
HandleTrigger(stateMachine.GetLastTrigger(), curState);
}
bool isRomBrowserVisible = IsRomBrowserVisible();
if (isRomBrowserVisible && !_exit && curState != RomBrowserState::Launching)
{
_focusManager.Update(_inputRepeater);
}
if (_topBackground)
_topBackground->Update();
if (_bottomBackground)
_bottomBackground->Update();
_dialogPresenter.Update();
_romBrowserBottomScreenView->Update();
if (isRomBrowserVisible)
{
_romBrowserTopScreenView->Update();
_romBrowserController.GetRomBrowserViewModel()->SetIconFrameCounter(
_romBrowserController.GetRomBrowserViewModel()->GetIconFrameCounter() + 1);
}
}
void App::Draw()
{
gx_reset();
Gx::Viewport(0, 0, 255, 191);
Gx::MtxMode(GX_MTX_MODE_POSITION_VECTOR);
Gx::MtxIdentity();
GraphicsContext mainGraphicsContext
{
&_mainOam,
&_mainObjPltt,
&_rgb6Palette
};
GraphicsContext subGraphicsContext
{
&_subOam,
&_subObjPltt,
nullptr
};
_mainOam.Clear();
_subOam.Clear();
_mainObjPltt.Reset();
_subObjPltt.Reset();
mainGraphicsContext.SetPriority(3);
subGraphicsContext.SetPriority(2);
if (_topBackground)
_topBackground->Draw(subGraphicsContext);
if (_bottomBackground)
_bottomBackground->Draw(mainGraphicsContext);
if (!_changeDisplayMode && IsRomBrowserVisible())
{
_romBrowserTopScreenView->Draw(subGraphicsContext);
}
_dialogPresenter.ApplyClipArea(mainGraphicsContext);
if (!_changeDisplayMode)
{
_romBrowserBottomScreenView->Draw(mainGraphicsContext);
}
mainGraphicsContext.ResetClipArea();
_dialogPresenter.Draw(mainGraphicsContext);
_mainObjPltt.EndOfFrame();
Gx::SwapBuffers(GX_XLU_SORT_MANUAL, GX_DEPTH_MODE_Z);
}
void App::VBlank()
{
dma_ntrStopDirect(0); // stop hblank dma
_inputProvider.Sample();
_inputRepeater.Update();
_mainOam.Apply(GFX_OAM_MAIN);
_subOam.Apply(GFX_OAM_SUB);
_subObjPltt.Apply(GFX_PLTT_OBJ_SUB);
if (!_vcountIrqStarted)
{
rtos_ackIrqMask(RTOS_IRQ_VCOUNT);
rtos_enableIrqMask(RTOS_IRQ_VCOUNT);
_vcountIrqStarted = true;
}
_mainObjPltt.VBlank();
if (_topBackground)
_topBackground->VBlank();
if (_bottomBackground)
_bottomBackground->VBlank();
_dialogPresenter.VBlank();
if (IsRomBrowserVisible())
{
_romBrowserTopScreenView->VBlank();
}
_romBrowserBottomScreenView->VBlank();
_vblankTextureLoader.VBlank();
}
void App::StoreVramState(VramState& vramState) const
{
vramState._mainObjVramState = _mainObjVram.GetState();
vramState._texVramState = _textureVram.GetState();
vramState._texPlttVramState = _texturePaletteVram.GetState();
vramState._subObjVramState = _subObjVram.GetState();
}
void App::RestoreVramState(const VramState& vramState)
{
_mainObjVram.SetState(vramState._mainObjVramState);
_textureVram.SetState(vramState._texVramState);
_texturePaletteVram.SetState(vramState._texPlttVramState);
_subObjVram.SetState(vramState._subObjVramState);
}

129
arm9/source/App.h Normal file
View File

@@ -0,0 +1,129 @@
#pragma once
#include "common.h"
#include <memory>
#include "services/settings/IAppSettingsService.h"
#include "bgm/IBgmService.h"
#include "services/process/IProcess.h"
#include "gui/SimplePaletteManager.h"
#include "gui/AdvancedPaletteManager.h"
#include "gui/OamManager.h"
#include "gui/VramContext.h"
#include "gui/AscendingStackVramManager.h"
#include "gui/DescendingStackVramManager.h"
#include "material/scheme/scheme.h"
#include "gui/input/PadInputSource.h"
#include "gui/input/SampledInputProvider.h"
#include "gui/input/InputRepeater.h"
#include "gui/VBlankTextureLoader.h"
#include "gui/Rgb6Palette.h"
#include "core/task/TaskQueue.h"
#include "themes/material/MaterialColorScheme.h"
#include "romBrowser/viewModels/RomBrowserBottomScreenViewModel.h"
#include "romBrowser/viewModels/DisplaySettingsViewModel.h"
#include "romBrowser/views/RomBrowserBottomScreenView.h"
#include "romBrowser/views/RomBrowserTopScreenView.h"
#include "romBrowser/views/IconButton2DView.h"
#include "romBrowser/views/ChipView.h"
#include "romBrowser/Theme/Material/MaterialThemeFileIconFactory.h"
#include "romBrowser/RomBrowserController.h"
#include "DialogPresenter.h"
#include "themes/ITheme.h"
#include "core/SharedPtr.h"
#include "animation/Animator.h"
class alignas(32) App : public IProcess
{
public:
App(IAppSettingsService& appSettingsService, IBgmService& bgmService);
void Run() override;
void Exit() override;
private:
struct VramState
{
u32 _subObjVramState;
u32 _mainObjVramState;
u32 _texVramState;
u32 _texPlttVramState;
};
AdvancedPaletteManager<64> _mainObjPltt;
OamManager _mainOam;
AscendingStackVramManager _mainObjVram;
DescendingStackVramManager _mainObjDialogVram;
OamManager _subOam;
SimplePaletteManager _subObjPltt;
AscendingStackVramManager _subObjVram;
AscendingStackVramManager _textureVram;
AscendingStackVramManager _texturePaletteVram;
VBlankTextureLoader _vblankTextureLoader;
VramContext _mainVramContext;
VramContext _subVramContext;
Rgb6Palette _rgb6Palette;
Animator<int> _fadeAnimator;
TaskQueue<32, 32> _ioTaskQueue;
u32 _ioTaskThreadStack[2048 / 4];
TaskQueue<32, 32> _bgTaskQueue;
u32 _bgTaskThreadStack[2048 / 4];
std::unique_ptr<ITheme> _theme;
std::unique_ptr<IThemeBackground> _topBackground;
std::unique_ptr<IThemeBackground> _bottomBackground;
IAppSettingsService& _appSettingsService;
IBgmService& _bgmService;
volatile bool _exit = false;
PadInputSource _inputSource;
SampledInputProvider _inputProvider;
InputRepeater _inputRepeater;
std::unique_ptr<RomBrowserBottomScreenView> _romBrowserBottomScreenView;
std::unique_ptr<RomBrowserTopScreenView> _romBrowserTopScreenView;
RomBrowserController _romBrowserController;
DisplaySettingsViewModel _displaySettingsBottomSheetViewModel;
FocusManager _focusManager;
std::unique_ptr<MaterialThemeFileIconFactory> _materialThemeFileIconFactory;
RomBrowserBottomScreenViewModel _romBrowserBottomScreenViewModel;
DialogPresenter _dialogPresenter;
VramState _vramStateBeforeMakeBottomScreenView;
VramState _vramStateAfterMakeBottomScreenView;
bool _changeDisplayMode = false;
ChipView::VramToken _chipViewVram;
IconButton2DView::VramToken _iconButtonViewVram;
bool _vcountIrqStarted = false;
void InitVramMapping() const;
void DisplaySplashScreen() const;
void LoadTheme();
void VCountIrq();
void HandleTrigger(RomBrowserStateTrigger trigger, RomBrowserState newState);
void HandleShowGameInfoTrigger();
void HandleHideGameInfoTrigger();
void HandleShowDisplaySettingsTrigger();
void HandleHideDisplaySettingsTrigger();
void HandleNavigateTrigger();
void HandleFolderLoadDoneTrigger();
void HandleChangeDisplayModeTrigger(RomBrowserState newState);
bool IsRomBrowserVisible() const;
void MainLoop();
void Update();
void Draw();
void VBlank();
void StoreVramState(VramState& vramState) const;
void RestoreVramState(const VramState& vramState);
};

View File

@@ -0,0 +1,155 @@
#include "common.h"
#include <libtwl/dma/dmaNitro.h>
#include <nds/arm9/background.h>
#include "gui/StackVramManager.h"
#include "gui/VramContext.h"
#include "bottomSheetBg.h"
#include "scrim.h"
#include "gui/materialDesign.h"
#include "gui/GraphicsContext.h"
#include "DialogPresenter.h"
DialogPresenter::DialogPresenter(FocusManager* focusManager, StackVramManager* vramManager)
: _focusManager(focusManager), _vramManager(vramManager)
, _scrimAnimator(0), _yAnimator(192)
{
_baseVramState = _vramManager->GetState();
}
void DialogPresenter::ShowDialog(std::unique_ptr<DialogView> dialog)
{
if (!_nextDialog)
_nextDialog = std::move(dialog);
}
void DialogPresenter::CloseDialog()
{
if (!_currentDialog || _curState != State::BottomSheetVisible)
return;
_newState = State::BottomSheetClosing;
}
void DialogPresenter::Update()
{
if (_curState != _newState)
{
_curState = _newState;
switch (_curState)
{
case State::BottomSheetVisible:
{
_scrimAnimator.Goto(5, md::sys::motion::duration::short2,
&md::sys::motion::easing::linear);
_yAnimator.Goto(32, md::sys::motion::duration::long2,
&md::sys::motion::easing::emphasizedDecelerate);
_oldFocus = _focusManager->GetCurrentFocus();
_currentDialog->Focus(*_focusManager);
break;
}
case State::BottomSheetClosing:
{
_scrimAnimator.Goto(0, md::sys::motion::duration::short4,
&md::sys::motion::easing::emphasizedAccelerate);
_yAnimator.Goto(192, md::sys::motion::duration::short4,
&md::sys::motion::easing::emphasizedAccelerate);
if (_oldFocus)
{
_focusManager->Focus(_oldFocus);
_oldFocus = nullptr;
}
break;
}
default:
{
break;
}
}
}
switch (_curState)
{
case State::Idle:
{
if (!_currentDialog && _nextDialog)
{
_currentDialog = std::move(_nextDialog);
_initVram = true;
_newState = State::BottomSheetVisible;
}
break;
}
case State::BottomSheetVisible:
{
if (!_yAnimator.IsFinished())
_yAnimator.Update();
break;
}
case State::BottomSheetClosing:
{
if (!_yAnimator.IsFinished())
{
_yAnimator.Update();
}
else
{
_newState = State::Idle;
_currentDialog.reset();
}
break;
}
}
if (_currentDialog)
{
_currentDialog->SetPosition(_currentDialog->GetPosition().x, _yAnimator.GetValue());
_currentDialog->Update();
}
}
void DialogPresenter::ApplyClipArea(GraphicsContext& graphicsContext) const
{
if (_currentDialog)
{
graphicsContext.SetClipArea(_currentDialog->GetFullyCoveredArea(), true);
}
}
void DialogPresenter::VBlank()
{
REG_BG1VOFS = -_yAnimator.GetValue();
if (_initVram && _currentDialog)
{
_vramManager->SetState(_baseVramState);
_currentDialog->InitVram(VramContext(nullptr, _vramManager, nullptr, nullptr));
_initVram = false;
}
if (_currentDialog)
_currentDialog->VBlank();
if (!_scrimAnimator.IsFinished())
{
_scrimAnimator.Update();
int scrimBlend = _scrimAnimator.GetValue();
REG_BLDALPHA = ((16 - scrimBlend) << 8) | scrimBlend;
}
}
void DialogPresenter::InitVram()
{
dma_ntrCopy32(3, scrimTiles, (vu8*)BG_GFX, scrimTilesLen);
dma_ntrCopy32(3, bottomSheetBgTiles, (vu8*)BG_GFX + 64, bottomSheetBgTilesLen);
dma_ntrCopy32(3, bottomSheetBgMap, (vu8*)BG_GFX + 0x4000, bottomSheetBgMapLen);
dma_ntrCopy32(3, scrimMap, (vu8*)BG_GFX + 0x5000, scrimMapLen);
REG_BG1CNT = BG_32x64 | BG_PRIORITY_1 | BG_COLOR_16 | BG_MAP_BASE(8) | BG_TILE_BASE(0);
REG_BG1HOFS = 0;
REG_BG1VOFS = 0;
REG_BG2CNT = BG_32x32 | BG_PRIORITY_2 | BG_COLOR_16 | BG_MAP_BASE(10) | BG_TILE_BASE(0);
REG_BG2HOFS = 0;
REG_BG2VOFS = 0;
REG_BLDCNT = 0x3944;
REG_BLDALPHA = (16 << 8) | 0;
}

View File

@@ -0,0 +1,76 @@
#pragma once
#include <memory>
#include "animation/Animator.h"
#include "gui/views/DialogView.h"
class StackVramManager;
class FocusManager;
/// @brief Class for displaying dialogs.
class DialogPresenter
{
public:
DialogPresenter(FocusManager* focusManager, StackVramManager* vramManager);
/// @brief Requests to show the given dialog.
/// @param dialog The dialog to show.
void ShowDialog(std::unique_ptr<DialogView> dialog);
/// @brief Closes the current dialog.
void CloseDialog();
/// @brief Updates the dialog presenter.
void Update();
/// @brief Applies the clip area of the currently displayed dialog,
/// or does nothing if no dialog is being shown.
/// @param graphicsContext The graphics context to apply to.
void ApplyClipArea(GraphicsContext& graphicsContext) const;
/// @brief If a dialog is currently being shown, draws the dialog.
/// @param graphicsContext The graphics context to use.
void Draw(GraphicsContext& graphicsContext)
{
if (_currentDialog)
_currentDialog->Draw(graphicsContext);
}
/// @brief Performs vblank processes for the displayed dialog.
void VBlank();
/// @brief Initializes vram that is needed for showing dialogs.
void InitVram();
/// @brief Clears the focus that was stored when a dialog was opened.
void ClearOldFocus()
{
_oldFocus = nullptr;
}
/// @brief Gets the focus that was stored when a dialog was opened.
/// @return The view that was focused when the current dialog was opened.
constexpr View* GetOldFocus() const
{
return _oldFocus;
}
private:
enum class State
{
Idle,
BottomSheetVisible,
BottomSheetClosing
};
FocusManager* _focusManager;
StackVramManager* _vramManager;
u32 _baseVramState;
std::unique_ptr<DialogView> _currentDialog;
std::unique_ptr<DialogView> _nextDialog;
bool _initVram = false;
View* _oldFocus = nullptr;
Animator<int> _scrimAnimator;
Animator<int> _yAnimator;
State _curState = State::Idle;
State _newState = State::Idle;
};

View File

@@ -0,0 +1,13 @@
#include "common.h"
#include <nds/arm9/video.h>
#include "picoLoaderBootstrap.h"
#include "PicoLoaderProcess.h"
void PicoLoaderProcess::Run()
{
REG_MASTER_BRIGHT = 0x401F;
REG_MASTER_BRIGHT_SUB = 0x401F;
REG_DISPCNT = 0;
REG_DISPCNT_SUB = 0;
pload_start();
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "services/process/IProcess.h"
class PicoLoaderProcess : public IProcess
{
public:
void Run() override;
void Exit() override
{
// This process never exits
}
};

4
arm9/source/VBlank.cpp Normal file
View File

@@ -0,0 +1,4 @@
#include "common.h"
#include "VBlank.h"
rtos_event_t VBlank::sEvent;

25
arm9/source/VBlank.h Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include <libtwl/rtos/rtosEvent.h>
/// @brief Helper class for waiting for vblank.
class VBlank
{
public:
static void Init()
{
rtos_createEvent(&sEvent);
}
static void Wait(bool waitNew = true, bool clearAfter = true)
{
rtos_waitEvent(&sEvent, waitNew, clearAfter);
}
static void NotifyIrq()
{
rtos_signalEvent(&sEvent);
}
private:
static rtos_event_t sEvent;
};

View File

@@ -0,0 +1,35 @@
#include "common.h"
#include "core/math/fixed.h"
#include "Animator.h"
template <>
bool Animator<int>::Update()
{
if (++_frame >= _duration)
{
_frame = _duration;
_value = _to;
return true;
}
auto relativePos = _curve->Compute(_frame * _invDuration);
_value = _from + (relativePos.LongMul(_to - _from) + 0.5).Int();
return false;
}
template <>
bool Animator<fix32<12>>::Update()
{
if (++_frame >= _duration)
{
_frame = _duration;
_value = _to;
return true;
}
auto relativePos = _curve->Compute(_frame * _invDuration);
_value = _from + fix32<12>(relativePos.LongMul(_to - _from));
return false;
}

View File

@@ -0,0 +1,50 @@
#pragma once
#include "Curve.h"
template <typename T>
class Animator
{
public:
constexpr Animator()
: _from(), _to(), _value(), _duration(0), _frame(0), _curve(nullptr) { }
/// @brief Constructs an Animator with the given initialValue.
/// @param initialValue The initial value for the animator.
constexpr Animator(const T& initialValue)
: _from(initialValue), _to(initialValue), _value(initialValue), _duration(0), _frame(0), _curve(nullptr) { }
/// @brief Constructs an Animator that animates from from to to with the given duration and curve.
/// @param from The start value.
/// @param to The end value.
/// @param duration The duration of the animation.
/// @param curve The curve to use for the animation.
constexpr Animator(const T& from, const T& to, u32 duration, const Curve* curve)
: _from(from), _to(to), _value(_from), _duration(duration), _invDuration(fix32<26>(1) / duration), _frame(0), _curve(curve) { }
bool Update();
constexpr const T& GetValue() const { return _value; }
constexpr const T& GetTargetValue() const { return _to; }
constexpr u32 GetDuration() const { return _duration; }
constexpr u32 GetFrame() const { return _frame; }
constexpr bool IsFinished() const { return _frame == _duration; }
/// @brief Restarts this animator from the current value to a new target
/// with the given duration and curve.
/// @param target The new target value.
/// @param duration The duration of the animation.
/// @param curve The curve to use.
void Goto(const T& target, u32 duration, const Curve* curve)
{
*this = Animator(_value, target, duration, curve);
}
private:
T _from;
T _to;
T _value;
u32 _duration;
fix32<26> _invDuration;
u32 _frame;
const Curve* _curve;
};

View File

@@ -0,0 +1,48 @@
#pragma once
#include "Curve.h"
class CubicBezierCurve : public Curve
{
using fx = fix16<13>;
const fx _cx;
const fx _bx;
const fx _ax;
const fx _cy;
const fx _by;
const fx _ay;
[[gnu::noinline]]
static constexpr fix32<26> SampleCurve(fx ax, fx bx, fx cx, fx t)
{
return ((ax * t + bx) * t + cx).LongMul(t);
}
public:
constexpr CubicBezierCurve(fx p1x, fx p1y, fx p2x, fx p2y)
: _cx(3 * p1x)
, _bx(3 * (p2x - p1x) - _cx)
, _ax(1 - _cx - _bx)
, _cy(3 * p1y)
, _by(3 * (p2y - p1y) - _cy)
, _ay(1 - _cy - _by) { }
[[gnu::noinline]]
fix32<26> Compute(fix32<26> t) const override
{
fx start = 0.0;
fx end = 1.0;
while (true)
{
const fx midpoint = (start + end) >> 1;
const fix32<26> estimate = SampleCurve(_ax, _bx, _cx, midpoint);
if ((t - estimate).Abs() < 0.001)
return SampleCurve(_ay, _by, _cy, midpoint);
if (estimate < t)
start = midpoint;
else
end = midpoint;
}
}
};

View File

@@ -0,0 +1,8 @@
#pragma once
#include "common.h"
class Curve
{
public:
virtual fix32<26> Compute(fix32<26> t) const = 0;
};

View File

@@ -0,0 +1,20 @@
#pragma once
#include "common.h"
class Interpolator
{
public:
template <typename T>
static constexpr T InterpolateLinear(T a, T b, T t)
{
return a + (b - a) * t;
}
template <typename T, u32 FractionBits>
static constexpr fixed<T, FractionBits> InterpolateCubic(
fixed<T, FractionBits> cf0, fixed<T, FractionBits> cf1,
fixed<T, FractionBits> cf2, fixed<T, FractionBits> cf3, fixed<T, FractionBits> t)
{
return ((((cf0 * t + cf1) * t) + cf2) * t) + cf3;
}
};

View File

@@ -0,0 +1,11 @@
#pragma once
#include "Curve.h"
class LinearCurve : public Curve
{
public:
fix32<26> Compute(fix32<26> t) const override
{
return t;
}
};

View File

@@ -0,0 +1,48 @@
#pragma once
#include "Curve.h"
#include "CubicBezierCurve.h"
class ThreePointCubicBezierCurve : public Curve
{
using fx = fix16<13>;
fix32<26> _midPointX;
fix32<26> _midPointY;
fix32<26> _invMidPointX;
fix32<26> _invOneMinusMidPointX;
const CubicBezierCurve _firstCurve;
const CubicBezierCurve _secondCurve;
public:
constexpr ThreePointCubicBezierCurve(
fx a1x, fx a1y, fx b1x, fx b1y,
fx midpointx, fx midpointy,
fx a2x, fx a2y, fx b2x, fx b2y)
: _midPointX(midpointx)
, _midPointY(midpointy)
, _invMidPointX(fix32<26>(1) / midpointx)
, _invOneMinusMidPointX(fix32<26>(1) / (1 - midpointx))
, _firstCurve(
a1x / midpointx,
a1y / midpointy,
b1x / midpointx,
b1y / midpointy)
, _secondCurve(
(a2x - midpointx) / (1 - midpointx),
(a2y - midpointy) / (1 - midpointy),
(b2x - midpointx) / (1 - midpointx),
(b2y - midpointy) / (1 - midpointy)) { }
[[gnu::noinline]]
fix32<26> Compute(fix32<26> t) const override
{
if (t < _midPointX)
return _firstCurve.Compute(t * _invMidPointX) * _midPointY;
else
{
fix32<26> scaledT = (t - _midPointX) * _invOneMinusMidPointX;
return _secondCurve.Compute(scaledT) * (1 - _midPointY) + _midPointY;
}
}
};

View File

@@ -0,0 +1,140 @@
#include "common.h"
#include <nds/arm9/cache.h>
#include <libtwl/ipc/ipcFifoSystem.h>
#include <libtwl/timer/timer.h>
#include <libtwl/rtos/rtosIrq.h>
#include "ipcChannels.h"
#include "AudioStreamPlayer.h"
/// @brief Hardware timer used for tracking audio blocks.
#define AUDIO_STREAM_PLAYER_TIMER 3
/// @brief Number of blocks that are not overwritten
/// between the read and write pointer to prevent
/// touching the current playing position.
#define AUDIO_STREAM_PLAYER_SAFETY_BLOCKS 4
/// @brief Priority of the stream thread.
#define AUDIO_STREAM_PLAYER_THREAD_PRIORITY 15
AudioStreamPlayer* AudioStreamPlayer::sCurrentPlayer = nullptr;
AudioStreamPlayer::AudioStreamPlayer()
{
rtos_createMutex(&_mutex);
rtos_createEvent(&_event);
DC_FlushRange(&_soundStopCmdList, sizeof(_soundStopCmdList));
}
bool AudioStreamPlayer::StartPlaybackIntern(std::unique_ptr<IAudioStream> audioStream)
{
if (_isPlaying)
StopPlaybackIntern();
_audioStream = std::move(audioStream);
// fill buffer
_readBlock = 0;
_writeBlock = 0;
for (u32 i = 0; i < AUDIO_STREAM_PLAYER_RING_BLOCKS - 1; i++)
{
FillRingBlock(i);
_writeBlock++;
}
u32 rate = _audioStream->GetSampleRate();
u32 timer = -((33513982 + rate) / (rate * 2));
_soundStartCmdList.leftSetup.timer = timer;
_soundStartCmdList.rightSetup.timer = timer;
DC_FlushRange(&_soundStartCmdList, sizeof(_soundStartCmdList));
sCurrentPlayer = this;
rtos_disableIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER));
rtos_ackIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER));
rtos_setIrqFunc(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER), [] (u32 irqMask)
{
u32 readBlock = sCurrentPlayer->_readBlock;
if (++readBlock == AUDIO_STREAM_PLAYER_RING_BLOCKS)
readBlock = 0;
sCurrentPlayer->_readBlock = readBlock;
rtos_signalEvent(&sCurrentPlayer->_event);
});
tmr_configure(AUDIO_STREAM_PLAYER_TIMER, TMCNT_H_CLK_SYS_DIV_256, timer << 1, true);
rtos_createThread(&_thread, AUDIO_STREAM_PLAYER_THREAD_PRIORITY, [] (void* arg)
{
static_cast<AudioStreamPlayer*>(arg)->ThreadMain();
}, this, _threadStack, sizeof(_threadStack));
tmr_start(AUDIO_STREAM_PLAYER_TIMER);
rtos_enableIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER));
_isPlaying = true;
rtos_wakeupThread(&_thread);
ipc_sendFifoMessage(IPC_CHANNEL_SOUND, (u32)&_soundStartCmdList);
return true;
}
void AudioStreamPlayer::StopPlaybackIntern()
{
if (!_isPlaying)
return;
_isPlaying = false;
rtos_wakeupThread(&_thread);
ipc_sendFifoMessage(IPC_CHANNEL_SOUND, (u32)&_soundStopCmdList);
tmr_stop(AUDIO_STREAM_PLAYER_TIMER);
rtos_disableIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER));
rtos_joinThread(&_thread);
sCurrentPlayer = nullptr;
_audioStream.reset();
}
void AudioStreamPlayer::ThreadMain()
{
do
{
bool doUpdate = true;
while(_isPlaying && doUpdate)
{
rtos_lockMutex(&_mutex);
{
u32 writeBlock = _writeBlock;
int freeBlocks = _readBlock - writeBlock - 1;
if (freeBlocks < 0)
freeBlocks += AUDIO_STREAM_PLAYER_RING_BLOCKS;
if (freeBlocks > AUDIO_STREAM_PLAYER_SAFETY_BLOCKS)
{
FillRingBlock(writeBlock);
if (++writeBlock == AUDIO_STREAM_PLAYER_RING_BLOCKS)
writeBlock = 0;
_writeBlock = writeBlock;
}
else
{
doUpdate = false;
}
}
rtos_unlockMutex(&_mutex);
}
if (!_isPlaying)
break;
rtos_waitEvent(&_event, false, true);
} while(_isPlaying);
}
void AudioStreamPlayer::FillRingBlock(u32 block)
{
s16* blockPtrL = &_audioRingL[block][0];
s16* blockPtrR = &_audioRingR[block][0];
_audioStream->ReadSamples(blockPtrL, blockPtrR, AUDIO_STREAM_PLAYER_BLOCK_SAMPLES);
DC_FlushRange(&blockPtrL, AUDIO_STREAM_PLAYER_BLOCK_SAMPLES * sizeof(s16));
DC_FlushRange(&blockPtrR, AUDIO_STREAM_PLAYER_BLOCK_SAMPLES * sizeof(s16));
}

View File

@@ -0,0 +1,106 @@
#pragma once
#include "common.h"
#include <memory>
#include <libtwl/rtos/rtosMutex.h>
#include <libtwl/rtos/rtosThread.h>
#include <libtwl/rtos/rtosEvent.h>
#include <../../libtwl7/include/libtwl/sound/soundChannel.h>
#include "fat/File.h"
#include "soundIpcCommand.h"
#include "IAudioStreamPlayer.h"
#define AUDIO_STREAM_PLAYER_RING_BLOCKS 32
#define AUDIO_STREAM_PLAYER_BLOCK_SAMPLES 256
/// @brief Class implementing an audio stream player.
class alignas(32) AudioStreamPlayer : public IAudioStreamPlayer
{
public:
AudioStreamPlayer();
~AudioStreamPlayer()
{
StopPlayback();
}
bool StartPlayback(std::unique_ptr<IAudioStream> audioStream) override
{
bool result;
rtos_lockMutex(&_mutex);
{
result = StartPlaybackIntern(std::move(audioStream));
}
rtos_unlockMutex(&_mutex);
return result;
}
void StopPlayback() override
{
rtos_lockMutex(&_mutex);
{
StopPlaybackIntern();
}
rtos_unlockMutex(&_mutex);
}
private:
struct alignas(32) SoundStartCmdList
{
u32 cmdCount;
snd_ipc_cmd_setup_channel_t leftSetup;
snd_ipc_cmd_setup_channel_t rightSetup;
u32 startChannels;
};
struct alignas(32) SoundStopCmdList
{
u32 cmdCount;
u32 stopChannels;
};
s16 _audioRingL[AUDIO_STREAM_PLAYER_RING_BLOCKS][AUDIO_STREAM_PLAYER_BLOCK_SAMPLES] alignas(32);
s16 _audioRingR[AUDIO_STREAM_PLAYER_RING_BLOCKS][AUDIO_STREAM_PLAYER_BLOCK_SAMPLES] alignas(32);
SoundStartCmdList _soundStartCmdList alignas(32)
{
3,
{
SND_IPC_CMD_SETUP_CHANNEL,
0,
_audioRingL,
0,
0,
sizeof(_audioRingL) >> 2,
SOUNDCNT_VOLUME(127) | SOUNDCNT_MODE_LOOP | SOUNDCNT_FORMAT_PCM16
},
{
SND_IPC_CMD_SETUP_CHANNEL,
1,
_audioRingR,
0,
0,
sizeof(_audioRingR) >> 2,
SOUNDCNT_VOLUME(127) | SOUNDCNT_PAN(127) | SOUNDCNT_MODE_LOOP | SOUNDCNT_FORMAT_PCM16
},
(0b11 << 8) | SND_IPC_CMD_START_CHANNELS
};
SoundStopCmdList _soundStopCmdList alignas(32)
{
1,
(0b11 << 8) | SND_IPC_CMD_STOP_CHANNELS
};
u32 _threadStack[2048 / sizeof(u32)] alignas(32);
rtos_mutex_t _mutex;
rtos_thread_t _thread;
rtos_event_t _event;
volatile bool _isPlaying = false;
volatile u8 _readBlock;
volatile u8 _writeBlock;
std::unique_ptr<IAudioStream> _audioStream;
static AudioStreamPlayer* sCurrentPlayer;
bool StartPlaybackIntern(std::unique_ptr<IAudioStream> audioStream);
void StopPlaybackIntern();
void ThreadMain();
void FillRingBlock(u32 block);
};

View File

@@ -0,0 +1,327 @@
#include "common.h"
#include <string.h>
#include <algorithm>
#include <memory>
#include "BcstmAudioStream.h"
bool BcstmAudioStream::Open(const TCHAR* filePath)
{
if (_audioFile.Open(filePath, FA_OPEN_EXISTING | FA_READ) != FR_OK)
return false;
return TryLoadBcstm();
}
bool BcstmAudioStream::Open(const FastFileRef& fastFileRef)
{
_audioFile.Open(fastFileRef, FA_OPEN_EXISTING | FA_READ);
return TryLoadBcstm();
}
bool BcstmAudioStream::TryLoadBcstm()
{
if (_audioFile.CreateClusterTable(_clusterTable, sizeof(_clusterTable)) != FR_OK)
return false;
bcstm_header_t header;
u32 bytesRead = 0;
if (_audioFile.Read(&header, sizeof(bcstm_header_t), bytesRead) != FR_OK ||
bytesRead != sizeof(bcstm_header_t))
{
return false;
}
if (_audioFile.Seek(header.infoBlockRef.offset) != FR_OK)
return false;
bcstm_info_t info;
if (_audioFile.Read(&info, sizeof(bcstm_info_t), bytesRead) != FR_OK ||
bytesRead != sizeof(bcstm_info_t))
{
return false;
}
if (_audioFile.Seek(header.infoBlockRef.offset + 8 + info.streamInfoRef.offset) != FR_OK)
return false;
if (_audioFile.Read(&_streamInfo, sizeof(bcstm_info_stream_t), bytesRead) != FR_OK ||
bytesRead != sizeof(bcstm_info_stream_t))
{
return false;
}
_dataOffset = header.dataBlockRef.offset + 8 + _streamInfo.dataRef.offset;
_channels = std::min<u32>(_streamInfo.nrChannels, 2);
if (_streamInfo.format == BCSTM_FORMAT_DSP_ADPCM)
{
u32 tableSize = sizeof(bcstm_ref_table_t) + sizeof(bcstm_ref_t) * (_streamInfo.nrChannels - 1);
auto channelInfoRefTab = std::unique_ptr<u8[]>(new u8[tableSize]);
if (_audioFile.Seek(header.infoBlockRef.offset + 8 + info.channelInfoRef.offset) != FR_OK)
return false;
if (_audioFile.Read(channelInfoRefTab.get(), tableSize, bytesRead) != FR_OK ||
bytesRead != tableSize)
{
return false;
}
auto channelInfoRefTabPtr = reinterpret_cast<bcstm_ref_table_t*>(channelInfoRefTab.get());
for (u32 i = 0; i < _channels; i++)
{
u32 offset = header.infoBlockRef.offset + 8 + info.channelInfoRef.offset + channelInfoRefTabPtr->references[i].offset;
if (_audioFile.Seek(offset) != FR_OK)
return false;
bcstm_info_channel_t channel;
if (_audioFile.Read(&channel, sizeof(bcstm_info_channel_t), bytesRead) != FR_OK ||
bytesRead != sizeof(bcstm_info_channel_t))
{
return false;
}
if (_audioFile.Seek(offset + channel.codecInfoRef.offset) != FR_OK)
return false;
if (_audioFile.Read(&_dspAdpcmInfo[i], sizeof(bcstm_dspadpcm_t), bytesRead) != FR_OK ||
bytesRead != sizeof(bcstm_dspadpcm_t))
{
return false;
}
}
}
for (u32 i = 0; i < _channels; i++)
{
_adpcmBlocks[i] = std::unique_ptr<u8[]>(new(cache_align) u8[_streamInfo.blockSize]);
if (_streamInfo.format == BCSTM_FORMAT_DSP_ADPCM)
{
_dspAdpcmContexts[i].coefTable = _dspAdpcmInfo[i].coefs;
_dspAdpcmContexts[i].frameData[12] = _dspAdpcmInfo[i].context.last2;
_dspAdpcmContexts[i].frameData[13] = _dspAdpcmInfo[i].context.last1;
}
}
_blockNumber = 0;
_sampleNumberInBlock = 0;
if (_streamInfo.loop)
{
_loopBlockNumber = _streamInfo.loopStart / _streamInfo.blockSampleCount;
_loopBlockStartSample = _streamInfo.loopStart % _streamInfo.blockSampleCount;
_loopEndBlockNumber = (_streamInfo.loopEnd - 1) / _streamInfo.blockSampleCount;
_loopEndBlockEndSample = (_streamInfo.loopEnd - 1) % _streamInfo.blockSampleCount;
}
return true;
}
void BcstmAudioStream::Close()
{
_audioFile.Close();
_adpcmBlocks[0].reset();
_adpcmBlocks[1].reset();
}
void BcstmAudioStream::ReadSamples(s16* left, s16* right, u32 count)
{
u32 remaining = count;
s16* curLeft = left;
s16* curRight = right;
do
{
if (_sampleNumberInBlock == 0)
FetchBlock();
u32 totalSamplesInBlock = GetTotalSamplesInCurrentBlock();
u32 samplesToDecode = std::min(totalSamplesInBlock - _sampleNumberInBlock, remaining);
DecodeAdpcmSamples(_dspAdpcmContexts[0], curLeft, samplesToDecode);
if (_channels == 2)
DecodeAdpcmSamples(_dspAdpcmContexts[1], curRight, samplesToDecode);
curLeft += samplesToDecode;
curRight += samplesToDecode;
remaining -= samplesToDecode;
_sampleNumberInBlock += samplesToDecode;
if (_sampleNumberInBlock == totalSamplesInBlock)
{
_blockNumber++;
_sampleNumberInBlock = 0;
}
} while (remaining != 0);
// when mono, copy the samples from the left to the right channel
if (_channels == 1)
memcpy(right, left, count * sizeof(s16));
}
u32 BcstmAudioStream::GetTotalSamplesInCurrentBlock()
{
u32 totalSamplesInBlock = _blockNumber == _streamInfo.nrBlocks - 1 ? _streamInfo.lastBlockSampleCount : _streamInfo.blockSampleCount;
if (_streamInfo.loop && _blockNumber == _loopEndBlockNumber)
totalSamplesInBlock = _loopEndBlockEndSample + 1;
return totalSamplesInBlock;
}
void BcstmAudioStream::FetchBlock()
{
bool looped = false;
if (_blockNumber == _streamInfo.nrBlocks || (_streamInfo.loop && _blockNumber == _loopEndBlockNumber + 1))
{
looped = true;
_blockNumber = _streamInfo.loop ? _loopBlockNumber : 0;
}
// fetch block
u32 blockOffset = _dataOffset + _streamInfo.blockSize * (_blockNumber * _streamInfo.nrChannels);
if (_audioFile.Seek(blockOffset) != FR_OK)
return;
for (u32 i = 0; i < _channels; i++)
{
u32 blockSize = _blockNumber == _streamInfo.nrBlocks - 1
? _streamInfo.lastBlockPaddedSize : _streamInfo.blockSize;
u32 bytesRead = 0;
if (_audioFile.Read(_adpcmBlocks[i].get(), blockSize, bytesRead) != FR_OK ||
bytesRead != blockSize)
{
return;
}
_dspAdpcmContexts[i].SetData(_adpcmBlocks[i].get());
}
if (looped)
{
_sampleNumberInBlock = _loopBlockStartSample;
if (_streamInfo.format == BCSTM_FORMAT_DSP_ADPCM)
{
if (_blockNumber == 0)
{
for (u32 i = 0; i < _channels; i++)
{
_dspAdpcmContexts[i].frameData[12] = _dspAdpcmInfo[i].context.last2;
_dspAdpcmContexts[i].frameData[13] = _dspAdpcmInfo[i].context.last1;
}
}
else
{
u32 frameOffset = _loopBlockStartSample % 14u;
u32 frame = _loopBlockStartSample / 14u;
for (u32 i = 0; i < _channels; i++)
{
_dspAdpcmContexts[i].SetData(_adpcmBlocks[i].get() + frame * 8);
if (frameOffset != 0)
{
DecodeLoopAdpcmFrame(
_dspAdpcmContexts[i],
_dspAdpcmInfo[i].loopContext.predictorAndScale,
_dspAdpcmInfo[i].loopContext.last1,
_dspAdpcmInfo[i].loopContext.last2,
_loopBlockStartSample % 14u);
}
else
{
_dspAdpcmContexts[i].frameData[12] = _dspAdpcmInfo[i].loopContext.last2;
_dspAdpcmContexts[i].frameData[13] = _dspAdpcmInfo[i].loopContext.last1;
}
}
}
}
}
}
extern "C" void dspadpcm_decode(DspAdpcmContext* context);
void BcstmAudioStream::DecodeAdpcmSamples(DspAdpcmContext& context, s16* dst, u32 count)
{
do
{
if (context.frameIdx == 0)
dspadpcm_decode(&context);
u32 toCopy = std::min<u32>(14 - context.frameIdx, count);
memcpy(dst, &context.frameData[context.frameIdx], toCopy * sizeof(s16));
context.frameIdx += toCopy;
if (context.frameIdx == 14)
context.frameIdx = 0;
count -= toCopy;
dst += toCopy;
} while (count != 0);
}
// ITCM_CODE void BcstmAudioStream::DecodeAdpcmFrame(DspAdpcmContext& context)
// {
// const u8* data = context.data;
// u32 scaleCoef = *data++;
// u32 scale = (scaleCoef & 0xF) + 11;
// u32 coefIdx = scaleCoef >> 4;
// s16 coef1 = context.coefTable[coefIdx * 2];
// s16 coef2 = context.coefTable[coefIdx * 2 + 1];
// s16 last2 = context.frameData[12];
// s16 last1 = context.frameData[13];
// for (u32 j = 0; j < 7; j++)
// {
// u32 dataValue = *data++;
// int high = (int)(dataValue << 24) >> 28;
// int val = ((high << scale) + (coef1 * last1 + coef2 * last2) + 1024) >> 11;
// int sample = std::clamp(val, -0x8000, 0x7FFF);
// context.frameData[j * 2] = sample;
// last2 = last1;
// last1 = sample;
// int low = (int)(dataValue << 28) >> 28;
// val = ((low << scale) + (coef1 * last1 + coef2 * last2) + 1024) >> 11;
// sample = std::clamp(val, -0x8000, 0x7FFF);
// context.frameData[j * 2 + 1] = sample;
// last2 = last1;
// last1 = sample;
// }
// context.frameIdx = 0;
// context.data = data;
// }
ITCM_CODE void BcstmAudioStream::DecodeLoopAdpcmFrame(
DspAdpcmContext& context, u32 scaleCoef, s16 last1, s16 last2, u32 sampleOffset)
{
const u8* data = context.data;
data += 2 + (sampleOffset >> 1);
u32 scale = (scaleCoef & 0xF) + 11;
u32 coefIdx = scaleCoef >> 4;
s16 coef1 = context.coefTable[coefIdx * 2];
s16 coef2 = context.coefTable[coefIdx * 2 + 1];
if ((int)sampleOffset - 2 >= 0)
context.frameData[sampleOffset - 2] = last2;
if ((int)sampleOffset - 1 >= 0)
context.frameData[sampleOffset - 1] = last1;
for (u32 j = sampleOffset; j < 14; j += 2)
{
u32 dataValue = *data++;
if ((j & 1) == 0)
{
int high = (int)(dataValue << 24) >> 28;
int val = ((high << scale) + (coef1 * last1 + coef2 * last2) + 1024) >> 11;
int sample = std::clamp(val, -0x8000, 0x7FFF);
context.frameData[j] = sample;
last2 = last1;
last1 = sample;
}
else
{
j--;
}
int low = (int)(dataValue << 28) >> 28;
int val = ((low << scale) + (coef1 * last1 + coef2 * last2) + 1024) >> 11;
int sample = std::clamp(val, -0x8000, 0x7FFF);
context.frameData[j + 1] = sample;
last2 = last1;
last1 = sample;
}
context.frameIdx = sampleOffset;
context.data = data;
}

View File

@@ -0,0 +1,66 @@
#pragma once
#include "common.h"
#include <memory>
#include "fat/File.h"
#include "IAudioStream.h"
#include "bcstm.h"
struct DspAdpcmContext
{
const s16* coefTable;
const u8* data;
s16 frameData[14];
u8 frameIdx;
void SetData(const u8* data)
{
this->data = data;
frameIdx = 0;
}
};
class alignas(32) BcstmAudioStream : public IAudioStream
{
public:
/// @brief Opens the given file. Call this function before
/// using ReadSamples.
/// @param filePath The path of the file to open.
/// @return True if the file was successfully opened, or false otherwise.
bool Open(const TCHAR* filePath);
/// @brief Opens the given file. Call this function before
/// using ReadSamples.
/// @param filePath The path of the file to open.
/// @return True if the file was successfully opened, or false otherwise.
bool Open(const FastFileRef& fastFileRef);
void Close() override;
void ReadSamples(s16* left, s16* right, u32 count) override;
u32 GetSampleRate() const override
{
return _streamInfo.sampleRate;
}
private:
File _audioFile alignas(32);
DWORD _clusterTable[1024] alignas(32);
bcstm_info_stream_t _streamInfo;
bcstm_dspadpcm_t _dspAdpcmInfo[2];
u32 _dataOffset;
std::unique_ptr<u8[]> _adpcmBlocks[2];
u32 _channels;
u32 _blockNumber;
u32 _sampleNumberInBlock;
u32 _loopBlockNumber;
u32 _loopEndBlockNumber;
u32 _loopBlockStartSample;
u32 _loopEndBlockEndSample;
DspAdpcmContext _dspAdpcmContexts[2];
bool TryLoadBcstm();
u32 GetTotalSamplesInCurrentBlock();
void FetchBlock();
void DecodeAdpcmSamples(DspAdpcmContext& context, s16* dst, u32 count);
void DecodeLoopAdpcmFrame(DspAdpcmContext& context, u32 scaleCoef, s16 last1, s16 last2, u32 sampleOffset);
};

View File

@@ -0,0 +1,44 @@
#include "common.h"
#include "core/mini-printf.h"
#include "Pcm16FileAudioStream.h"
#include "BcstmAudioStream.h"
#include "romBrowser/SdFolder.h"
#include "romBrowser/SdFolderFactory.h"
#include "romBrowser/FileType/NullFileTypeProvider.h"
#include "BgmService.h"
bool BgmService::StartBgm(const TCHAR* filePath)
{
auto stream = std::make_unique<BcstmAudioStream>();
if (!stream->Open(filePath))
return false;
return _audioStreamPlayer->StartPlayback(std::move(stream));
}
void BgmService::StartBgmFromConfig()
{
TCHAR pathBuffer[128];
mini_snprintf(pathBuffer, sizeof(pathBuffer), "/_pico/themes/%s/bgm", _appSettingsService.GetAppSettings().theme.GetString());
NullFileTypeProvider fileTypeProvider;
auto bgmFolder = SdFolderFactory(&fileTypeProvider).CreateFromPath(pathBuffer);
if (!bgmFolder || bgmFolder->GetFileCount() == 0)
{
StopBgm();
return;
}
u32 bgmToPlay = _randomGenerator.NextU32(bgmFolder->GetFileCount());
auto stream = std::make_unique<BcstmAudioStream>();
if (!stream->Open(bgmFolder->GetFiles()[bgmToPlay]->GetFastFileRef()))
{
StopBgm();
return;
}
_audioStreamPlayer->StartPlayback(std::move(stream));
}
void BgmService::StopBgm()
{
_audioStreamPlayer->StopPlayback();
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include <memory>
#include "IAudioStreamPlayer.h"
#include "services/settings/IAppSettingsService.h"
#include "rng/RandomGenerator.h"
#include "IBgmService.h"
/// @brief Class implementing a background music service.
class BgmService : public IBgmService
{
public:
constexpr BgmService(
std::unique_ptr<IAudioStreamPlayer> audioStreamPlayer,
IAppSettingsService& appSettingsService,
RandomGenerator& randomGenerator)
: _audioStreamPlayer(std::move(audioStreamPlayer))
, _appSettingsService(appSettingsService)
, _randomGenerator(randomGenerator) { }
bool StartBgm(const TCHAR* filePath) override;
void StartBgmFromConfig() override;
void StopBgm() override;
private:
std::unique_ptr<IAudioStreamPlayer> _audioStreamPlayer;
IAppSettingsService& _appSettingsService;
RandomGenerator& _randomGenerator;
};

View File

@@ -0,0 +1,24 @@
#pragma once
/// @brief Interface for an audio stream.
class IAudioStream
{
public:
virtual ~IAudioStream() = 0;
/// @brief Returns the sample rate of the audio stream in Hertz.
/// @return The sample rate in Hertz.
virtual u32 GetSampleRate() const = 0;
/// @brief Reads the given number of samples from the audio stream
/// to the given buffers for the left and right channel.
/// @param left The left channel buffer.
/// @param right The right channel buffer.
/// @param count The number of samples to read.
virtual void ReadSamples(s16* left, s16* right, u32 count) = 0;
/// @brief Closes the audio stream.
virtual void Close() = 0;
};
inline IAudioStream::~IAudioStream() { }

Some files were not shown because too many files have changed in this diff Show More