mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-22 13:55:38 +00:00
Added support for custom MPEG music in a new Music.xml config file
This commit is contained in:
parent
dd90d0e2e0
commit
dbfe2b1a72
31
Config/Music.xml
Normal file
31
Config/Music.xml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<!--
|
||||||
|
Supermodel
|
||||||
|
A Sega Model 3 Arcade Emulator.
|
||||||
|
Copyright 2003-2024 The Supermodel Team
|
||||||
|
|
||||||
|
Music.xml
|
||||||
|
|
||||||
|
This file defines custom MPEG music tracks to be used in DSB1 and DSB2 games.
|
||||||
|
Make sure to use MP3 files sampled at 32 KHz. Normal sampling rates (e.g. 44.1
|
||||||
|
or 48 KHz) will sound slow.
|
||||||
|
|
||||||
|
The example below is wrapped in a dummy <comment></comment> block to render it
|
||||||
|
inactive. Remove these and add your own MP3 files.
|
||||||
|
-->
|
||||||
|
<games>
|
||||||
|
<!-- Scud Race (Australian version) example -->
|
||||||
|
<comment> <!-- Remove this tag -->
|
||||||
|
<game name="scudau">
|
||||||
|
<track mpeg_rom_start_offset="0x001d4b42" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Beginner Day -->
|
||||||
|
<track mpeg_rom_start_offset="0x000e7e41" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Beginner Night (initial start) -->
|
||||||
|
<track mpeg_rom_start_offset="0x00103981" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Beginner Night (loop point) -->
|
||||||
|
<track mpeg_rom_start_offset="0x00000000" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Medium (initial start) -->
|
||||||
|
<track mpeg_rom_start_offset="0x0001caff" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Medium (loop point) -->
|
||||||
|
<track mpeg_rom_start_offset="0x0037a4c4" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Expert (initial start) -->
|
||||||
|
<track mpeg_rom_start_offset="0x0037fc84" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Expert (loop point) -->
|
||||||
|
<track mpeg_rom_start_offset="0x002afa83" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Extra (initial start) -->
|
||||||
|
<track mpeg_rom_start_offset="0x002b4943" filepath="Rick Astley - Together Forever - 32 KHz.mp3" /> <!-- Extra (loop point) -->
|
||||||
|
<track mpeg_rom_start_offset="0x00463745" filepath="Rick Astley - Never Gonna Give You Up - 32 KHz.mp3" /> <!-- Selector -->
|
||||||
|
</game>
|
||||||
|
</comment> <!-- Remove this tag! -->
|
||||||
|
</games>
|
|
@ -221,7 +221,7 @@ void CDSB1::IOWrite8(UINT32 addr, UINT8 data)
|
||||||
usingMPEGStart = mpegStart;
|
usingMPEGStart = mpegStart;
|
||||||
usingMPEGEnd = mpegEnd;
|
usingMPEGEnd = mpegEnd;
|
||||||
|
|
||||||
MpegDec::SetMemory(&mpegROM[mpegStart], mpegEnd - mpegStart, false);
|
MpegDec::SetMemory(mpegROM, mpegStart, mpegEnd - mpegStart, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,7 +233,7 @@ void CDSB1::IOWrite8(UINT32 addr, UINT8 data)
|
||||||
usingMPEGStart = mpegStart;
|
usingMPEGStart = mpegStart;
|
||||||
usingMPEGEnd = mpegEnd;
|
usingMPEGEnd = mpegEnd;
|
||||||
|
|
||||||
MpegDec::SetMemory(&mpegROM[mpegStart], mpegEnd - mpegStart, false); // assume not looped for now
|
MpegDec::SetMemory(mpegROM, mpegStart, mpegEnd - mpegStart, false); // assume not looped for now
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -266,13 +266,13 @@ void CDSB1::IOWrite8(UINT32 addr, UINT8 data)
|
||||||
{
|
{
|
||||||
usingLoopStart = loopStart;
|
usingLoopStart = loopStart;
|
||||||
usingLoopEnd = mpegEnd-loopStart;
|
usingLoopEnd = mpegEnd-loopStart;
|
||||||
MpegDec::UpdateMemory(&mpegROM[usingLoopStart], usingLoopEnd, true);
|
MpegDec::UpdateMemory(mpegROM, usingLoopStart, usingLoopEnd, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
usingLoopStart = loopStart;
|
usingLoopStart = loopStart;
|
||||||
usingLoopEnd = loopEnd-loopStart;
|
usingLoopEnd = loopEnd-loopStart;
|
||||||
MpegDec::UpdateMemory(&mpegROM[usingLoopStart], usingLoopEnd, true);
|
MpegDec::UpdateMemory(mpegROM, usingLoopStart, usingLoopEnd, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,7 +302,7 @@ void CDSB1::IOWrite8(UINT32 addr, UINT8 data)
|
||||||
loopEnd = endLatch;
|
loopEnd = endLatch;
|
||||||
usingLoopStart = loopStart;
|
usingLoopStart = loopStart;
|
||||||
usingLoopEnd = loopEnd-loopStart;
|
usingLoopEnd = loopEnd-loopStart;
|
||||||
MpegDec::UpdateMemory(&mpegROM[usingLoopStart], usingLoopEnd, true);
|
MpegDec::UpdateMemory(mpegROM, usingLoopStart, usingLoopEnd, true);
|
||||||
//printf("loopEnd = %08X\n", loopEnd);
|
//printf("loopEnd = %08X\n", loopEnd);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -529,10 +529,10 @@ void CDSB1::LoadState(CBlockFile *StateFile)
|
||||||
// Restart MPEG audio at the appropriate position
|
// Restart MPEG audio at the appropriate position
|
||||||
if (isPlaying)
|
if (isPlaying)
|
||||||
{
|
{
|
||||||
MpegDec::SetMemory(&mpegROM[usingMPEGStart], usingMPEGEnd - usingMPEGStart, false);
|
MpegDec::SetMemory(mpegROM, usingMPEGStart, usingMPEGEnd - usingMPEGStart, false);
|
||||||
|
|
||||||
if (usingLoopEnd != 0) { // only if looping was actually enabled
|
if (usingLoopEnd != 0) { // only if looping was actually enabled
|
||||||
MpegDec::UpdateMemory(&mpegROM[usingLoopStart], usingLoopEnd, true);
|
MpegDec::UpdateMemory(mpegROM, usingLoopStart, usingLoopEnd, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
MpegDec::SetPosition(playOffset);
|
MpegDec::SetPosition(playOffset);
|
||||||
|
@ -691,7 +691,7 @@ void CDSB2::WriteMPEGFIFO(UINT8 byte)
|
||||||
usingMPEGEnd = mpegEnd;
|
usingMPEGEnd = mpegEnd;
|
||||||
playing = 1;
|
playing = 1;
|
||||||
|
|
||||||
MpegDec::SetMemory(&mpegROM[mpegStart], mpegEnd - mpegStart, false);
|
MpegDec::SetMemory(mpegROM, mpegStart, mpegEnd - mpegStart, false);
|
||||||
|
|
||||||
mpegState = ST_IDLE;
|
mpegState = ST_IDLE;
|
||||||
}
|
}
|
||||||
|
@ -737,7 +737,7 @@ void CDSB2::WriteMPEGFIFO(UINT8 byte)
|
||||||
{
|
{
|
||||||
usingLoopStart = mpegStart;
|
usingLoopStart = mpegStart;
|
||||||
usingLoopEnd = mpegEnd - mpegStart;
|
usingLoopEnd = mpegEnd - mpegStart;
|
||||||
MpegDec::UpdateMemory(&mpegROM[usingLoopStart], usingLoopEnd, true);
|
MpegDec::UpdateMemory(mpegROM, usingLoopStart, usingLoopEnd, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -773,7 +773,7 @@ void CDSB2::WriteMPEGFIFO(UINT8 byte)
|
||||||
usingMPEGStart = mpegStart;
|
usingMPEGStart = mpegStart;
|
||||||
usingMPEGEnd = mpegEnd;
|
usingMPEGEnd = mpegEnd;
|
||||||
playing = 1;
|
playing = 1;
|
||||||
MpegDec::SetMemory(&mpegROM[mpegStart], mpegEnd - mpegStart, false);
|
MpegDec::SetMemory(mpegROM, mpegStart, mpegEnd - mpegStart, false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ST_GOTA5:
|
case ST_GOTA5:
|
||||||
|
@ -1165,10 +1165,10 @@ void CDSB2::LoadState(CBlockFile *StateFile)
|
||||||
// Restart MPEG audio at the appropriate position
|
// Restart MPEG audio at the appropriate position
|
||||||
if (isPlaying)
|
if (isPlaying)
|
||||||
{
|
{
|
||||||
MpegDec::SetMemory(&mpegROM[usingMPEGStart], usingMPEGEnd - usingMPEGStart, false);
|
MpegDec::SetMemory(mpegROM, usingMPEGStart, usingMPEGEnd - usingMPEGStart, false);
|
||||||
|
|
||||||
if (usingLoopEnd != 0) { // only if looping was actually enabled
|
if (usingLoopEnd != 0) { // only if looping was actually enabled
|
||||||
MpegDec::UpdateMemory(&mpegROM[usingLoopStart], usingLoopEnd, true);
|
MpegDec::UpdateMemory(mpegROM, usingLoopStart, usingLoopEnd, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
MpegDec::SetPosition(playOffset);
|
MpegDec::SetPosition(playOffset);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
** Supermodel
|
** Supermodel
|
||||||
** A Sega Model 3 Arcade Emulator.
|
** A Sega Model 3 Arcade Emulator.
|
||||||
** Copyright 2003-2023 The Supermodel Team
|
** Copyright 2003-2024 The Supermodel Team
|
||||||
**
|
**
|
||||||
** This file is part of Supermodel.
|
** This file is part of Supermodel.
|
||||||
**
|
**
|
||||||
|
@ -95,6 +95,7 @@
|
||||||
#include "OSD/Audio.h"
|
#include "OSD/Audio.h"
|
||||||
#include "Graphics/New3D/VBO.h"
|
#include "Graphics/New3D/VBO.h"
|
||||||
#include "Graphics/SuperAA.h"
|
#include "Graphics/SuperAA.h"
|
||||||
|
#include "Sound/MPEG/MpegAudio.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "Util/BMPFile.h"
|
#include "Util/BMPFile.h"
|
||||||
|
@ -105,6 +106,12 @@
|
||||||
Global Run-time Config
|
Global Run-time Config
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
|
static const std::string s_analysisPath = Util::Format() << FileSystemPath::GetPath(FileSystemPath::Analysis);
|
||||||
|
static const std::string s_configFilePath = Util::Format() << FileSystemPath::GetPath(FileSystemPath::Config) << "Supermodel.ini";
|
||||||
|
static const std::string s_gameXMLFilePath = Util::Format() << FileSystemPath::GetPath(FileSystemPath::Config) << "Games.xml";
|
||||||
|
static const std::string s_musicXMLFilePath = Util::Format() << FileSystemPath::GetPath(FileSystemPath::Config) << "Music.xml";
|
||||||
|
static const std::string s_logFilePath = Util::Format() << FileSystemPath::GetPath(FileSystemPath::Log) << "Supermodel.log";
|
||||||
|
|
||||||
static Util::Config::Node s_runtime_config("Global");
|
static Util::Config::Node s_runtime_config("Global");
|
||||||
|
|
||||||
|
|
||||||
|
@ -914,6 +921,9 @@ int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *In
|
||||||
return 1;
|
return 1;
|
||||||
*rom_set = ROMSet(); // free up this memory we won't need anymore
|
*rom_set = ROMSet(); // free up this memory we won't need anymore
|
||||||
|
|
||||||
|
// Customized music for games with MPEG boards
|
||||||
|
MpegDec::LoadCustomTracks(s_musicXMLFilePath, game);
|
||||||
|
|
||||||
// Load NVRAM
|
// Load NVRAM
|
||||||
LoadNVRAM(Model3);
|
LoadNVRAM(Model3);
|
||||||
|
|
||||||
|
@ -1356,12 +1366,6 @@ QuitError:
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
Entry Point and Command Line Procesing
|
Entry Point and Command Line Procesing
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
static const std::string s_analysisPath = Util::Format() << FileSystemPath::GetPath(FileSystemPath::Analysis);
|
|
||||||
static const std::string s_configFilePath = Util::Format() << FileSystemPath::GetPath(FileSystemPath::Config) << "Supermodel.ini";
|
|
||||||
static const std::string s_gameXMLFilePath = Util::Format() << FileSystemPath::GetPath(FileSystemPath::Config) << "Games.xml";
|
|
||||||
static const std::string s_logFilePath = Util::Format() << FileSystemPath::GetPath(FileSystemPath::Log) << "Supermodel.log";
|
|
||||||
|
|
||||||
// Create and configure inputs
|
// Create and configure inputs
|
||||||
static bool ConfigureInputs(CInputs *Inputs, Util::Config::Node *fileConfig, Util::Config::Node *runtimeConfig, const Game &game, bool configure)
|
static bool ConfigureInputs(CInputs *Inputs, Util::Config::Node *fileConfig, Util::Config::Node *runtimeConfig, const Game &game, bool configure)
|
||||||
{
|
{
|
||||||
|
@ -1543,7 +1547,7 @@ static Util::Config::Node DefaultConfig()
|
||||||
static void Title(void)
|
static void Title(void)
|
||||||
{
|
{
|
||||||
puts("Supermodel: A Sega Model 3 Arcade Emulator (Version " SUPERMODEL_VERSION ")");
|
puts("Supermodel: A Sega Model 3 Arcade Emulator (Version " SUPERMODEL_VERSION ")");
|
||||||
puts("Copyright 2003-2023 by The Supermodel Team");
|
puts("Copyright 2003-2024 by The Supermodel Team");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Help(void)
|
static void Help(void)
|
||||||
|
|
|
@ -1,6 +1,205 @@
|
||||||
|
/**
|
||||||
|
** Supermodel
|
||||||
|
** A Sega Model 3 Arcade Emulator.
|
||||||
|
** Copyright 2003-2024 The Supermodel Team
|
||||||
|
**
|
||||||
|
** This file is part of Supermodel.
|
||||||
|
**
|
||||||
|
** Supermodel is free software: you can redistribute it and/or modify it under
|
||||||
|
** the terms of the GNU General Public License as published by the Free
|
||||||
|
** Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
** any later version.
|
||||||
|
**
|
||||||
|
** Supermodel is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
** more details.
|
||||||
|
**
|
||||||
|
** You should have received a copy of the GNU General Public License along
|
||||||
|
** with Supermodel. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
**/
|
||||||
|
|
||||||
#define MINIMP3_IMPLEMENTATION
|
#define MINIMP3_IMPLEMENTATION
|
||||||
#include "Pkgs/minimp3.h"
|
#include "Pkgs/minimp3.h"
|
||||||
#include "MpegAudio.h"
|
#include "MpegAudio.h"
|
||||||
|
#include "Util/ConfigBuilders.h"
|
||||||
|
#include "OSD/Logger.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
Custom MPEG Tracks
|
||||||
|
***************************************************************************************************/
|
||||||
|
|
||||||
|
struct CustomTrack
|
||||||
|
{
|
||||||
|
std::shared_ptr<uint8_t[]> mpeg_data;
|
||||||
|
size_t mpeg_data_size;
|
||||||
|
uint32_t mpeg_rom_start_offset;
|
||||||
|
size_t file_start_offset;
|
||||||
|
|
||||||
|
CustomTrack()
|
||||||
|
: mpeg_data(nullptr),
|
||||||
|
mpeg_data_size(0),
|
||||||
|
mpeg_rom_start_offset(0),
|
||||||
|
file_start_offset(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomTrack(const std::shared_ptr<uint8_t[]> &mpeg_data, size_t mpeg_data_size, uint32_t mpeg_rom_start_offset, size_t file_start_offset)
|
||||||
|
: mpeg_data(mpeg_data),
|
||||||
|
mpeg_data_size(mpeg_data_size),
|
||||||
|
mpeg_rom_start_offset(mpeg_rom_start_offset),
|
||||||
|
file_start_offset(file_start_offset)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FileContents
|
||||||
|
{
|
||||||
|
std::shared_ptr<uint8_t[]> bytes;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::map<uint32_t, CustomTrack> s_custom_tracks_by_mpeg_rom_address;
|
||||||
|
|
||||||
|
static FileContents LoadFile(const std::string &filepath)
|
||||||
|
{
|
||||||
|
FILE *fp = fopen(filepath.c_str(), "rb");
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
ErrorLog("Unable to load music track from disk: %s.", filepath.c_str());
|
||||||
|
return { .bytes = nullptr, .size = 0 };
|
||||||
|
}
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
long file_size = ftell(fp);
|
||||||
|
fseek(fp, 0, SEEK_SET);
|
||||||
|
std::shared_ptr<uint8_t[]> mpeg_data(new uint8_t[file_size], std::default_delete<uint8_t[]>());
|
||||||
|
fread(mpeg_data.get(), sizeof(uint8_t), file_size, fp);
|
||||||
|
fclose(fp);
|
||||||
|
return { .bytes = mpeg_data, .size = size_t(file_size) };
|
||||||
|
}
|
||||||
|
|
||||||
|
void MpegDec::LoadCustomTracks(const std::string &music_filepath, const Game &game)
|
||||||
|
{
|
||||||
|
s_custom_tracks_by_mpeg_rom_address.clear();
|
||||||
|
|
||||||
|
if (game.mpeg_board.empty())
|
||||||
|
{
|
||||||
|
// No MPEG board
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(music_filepath))
|
||||||
|
{
|
||||||
|
// Custom music configuration file is optional
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Util::Config::Node xml("xml");
|
||||||
|
if (Util::Config::FromXMLFile(&xml, music_filepath))
|
||||||
|
{
|
||||||
|
ErrorLog("Custom music configuration file could not be loaded. Original game tracks will be used.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sample XML:
|
||||||
|
*
|
||||||
|
* <games>
|
||||||
|
* <game name="scud">
|
||||||
|
* <track mpeg_rom_start_offset="" file_start_offset="0" filepath="song1.mp3" />
|
||||||
|
* <track mpeg_rom_start_offset="" file_start_offset="0x1000" filepath="song2.mp3" />
|
||||||
|
* </game>
|
||||||
|
* </games>
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::map<std::string, FileContents> file_contents_by_filepath;
|
||||||
|
|
||||||
|
for (auto it = xml.begin(); it != xml.end(); ++it)
|
||||||
|
{
|
||||||
|
auto &root_node = *it;
|
||||||
|
if (root_node.Key() != "games")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &game_node: root_node)
|
||||||
|
{
|
||||||
|
if (game_node.Key() != "game")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game_node["name"].Empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string game_name = game_node["name"].ValueAs<std::string>();
|
||||||
|
if (game_name != game.name)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &track_node: game_node)
|
||||||
|
{
|
||||||
|
if (track_node.Key() != "track")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t file_start_offset = 0;
|
||||||
|
if (track_node["mpeg_rom_start_offset"].Empty())
|
||||||
|
{
|
||||||
|
ErrorLog("%s: Track in '%s' is missing 'mpeg_rom_start_offset' attribute and will be ignored.", music_filepath.c_str(), game.name.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (track_node["filepath"].Empty())
|
||||||
|
{
|
||||||
|
ErrorLog("%s: Track in '%s' is missing 'filepath' attribute and will be ignored.", music_filepath.c_str(), game.name.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (track_node["file_start_offset"].Exists())
|
||||||
|
{
|
||||||
|
file_start_offset = track_node["file_start_offset"].ValueAs<size_t>();
|
||||||
|
}
|
||||||
|
const std::string filepath = track_node["filepath"].ValueAs<std::string>();
|
||||||
|
const uint32_t mpeg_rom_start_offset = track_node["mpeg_rom_start_offset"].ValueAs<uint32_t>();
|
||||||
|
|
||||||
|
if (s_custom_tracks_by_mpeg_rom_address.count(mpeg_rom_start_offset) != 0)
|
||||||
|
{
|
||||||
|
ErrorLog("%s: Multiple tracks defined for '%s' MPEG ROM offset 0x%08x. Only the first will be used.", music_filepath.c_str(), game.name.c_str(), mpeg_rom_start_offset);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_contents_by_filepath.count(filepath) == 0)
|
||||||
|
{
|
||||||
|
FileContents contents = LoadFile(filepath);
|
||||||
|
if (contents.bytes == nullptr)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
file_contents_by_filepath[filepath] = contents;
|
||||||
|
InfoLog("Loaded custom track: %s.", filepath.c_str());
|
||||||
|
printf("Loaded custom track: %s.\n", filepath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
FileContents contents = file_contents_by_filepath[filepath];
|
||||||
|
s_custom_tracks_by_mpeg_rom_address[mpeg_rom_start_offset] = CustomTrack(contents.bytes, contents.size, mpeg_rom_start_offset, file_start_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
MPEG Music Playback
|
||||||
|
***************************************************************************************************/
|
||||||
|
|
||||||
struct Decoder
|
struct Decoder
|
||||||
{
|
{
|
||||||
|
@ -13,37 +212,95 @@ struct Decoder
|
||||||
int numSamples;
|
int numSamples;
|
||||||
int pcmPos;
|
int pcmPos;
|
||||||
short pcm[MINIMP3_MAX_SAMPLES_PER_FRAME];
|
short pcm[MINIMP3_MAX_SAMPLES_PER_FRAME];
|
||||||
|
|
||||||
|
std::shared_ptr<uint8_t[]> custom_mpeg_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
static Decoder dec = { 0 };
|
static Decoder dec = { 0 };
|
||||||
|
|
||||||
void MpegDec::SetMemory(const uint8_t *data, int length, bool loop)
|
void MpegDec::SetMemory(const uint8_t *data, int offset, int length, bool loop)
|
||||||
{
|
{
|
||||||
mp3dec_init(&dec.mp3d);
|
mp3dec_init(&dec.mp3d);
|
||||||
|
|
||||||
dec.buffer = data;
|
auto it = s_custom_tracks_by_mpeg_rom_address.find(offset);
|
||||||
|
if (it == s_custom_tracks_by_mpeg_rom_address.end()) {
|
||||||
|
// MPEG ROM
|
||||||
|
dec.buffer = data + offset;
|
||||||
dec.size = length;
|
dec.size = length;
|
||||||
|
dec.custom_mpeg_data = nullptr;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Custom track available
|
||||||
|
const CustomTrack &track = it->second;
|
||||||
|
|
||||||
|
size_t offset_in_file = track.file_start_offset;
|
||||||
|
if (offset_in_file >= track.mpeg_data_size)
|
||||||
|
{
|
||||||
|
// Out of bounds, go to start of file
|
||||||
|
offset_in_file = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dec.buffer = track.mpeg_data.get() + offset_in_file;
|
||||||
|
dec.size = track.mpeg_data_size - offset_in_file;
|
||||||
|
dec.custom_mpeg_data = track.mpeg_data;
|
||||||
|
}
|
||||||
|
|
||||||
dec.pos = 0;
|
dec.pos = 0;
|
||||||
dec.numSamples = 0;
|
dec.numSamples = 0;
|
||||||
dec.pcmPos = 0;
|
dec.pcmPos = 0;
|
||||||
dec.loop = loop;
|
dec.loop = loop;
|
||||||
dec.stopped = false;
|
dec.stopped = false;
|
||||||
|
|
||||||
|
printf("SET MEMORY: %08x\n", offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MpegDec::UpdateMemory(const uint8_t* data, int length, bool loop)
|
void MpegDec::UpdateMemory(const uint8_t* data, int offset, int length, bool loop)
|
||||||
{
|
{
|
||||||
|
auto it = s_custom_tracks_by_mpeg_rom_address.find(offset);
|
||||||
|
if (it == s_custom_tracks_by_mpeg_rom_address.end()) {
|
||||||
|
// MPEG ROM
|
||||||
int diff;
|
int diff;
|
||||||
if (data > dec.buffer) {
|
if ((data + offset) > dec.buffer) {
|
||||||
diff = (int)(data - dec.buffer);
|
diff = (int)(data + offset - dec.buffer);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
diff = -(int)(dec.buffer - data);
|
diff = -(int)(dec.buffer - data - offset);
|
||||||
}
|
}
|
||||||
|
dec.buffer = data + offset;
|
||||||
dec.buffer = data;
|
|
||||||
dec.size = length;
|
dec.size = length;
|
||||||
dec.pos = dec.pos - diff; // update position relative to our new start location
|
dec.pos = dec.pos - diff; // update position relative to our new start location
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Custom track available. This is tricky. This command updates the start/end pointers (usually
|
||||||
|
// used by games to create a loop point). We need to ensure that the custom track definition is
|
||||||
|
// consistent: the custom track associated with this ROM offset must be the same file as is
|
||||||
|
// currently playing, otherwise we do nothing.
|
||||||
|
CustomTrack &track = it->second;
|
||||||
|
if (track.mpeg_data == dec.custom_mpeg_data)
|
||||||
|
{
|
||||||
|
size_t offset_in_file = track.file_start_offset;
|
||||||
|
if (offset_in_file >= track.mpeg_data_size)
|
||||||
|
{
|
||||||
|
// Out of bounds, just use start of file
|
||||||
|
offset_in_file = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int diff;
|
||||||
|
if ((track.mpeg_data.get() + offset_in_file) > dec.buffer) {
|
||||||
|
diff = (int)(track.mpeg_data.get() + offset_in_file - dec.buffer);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
diff = -(int)(dec.buffer - track.mpeg_data.get() - offset_in_file);
|
||||||
|
}
|
||||||
|
dec.buffer = track.mpeg_data.get() + offset_in_file;
|
||||||
|
dec.size = track.mpeg_data_size - offset_in_file; // ignoring length specified by caller because MPEG ROM end offsets won't in general match with track, so we always have to use EOF
|
||||||
|
dec.pos = dec.pos - diff; // update position relative to our new start location
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dec.loop = loop;
|
dec.loop = loop;
|
||||||
|
|
||||||
|
printf("UPDATE MEMORY: %08x\n", offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MpegDec::GetPosition()
|
int MpegDec::GetPosition()
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
#ifndef _MPEG_AUDIO_H_
|
#ifndef _MPEG_AUDIO_H_
|
||||||
#define _MPEG_AUDIO_H_
|
#define _MPEG_AUDIO_H_
|
||||||
|
|
||||||
|
#include "Game.h"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
namespace MpegDec
|
namespace MpegDec
|
||||||
{
|
{
|
||||||
void SetMemory(const uint8_t *data, int length, bool loop);
|
void LoadCustomTracks(const std::string &music_filepath, const Game &game);
|
||||||
void UpdateMemory(const uint8_t *data, int length, bool loop);
|
void SetMemory(const uint8_t *data, int offset, int length, bool loop);
|
||||||
|
void UpdateMemory(const uint8_t *data, int offset, int length, bool loop);
|
||||||
int GetPosition();
|
int GetPosition();
|
||||||
void SetPosition(int pos);
|
void SetPosition(int pos);
|
||||||
void DecodeAudio(int16_t* left, int16_t* right, int numStereoSamples);
|
void DecodeAudio(int16_t* left, int16_t* right, int numStereoSamples);
|
||||||
|
|
Loading…
Reference in a new issue