Compare commits
No commits in common. "23fcb4ce959216d7155841fd6dc5275b46aa2f82" and "cb509877aa7c6aab9d5122193770a69f461d4ed5" have entirely different histories.
23fcb4ce95
...
cb509877aa
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,5 +1,3 @@
|
|||
## speccy player part
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
|
|
|
@ -26,7 +26,7 @@ enum {
|
|||
MAX_FRAMES_PER_BUFFER = 4096,
|
||||
};
|
||||
|
||||
const double OPN_CLOCK_RATE = 3500000;
|
||||
const double OPNA_CLOCK_RATE = 7987000;
|
||||
|
||||
PaStreamParameters outputParameters;
|
||||
PaStream* stream;
|
||||
|
@ -86,11 +86,11 @@ private:
|
|||
int chip, reg, data;
|
||||
};
|
||||
std::queue<reg_entry_t> reg_queue;
|
||||
ymfm::ym2203* chip;
|
||||
ymfm::ym2608* chip;
|
||||
public:
|
||||
opnx_register_queue_t() : opnx_register_queue_t(nullptr) {}
|
||||
opnx_register_queue_t(ymfm::ym2203* _chip) : clock(0), write_delay(0), chip(_chip) {}
|
||||
void set_chip(ymfm::ym2203* _chip) { chip = _chip; }
|
||||
opnx_register_queue_t(ymfm::ym2608* _chip) : clock(0), write_delay(0), chip(_chip) {}
|
||||
void set_chip(ymfm::ym2608* _chip) { chip = _chip; }
|
||||
void reset() {
|
||||
clock = 0;
|
||||
write_delay = 0;
|
||||
|
@ -108,7 +108,11 @@ public:
|
|||
void pop_clock() {
|
||||
if (!reg_queue.empty() && (reg_queue.front().clock <= clock)) {
|
||||
auto &r = reg_queue.front();
|
||||
if (chip) {
|
||||
if (r.chip == 1 && chip) {
|
||||
chip->write_address_hi(r.reg);
|
||||
chip->write_data_hi(r.data);
|
||||
}
|
||||
else if (chip) {
|
||||
chip->write_address(r.reg);
|
||||
chip->write_data(r.data);
|
||||
}
|
||||
|
@ -118,10 +122,10 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
opna_interface_t opna_interface[2];
|
||||
ymfm::ym2203 *opnachip[2];
|
||||
opnx_register_queue_t opna_regqueue[2];
|
||||
ymfm::ym2203::output_data opna_out[MAX_FRAMES_PER_BUFFER][2];
|
||||
opna_interface_t opna_interface;
|
||||
ymfm::ym2608 *opnachip;
|
||||
opnx_register_queue_t opna_regqueue;
|
||||
ymfm::ym2608::output_data opna_out[MAX_FRAMES_PER_BUFFER];
|
||||
|
||||
// ------------------
|
||||
|
||||
|
@ -237,18 +241,18 @@ int vgm_parse_frame() {
|
|||
|
||||
// first, do OPL cases
|
||||
switch ((VGM_Stream_Opcode)*it) {
|
||||
case VGM_Stream_Opcode::YM2203_WRITE: {
|
||||
case VGM_Stream_Opcode::YM2608_PORT1_WRITE: {
|
||||
// get register and data
|
||||
int reg = *(it + 1), data = *(it + 2);
|
||||
opna_regqueue[0].add(0, reg, data, 4);
|
||||
opna_regqueue.add(1, reg, data, 4);
|
||||
//opnachip->write(2, reg);
|
||||
//opnachip->write(3, data);
|
||||
break;
|
||||
}
|
||||
case VGM_Stream_Opcode::YM2203_CHIP2_WRITE: {
|
||||
case VGM_Stream_Opcode::YM2608_PORT0_WRITE: {
|
||||
// get register and data
|
||||
int reg = *(it + 1), data = *(it + 2);
|
||||
opna_regqueue[1].add(0, reg, data, 4);
|
||||
opna_regqueue.add(0, reg, data, 4);
|
||||
//opnachip->write(0, reg);
|
||||
//opnachip->write(1, data);
|
||||
break;
|
||||
|
@ -309,37 +313,51 @@ int vgm_parse_frame() {
|
|||
int synth_render(int16_t* buffer, uint32_t num_samples) {
|
||||
int samples_to_render = num_samples;
|
||||
|
||||
memset(buffer, 0, sizeof(int16_t) * 2 * num_samples);
|
||||
#if 0
|
||||
while (samples_to_render > 0) {
|
||||
if (samples_to_render < opmctx.delay_count) {
|
||||
OPL3_GenerateStream(&opl3, buffer, samples_to_render);
|
||||
opmctx.delay_count -= samples_to_render;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// calculate new delay
|
||||
OPL3_GenerateStream(&opl3, buffer, opmctx.delay_count);
|
||||
buffer += CHANNELS * opmctx.delay_count;
|
||||
samples_to_render -= opmctx.delay_count;
|
||||
|
||||
// parse VGM stream
|
||||
opmplay_tick(&opmctx.opm);
|
||||
opmctx.delay_count = (44100.0 / ((double)0x1234DD / opmctx.opm.header.frame_rate));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
while (samples_to_render > 0) {
|
||||
if (samples_to_render < vgmctx.delay_count) {
|
||||
for (int i = 0; i < samples_to_render; i++) {
|
||||
for (int chip = 0; chip < 2; chip++) {
|
||||
opna_regqueue[chip].pop_clock();
|
||||
opnachip[chip]->generate(opna_out[chip] + i, 1);
|
||||
opna_out[chip][i].clamp16();
|
||||
buffer[2*i+0] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (1.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 0.0*opna_out[chip][i].data[3])); // mix FM and SSG
|
||||
buffer[2*i+1] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (0.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 1.0*opna_out[chip][i].data[3]));
|
||||
}
|
||||
opna_regqueue.pop_clock();
|
||||
opnachip->generate(opna_out + i, 1);
|
||||
opna_out[i].clamp16();
|
||||
*(buffer + 0) = 0.8*opna_out[i].data[0] + 0.2* opna_out[i].data[2]; // mix FM and SSG
|
||||
*(buffer + 1) = 0.8*opna_out[i].data[1] + 0.2* opna_out[i].data[2];
|
||||
buffer += 2;
|
||||
}
|
||||
|
||||
vgmctx.delay_count -= samples_to_render;
|
||||
buffer += 2 * samples_to_render;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// calculate new delay
|
||||
for (int i = 0; i < vgmctx.delay_count; i++) {
|
||||
for (int chip = 0; chip < 2; chip++) {
|
||||
opna_regqueue[chip].pop_clock();
|
||||
opnachip[chip]->generate(opna_out[chip] + i, 1);
|
||||
opna_out[chip][i].clamp16();
|
||||
buffer[2*i+0] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (1.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 0.0*opna_out[chip][i].data[3])); // mix FM and SSG
|
||||
buffer[2*i+1] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (0.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 1.0*opna_out[chip][i].data[3]));
|
||||
}
|
||||
opna_regqueue.pop_clock();
|
||||
opnachip->generate(opna_out + i, 1);
|
||||
opna_out[i].clamp16();
|
||||
*(buffer + 0) = 0.8*opna_out[i].data[0] + 0.2* opna_out[i].data[2]; // mix FM and SSG
|
||||
*(buffer + 1) = 0.8*opna_out[i].data[1] + 0.2* opna_out[i].data[2];
|
||||
buffer += 2;
|
||||
}
|
||||
samples_to_render -= vgmctx.delay_count;
|
||||
buffer += 2 * vgmctx.delay_count;
|
||||
|
||||
// parse VGM stream
|
||||
vgm_parse_frame();
|
||||
|
@ -407,22 +425,20 @@ int main(int argc, char* argv[])
|
|||
{
|
||||
bool render_to_wave = (argc >= 3);
|
||||
|
||||
uint32_t sample_rate;
|
||||
for (int chip = 0; chip < 2; chip++) {
|
||||
opnachip[chip] = new ymfm::ym2203(opna_interface[chip]);
|
||||
if (opnachip[chip] == nullptr) {
|
||||
printf("error: unable to init ymfm!\n");
|
||||
return 1;
|
||||
}
|
||||
opnachip[chip]->reset();
|
||||
opnachip[chip]->set_fidelity(ymfm::OPN_FIDELITY_MIN);
|
||||
sample_rate = opnachip[chip]->sample_rate(OPN_CLOCK_RATE);
|
||||
vgmctx.rescaler = ((double)sample_rate / 44100.0);
|
||||
printf("sample rate - %d hz\n", sample_rate);
|
||||
|
||||
opna_regqueue[chip].reset();
|
||||
opna_regqueue[chip].set_chip(opnachip[chip]);
|
||||
//OPL3_Reset(&opl3, SAMPLE_RATE);
|
||||
opnachip = new ymfm::ym2608(opna_interface);
|
||||
if(opnachip == nullptr) {
|
||||
printf("error: unable to init ymfm!\n");
|
||||
return 1;
|
||||
}
|
||||
opnachip->reset();
|
||||
opnachip->set_fidelity(ymfm::OPN_FIDELITY_MIN);
|
||||
uint32_t sample_rate = opnachip->sample_rate(OPNA_CLOCK_RATE);
|
||||
vgmctx.rescaler = ((double)sample_rate / 44100.0);
|
||||
printf("sample rate - %d hz\n", sample_rate);
|
||||
|
||||
opna_regqueue.reset();
|
||||
opna_regqueue.set_chip(opnachip);
|
||||
|
||||
if (!render_to_wave) {
|
||||
if (pa_init(sample_rate) != 0) {
|
||||
|
@ -501,13 +517,10 @@ int main(int argc, char* argv[])
|
|||
else {
|
||||
pa_write(buf, FRAMES_PER_BUFFER);
|
||||
}
|
||||
ff_pos += FRAMES_PER_BUFFER;
|
||||
|
||||
// update console
|
||||
memset(console.buffer, 0, sizeof(CHAR_INFO) * console.bufsize.X * console.bufsize.Y);
|
||||
|
||||
tprintf(0, 0, "frame = %d", ff_pos);
|
||||
|
||||
console_update();
|
||||
|
||||
if (_kbhit()) {
|
|
@ -1,168 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{57a21450-1b4a-4c1c-8807-735a6f2f5929}</ProjectGuid>
|
||||
<RootNamespace>lxmplay</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IncludePath>$(ProjectDir)/include;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
|
||||
<LibraryPath>$(ProjectDir)/lib;$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IncludePath>$(ProjectDir)/include;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
|
||||
<LibraryPath>$(ProjectDir)/lib;$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>portaudio_static_x86.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>portaudio_static_x86.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="opmplay.cpp" />
|
||||
<ClCompile Include="ymfm\src\ymfm_adpcm.cpp" />
|
||||
<ClCompile Include="ymfm\src\ymfm_misc.cpp" />
|
||||
<ClCompile Include="ymfm\src\ymfm_opn.cpp" />
|
||||
<ClCompile Include="ymfm\src\ymfm_pcm.cpp" />
|
||||
<ClCompile Include="ymfm\src\ymfm_ssg.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="include\portaudio.h" />
|
||||
<ClInclude Include="include\vgm.h" />
|
||||
<ClInclude Include="opmfile.h" />
|
||||
<ClInclude Include="opmplay.h" />
|
||||
<ClInclude Include="wavehead.h" />
|
||||
<ClInclude Include="ymfm\src\ymfm.h" />
|
||||
<ClInclude Include="ymfm\src\ymfm_opn.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
|
@ -1,69 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Исходные файлы">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Файлы заголовков">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Файлы ресурсов">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Исходные файлы\ymfm">
|
||||
<UniqueIdentifier>{138d41e0-de89-403a-801c-cd6e8c729114}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Файлы заголовков\ymfm">
|
||||
<UniqueIdentifier>{9101f88f-74cd-4172-9731-8e247f8dd520}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp">
|
||||
<Filter>Исходные файлы</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ymfm\src\ymfm_opn.cpp">
|
||||
<Filter>Исходные файлы\ymfm</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ymfm\src\ymfm_misc.cpp">
|
||||
<Filter>Исходные файлы\ymfm</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ymfm\src\ymfm_pcm.cpp">
|
||||
<Filter>Исходные файлы\ymfm</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ymfm\src\ymfm_ssg.cpp">
|
||||
<Filter>Исходные файлы\ymfm</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ymfm\src\ymfm_adpcm.cpp">
|
||||
<Filter>Исходные файлы\ymfm</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="opmplay.cpp">
|
||||
<Filter>Исходные файлы</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="wavehead.h">
|
||||
<Filter>Файлы заголовков</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\portaudio.h">
|
||||
<Filter>Файлы заголовков</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\vgm.h">
|
||||
<Filter>Файлы заголовков</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="opmfile.h">
|
||||
<Filter>Файлы заголовков</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ymfm\src\ymfm_opn.h">
|
||||
<Filter>Файлы заголовков\ymfm</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ymfm\src\ymfm.h">
|
||||
<Filter>Файлы заголовков\ymfm</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="opmplay.h">
|
||||
<Filter>Файлы заголовков</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,592 +0,0 @@
|
|||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <portaudio.h>
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <queue>
|
||||
|
||||
#include "wavehead.h"
|
||||
#include "opmplay.h"
|
||||
#include "vgm.h"
|
||||
#include "rss.h" // YM2608 internal ADPCM-A ROM dump
|
||||
#include "ymfm/src/ymfm_opn.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_EXTRA_LEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
|
||||
enum {
|
||||
CHANNELS = 2,
|
||||
FRAMES_PER_BUFFER = 512,
|
||||
MAX_FRAMES_PER_BUFFER = 4096,
|
||||
};
|
||||
|
||||
const double OPN_CLOCK_RATE = 3500000;
|
||||
//const double OPN_CLOCK_RATE = 3540000;
|
||||
|
||||
PaStreamParameters outputParameters;
|
||||
PaStream* stream;
|
||||
|
||||
// console stuff
|
||||
struct {
|
||||
HANDLE hStdout, hScreenBuffer;
|
||||
COORD bufcoord, bufsize;
|
||||
SMALL_RECT bufDestRect;
|
||||
CHAR_INFO* buffer; // the main buffer to write to
|
||||
} console;
|
||||
|
||||
struct opm_context_t {
|
||||
// full context
|
||||
opmplay_context_t opm;
|
||||
opmplay_io_t io;
|
||||
|
||||
// delay count relative to sample rate
|
||||
int32_t delay_count;
|
||||
int32_t delay_count_reload;
|
||||
};
|
||||
|
||||
struct vgm_context_t {
|
||||
std::vector<uint8_t> vgmfile;
|
||||
std::vector<uint8_t>::iterator vgmfile_it;
|
||||
VGMHeader* header;
|
||||
uint32_t loop_pos;
|
||||
uint32_t start, end; // offsets
|
||||
|
||||
// delay count
|
||||
int32_t delay_count;
|
||||
|
||||
// rescaler for 44100hz delays
|
||||
double rescaler;
|
||||
};
|
||||
|
||||
vgm_context_t vgmctx;
|
||||
opm_context_t opmctx;
|
||||
|
||||
class opna_interface_t : public ymfm::ymfm_interface {
|
||||
protected:
|
||||
int32_t timer_a_count, timer_a_reload;
|
||||
public:
|
||||
opna_interface_t() : timer_a_count(0), timer_a_reload(0) {};
|
||||
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address) {
|
||||
switch (type) {
|
||||
case ymfm::ACCESS_ADPCM_A: return YM2608_ADPCM_ROM[address & 0x1FFF];
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) {
|
||||
if (tnum == 0) {
|
||||
timer_a_reload = duration_in_clocks;
|
||||
}
|
||||
}
|
||||
void timer_advance(int32_t ticks) {
|
||||
// ripped from furnace :grins:
|
||||
if (timer_a_reload >= 0) {
|
||||
timer_a_count -= ticks;
|
||||
if (timer_a_count < 0) {
|
||||
m_engine->engine_timer_expired(0);
|
||||
timer_a_count += timer_a_reload;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// register write queue
|
||||
class opnx_register_queue_t {
|
||||
private:
|
||||
uint64_t write_delay;
|
||||
uint64_t clock;
|
||||
struct reg_entry_t {
|
||||
uint64_t clock;
|
||||
int reg, data;
|
||||
};
|
||||
std::queue<reg_entry_t> reg_queue;
|
||||
ymfm::ym2203* chip;
|
||||
public:
|
||||
opnx_register_queue_t() : opnx_register_queue_t(nullptr) {}
|
||||
opnx_register_queue_t(ymfm::ym2203* _chip) : clock(0), write_delay(0), chip(_chip) {}
|
||||
void set_chip(ymfm::ym2203* _chip) { chip = _chip; }
|
||||
void reset() {
|
||||
clock = 0;
|
||||
write_delay = 0;
|
||||
while (!reg_queue.empty()) reg_queue.pop(); // flush queue
|
||||
}
|
||||
void add(int chip, int reg, int data, uint64_t delay) {
|
||||
reg_entry_t r;
|
||||
r.clock = clock + write_delay;
|
||||
r.reg = reg;
|
||||
r.data = data;
|
||||
reg_queue.push(r);
|
||||
write_delay += delay;
|
||||
}
|
||||
void pop_clock() {
|
||||
if (!reg_queue.empty() && (reg_queue.front().clock <= clock)) {
|
||||
auto &r = reg_queue.front();
|
||||
if (chip) {
|
||||
chip->write_address(r.reg);
|
||||
chip->write_data(r.data);
|
||||
}
|
||||
reg_queue.pop();
|
||||
}
|
||||
clock++; write_delay = 0;
|
||||
}
|
||||
};
|
||||
|
||||
opna_interface_t opna_interface[2];
|
||||
ymfm::ym2203 *opnachip[2];
|
||||
opnx_register_queue_t opna_regqueue[2];
|
||||
ymfm::ym2203::output_data opna_out[MAX_FRAMES_PER_BUFFER][2];
|
||||
|
||||
// hack af
|
||||
uint8_t opn_reg_view[2][256];
|
||||
|
||||
// generic output routine
|
||||
void opn_write_reg(int chip, int reg, int data) {
|
||||
if (chip >= 2) return;
|
||||
opn_reg_view[chip][reg] = data;
|
||||
opna_regqueue[chip].add(0, reg, data, 4);
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
// draw plain string
|
||||
void drawstring(const char* str, unsigned long x, unsigned long y, unsigned char attr) {
|
||||
CHAR_INFO* p = (CHAR_INFO*)console.buffer + (console.bufsize.X * y) + x;
|
||||
|
||||
while (*str != '\0') {
|
||||
p->Char.AsciiChar = *str++;
|
||||
p->Attributes = attr;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
// draw string with attributes
|
||||
// '\0' - end, '\xFF\xaa' - set attribute byte 'aa'
|
||||
void drawastring(const char* str, unsigned long x, unsigned long y) {
|
||||
CHAR_INFO* p = (CHAR_INFO*)console.buffer + (console.bufsize.X * y) + x;
|
||||
|
||||
unsigned short attr = 0x07;
|
||||
|
||||
while (*str != '\0') if (*str == '\xFF') {
|
||||
attr = (*++str); str++;
|
||||
}
|
||||
else {
|
||||
p->Char.AsciiChar = *str++;
|
||||
p->Attributes = attr;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
// printf
|
||||
int tprintf(uint32_t x, uint32_t y, const char* format, ...) {
|
||||
char buffer[1024]; // large enough
|
||||
va_list arglist;
|
||||
|
||||
va_start(arglist, format);
|
||||
int rtn = vsnprintf(buffer, sizeof(buffer), format, arglist);
|
||||
drawastring(buffer, x, y);
|
||||
va_end(arglist);
|
||||
|
||||
return rtn;
|
||||
};
|
||||
|
||||
// -------------------
|
||||
|
||||
|
||||
int console_open() {
|
||||
// Get a handle to the STDOUT screen buffer to copy from and
|
||||
// create a new screen buffer to copy to.
|
||||
|
||||
console.hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
console.hScreenBuffer = CreateConsoleScreenBuffer(
|
||||
GENERIC_READ | // read/write access
|
||||
GENERIC_WRITE,
|
||||
FILE_SHARE_READ |
|
||||
FILE_SHARE_WRITE, // shared
|
||||
NULL, // default security attributes
|
||||
CONSOLE_TEXTMODE_BUFFER, // must be TEXTMODE
|
||||
NULL); // reserved; must be NULL
|
||||
if (console.hStdout == INVALID_HANDLE_VALUE ||
|
||||
console.hScreenBuffer == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
printf("CreateConsoleScreenBuffer failed - (%d)\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// resize
|
||||
console.bufsize.X = 132;
|
||||
console.bufsize.Y = 40;
|
||||
SetConsoleScreenBufferSize(console.hScreenBuffer, console.bufsize);
|
||||
|
||||
// allocate console buffer
|
||||
console.buffer = new CHAR_INFO[console.bufsize.X * console.bufsize.Y];
|
||||
memset(console.buffer, 0, sizeof(CHAR_INFO) * console.bufsize.X * console.bufsize.Y);
|
||||
|
||||
// Make the new screen buffer the active screen buffer.
|
||||
if (!SetConsoleActiveScreenBuffer(console.hScreenBuffer))
|
||||
{
|
||||
printf("SetConsoleActiveScreenBuffer failed - (%d)\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void console_update() {
|
||||
console.bufDestRect.Top = 0;
|
||||
console.bufDestRect.Left = 0;
|
||||
console.bufDestRect.Bottom = console.bufsize.Y - 1;
|
||||
console.bufDestRect.Right = console.bufsize.X - 1;
|
||||
|
||||
console.bufcoord.X = console.bufcoord.Y = 0;
|
||||
|
||||
WriteConsoleOutput(
|
||||
console.hScreenBuffer, // screen buffer to write to
|
||||
console.buffer, // buffer to copy from
|
||||
console.bufsize, // col-row size of chiBuffer
|
||||
console.bufcoord, // top left src cell in chiBuffer
|
||||
&console.bufDestRect); // dest. screen buffer rectangle
|
||||
}
|
||||
|
||||
void console_done() {
|
||||
SetConsoleActiveScreenBuffer(console.hStdout);
|
||||
}
|
||||
|
||||
// -------------------
|
||||
// synth render
|
||||
int synth_render(int16_t* buffer, uint32_t num_samples) {
|
||||
int samples_to_render = num_samples;
|
||||
|
||||
memset(buffer, 0, sizeof(int16_t) * 2 * num_samples);
|
||||
|
||||
while (samples_to_render > 0) {
|
||||
if (samples_to_render < opmctx.delay_count) {
|
||||
for (int i = 0; i < samples_to_render; i++) {
|
||||
for (int chip = 0; chip < 2; chip++) {
|
||||
opna_regqueue[chip].pop_clock();
|
||||
opnachip[chip]->generate(opna_out[chip] + i, 1);
|
||||
opna_interface[chip].timer_advance(24);
|
||||
opna_out[chip][i].clamp16();
|
||||
buffer[2*i+0] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (1.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 0.0*opna_out[chip][i].data[3])); // mix FM and SSG
|
||||
buffer[2*i+1] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (0.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 1.0*opna_out[chip][i].data[3]));
|
||||
}
|
||||
}
|
||||
|
||||
opmctx.delay_count -= samples_to_render;
|
||||
buffer += 2 * samples_to_render;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// calculate new delay
|
||||
for (int i = 0; i < opmctx.delay_count; i++) {
|
||||
for (int chip = 0; chip < 2; chip++) {
|
||||
opna_regqueue[chip].pop_clock();
|
||||
opnachip[chip]->generate(opna_out[chip] + i, 1);
|
||||
opna_interface[chip].timer_advance(24);
|
||||
opna_out[chip][i].clamp16();
|
||||
buffer[2*i+0] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (1.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 0.0*opna_out[chip][i].data[3])); // mix FM and SSG
|
||||
buffer[2*i+1] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (0.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 1.0*opna_out[chip][i].data[3]));
|
||||
}
|
||||
}
|
||||
samples_to_render -= opmctx.delay_count;
|
||||
buffer += 2 * opmctx.delay_count;
|
||||
|
||||
opmplay_tick(&opmctx.opm);
|
||||
opmctx.delay_count = opmctx.delay_count_reload;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int pa_init(double sample_rate) {
|
||||
PaError err;
|
||||
|
||||
// init portaudio
|
||||
err = Pa_Initialize();
|
||||
if (err != paNoError) return 1;
|
||||
|
||||
outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
|
||||
if (outputParameters.device == paNoDevice) {
|
||||
fprintf(stderr, "Error: No default output device.\n");
|
||||
return 1;
|
||||
}
|
||||
outputParameters.channelCount = CHANNELS;
|
||||
outputParameters.sampleFormat = paInt16;
|
||||
outputParameters.suggestedLatency = 0.04;
|
||||
outputParameters.hostApiSpecificStreamInfo = NULL;
|
||||
|
||||
err = Pa_OpenStream(
|
||||
&stream,
|
||||
NULL, /* no input */
|
||||
&outputParameters,
|
||||
sample_rate,
|
||||
FRAMES_PER_BUFFER,
|
||||
0, /* we won't output out of range samples so don't bother clipping them */
|
||||
NULL, /* no callback, use blocking API */
|
||||
NULL); /* no callback, so no callback userData */
|
||||
if (err != paNoError) return 1;
|
||||
|
||||
err = Pa_StartStream(stream);
|
||||
if (err != paNoError) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_write(void* data, int32_t count) {
|
||||
PaError err;
|
||||
err = Pa_WriteStream(stream, data, count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_done() {
|
||||
PaError err;
|
||||
err = Pa_StopStream(stream);
|
||||
if (err != paNoError) return 1;
|
||||
|
||||
// deinit portaudio
|
||||
err = Pa_CloseStream(stream);
|
||||
if (err != paNoError) return 1;
|
||||
|
||||
Pa_Terminate();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
bool render_to_wave = (argc >= 3);
|
||||
|
||||
uint32_t sample_rate;
|
||||
for (int chip = 0; chip < 2; chip++) {
|
||||
opnachip[chip] = new ymfm::ym2203(opna_interface[chip]);
|
||||
if (opnachip[chip] == nullptr) {
|
||||
printf("error: unable to init ymfm!\n");
|
||||
return 1;
|
||||
}
|
||||
opnachip[chip]->reset();
|
||||
opnachip[chip]->set_fidelity(ymfm::OPN_FIDELITY_MIN);
|
||||
sample_rate = opnachip[chip]->sample_rate(OPN_CLOCK_RATE);
|
||||
vgmctx.rescaler = ((double)sample_rate / 44100.0);
|
||||
printf("sample rate - %d hz\n", sample_rate);
|
||||
|
||||
opna_regqueue[chip].reset();
|
||||
opna_regqueue[chip].set_chip(opnachip[chip]);
|
||||
}
|
||||
|
||||
if (!render_to_wave) {
|
||||
if (pa_init(sample_rate) != 0) {
|
||||
printf("error: unable to init PortAudio!\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
FILE* f = fopen(argv[1], "rb");
|
||||
if (f == NULL) {
|
||||
printf("error: unable to open file!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if 1
|
||||
opmctx.io.type = OPMPLAY_IO_FILE;
|
||||
opmctx.io.io = f;
|
||||
|
||||
int rtn;
|
||||
|
||||
if ((rtn = opmplay_init(&opmctx.opm)) != OPMPLAY_ERR_OK) {
|
||||
printf("unable to init OPMPlay (error = %d)\n", rtn);
|
||||
return 1;
|
||||
}
|
||||
if ((rtn = opmplay_load_header(&opmctx.opm, &opmctx.io)) != OPMPLAY_ERR_OK) {
|
||||
printf("unable to load OPM header (error = %d)\n", rtn);
|
||||
return 1;
|
||||
};
|
||||
if ((rtn = opmplay_load_module(&opmctx.opm, &opmctx.io)) != OPMPLAY_ERR_OK) {
|
||||
printf("unable to load OPM module (error = %d)\n", rtn);
|
||||
return 1;
|
||||
};
|
||||
opmctx.delay_count_reload = opmctx.delay_count = ((double)sample_rate / 50.0);
|
||||
#else
|
||||
// open VGM file, ready to parse
|
||||
std::ifstream infile(argv[1], std::ios::in | std::ios::binary);
|
||||
infile.unsetf(std::ios::skipws);
|
||||
|
||||
// get filesize
|
||||
infile.seekg(0, std::ios::end);
|
||||
uint64_t fsize = infile.tellg();
|
||||
infile.seekg(0, std::ios::beg);
|
||||
|
||||
// read whole file
|
||||
vgmctx.vgmfile.reserve(fsize);
|
||||
vgmctx.vgmfile.insert(vgmctx.vgmfile.begin(), std::istream_iterator<uint8_t>(infile), std::istream_iterator<uint8_t>());
|
||||
|
||||
// get header
|
||||
vgmctx.header = reinterpret_cast<VGMHeader*>(vgmctx.vgmfile.data());
|
||||
|
||||
// check header
|
||||
if (memcmp(vgmctx.header->id, "Vgm\x20", sizeof(vgmctx.header->id)) != 0) {
|
||||
printf("not a vaild VGM file!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// parse basic VGM structure
|
||||
printf("VGM %d.%d file found\n", (vgmctx.header->version >> 8) & 0xFF, vgmctx.header->version & 0xFF);
|
||||
if (vgmctx.header->loopOffset != 0) vgmctx.loop_pos = vgmctx.header->loopOffset + offsetof(VGMHeader, loopOffset);
|
||||
vgmctx.end = vgmctx.header->eofOffset + offsetof(VGMHeader, eofOffset);
|
||||
vgmctx.start = ((vgmctx.header->version < 0x150) ? 0x40 : vgmctx.header->dataOffset + offsetof(VGMHeader, dataOffset));
|
||||
vgmctx.vgmfile_it = vgmctx.vgmfile.begin() + vgmctx.start;
|
||||
vgmctx.delay_count = 0;
|
||||
#endif
|
||||
|
||||
console_open();
|
||||
std::vector<int16_t> wavedata;
|
||||
|
||||
int ff_pos = 0, ff_counter = 0;
|
||||
int16_t buf[FRAMES_PER_BUFFER * CHANNELS] = { 0 };
|
||||
while (1) {
|
||||
int rtn = synth_render(buf, FRAMES_PER_BUFFER);
|
||||
if (render_to_wave) {
|
||||
wavedata.insert(wavedata.end(), buf, buf + FRAMES_PER_BUFFER * CHANNELS);
|
||||
}
|
||||
else {
|
||||
pa_write(buf, FRAMES_PER_BUFFER);
|
||||
}
|
||||
ff_pos += FRAMES_PER_BUFFER;
|
||||
|
||||
// update console
|
||||
memset(console.buffer, 0, sizeof(CHAR_INFO) * console.bufsize.X * console.bufsize.Y);
|
||||
|
||||
tprintf(0, 0, "frame = %d", opmctx.opm.pos.frame);
|
||||
|
||||
{
|
||||
int yy = 2;
|
||||
for (int ch = 0; ch < 6; ch++) {
|
||||
int cc = ch / 3;
|
||||
int co = ch % 3;
|
||||
tprintf(0, yy, "FM%d: [%02X %02X %02X %02X %02X %02X %02X] [%02X %02X %02X %02X %02X %02X %02X] [%02X %02X %02X %02X %02X %02X %02X] [%02X %02X %02X %02X %02X %02X %02X] - %02X %02X %02X",
|
||||
ch,
|
||||
opn_reg_view[cc][0x30 + co],
|
||||
opn_reg_view[cc][0x40 + co],
|
||||
opn_reg_view[cc][0x50 + co],
|
||||
opn_reg_view[cc][0x60 + co],
|
||||
opn_reg_view[cc][0x70 + co],
|
||||
opn_reg_view[cc][0x80 + co],
|
||||
opn_reg_view[cc][0x90 + co],
|
||||
|
||||
opn_reg_view[cc][0x34 + co],
|
||||
opn_reg_view[cc][0x44 + co],
|
||||
opn_reg_view[cc][0x54 + co],
|
||||
opn_reg_view[cc][0x64 + co],
|
||||
opn_reg_view[cc][0x74 + co],
|
||||
opn_reg_view[cc][0x84 + co],
|
||||
opn_reg_view[cc][0x94 + co],
|
||||
|
||||
opn_reg_view[cc][0x38 + co],
|
||||
opn_reg_view[cc][0x48 + co],
|
||||
opn_reg_view[cc][0x58 + co],
|
||||
opn_reg_view[cc][0x68 + co],
|
||||
opn_reg_view[cc][0x78 + co],
|
||||
opn_reg_view[cc][0x88 + co],
|
||||
opn_reg_view[cc][0x98 + co],
|
||||
|
||||
opn_reg_view[cc][0x3C + co],
|
||||
opn_reg_view[cc][0x4C + co],
|
||||
opn_reg_view[cc][0x5C + co],
|
||||
opn_reg_view[cc][0x6C + co],
|
||||
opn_reg_view[cc][0x7C + co],
|
||||
opn_reg_view[cc][0x8C + co],
|
||||
opn_reg_view[cc][0x9C + co],
|
||||
|
||||
opn_reg_view[cc][0xA0 + co],
|
||||
opn_reg_view[cc][0xA4 + co],
|
||||
opn_reg_view[cc][0xB0 + co]
|
||||
);
|
||||
|
||||
yy++;
|
||||
|
||||
if ((ch % 3) == 2) {
|
||||
tprintf(0, yy, "AY%d: [%02X %02X] [%02X %02X] [%02X %02X] %02X %02X [%02X %02X %02X] [%02X %02X] <- %02X",
|
||||
cc,
|
||||
opn_reg_view[cc][0],
|
||||
opn_reg_view[cc][1],
|
||||
opn_reg_view[cc][2],
|
||||
opn_reg_view[cc][3],
|
||||
opn_reg_view[cc][4],
|
||||
opn_reg_view[cc][5],
|
||||
opn_reg_view[cc][6],
|
||||
opn_reg_view[cc][7],
|
||||
opn_reg_view[cc][8],
|
||||
opn_reg_view[cc][9],
|
||||
opn_reg_view[cc][10],
|
||||
opn_reg_view[cc][11],
|
||||
opn_reg_view[cc][12],
|
||||
opn_reg_view[cc][13]
|
||||
);
|
||||
yy++;
|
||||
tprintf(0, yy, "EC%d: [%02X %02X %02X %02X %02X %02X %02X %02X]",
|
||||
cc,
|
||||
opn_reg_view[cc][0xAD],
|
||||
opn_reg_view[cc][0xA9],
|
||||
opn_reg_view[cc][0xAC],
|
||||
opn_reg_view[cc][0xA8],
|
||||
opn_reg_view[cc][0xAE],
|
||||
opn_reg_view[cc][0xAA],
|
||||
opn_reg_view[cc][0xA6],
|
||||
opn_reg_view[cc][0xA2]
|
||||
);
|
||||
yy++;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console_update();
|
||||
|
||||
if (_kbhit()) {
|
||||
_getch();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// write wave file
|
||||
if (render_to_wave) {
|
||||
// create headers
|
||||
RIFF_Header riffHeader;
|
||||
memcpy(&riffHeader.id, "RIFF", sizeof(riffHeader.id));
|
||||
memcpy(&riffHeader.fourcc, "WAVE", sizeof(riffHeader.fourcc));
|
||||
riffHeader.size = sizeof(riffHeader.fourcc) + sizeof(fmt_Header) + sizeof(chunk_Header) + (wavedata.size() * sizeof(decltype(wavedata)::value_type));
|
||||
|
||||
fmt_Header fmtHeader;
|
||||
memcpy(&fmtHeader.id, "fmt ", sizeof(fmtHeader.id));
|
||||
fmtHeader.size = sizeof(fmtHeader) - 8;
|
||||
fmtHeader.wFormatTag = 1; // plain uncompressed PCM
|
||||
fmtHeader.nSamplesPerSec = sample_rate;
|
||||
fmtHeader.nBlockAlign = CHANNELS;
|
||||
fmtHeader.nAvgBytesPerSec = sample_rate * CHANNELS;
|
||||
fmtHeader.nChannels = CHANNELS;
|
||||
fmtHeader.wBitsPerSample = 8;
|
||||
|
||||
chunk_Header dataHeader;
|
||||
memcpy(&dataHeader.id, "data", sizeof(dataHeader.id));
|
||||
dataHeader.size = (wavedata.size() * sizeof(decltype(wavedata)::value_type));
|
||||
|
||||
// write wave file
|
||||
FILE* outfile = fopen("out.wav", "wb");
|
||||
|
||||
fwrite(&riffHeader, sizeof(riffHeader), 1, outfile);
|
||||
fwrite(&fmtHeader, sizeof(fmtHeader), 1, outfile);
|
||||
fwrite(&dataHeader, sizeof(dataHeader), 1, outfile);
|
||||
fwrite(wavedata.data(), (wavedata.size() * sizeof(decltype(wavedata)::value_type)), 1, outfile);
|
||||
|
||||
fclose(outfile);
|
||||
} else
|
||||
pa_done();
|
||||
|
||||
console_done();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
enum {
|
||||
OPM_FORMAT_VERSION = 0x0010
|
||||
};
|
||||
|
||||
struct opm_header_stream_desc_t {
|
||||
//uint16_t ptr; // offset to data stream in paragraphs (bytes*16)
|
||||
uint16_t size; // stream data size in bytes (max. 65520 bytes)
|
||||
};
|
||||
|
||||
enum {
|
||||
OPM_CHIPTYPE_OPL = (0 << 0),
|
||||
OPM_CHIPTYPE_OPN = (1 << 0),
|
||||
};
|
||||
|
||||
enum {
|
||||
OPM_FLAG_CHIP_OPN = (0 << 0),
|
||||
OPM_FLAG_CHIP_OPN_DUAL = (1 << 0),
|
||||
OPM_FLAG_CHIP_OPNA = (2 << 0),
|
||||
OPM_FLAG_CHIP_OPN3 = (3 << 0),
|
||||
|
||||
OPM_FLAG_CHIP_TYPE = (3 << 0),
|
||||
};
|
||||
|
||||
struct opm_header_t {
|
||||
char magic[4]; // "OPM\x1A"
|
||||
union {
|
||||
struct {
|
||||
uint8_t minor;
|
||||
uint8_t major;
|
||||
};
|
||||
uint16_t v;
|
||||
} version;
|
||||
uint8_t chip_type; // see above
|
||||
uint8_t reserved0; //
|
||||
uint32_t clock_rate; // hz, integer
|
||||
uint16_t frame_rate; // hz, 8.8 fixedpoint
|
||||
uint16_t flags; // see above
|
||||
uint8_t callstack_depth;
|
||||
uint8_t streams; // including control streams
|
||||
uint32_t stream_mask; // used channel mask, LSB = ch 0
|
||||
|
||||
// opm_header_stream_desc_t stream[opm_header_t::channels]; // first is control stream
|
||||
};
|
||||
|
||||
// OPM v0 stream data, stream-independent commands
|
||||
enum {
|
||||
OPM_STREAM_END_FRAME = 0xFF, // end of frame, next channel
|
||||
OPM_STREAM_END = 0xFE, // end of stream, stop here or loop to OPM_STREAM_LOOP stream point
|
||||
OPM_STREAM_NOP = 0xFD,
|
||||
OPM_STREAM_NEW_ORDER = 0xFC, // nop, marks new order
|
||||
OPM_STREAM_SET_FRAME_RATE = 0xFB, // word rate (as in opm_header_t::frame_rate)
|
||||
OPM_STREAM_LOOP = 0xFA, // set loop point here
|
||||
|
||||
// delay commands
|
||||
OPM_STREAM_DELAY_INT32 = 0xF9, // dword delay
|
||||
OPM_STREAM_DELAY_INT16 = 0xF8, // word delay
|
||||
OPM_STREAM_DELAY_INT12 = 0xD0, // D0..DF - 0..4095 frames delay (hibyte in low 4 bits of command)
|
||||
OPM_STERAM_DELAY_SHORT = 0xC0, // C0..CF - 1..16 frames delay
|
||||
|
||||
// back reference
|
||||
OPM_STREAM_BACKREF = 0xE0, // E0..EF - word backrefpos (12 bit), byte frames
|
||||
};
|
||||
|
||||
// OPN control stream commands
|
||||
enum {
|
||||
OPM_CTRL_EXTCH3 = 0x00, // 00..7F - ext. CH3 op1-3 frequency
|
||||
OPM_CTRL_TIMER_CSM = 0x80, // 80..9F - set timer/CSM/LFO frequency
|
||||
OPM_CTRL_RHYTHM = 0xA0, // A0..BF - rhythm control
|
||||
|
||||
OPM_CTRL_CMD80_REG25 = (1 << 0),
|
||||
OPM_CTRL_CMD80_REG24 = (1 << 1),
|
||||
OPM_CTRL_CMD80_REG27 = (1 << 2),
|
||||
OPM_CTRL_CMD80_REG22 = (1 << 3),
|
||||
OPM_CTRL_CMD80_EOF = (1 << 4),
|
||||
|
||||
OPM_CTRL_EXTCH3_OP1_HIGH = (1 << 0),
|
||||
OPM_CTRL_EXTCH3_OP1_LOW = (1 << 1),
|
||||
OPM_CTRL_EXTCH3_OP2_HIGH = (1 << 2),
|
||||
OPM_CTRL_EXTCH3_OP2_LOW = (1 << 3),
|
||||
OPM_CTRL_EXTCH3_OP3_HIGH = (1 << 4),
|
||||
OPM_CTRL_EXTCH3_OP3_LOW = (1 << 5),
|
||||
OPM_CTRL_EXTCH3_EOF = (1 << 6),
|
||||
|
||||
OPM_CTRL_CMDA0_REG_MASK = (0x0F << 0),
|
||||
OPM_CTRL_CMDA0_EOF = (1 << 4),
|
||||
};
|
||||
|
||||
// OPN FM stream commands
|
||||
enum {
|
||||
OPM_FM_ADSR = 0x00, // 00..3F - set ADSR
|
||||
OPM_FM_MUL_TL_EG = 0x40, // 40..7F - set MULT/TL/SSG-EG
|
||||
OPM_FM_FREQ_FB_PAN = 0x80, // 80..9F - set frequency/feedback/panning
|
||||
OPM_FM_KEY = 0xA0, // A0..BF - key on/off
|
||||
|
||||
OPM_FM_CMD00_REG50 = (1 << 0),
|
||||
OPM_FM_CMD00_REG60 = (1 << 1),
|
||||
OPM_FM_CMD00_REG70 = (1 << 2),
|
||||
OPM_FM_CMD00_REG80 = (1 << 3),
|
||||
OPM_FM_CMD00_OP_SHIFT = 4,
|
||||
OPM_FM_CMD00_OP_MASK = (3 << OPM_FM_CMD00_OP_SHIFT),
|
||||
|
||||
OPM_FM_CMD40_REG30 = (1 << 0),
|
||||
OPM_FM_CMD40_REG40 = (1 << 1),
|
||||
OPM_FM_CMD40_REG90 = (1 << 2),
|
||||
OPM_FM_CMD40_EOF = (1 << 3),
|
||||
OPM_FM_CMD40_OP_SHIFT = OPM_FM_CMD00_OP_SHIFT,
|
||||
OPM_FM_CMD40_OP_MASK = OPM_FM_CMD00_OP_MASK,
|
||||
|
||||
OPM_FM_CMD80_REGA4 = (1 << 0),
|
||||
OPM_FM_CMD80_REGA0 = (1 << 1),
|
||||
OPM_FM_CMD80_REGB0 = (1 << 2),
|
||||
OPM_FM_CMD80_REGB4 = (1 << 3),
|
||||
OPM_FM_CMD80_EOF = (1 << 4),
|
||||
|
||||
OPM_FM_CMDA0_OP_SHIFT = 0,
|
||||
OPM_FM_CMDA0_OP_MASK = (0x0F << OPM_FM_CMDA0_OP_SHIFT),
|
||||
OPM_FM_CMDA0_EOF = (1 << 4),
|
||||
};
|
||||
|
||||
// OPNA rhythm channel stream
|
||||
enum {
|
||||
OPN_RHYTHM_KEY = 0x00, // 00..7F - key on
|
||||
OPN_RHYTHM_REGS1 = 0x80, // 80..9F - write reg set 1
|
||||
OPN_RHYTHM_REGS2 = 0xA0, // A0..BF - write reg set 2
|
||||
|
||||
OPN_RHYTHM_KEY_EOF = (1 << 6),
|
||||
OPN_RHYTHM_CMD80_REG10 = (1 << 0),
|
||||
OPN_RHYTHM_CMD80_REG11 = (1 << 1),
|
||||
OPN_RHYTHM_CMD80_REG18 = (1 << 2),
|
||||
OPN_RHYTHM_CMD80_REG19 = (1 << 3),
|
||||
OPN_RHYTHM_CMDA0_REG1A = (1 << 0),
|
||||
OPN_RHYTHM_CMDA0_REG1B = (1 << 1),
|
||||
OPN_RHYTHM_CMDA0_REG1C = (1 << 2),
|
||||
OPN_RHYTHM_CMDA0_REG1D = (1 << 3),
|
||||
OPN_RHYTHM_REGS_EOF = (1 << 4),
|
||||
};
|
||||
|
||||
// OPN SSG tone stream commands (shared with AY chip type)
|
||||
enum {
|
||||
OPM_AYTONE_REGS = 0x00, // 00..7F - set volume and period low
|
||||
OPM_AYTONE_PERIOD = 0x80, // 80..BF - set period
|
||||
OPM_AYTONE_MASK = 0xF0, // F0..F7 - set tone/noise mask
|
||||
|
||||
OPM_AYTONE_CMD00_VOLUME_MASK = (0x1F << 0),
|
||||
OPM_AYTONE_CMD00_PERIOD_LOW = (1 << 5),
|
||||
OPM_AYTONE_CMD00_EOF = (1 << 6),
|
||||
|
||||
OPM_AYTONE_CMD80_PERIOD_HIGH = (0xF << 0),
|
||||
OPM_AYTONE_CMD80_PERIOD_LOW = (1 << 4),
|
||||
OPM_AYTONE_CMD80_EOF = (1 << 5),
|
||||
|
||||
OPM_AYTONE_MASK_TONE = (1 << 0),
|
||||
OPM_AYTONE_MASK_NOISE = (1 << 1),
|
||||
OPM_AYTONE_MASK_EOF = (1 << 2),
|
||||
};
|
||||
|
||||
// OPN SSG envelope/noise stream commands (shared with AY chip type)
|
||||
enum {
|
||||
OPM_AYENV_REGS = 0x00, // 00..7F - set noise and period low
|
||||
OPM_AYENV_ENVTYPE = 0x80, // 80..BF - set env type and period low
|
||||
OPM_AYENV_PERIOD_FULL = 0xF0, // F0..F7 - set full envelope period
|
||||
|
||||
OPM_AYENV_CMD00_NOISE_MASK = (0x1F << 0),
|
||||
OPM_AYENV_CMD00_PERIOD_LOW = (1 << 5),
|
||||
OPM_AYENV_CMD00_EOF = (1 << 6),
|
||||
|
||||
OPM_AYENV_CMD80_ENV_TYPE = (0xF << 0),
|
||||
OPM_AYENV_CMD80_PERIOD_LOW = (1 << 4),
|
||||
OPM_AYENV_CMD80_EOF = (1 << 5),
|
||||
|
||||
OPM_AYENV_CMDF0_PERIOD_LOW = (1 << 0),
|
||||
OPM_AYENV_CMDF0_PERIOD_HIGH = (1 << 1),
|
||||
OPM_AYENV_CMDF0_EOF = (1 << 2),
|
||||
};
|
||||
|
||||
|
||||
#pragma pack(pop)
|
|
@ -1,500 +0,0 @@
|
|||
#include <stdint.h>
|
||||
#include "opmplay.h"
|
||||
#include "opmfile.h"
|
||||
|
||||
// very hacky extern lmao
|
||||
void opn_write_reg(int chip, int reg, int data);
|
||||
|
||||
// -------------------------
|
||||
// file I/O procedures
|
||||
static uint32_t opmplay_mem_read(opmplay_io_t* io, void* dst, uint32_t size) {
|
||||
if ((io == NULL) || dst == NULL) return OPMPLAY_ERR_NULLPTR;
|
||||
if ((size + io->offset) > io->size) return OPMPLAY_ERR_IO;
|
||||
opmplay_memcpy(dst, (uint8_t*)io->buf + io->offset, io->size);
|
||||
io->offset += io->size;
|
||||
return size;
|
||||
}
|
||||
static uint32_t opmplay_mem_seek(opmplay_io_t* io, uint32_t pos) {
|
||||
if (io == NULL) return OPMPLAY_ERR_NULLPTR;
|
||||
if (pos > io->size) return OPMPLAY_ERR_IO;
|
||||
io->offset = pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef OPMPLAY_ENABLE_STDIO
|
||||
static uint32_t opmplay_file_read(opmplay_io_t* io, void* dst, uint32_t size) {
|
||||
return fread(dst, 1, size, io->io);
|
||||
}
|
||||
static uint32_t opmplay_file_seek(opmplay_io_t* io, uint32_t pos) {
|
||||
return fseek(io->io, pos, SEEK_SET);
|
||||
}
|
||||
#endif
|
||||
|
||||
// -------------------------
|
||||
|
||||
int opmplay_init(opmplay_context_t* ctx) {
|
||||
if ((ctx == NULL)) return OPMPLAY_ERR_NULLPTR;
|
||||
|
||||
opmplay_memset(ctx, 0, sizeof(opmplay_context_t));
|
||||
|
||||
return OPMPLAY_ERR_OK;
|
||||
}
|
||||
|
||||
int opmplay_free(opmplay_context_t* ctx)
|
||||
{
|
||||
for (int ch = 0; ch < ctx->header.streams; ch++) {
|
||||
if (ctx->channels[ch].stream.data != NULL) {
|
||||
opmplay_memfree((uint8_t*)ctx->channels[ch].stream.data); ctx->channels[ch].stream.data = NULL;
|
||||
}
|
||||
}
|
||||
return OPMPLAY_ERR_OK;
|
||||
}
|
||||
|
||||
// load file header
|
||||
int opmplay_load_header(opmplay_context_t* ctx, opmplay_io_t* io) {
|
||||
if ((ctx == NULL) || (io == NULL)) return OPMPLAY_ERR_NULLPTR;
|
||||
|
||||
// init file I/O handlers
|
||||
switch (io->type) {
|
||||
#ifdef OPMPLAY_ENABLE_STDIO
|
||||
case OPMPLAY_IO_FILE:
|
||||
io->read = &opmplay_file_read;
|
||||
io->seek = &opmplay_file_seek;
|
||||
break;
|
||||
#endif
|
||||
case OPMPLAY_IO_MEMORY:
|
||||
io->read = &opmplay_mem_read;
|
||||
io->seek = &opmplay_mem_seek;
|
||||
break;
|
||||
case OPMPLAY_IO_USER:
|
||||
// use user-provided I/O functions
|
||||
if ((io->read == NULL) || (io->seek == NULL)) return OPMPLAY_ERR_BAD_PARAMETER;
|
||||
break;
|
||||
default:
|
||||
return OPMPLAY_ERR_BAD_PARAMETER;
|
||||
}
|
||||
|
||||
// read header
|
||||
if (io->read(io, &ctx->header, sizeof(ctx->header)) != sizeof(ctx->header)) return OPMPLAY_ERR_IO;
|
||||
|
||||
// and validate it
|
||||
if ((opmplay_memcmp(ctx->header.magic, "OPM\x1A", sizeof(ctx->header.magic))) || (ctx->header.version.v != OPM_FORMAT_VERSION))
|
||||
return OPMPLAY_ERR_BAD_FILE_STRUCTURE;
|
||||
|
||||
// done for now, waiting for opmplay_load_module :)
|
||||
return OPMPLAY_ERR_OK;
|
||||
}
|
||||
|
||||
int opmplay_load_module(opmplay_context_t* ctx, opmplay_io_t* io) {
|
||||
if ((ctx == NULL) || (io == NULL)) return OPMPLAY_ERR_NULLPTR;
|
||||
uint32_t filepos = sizeof(opm_header_t);
|
||||
|
||||
// allocate and copy stream data
|
||||
opm_header_stream_desc_t* streamdesc = (opm_header_stream_desc_t*)opmplay_alloc(sizeof(opm_header_stream_desc_t) * (ctx->header.streams));
|
||||
if (streamdesc == NULL) return OPMPLAY_ERR_NULLPTR;
|
||||
if (io->seek(io, filepos)) return OPMPLAY_ERR_IO;
|
||||
if (io->read(io, streamdesc, sizeof(opm_header_stream_desc_t) * (ctx->header.streams))
|
||||
!= sizeof(opm_header_stream_desc_t) * (ctx->header.streams))
|
||||
return OPMPLAY_ERR_IO;
|
||||
|
||||
// allocate and copy channel streams
|
||||
for (int ch = 0; ch < ctx->header.streams; ch++) {
|
||||
if (streamdesc[ch].size > 0) {
|
||||
ctx->channels[ch].stream.data = (const uint8_t*)opmplay_alloc(sizeof(uint8_t*) * (streamdesc[ch].size));
|
||||
if (ctx->channels[ch].stream.data == NULL) return OPMPLAY_ERR_MEMALLOC;
|
||||
if (io->read(io, (uint8_t*)ctx->channels[ch].stream.data, streamdesc[ch].size) != streamdesc[ch].size) return OPMPLAY_ERR_IO;
|
||||
ctx->channels[ch].stream.delay = 1;
|
||||
}
|
||||
else ctx->channels[ch].stream.data = NULL;
|
||||
}
|
||||
|
||||
// rewind to start
|
||||
opmplay_rewind(ctx);
|
||||
|
||||
// done :)
|
||||
return OPMPLAY_ERR_OK;
|
||||
}
|
||||
|
||||
void opmplay_pop_stack(opmplay_channel_context_t* chctx) {
|
||||
opmplay_channel_stack_t* st = chctx->stack + (--chctx->stack_pos);
|
||||
chctx->stream.ptr = st->ptr;
|
||||
chctx->stream.samples_to_play = st->frames_to_play;
|
||||
}
|
||||
|
||||
void opmplay_push_stack(opmplay_channel_context_t* chctx) {
|
||||
opmplay_channel_stack_t* st = chctx->stack + chctx->stack_pos;
|
||||
st->ptr = chctx->stream.ptr;
|
||||
st->frames_to_play = chctx->stream.samples_to_play;
|
||||
chctx->stack_pos++;
|
||||
}
|
||||
|
||||
int opmplay_loop(opmplay_context_t* ctx) {
|
||||
// channel streams
|
||||
for (int ch = 0; ch < ctx->header.streams; ch++) {
|
||||
// init stack
|
||||
ctx->channels[ch].stack_pos = 0;
|
||||
ctx->channels[ch].stream.samples_to_play = -1;
|
||||
ctx->channels[ch].stream.ptr = ctx->channels[ch].stream.loop;
|
||||
ctx->channels[ch].stream.delay = ctx->channels[ch].stream.reload = 1;
|
||||
}
|
||||
|
||||
return OPMPLAY_ERR_OK;
|
||||
}
|
||||
|
||||
int opmplay_rewind(opmplay_context_t* ctx) {
|
||||
// set chip mode
|
||||
switch (ctx->header.flags & OPM_FLAG_CHIP_TYPE) {
|
||||
case OPM_FLAG_CHIP_OPN_DUAL:
|
||||
for (int chip = 0; chip < 2; chip++) {
|
||||
for (int r = 0; r < 0xF0; r++) {
|
||||
//opn_write_reg(chip, r, 0);
|
||||
}
|
||||
// setup prescaler
|
||||
opn_write_reg(chip, 0x2F, 0);
|
||||
opn_write_reg(chip, 0x2D, 0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return OPMPLAY_ERR_DEVICE;
|
||||
}
|
||||
|
||||
for (int ch = 0; ch < ctx->header.streams; ch++) {
|
||||
ctx->channels[ch].stream.loop = ctx->channels[ch].stream.data;
|
||||
}
|
||||
|
||||
opmplay_loop(ctx);
|
||||
|
||||
return OPMPLAY_ERR_OK;
|
||||
}
|
||||
|
||||
// get and parse delay
|
||||
static uint32_t opmplay_set_delay(const uint8_t** data) {
|
||||
uint32_t delay = 0;
|
||||
if (**data == OPM_STREAM_DELAY_INT32) {
|
||||
delay = (
|
||||
(*(*data + 1) << 0) |
|
||||
(*(*data + 2) << 8) |
|
||||
(*(*data + 3) << 16) |
|
||||
(*(*data + 4) << 24)
|
||||
);
|
||||
*data += 5;
|
||||
}
|
||||
else if (**data == OPM_STREAM_DELAY_INT16) {
|
||||
delay = (
|
||||
(*(*data + 1) << 0) |
|
||||
(*(*data + 2) << 8)
|
||||
);
|
||||
*data += 3;
|
||||
}
|
||||
else if ((**data & 0xF0) == OPM_STREAM_DELAY_INT12) {
|
||||
delay = ((**data & 0x0F) << 8) | (*(*data + 1));
|
||||
*data += 2;
|
||||
}
|
||||
else if ((**data & 0xF0) == OPM_STERAM_DELAY_SHORT) {
|
||||
delay = (**data & 0xF) + 1;
|
||||
(*data)++;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// stream parsing procedures
|
||||
// bruuuuuuuhhhhhhhhhhhh
|
||||
extern "C" {
|
||||
int chip_index;
|
||||
bool endOfFrame;
|
||||
bool doNextOp;
|
||||
}
|
||||
|
||||
// AY channel stream
|
||||
const uint8_t* opmplay_parse_ay_channel_stream(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* data) {
|
||||
if ((*(data) & 0x80) == OPM_AYTONE_REGS) {
|
||||
// volume and period low
|
||||
int mask = *data;
|
||||
data++;
|
||||
opn_write_reg(chip_index, 8 + ch, (mask & OPM_AYTONE_CMD00_VOLUME_MASK));
|
||||
if (mask & OPM_AYTONE_CMD00_PERIOD_LOW) opn_write_reg(chip_index, 0 + (ch<<1), *data++);
|
||||
if (mask & OPM_AYTONE_CMD00_EOF) endOfFrame = true;
|
||||
doNextOp = true;
|
||||
goto done;
|
||||
}
|
||||
if ((*(data) & 0xC0) == OPM_AYTONE_PERIOD) {
|
||||
// period high/low
|
||||
int mask = *data;
|
||||
data++;
|
||||
opn_write_reg(chip_index, 1 + (ch<<1), (mask & OPM_AYTONE_CMD80_PERIOD_HIGH));
|
||||
if (mask & OPM_AYTONE_CMD80_PERIOD_LOW) opn_write_reg(chip_index, 0 + (ch << 1), *data++);
|
||||
if (mask & OPM_AYTONE_CMD80_EOF) endOfFrame = true;
|
||||
doNextOp = true;
|
||||
goto done;
|
||||
}
|
||||
if ((*(data) & 0xF8) == OPM_AYTONE_MASK) {
|
||||
const static uint8_t mask_lookup[4] = {(0<<3)|(0<<0), (0<<3)|(1<<0), (1<<3)|(0<<0), (1<<3)|(1<<0)};
|
||||
|
||||
// mask
|
||||
int mask = *data;
|
||||
data++;
|
||||
ctx->ssg_r7[chip_index] &= ~(((1 << 3) | (1 << 0)) << ch);
|
||||
ctx->ssg_r7[chip_index] |= (mask_lookup[mask & 3] << ch);
|
||||
if (mask & OPM_AYTONE_MASK_EOF) endOfFrame = true;
|
||||
doNextOp = true;
|
||||
goto done;
|
||||
}
|
||||
done:
|
||||
return data;
|
||||
}
|
||||
|
||||
// AY noise/envelope stream
|
||||
const uint8_t* opmplay_parse_ay_envnoise_stream(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* data) {
|
||||
if ((*(data) & 0x80) == OPM_AYENV_REGS) {
|
||||
// noise and period low
|
||||
int mask = *data;
|
||||
data++;
|
||||
opn_write_reg(chip_index, 6, (mask & OPM_AYENV_CMD00_NOISE_MASK));
|
||||
if (mask & OPM_AYENV_CMD00_PERIOD_LOW) opn_write_reg(chip_index, 11, *data++);
|
||||
if (mask & OPM_AYENV_CMD00_EOF) endOfFrame = true;
|
||||
doNextOp = true;
|
||||
goto done;
|
||||
}
|
||||
if ((*(data) & 0xC0) == OPM_AYENV_ENVTYPE) {
|
||||
// envelope type (and retrig)
|
||||
int mask = *data;
|
||||
data++;
|
||||
opn_write_reg(chip_index, 13, (mask & OPM_AYENV_CMD80_ENV_TYPE));
|
||||
if (mask & OPM_AYENV_CMD80_PERIOD_LOW) opn_write_reg(chip_index, 11, *data++);
|
||||
if (mask & OPM_AYENV_CMD80_EOF) endOfFrame = true;
|
||||
doNextOp = true;
|
||||
goto done;
|
||||
}
|
||||
if ((*(data) & 0xF8) == OPM_AYENV_PERIOD_FULL) {
|
||||
// mask
|
||||
int mask = *data;
|
||||
data++;
|
||||
if (mask & OPM_AYENV_CMDF0_PERIOD_LOW) opn_write_reg(chip_index, 11, *data++);
|
||||
if (mask & OPM_AYENV_CMDF0_PERIOD_HIGH) opn_write_reg(chip_index, 12, *data++);
|
||||
if (mask & OPM_AYENV_CMDF0_EOF) endOfFrame = true;
|
||||
doNextOp = true;
|
||||
goto done;
|
||||
}
|
||||
done:
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
// FM control stream
|
||||
const uint8_t* opmplay_parse_fm_control_stream(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* data) {
|
||||
if ((*(data) & 0xE0) == OPM_CTRL_TIMER_CSM) {
|
||||
// CSM/timer stuff
|
||||
int mask = *data;
|
||||
data++;
|
||||
if (mask & OPM_CTRL_CMD80_REG25) opn_write_reg(chip_index, 0x25, *data++);
|
||||
if (mask & OPM_CTRL_CMD80_REG24) opn_write_reg(chip_index, 0x24, *data++);
|
||||
if (mask & OPM_CTRL_CMD80_REG27) opn_write_reg(chip_index, 0x27, *data++);
|
||||
if (mask & OPM_CTRL_CMD80_REG22) opn_write_reg(chip_index, 0x22, *data++);
|
||||
if (mask & OPM_CTRL_CMD80_EOF) endOfFrame = true;
|
||||
doNextOp = true;
|
||||
goto done;
|
||||
}
|
||||
if ((*(data) & 0x80) == OPM_CTRL_EXTCH3) {
|
||||
// channel 3 ext mode
|
||||
// OP4 freq is handled by FM channel 3 stream
|
||||
int mask = *data;
|
||||
data++;
|
||||
if (mask & OPM_CTRL_EXTCH3_OP1_HIGH) ctx->extch3_block[chip_index][0] = *data++;
|
||||
if (mask & OPM_CTRL_EXTCH3_OP1_LOW) {
|
||||
opn_write_reg(chip_index, 0xAD, ctx->extch3_block[chip_index][0]);
|
||||
opn_write_reg(chip_index, 0xA9, *data++);
|
||||
}
|
||||
if (mask & OPM_CTRL_EXTCH3_OP2_HIGH) ctx->extch3_block[chip_index][1] = *data++;
|
||||
if (mask & OPM_CTRL_EXTCH3_OP2_LOW) {
|
||||
opn_write_reg(chip_index, 0xAC, ctx->extch3_block[chip_index][1]);
|
||||
opn_write_reg(chip_index, 0xA8, *data++);
|
||||
}
|
||||
if (mask & OPM_CTRL_EXTCH3_OP3_HIGH) ctx->extch3_block[chip_index][2] = *data++;
|
||||
if (mask & OPM_CTRL_EXTCH3_OP3_LOW) {
|
||||
opn_write_reg(chip_index, 0xAE, ctx->extch3_block[chip_index][2]);
|
||||
opn_write_reg(chip_index, 0xAA, *data++);
|
||||
}
|
||||
if (mask & OPM_CTRL_EXTCH3_EOF) endOfFrame = true;
|
||||
doNextOp = true;
|
||||
goto done;
|
||||
}
|
||||
done:
|
||||
return data;
|
||||
}
|
||||
|
||||
// FM channel stream
|
||||
const uint8_t* opmplay_parse_fm_channel_stream(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* data) {
|
||||
if ((*(data) & 0xC0) == OPM_FM_ADSR) {
|
||||
// ADSR
|
||||
int mask = *data;
|
||||
int regbase = (ch & 3) + ((mask & OPM_FM_CMD00_OP_MASK) >> 2);
|
||||
data++;
|
||||
if (mask & OPM_FM_CMD00_REG50) opn_write_reg(chip_index, 0x50 + regbase, *data++);
|
||||
if (mask & OPM_FM_CMD00_REG60) opn_write_reg(chip_index, 0x60 + regbase, *data++);
|
||||
if (mask & OPM_FM_CMD00_REG70) opn_write_reg(chip_index, 0x70 + regbase, *data++);
|
||||
if (mask & OPM_FM_CMD00_REG80) opn_write_reg(chip_index, 0x80 + regbase, *data++);
|
||||
doNextOp = true;
|
||||
goto done;
|
||||
}
|
||||
if ((*(data) & 0xC0) == OPM_FM_MUL_TL_EG) {
|
||||
// MULT/TL/SSG-EG
|
||||
int mask = *data;
|
||||
int regbase = (ch & 3) + ((mask & OPM_FM_CMD40_OP_MASK) >> 2);
|
||||
data++;
|
||||
if (mask & OPM_FM_CMD40_REG30) opn_write_reg(chip_index, 0x30 + regbase, *data++);
|
||||
if (mask & OPM_FM_CMD40_REG40) opn_write_reg(chip_index, 0x40 + regbase, *data++);
|
||||
if (mask & OPM_FM_CMD40_REG90) opn_write_reg(chip_index, 0x90 + regbase, *data++);
|
||||
if (mask & OPM_FM_CMD40_EOF) endOfFrame = true;
|
||||
doNextOp = true;
|
||||
goto done;
|
||||
}
|
||||
if ((*(data) & 0xE0) == OPM_FM_FREQ_FB_PAN) {
|
||||
// frequency/feedpack
|
||||
int mask = *data;
|
||||
data++;
|
||||
if (mask & OPM_FM_CMD80_REGA4) chctx->block = *data++;
|
||||
if (mask & OPM_FM_CMD80_REGA0) {
|
||||
opn_write_reg(chip_index, 0xA4 + ch, chctx->block);
|
||||
opn_write_reg(chip_index, 0xA0 + ch, *data++);
|
||||
}
|
||||
if (mask & OPM_FM_CMD80_REGB0) opn_write_reg(chip_index, 0xB0 + ch, *data++);
|
||||
if (mask & OPM_FM_CMD80_REGB4) opn_write_reg(chip_index, 0xB4 + ch, *data++);
|
||||
if (mask & OPM_FM_CMD80_EOF) endOfFrame = true;
|
||||
doNextOp = true;
|
||||
goto done;
|
||||
}
|
||||
if ((*(data) & 0xE0) == OPM_FM_KEY) {
|
||||
// frequency/feedpack
|
||||
int mask = *data; data++;
|
||||
opn_write_reg(chip_index, 0x28, ((mask & OPM_FM_CMDA0_OP_MASK) << 4) + ch);
|
||||
if (mask & OPM_FM_CMDA0_EOF) endOfFrame = true;
|
||||
doNextOp = true;
|
||||
goto done;
|
||||
}
|
||||
done:
|
||||
return data;
|
||||
}
|
||||
|
||||
// parse one individual stream
|
||||
const int opmplay_parse_stream(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* (*proc)(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* data)) {
|
||||
uint16_t newdelay;
|
||||
const uint8_t* data = chctx->stream.ptr;
|
||||
endOfFrame = false;
|
||||
|
||||
if (--chctx->stream.delay == 0) {
|
||||
do {
|
||||
|
||||
doNextOp = false;
|
||||
data = proc(ctx, chctx, ch, data);
|
||||
|
||||
if (doNextOp) {
|
||||
doNextOp = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((*data & 0xF0) == OPM_STREAM_BACKREF) {
|
||||
// back reference, nested call :)
|
||||
int distance = ((*(data + 0) & 0x0F) << 8) | (*(data + 1));
|
||||
int frames_to_play = *(data + 2);
|
||||
chctx->stream.ptr = data + 3;
|
||||
opmplay_push_stack(chctx);
|
||||
data -= distance;
|
||||
chctx->stream.samples_to_play = frames_to_play; // hack?
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for common stuff
|
||||
switch (*data) {
|
||||
// just an NOP, break
|
||||
case OPM_STREAM_NEW_ORDER:
|
||||
case OPM_STREAM_NOP:
|
||||
data++;
|
||||
break;
|
||||
case OPM_STREAM_LOOP:
|
||||
// save loop point
|
||||
ctx->pos.frame_looped = ctx->pos.frame;
|
||||
chctx->stream.loop = data;
|
||||
data++;
|
||||
break;
|
||||
case OPM_STREAM_END:
|
||||
// rewind to start
|
||||
chctx->stream.reload = -1;
|
||||
endOfFrame = true;
|
||||
break;
|
||||
// set new frame rate
|
||||
case OPM_STREAM_SET_FRAME_RATE:
|
||||
ctx->header.frame_rate = *(uint16_t*)(data + 1); data += 3;
|
||||
break;
|
||||
case OPM_STREAM_END_FRAME:
|
||||
// end of frame - special case here
|
||||
data++;
|
||||
endOfFrame = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
// test for delay
|
||||
newdelay = opmplay_set_delay(&data);
|
||||
if (newdelay > 0) {
|
||||
chctx->stream.reload = newdelay;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("unknown token %02x!\n", *data);
|
||||
return OPMPLAY_ERR_BAD_FILE_STRUCTURE;
|
||||
}
|
||||
}
|
||||
} while (!endOfFrame);
|
||||
chctx->stream.delay = chctx->stream.reload;
|
||||
// decrement samples to play counter
|
||||
if (--chctx->stream.samples_to_play == 0) {
|
||||
// pop context from the stack
|
||||
do opmplay_pop_stack(chctx); while (--chctx->stream.samples_to_play == 0);
|
||||
}
|
||||
else {
|
||||
// save data pointer
|
||||
chctx->stream.ptr = data;
|
||||
}
|
||||
}
|
||||
|
||||
return OPMPLAY_ERR_OK;
|
||||
}
|
||||
|
||||
int opmplay_tick(opmplay_context_t* ctx) {
|
||||
int ch = 0;
|
||||
int rtn = OPMPLAY_ERR_OK;
|
||||
|
||||
// unroll this shit =)
|
||||
chip_index = 0;
|
||||
opmplay_parse_stream(ctx, ctx->channels + 0, 0, opmplay_parse_fm_control_stream);
|
||||
opmplay_parse_stream(ctx, ctx->channels + 1, 0, opmplay_parse_fm_channel_stream);
|
||||
opmplay_parse_stream(ctx, ctx->channels + 2, 1, opmplay_parse_fm_channel_stream);
|
||||
opmplay_parse_stream(ctx, ctx->channels + 3, 2, opmplay_parse_fm_channel_stream);
|
||||
|
||||
opmplay_parse_stream(ctx, ctx->channels + 4, 0, opmplay_parse_ay_channel_stream);
|
||||
opmplay_parse_stream(ctx, ctx->channels + 5, 1, opmplay_parse_ay_channel_stream);
|
||||
opmplay_parse_stream(ctx, ctx->channels + 6, 2, opmplay_parse_ay_channel_stream);
|
||||
opmplay_parse_stream(ctx, ctx->channels + 7, 0, opmplay_parse_ay_envnoise_stream);
|
||||
opn_write_reg(chip_index, 7, ctx->ssg_r7[chip_index]);
|
||||
|
||||
chip_index = 1;
|
||||
opmplay_parse_stream(ctx, ctx->channels + 8, 0, opmplay_parse_fm_control_stream);
|
||||
opmplay_parse_stream(ctx, ctx->channels + 9, 0, opmplay_parse_fm_channel_stream);
|
||||
opmplay_parse_stream(ctx, ctx->channels + 10, 1, opmplay_parse_fm_channel_stream);
|
||||
opmplay_parse_stream(ctx, ctx->channels + 11, 2, opmplay_parse_fm_channel_stream);
|
||||
|
||||
opmplay_parse_stream(ctx, ctx->channels + 12, 0, opmplay_parse_ay_channel_stream);
|
||||
opmplay_parse_stream(ctx, ctx->channels + 13, 1, opmplay_parse_ay_channel_stream);
|
||||
opmplay_parse_stream(ctx, ctx->channels + 14, 2, opmplay_parse_ay_channel_stream);
|
||||
opmplay_parse_stream(ctx, ctx->channels + 15, 0, opmplay_parse_ay_envnoise_stream);
|
||||
opn_write_reg(chip_index, 7, ctx->ssg_r7[chip_index]);
|
||||
|
||||
// advance frame counter
|
||||
ctx->pos.frame++;
|
||||
|
||||
return rtn;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "opmfile.h"
|
||||
|
||||
// OPMPlay setup defines
|
||||
#define OPMPLAY_ENABLE_STDIO
|
||||
|
||||
#ifdef OPMPLAY_ENABLE_STDIO
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
// LXMPlay import defines
|
||||
#define opmplay_memcpy memcpy
|
||||
#define opmplay_memset memset
|
||||
#define opmplay_memcmp memcmp
|
||||
#define opmplay_alloc malloc
|
||||
#define opmplay_memfree free
|
||||
#define opmplay_debug_printf(...) printf(__VA_ARGS__)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// general enums
|
||||
enum {
|
||||
OPMPLAY_MAX_CHANNLES = 16,
|
||||
OPMPLAY_MAX_STACK_DEPTH = 4,
|
||||
};
|
||||
|
||||
// return error codes
|
||||
enum {
|
||||
OPMPLAY_ERR_OK = 0,
|
||||
OPMPLAY_ERR_END_OF_STREAM = 1,
|
||||
OPMPLAY_ERR_BAD_FILE_STRUCTURE = -1,
|
||||
OPMPLAY_ERR_MEMALLOC = -2,
|
||||
OPMPLAY_ERR_NULLPTR = -3,
|
||||
OPMPLAY_ERR_NO_SOUNDRAM = -4,
|
||||
OPMPLAY_ERR_DEVICE = -5,
|
||||
OPMPLAY_ERR_BAD_PARAMETER = -6,
|
||||
OPMPLAY_ERR_IO = -7,
|
||||
|
||||
|
||||
OPMPLAY_ERR_UNKNOWN = -20,
|
||||
};
|
||||
|
||||
enum {
|
||||
OPMPLAY_IO_USER = 0,
|
||||
OPMPLAY_IO_FILE = 1,
|
||||
OPMPLAY_IO_MEMORY = 2,
|
||||
};
|
||||
|
||||
// file I/O structs
|
||||
struct opmplay_io_t {
|
||||
uint32_t type; // i/o type
|
||||
union {
|
||||
void* buf;
|
||||
#ifdef OPMPLAY_ENABLE_STDIO
|
||||
FILE* io;
|
||||
#endif
|
||||
};
|
||||
uint32_t size;
|
||||
|
||||
// internal
|
||||
uint32_t offset;
|
||||
uint32_t(*read)(opmplay_io_t* io, void* dst, uint32_t size); // returns bytes read
|
||||
uint32_t(*seek)(opmplay_io_t* io, uint32_t offset); // returns 0 if success
|
||||
};
|
||||
|
||||
struct opmplay_channel_stack_t {
|
||||
const uint8_t* ptr;
|
||||
uint16_t frames_to_play;
|
||||
};
|
||||
|
||||
struct opmplay_channel_context_t {
|
||||
// stack
|
||||
opmplay_channel_stack_t stack[OPMPLAY_MAX_STACK_DEPTH];
|
||||
uint16_t stack_pos;
|
||||
|
||||
// stream data
|
||||
struct {
|
||||
uint16_t samples_to_play;
|
||||
uint16_t delay;
|
||||
uint16_t reload;
|
||||
|
||||
const uint8_t* data;
|
||||
const uint8_t* ptr;
|
||||
const uint8_t* loop; // if active
|
||||
} stream;
|
||||
|
||||
// private stuff
|
||||
uint8_t block; // cached block register
|
||||
};
|
||||
|
||||
struct opmplay_context_t {
|
||||
// LXM file header
|
||||
opm_header_t header;
|
||||
|
||||
// channel context
|
||||
opmplay_channel_context_t channels[OPMPLAY_MAX_CHANNLES];
|
||||
|
||||
// position data
|
||||
struct {
|
||||
uint16_t frame;
|
||||
uint16_t frame_looped;
|
||||
uint32_t samples;
|
||||
} pos;
|
||||
|
||||
// private stuff
|
||||
uint8_t ssg_r7[2]; // cached SSG mask register
|
||||
uint8_t extch3_block[2][3]; // cached extch3 block
|
||||
};
|
||||
|
||||
|
||||
// init context
|
||||
int opmplay_init(opmplay_context_t* ctx);
|
||||
|
||||
// free context
|
||||
int opmplay_free(opmplay_context_t* ctx);
|
||||
|
||||
// load file header
|
||||
int opmplay_load_header(opmplay_context_t* ctx, opmplay_io_t* io);
|
||||
|
||||
// load file contents
|
||||
int opmplay_load_module(opmplay_context_t* ctx, opmplay_io_t* io);
|
||||
|
||||
// reset to start
|
||||
int opmplay_rewind(opmplay_context_t* ctx);
|
||||
|
||||
// play one tick, render changes to opl3 device
|
||||
int opmplay_tick(opmplay_context_t* ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
13
readme.md
13
readme.md
|
@ -1,13 +0,0 @@
|
|||
# moe-bius
|
||||
|
||||
an Yamaha OPN series music register dump compressor and player. so far targeted for the ZX Spectrum + TurboSound FM sound card (2 x YM2203)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
the repo is a mess at this moment, code quality is not great as well. sorry :)
|
||||
|
||||
|
||||
|
||||
-- artёmka 14.o8.2o25
|
|
@ -159,7 +159,6 @@ void opm_compress(opm_convert_context_t* ctx) {
|
|||
case 1: backref_len.min = backref_len.max = 4; break; // fixed
|
||||
case 0:
|
||||
default:
|
||||
backref_len.min = backref_len.max = 4;
|
||||
ctx->flags.max_stack_depth = 0;
|
||||
break;
|
||||
};
|
||||
|
|
|
@ -31,6 +31,7 @@ enum : uint64_t {
|
|||
OPM_REC_EXTCH3_OP2_HIGH = (1 << 7),
|
||||
OPM_REC_EXTCH3_OP3_LOW = (1 << 8),
|
||||
OPM_REC_EXTCH3_OP3_HIGH = (1 << 9),
|
||||
OPM_REC_RHYTHM_SHIFT = 10,
|
||||
|
||||
// FM stream
|
||||
OPM_REC_REG30 = (1 << 0),
|
||||
|
@ -69,9 +70,6 @@ enum : uint64_t {
|
|||
OPM_REC_AYENV_PERIOD_LOW = (1 << 1),
|
||||
OPM_REC_AYENV_PERIOD_HIGH = (1 << 2),
|
||||
OPM_REC_AYENV_ENVTYPE = (1 << 3),
|
||||
|
||||
// rhythm stream
|
||||
OPM_RHYTHM_REG10_KEYON = (1 << 16),
|
||||
};
|
||||
|
||||
struct opm_frame_record {
|
||||
|
@ -84,6 +82,7 @@ struct opm_frame_record {
|
|||
struct {
|
||||
int freq[2][4];
|
||||
} extch3;
|
||||
int rhythm[16];
|
||||
};
|
||||
struct {
|
||||
// fm channel
|
||||
|
@ -98,18 +97,12 @@ struct opm_frame_record {
|
|||
// SSG envelope/noise channel
|
||||
int noise, envtype, period_low, period_hi;
|
||||
} ayenv;
|
||||
struct {
|
||||
// rhythm channels
|
||||
int regs[16];
|
||||
int key_on;
|
||||
} rhythm;
|
||||
};
|
||||
};
|
||||
|
||||
enum {
|
||||
OPM_CHAN_NEWPATTERN = (1 << 0),
|
||||
OPM_CHAN_LOOP_POINT = (1 << 1),
|
||||
OPM_FRAME0 = (1 << 2),
|
||||
|
||||
OPM_CHAN_BACKREF = (1 << 8),
|
||||
};
|
||||
|
@ -226,9 +219,7 @@ struct opm_convert_context_t {
|
|||
|
||||
// -----------------------------------
|
||||
struct {
|
||||
std::string name_prefix;
|
||||
std::string filename;
|
||||
std::string foldername;
|
||||
opm_header_t header;
|
||||
std::vector<opm_header_stream_desc_t> streamdesc;
|
||||
} opmfile;
|
||||
|
|
|
@ -40,7 +40,7 @@ struct opm_header_t {
|
|||
uint32_t clock_rate; // hz, integer
|
||||
uint16_t frame_rate; // hz, 8.8 fixedpoint
|
||||
uint16_t flags; // see above
|
||||
uint8_t callstack_depth;
|
||||
uint8_t callstack_depth; // reserved, 0 at this moment
|
||||
uint8_t streams; // including control streams
|
||||
uint32_t stream_mask; // used channel mask, LSB = ch 0
|
||||
|
||||
|
@ -72,18 +72,18 @@ enum {
|
|||
OPM_CTRL_TIMER_CSM = 0x80, // 80..9F - set timer/CSM/LFO frequency
|
||||
OPM_CTRL_RHYTHM = 0xA0, // A0..BF - rhythm control
|
||||
|
||||
OPM_CTRL_CMD80_REG25 = (1 << 0),
|
||||
OPM_CTRL_CMD80_REG24 = (1 << 1),
|
||||
OPM_CTRL_CMD80_REG24 = (1 << 0),
|
||||
OPM_CTRL_CMD80_REG25 = (1 << 1),
|
||||
OPM_CTRL_CMD80_REG27 = (1 << 2),
|
||||
OPM_CTRL_CMD80_REG22 = (1 << 3),
|
||||
OPM_CTRL_CMD80_EOF = (1 << 4),
|
||||
|
||||
OPM_CTRL_EXTCH3_OP1_HIGH = (1 << 0),
|
||||
OPM_CTRL_EXTCH3_OP1_LOW = (1 << 1),
|
||||
OPM_CTRL_EXTCH3_OP2_HIGH = (1 << 2),
|
||||
OPM_CTRL_EXTCH3_OP2_LOW = (1 << 3),
|
||||
OPM_CTRL_EXTCH3_OP3_HIGH = (1 << 4),
|
||||
OPM_CTRL_EXTCH3_OP3_LOW = (1 << 5),
|
||||
OPM_CTRL_EXTCH3_OP1_LOW = (1 << 0),
|
||||
OPM_CTRL_EXTCH3_OP1_HIGH = (1 << 1),
|
||||
OPM_CTRL_EXTCH3_OP2_LOW = (1 << 2),
|
||||
OPM_CTRL_EXTCH3_OP2_HIGH = (1 << 3),
|
||||
OPM_CTRL_EXTCH3_OP3_LOW = (1 << 4),
|
||||
OPM_CTRL_EXTCH3_OP3_HIGH = (1 << 5),
|
||||
OPM_CTRL_EXTCH3_EOF = (1 << 6),
|
||||
|
||||
OPM_CTRL_CMDA0_REG_MASK = (0x0F << 0),
|
||||
|
@ -111,8 +111,8 @@ enum {
|
|||
OPM_FM_CMD40_OP_SHIFT = OPM_FM_CMD00_OP_SHIFT,
|
||||
OPM_FM_CMD40_OP_MASK = OPM_FM_CMD00_OP_MASK,
|
||||
|
||||
OPM_FM_CMD80_REGA4 = (1 << 0),
|
||||
OPM_FM_CMD80_REGA0 = (1 << 1),
|
||||
OPM_FM_CMD80_REGA0 = (1 << 0),
|
||||
OPM_FM_CMD80_REGA4 = (1 << 1),
|
||||
OPM_FM_CMD80_REGB0 = (1 << 2),
|
||||
OPM_FM_CMD80_REGB4 = (1 << 3),
|
||||
OPM_FM_CMD80_EOF = (1 << 4),
|
||||
|
@ -128,7 +128,6 @@ enum {
|
|||
OPN_RHYTHM_REGS1 = 0x80, // 80..9F - write reg set 1
|
||||
OPN_RHYTHM_REGS2 = 0xA0, // A0..BF - write reg set 2
|
||||
|
||||
OPN_RHYTHM_KEY_EOF = (1 << 6),
|
||||
OPN_RHYTHM_CMD80_REG10 = (1 << 0),
|
||||
OPN_RHYTHM_CMD80_REG11 = (1 << 1),
|
||||
OPN_RHYTHM_CMD80_REG18 = (1 << 2),
|
||||
|
@ -137,7 +136,6 @@ enum {
|
|||
OPN_RHYTHM_CMDA0_REG1B = (1 << 1),
|
||||
OPN_RHYTHM_CMDA0_REG1C = (1 << 2),
|
||||
OPN_RHYTHM_CMDA0_REG1D = (1 << 3),
|
||||
OPN_RHYTHM_REGS_EOF = (1 << 4),
|
||||
};
|
||||
|
||||
// OPN SSG tone stream commands (shared with AY chip type)
|
||||
|
@ -150,7 +148,7 @@ enum {
|
|||
OPM_AYTONE_CMD00_PERIOD_LOW = (1 << 5),
|
||||
OPM_AYTONE_CMD00_EOF = (1 << 6),
|
||||
|
||||
OPM_AYTONE_CMD80_PERIOD_HIGH_MASK = (0xF << 0),
|
||||
OPM_AYTONE_CMD80_PERIOD_HIGH = (0xF << 0),
|
||||
OPM_AYTONE_CMD80_PERIOD_LOW = (1 << 4),
|
||||
OPM_AYTONE_CMD80_EOF = (1 << 5),
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <direct.h>
|
||||
|
||||
#include "cmdline.h"
|
||||
#include "opmfile.h"
|
||||
|
@ -163,7 +162,7 @@ int opm_requantize(opm_convert_context_t* ctx) {
|
|||
|
||||
if (it == ctx->vgm.vgmfile.begin() + ctx->vgm.loop_pos) {
|
||||
loopFrame = true;
|
||||
for (int i = 0; i < ctx->max_channels; i++) {
|
||||
for (int i = 0; i < ctx->max_channels + 1; i++) {
|
||||
oplchans[i].frame = currentFrame;
|
||||
}
|
||||
}
|
||||
|
@ -171,10 +170,12 @@ int opm_requantize(opm_convert_context_t* ctx) {
|
|||
// first, do OPNx cases
|
||||
int chip_idx = 0, chip_chidx = 0;
|
||||
switch ((VGM_Stream_Opcode)*it) {
|
||||
case VGM_Stream_Opcode::YM2608_PORT1_WRITE:
|
||||
case VGM_Stream_Opcode::YM2203_CHIP2_WRITE:
|
||||
if (ctx->chip_type < OPM_FLAG_CHIP_OPN_DUAL) break;
|
||||
chip_idx = 1; chip_chidx = ctx->dual_chidx;
|
||||
[[fallthrough]]
|
||||
case VGM_Stream_Opcode::YM2608_PORT0_WRITE:
|
||||
case VGM_Stream_Opcode::YM2203_WRITE: {
|
||||
// get register and data
|
||||
int reg = *(it + 1), data = *(it + 2);
|
||||
|
@ -186,7 +187,7 @@ int opm_requantize(opm_convert_context_t* ctx) {
|
|||
if (reg < 6) {
|
||||
// tone period
|
||||
channel = reg >> 1;
|
||||
channel += chip_chidx + ctx->ssg_chidx;
|
||||
channel + chip_chidx + ctx->ssg_chidx;
|
||||
oplchans[channel].frame = currentFrame;
|
||||
oplchans[channel].data.push_back(reg & 1);
|
||||
oplchans[channel].data.push_back(data);
|
||||
|
@ -194,7 +195,7 @@ int opm_requantize(opm_convert_context_t* ctx) {
|
|||
else if ((reg >= 8) && (reg <= 10)) {
|
||||
// volume
|
||||
channel = (reg - 8);
|
||||
channel += chip_chidx + ctx->ssg_chidx;
|
||||
channel = chip_chidx + ctx->ssg_chidx;
|
||||
oplchans[channel].frame = currentFrame;
|
||||
oplchans[channel].data.push_back(8);
|
||||
oplchans[channel].data.push_back(data);
|
||||
|
@ -208,7 +209,7 @@ int opm_requantize(opm_convert_context_t* ctx) {
|
|||
oplchans[channel].data.push_back((data >> ch) & ((1 << 0)|(1 << 3)));
|
||||
}
|
||||
break;
|
||||
case 6: case 11: case 12: case 13:
|
||||
case 5: case 11: case 12: case 13:
|
||||
// noise and envelope stuff
|
||||
channel = chip_chidx + ctx->ssg_chidx + 3;
|
||||
oplchans[channel].frame = currentFrame;
|
||||
|
@ -222,6 +223,12 @@ int opm_requantize(opm_convert_context_t* ctx) {
|
|||
// rhythm registers
|
||||
case 0x10:
|
||||
if ((ctx->chip_type != OPM_FLAG_CHIP_OPNA) || (chip_idx == 1)) break; // only OPNA has rhythm unit
|
||||
// add to control stream
|
||||
channel = chip_chidx + ctx->ctrl_chidx;
|
||||
oplchans[channel].frame = currentFrame;
|
||||
oplchans[channel].data.push_back(reg);
|
||||
oplchans[channel].data.push_back(data);
|
||||
break;
|
||||
// FM registers
|
||||
case 0x20:
|
||||
if ((ctx->chip_type != OPM_FLAG_CHIP_OPN_DUAL) && (chip_idx == 1)) break; // all OPN chips have only one 0x20-0x2F reg instance
|
||||
|
@ -234,8 +241,8 @@ int opm_requantize(opm_convert_context_t* ctx) {
|
|||
}
|
||||
else {
|
||||
// 0x28 - key on/off
|
||||
int chmask = 3;
|
||||
channel = chip_chidx + ctx->fm_chidx + (data & chmask);
|
||||
int chmask = (ctx->chip_type < OPM_FLAG_CHIP_OPNA) ? 3 : 7;
|
||||
channel = chip_chidx + ctx->fm_chidx + (data & 3);
|
||||
oplchans[channel].frame = currentFrame;
|
||||
oplchans[channel].data.push_back(reg);
|
||||
oplchans[channel].data.push_back(data & ~chmask); // normalize to ch0
|
||||
|
@ -260,8 +267,7 @@ int opm_requantize(opm_convert_context_t* ctx) {
|
|||
if (reg >= 0xA8) {
|
||||
// ext ch3 frequency, put to control stream
|
||||
if ((ctx->chip_type != OPM_FLAG_CHIP_OPN_DUAL) && (chip_idx == 1)) break; // all OPN chips have only one ext.ch3!
|
||||
channel = chip_chidx + ctx->ctrl_chidx;
|
||||
oplchans[channel].frame = currentFrame;
|
||||
auto& chch = oplchans[chip_chidx + ctx->ctrl_chidx];
|
||||
oplchans[channel].data.push_back(reg);
|
||||
oplchans[channel].data.push_back(data);
|
||||
break;
|
||||
|
@ -277,10 +283,9 @@ int opm_requantize(opm_convert_context_t* ctx) {
|
|||
break;
|
||||
default:
|
||||
global_reg:
|
||||
channel = chip_chidx + ctx->fm_chidx;
|
||||
oplchans[channel].frame = currentFrame;
|
||||
oplchans[channel].data.push_back(reg);
|
||||
oplchans[channel].data.push_back(data);
|
||||
oplchans[0].frame = currentFrame;
|
||||
oplchans[0].data.push_back(reg);
|
||||
oplchans[0].data.push_back(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -348,7 +353,6 @@ int opm_requantize(opm_convert_context_t* ctx) {
|
|||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// save total count of frames
|
||||
if (ctx->ch[0].opl.back().frame == currentFrame) {
|
||||
ctx->ch[0].opl.back().data.push_back(OPM_STREAM_END);
|
||||
|
@ -360,7 +364,6 @@ int opm_requantize(opm_convert_context_t* ctx) {
|
|||
endmarker.data.push_back(OPM_STREAM_END);
|
||||
ctx->ch[0].opl.push_back(endmarker);
|
||||
}
|
||||
#endif
|
||||
ctx->total_frames = currentFrame;
|
||||
|
||||
return 0;
|
||||
|
@ -383,7 +386,7 @@ int opm_write_file(opm_convert_context_t* ctx) {
|
|||
ctx->opmfile.header.callstack_depth = ctx->flags.max_stack_depth;
|
||||
ctx->opmfile.header.frame_rate = ((double)(44100 / ctx->delay) * 256.0);
|
||||
ctx->opmfile.header.streams = ctx->max_channels;
|
||||
ctx->opmfile.streamdesc.resize(ctx->max_channels);
|
||||
ctx->opmfile.streamdesc.resize(ctx->max_channels + 1);
|
||||
for (int ch = 0; ch < ctx->max_channels; ch++) {
|
||||
if (ctx->ch[ch].used) ctx->opmfile.header.stream_mask |= (1 << ch);
|
||||
}
|
||||
|
@ -404,42 +407,6 @@ int opm_write_file(opm_convert_context_t* ctx) {
|
|||
}
|
||||
fclose(f);
|
||||
|
||||
// now save ALL streams
|
||||
_mkdir(ctx->opmfile.foldername.c_str());
|
||||
{
|
||||
auto fname = ctx->opmfile.foldername + "/" + ctx->opmfile.name_prefix + "_header.bin";
|
||||
FILE* hf = fopen(fname.c_str(), "wb");
|
||||
if (hf) {
|
||||
fwrite(&ctx->opmfile.header, sizeof(opm_header_t), 1, hf);
|
||||
fclose(hf);
|
||||
}
|
||||
|
||||
for (int ch = 0; ch < ctx->max_channels; ch++) {
|
||||
auto& chctx = ctx->ch[ch];
|
||||
auto fname = ctx->opmfile.foldername + "/" + ctx->opmfile.name_prefix + "_ch" + std::to_string(ch) + ".bin";
|
||||
FILE* sf = fopen(fname.c_str(), "wb");
|
||||
if (sf) {
|
||||
fwrite(chctx.rawstream.data(), sizeof(decltype(chctx.rawstream)::value_type), chctx.rawstream.size(), sf);
|
||||
fclose(sf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// export config file
|
||||
{
|
||||
auto fname = ctx->opmfile.foldername + "/" + "music.inc";
|
||||
FILE* cf = fopen(fname.c_str(), "w");
|
||||
fprintf(cf, " ; autogenerated, edit with care!\n");
|
||||
fprintf(cf, " ifndef MUSIC_INC\n");
|
||||
fprintf(cf, " define MUSIC_INC\n\n");
|
||||
fprintf(cf, "TICK_RATE equ %d\n", (int)(44100.0 / ctx->delay));
|
||||
fprintf(cf, "TOTAL_FRAMES equ %d\n", ctx->total_frames);
|
||||
fprintf(cf, "TOTAL_CHANNELS equ %d\n", ctx->max_channels);
|
||||
fprintf(cf, "FILEPATH equ \"%s\"\n", (ctx->opmfile.name_prefix + '/' + ctx->opmfile.name_prefix).c_str());
|
||||
fprintf(cf, " endif\n");
|
||||
fclose(cf);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -450,10 +417,10 @@ int opm_group_control_stream(opm_convert_context_t* ctx, opm_channel_t* chctx, i
|
|||
|
||||
for (int f = 0; f < chctx->opl.size(); f++) {
|
||||
opm_channel_record_t chanrec;
|
||||
chanrec.flags = 0;
|
||||
chanrec.frame = chctx->opl[f].frame;
|
||||
if (chctx->opl[f].loop) chanrec.flags |= OPM_CHAN_LOOP_POINT;
|
||||
if (chctx->opl[f].frame == 0) chanrec.flags |= OPM_FRAME0;
|
||||
chanrec.frame_dist = f >= chctx->opl.size() - 1 ? 0 : chctx->opl[f + 1].frame - chctx->opl[f].frame;
|
||||
chanrec.flags = 0;
|
||||
if (chctx->opl[f].loop) chanrec.flags |= OPM_CHAN_LOOP_POINT;
|
||||
defrec.flags = 0;
|
||||
|
||||
// parse register writes
|
||||
|
@ -464,7 +431,10 @@ int opm_group_control_stream(opm_convert_context_t* ctx, opm_channel_t* chctx, i
|
|||
int reg = *it++; if (reg == OPM_STREAM_END) break;
|
||||
int data = *it++;
|
||||
|
||||
switch (reg) {
|
||||
if ((reg >= 0x10) && (reg <= 0x1F)) {
|
||||
defrec.rhythm[reg - 0x10] = data; defrec.flags |= (1ULL << (reg - 0x10 + OPM_REC_RHYTHM_SHIFT));
|
||||
}
|
||||
else switch (reg) {
|
||||
case 0x24: defrec.reg24 = data; defrec.flags |= OPM_REC_REG24; break;
|
||||
case 0x25: defrec.reg25 = data; defrec.flags |= OPM_REC_REG25; break;
|
||||
case 0x27: defrec.reg27 = data; defrec.flags |= OPM_REC_REG27; break;
|
||||
|
@ -472,11 +442,11 @@ int opm_group_control_stream(opm_convert_context_t* ctx, opm_channel_t* chctx, i
|
|||
|
||||
// ext ch3 frequencies
|
||||
case 0xA9: defrec.extch3.freq[0][0] = data; defrec.flags |= OPM_REC_EXTCH3_OP1_LOW; break;
|
||||
case 0xAD: if (defrec.extch3.freq[1][0] != data) { defrec.extch3.freq[1][0] = data; defrec.flags |= OPM_REC_EXTCH3_OP1_HIGH; } break;
|
||||
case 0xAD: defrec.extch3.freq[1][0] = data; defrec.flags |= OPM_REC_EXTCH3_OP1_HIGH; break;
|
||||
case 0xA8: defrec.extch3.freq[0][1] = data; defrec.flags |= OPM_REC_EXTCH3_OP2_LOW; break;
|
||||
case 0xAC: if (defrec.extch3.freq[1][1] != data) { defrec.extch3.freq[1][1] = data; defrec.flags |= OPM_REC_EXTCH3_OP2_HIGH; } break;
|
||||
case 0xAC: defrec.extch3.freq[1][1] = data; defrec.flags |= OPM_REC_EXTCH3_OP2_HIGH; break;
|
||||
case 0xAA: defrec.extch3.freq[0][2] = data; defrec.flags |= OPM_REC_EXTCH3_OP3_LOW; break;
|
||||
case 0xAE: if (defrec.extch3.freq[1][2] != data) { defrec.extch3.freq[1][2] = data; defrec.flags |= OPM_REC_EXTCH3_OP3_HIGH; } break;
|
||||
case 0xAE: defrec.extch3.freq[1][2] = data; defrec.flags |= OPM_REC_EXTCH3_OP3_HIGH; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
@ -489,52 +459,6 @@ int opm_group_control_stream(opm_convert_context_t* ctx, opm_channel_t* chctx, i
|
|||
return 0;
|
||||
}
|
||||
|
||||
int opm_group_rhythm_stream(opm_convert_context_t* ctx, opm_channel_t* chctx, int ch) {
|
||||
// default (startup) frame record
|
||||
opm_frame_record defrec;
|
||||
memset(&defrec, -1, sizeof(defrec));
|
||||
|
||||
for (int f = 0; f < chctx->opl.size(); f++) {
|
||||
opm_channel_record_t chanrec;
|
||||
chanrec.frame = chctx->opl[f].frame;
|
||||
chanrec.flags = 0;
|
||||
if (chctx->opl[f].loop) chanrec.flags |= OPM_CHAN_LOOP_POINT;
|
||||
if (chctx->opl[f].frame == 0) chanrec.flags |= OPM_FRAME0;
|
||||
defrec.flags = 0;
|
||||
|
||||
// parse register writes
|
||||
bool isFrame = true; auto it = chctx->opl[f].data.begin();
|
||||
while (isFrame && (it < chctx->opl[f].data.end())) {
|
||||
|
||||
// get reg:data pair
|
||||
int reg = *it++; if (reg == OPM_STREAM_END) break;
|
||||
int data = *it++;
|
||||
|
||||
if ((reg >= 0x11) && (reg <= 0x1F)) {
|
||||
defrec.rhythm.regs[reg - 0x10] = data; defrec.flags |= (1ULL << (reg - 0x10));
|
||||
}
|
||||
else if (reg == 0x10) {
|
||||
if (data & (1 << 7)) {
|
||||
// dump
|
||||
defrec.flags |= (1 << 0);
|
||||
defrec.rhythm.regs[0] = data;
|
||||
}
|
||||
else {
|
||||
// key
|
||||
defrec.flags |= OPM_RHYTHM_REG10_KEYON;
|
||||
defrec.rhythm.key_on = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defrec.flags) {
|
||||
chanrec.records.push_back(defrec); defrec.flags = 0;
|
||||
}
|
||||
chctx->records.push_back(chanrec);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int opm_group_fm_stream(opm_convert_context_t* ctx, opm_channel_t* chctx, int ch) {
|
||||
// default (startup) frame record
|
||||
opm_frame_record defrec;
|
||||
|
@ -543,9 +467,9 @@ int opm_group_fm_stream(opm_convert_context_t* ctx, opm_channel_t* chctx, int ch
|
|||
for (int f = 0; f < chctx->opl.size(); f++) {
|
||||
opm_channel_record_t chanrec;
|
||||
chanrec.frame = chctx->opl[f].frame;
|
||||
chanrec.frame_dist = f >= chctx->opl.size() - 1 ? 0 : chctx->opl[f + 1].frame - chctx->opl[f].frame;
|
||||
chanrec.flags = 0;
|
||||
if (chctx->opl[f].loop) chanrec.flags |= OPM_CHAN_LOOP_POINT;
|
||||
if (chctx->opl[f].frame == 0) chanrec.flags |= OPM_FRAME0;
|
||||
defrec.flags = 0;
|
||||
|
||||
// parse register writes
|
||||
|
@ -569,10 +493,8 @@ int opm_group_fm_stream(opm_convert_context_t* ctx, opm_channel_t* chctx, int ch
|
|||
defrec.fnum = data;
|
||||
break;
|
||||
case 0xA4:
|
||||
if (defrec.block != data) {
|
||||
defrec.flags |= OPM_REC_REGA4;
|
||||
defrec.block = data;
|
||||
}
|
||||
defrec.flags |= OPM_REC_REGA4;
|
||||
defrec.block = data;
|
||||
break;
|
||||
case 0xB0:
|
||||
defrec.flags |= OPM_REC_REGB0;
|
||||
|
@ -608,9 +530,9 @@ int opm_group_ssg_tone_stream(opm_convert_context_t* ctx, opm_channel_t* chctx,
|
|||
for (int f = 0; f < chctx->opl.size(); f++) {
|
||||
opm_channel_record_t chanrec;
|
||||
chanrec.frame = chctx->opl[f].frame;
|
||||
chanrec.frame_dist = f >= chctx->opl.size() - 1 ? 0 : chctx->opl[f + 1].frame - chctx->opl[f].frame;
|
||||
chanrec.flags = 0;
|
||||
if (chctx->opl[f].loop) chanrec.flags |= OPM_CHAN_LOOP_POINT;
|
||||
if (chctx->opl[f].frame == 0) chanrec.flags |= OPM_FRAME0;
|
||||
defrec.flags = 0;
|
||||
|
||||
// parse register writes
|
||||
|
@ -646,9 +568,9 @@ int opm_group_ssg_env_stream(opm_convert_context_t* ctx, opm_channel_t* chctx, i
|
|||
for (int f = 0; f < chctx->opl.size(); f++) {
|
||||
opm_channel_record_t chanrec;
|
||||
chanrec.frame = chctx->opl[f].frame;
|
||||
chanrec.frame_dist = f >= chctx->opl.size() - 1 ? 0 : chctx->opl[f + 1].frame - chctx->opl[f].frame;
|
||||
chanrec.flags = 0;
|
||||
if (chctx->opl[f].loop) chanrec.flags |= OPM_CHAN_LOOP_POINT;
|
||||
if (chctx->opl[f].frame == 0) chanrec.flags |= OPM_FRAME0;
|
||||
defrec.flags = 0;
|
||||
|
||||
// parse register writes
|
||||
|
@ -684,7 +606,7 @@ int opm_group_registers(opm_convert_context_t* ctx) {
|
|||
// ah fuck do it for each chip type separately
|
||||
if (ctx->chip_type <= OPM_FLAG_CHIP_OPN_DUAL) {
|
||||
int dch = 0;
|
||||
for (int chip = 0; chip < (ctx->chip_type == OPM_FLAG_CHIP_OPN_DUAL ? 2 : 1); chip++) {
|
||||
for (int chip = 0; chip < ctx->chip_type == OPM_FLAG_CHIP_OPN_DUAL ? 2 : 1; chip++) {
|
||||
opm_group_control_stream(ctx, dch + ctx->ch.data() + ctx->ctrl_chidx, 0);
|
||||
for (int fmch = 0; fmch < 3; fmch++) {
|
||||
opm_group_fm_stream(ctx, dch + ctx->ch.data() + ctx->fm_chidx + fmch, fmch);
|
||||
|
@ -705,7 +627,6 @@ int opm_group_registers(opm_convert_context_t* ctx) {
|
|||
opm_group_ssg_tone_stream(ctx, ctx->ch.data() + ctx->ssg_chidx + aych, aych);
|
||||
}
|
||||
opm_group_ssg_env_stream(ctx, ctx->ch.data() + ctx->ssg_chidx + 3, 0);
|
||||
opm_group_rhythm_stream(ctx, ctx->ch.data() + ctx->rhy_chidx, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -740,16 +661,14 @@ void opm_set_delay(std::vector<uint8_t>& vec, uint64_t delay) {
|
|||
|
||||
int opm_serialize_control_stream(opm_channel_t* chctx) {
|
||||
int old_delay = -1;
|
||||
for (int f = 0; f < chctx->records.size(); f++) {
|
||||
auto& s = chctx->records[f];
|
||||
for (auto& s : chctx->records) {
|
||||
s.rawdata.clear();
|
||||
|
||||
// set loop point
|
||||
if (s.flags & OPM_CHAN_LOOP_POINT) s.rawdata.push_back(OPM_STREAM_LOOP);
|
||||
|
||||
// set delay
|
||||
s.frame_dist = f >= chctx->records.size() - 1 ? 0 : chctx->records[f + 1].frame - chctx->records[f].frame;
|
||||
if (s.frame_dist != old_delay || (s.flags & OPM_CHAN_LOOP_POINT)) {
|
||||
if (s.frame_dist != old_delay) {
|
||||
old_delay = s.frame_dist;
|
||||
opm_set_delay(s.rawdata, s.frame_dist);
|
||||
}
|
||||
|
@ -763,6 +682,19 @@ int opm_serialize_control_stream(opm_channel_t* chctx) {
|
|||
s.rawdata.push_back(OPM_STREAM_END_FRAME);
|
||||
}
|
||||
else {
|
||||
// write rhythm registers first
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (ef & (1ULL << (i + OPM_REC_RHYTHM_SHIFT))) {
|
||||
ef &= ~(1ULL << (i + OPM_REC_RHYTHM_SHIFT));
|
||||
s.rawdata.push_back(
|
||||
OPM_CTRL_RHYTHM |
|
||||
((ef == 0) && (i == s.records.size() - 1) ? OPM_CTRL_CMDA0_EOF : 0) |
|
||||
i
|
||||
);
|
||||
s.rawdata.push_back(e.rhythm[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// then parse mask
|
||||
int cmdmask = 0;
|
||||
cmdmask |= ef & (OPM_REC_REG24 | OPM_REC_REG25 | OPM_REC_REG27 | OPM_REC_REG22) ? 1 : 0;
|
||||
|
@ -778,13 +710,13 @@ int opm_serialize_control_stream(opm_channel_t* chctx) {
|
|||
s.rawdata.push_back(
|
||||
OPM_CTRL_TIMER_CSM |
|
||||
((cmdmask == 0) && (i == s.records.size() - 1) ? OPM_CTRL_CMD80_EOF : 0) |
|
||||
(ef & OPM_REC_REG25 ? OPM_CTRL_CMD80_REG25 : 0) |
|
||||
(ef & OPM_REC_REG24 ? OPM_CTRL_CMD80_REG24 : 0) |
|
||||
(ef & OPM_REC_REG25 ? OPM_CTRL_CMD80_REG25 : 0) |
|
||||
(ef & OPM_REC_REG27 ? OPM_CTRL_CMD80_REG27 : 0) |
|
||||
(ef & OPM_REC_REG22 ? OPM_CTRL_CMD80_REG22 : 0)
|
||||
);
|
||||
if (ef & OPM_REC_REG25) s.rawdata.push_back(e.reg25);
|
||||
if (ef & OPM_REC_REG24) s.rawdata.push_back(e.reg24);
|
||||
if (ef & OPM_REC_REG25) s.rawdata.push_back(e.reg25);
|
||||
if (ef & OPM_REC_REG27) s.rawdata.push_back(e.reg27);
|
||||
if (ef & OPM_REC_REG22) s.rawdata.push_back(e.reg22);
|
||||
}
|
||||
|
@ -801,12 +733,12 @@ int opm_serialize_control_stream(opm_channel_t* chctx) {
|
|||
(ef & OPM_REC_EXTCH3_OP3_LOW ? OPM_CTRL_EXTCH3_OP3_LOW : 0) |
|
||||
(ef & OPM_REC_EXTCH3_OP3_HIGH ? OPM_CTRL_EXTCH3_OP3_HIGH : 0)
|
||||
);
|
||||
if (ef & OPM_REC_EXTCH3_OP1_HIGH) s.rawdata.push_back(e.extch3.freq[1][0]);
|
||||
if (ef & OPM_REC_EXTCH3_OP1_LOW) s.rawdata.push_back(e.extch3.freq[0][0]);
|
||||
if (ef & OPM_REC_EXTCH3_OP2_HIGH) s.rawdata.push_back(e.extch3.freq[1][1]);
|
||||
if (ef & OPM_REC_EXTCH3_OP1_HIGH) s.rawdata.push_back(e.extch3.freq[1][0]);
|
||||
if (ef & OPM_REC_EXTCH3_OP2_LOW) s.rawdata.push_back(e.extch3.freq[0][1]);
|
||||
if (ef & OPM_REC_EXTCH3_OP3_HIGH) s.rawdata.push_back(e.extch3.freq[1][2]);
|
||||
if (ef & OPM_REC_EXTCH3_OP2_HIGH) s.rawdata.push_back(e.extch3.freq[1][1]);
|
||||
if (ef & OPM_REC_EXTCH3_OP3_LOW) s.rawdata.push_back(e.extch3.freq[0][2]);
|
||||
if (ef & OPM_REC_EXTCH3_OP3_HIGH) s.rawdata.push_back(e.extch3.freq[1][2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -814,112 +746,16 @@ int opm_serialize_control_stream(opm_channel_t* chctx) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int opm_serialize_rhythm_stream(opm_channel_t* chctx) {
|
||||
int old_delay = -1;
|
||||
for (int f = 0; f < chctx->records.size(); f++) {
|
||||
auto& s = chctx->records[f];
|
||||
s.rawdata.clear();
|
||||
|
||||
// set loop point
|
||||
if (s.flags & OPM_CHAN_LOOP_POINT) s.rawdata.push_back(OPM_STREAM_LOOP);
|
||||
|
||||
// set delay
|
||||
s.frame_dist = f >= chctx->records.size() - 1 ? 0 : chctx->records[f + 1].frame - chctx->records[f].frame;
|
||||
if (s.frame_dist != old_delay || (s.flags & OPM_CHAN_LOOP_POINT)) {
|
||||
old_delay = s.frame_dist;
|
||||
opm_set_delay(s.rawdata, s.frame_dist);
|
||||
}
|
||||
|
||||
// process events
|
||||
if (s.records.size() > 0) for (int i = 0; i < s.records.size(); i++) {
|
||||
auto& e = s.records[i];
|
||||
uint64_t ef = e.flags;
|
||||
|
||||
if ((ef == 0) && (i == s.records.size() - 1)) {
|
||||
s.rawdata.push_back(OPM_STREAM_END_FRAME);
|
||||
}
|
||||
else {
|
||||
// then parse mask
|
||||
int cmdmask = 0;
|
||||
cmdmask |= ef & (0x303) ? 1 : 0; // 10,11,18,19
|
||||
cmdmask |= ef & (0x3C00) ? 2 : 0; // 1a,1b,1c,1d
|
||||
cmdmask |= ef & OPM_RHYTHM_REG10_KEYON ? 4 : 0;
|
||||
|
||||
if (cmdmask & 1) {
|
||||
cmdmask &= ~1;
|
||||
// write registers
|
||||
s.rawdata.push_back(
|
||||
OPN_RHYTHM_REGS1 |
|
||||
((cmdmask == 0) && (i == s.records.size() - 1) ? OPN_RHYTHM_REGS_EOF : 0) |
|
||||
(ef & 0x001 ? OPN_RHYTHM_CMD80_REG10 : 0) |
|
||||
(ef & 0x002 ? OPN_RHYTHM_CMD80_REG11 : 0) |
|
||||
(ef & 0x100 ? OPN_RHYTHM_CMD80_REG18 : 0) |
|
||||
(ef & 0x200 ? OPN_RHYTHM_CMD80_REG19 : 0)
|
||||
);
|
||||
if (ef & 0x001) s.rawdata.push_back(e.rhythm.regs[0]);
|
||||
if (ef & 0x002) s.rawdata.push_back(e.rhythm.regs[1]);
|
||||
if (ef & 0x100) s.rawdata.push_back(e.rhythm.regs[8]);
|
||||
if (ef & 0x200) s.rawdata.push_back(e.rhythm.regs[9]);
|
||||
}
|
||||
if (cmdmask & 2) {
|
||||
cmdmask &= ~2;
|
||||
// write registers
|
||||
s.rawdata.push_back(
|
||||
OPN_RHYTHM_REGS2 |
|
||||
((cmdmask == 0) && (i == s.records.size() - 1) ? OPN_RHYTHM_REGS_EOF : 0) |
|
||||
(ef & 0x400 ? OPN_RHYTHM_CMDA0_REG1A : 0) |
|
||||
(ef & 0x800 ? OPN_RHYTHM_CMDA0_REG1B : 0) |
|
||||
(ef & 0x1000 ? OPN_RHYTHM_CMDA0_REG1C : 0) |
|
||||
(ef & 0x2000 ? OPN_RHYTHM_CMDA0_REG1D : 0)
|
||||
);
|
||||
if (ef & 0x400) s.rawdata.push_back(e.rhythm.regs[10]);
|
||||
if (ef & 0x800) s.rawdata.push_back(e.rhythm.regs[11]);
|
||||
if (ef & 0x1000) s.rawdata.push_back(e.rhythm.regs[12]);
|
||||
if (ef & 0x2000) s.rawdata.push_back(e.rhythm.regs[13]);
|
||||
}
|
||||
if (cmdmask & 4) {
|
||||
cmdmask &= ~4;
|
||||
// write registers
|
||||
s.rawdata.push_back(
|
||||
OPN_RHYTHM_KEY |
|
||||
((cmdmask == 0) && (i == s.records.size() - 1) ? OPN_RHYTHM_KEY_EOF : 0) |
|
||||
(e.rhythm.key_on & 0x3F)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
|
||||
#if 0
|
||||
// write rhythm registers first
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (ef & (1ULL << (i + OPM_REC_RHYTHM_SHIFT))) {
|
||||
ef &= ~(1ULL << (i + OPM_REC_RHYTHM_SHIFT));
|
||||
s.rawdata.push_back(
|
||||
OPM_CTRL_RHYTHM |
|
||||
((ef == 0) && (i == s.records.size() - 1) ? OPM_CTRL_CMDA0_EOF : 0) |
|
||||
i
|
||||
);
|
||||
s.rawdata.push_back(e.rhythm[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int opm_serialize_fm_stream(opm_channel_t* chctx) {
|
||||
int old_delay = -1;
|
||||
for (int f = 0; f < chctx->records.size(); f++) {
|
||||
auto& s = chctx->records[f];
|
||||
for (auto& s : chctx->records) {
|
||||
s.rawdata.clear();
|
||||
|
||||
// set loop point
|
||||
if (s.flags & OPM_CHAN_LOOP_POINT) s.rawdata.push_back(OPM_STREAM_LOOP);
|
||||
|
||||
// set delay
|
||||
s.frame_dist = f >= chctx->records.size() - 1 ? 0 : chctx->records[f + 1].frame - chctx->records[f].frame;
|
||||
if (s.frame_dist != old_delay || (s.flags & OPM_CHAN_LOOP_POINT)) {
|
||||
if (s.frame_dist != old_delay) {
|
||||
old_delay = s.frame_dist;
|
||||
opm_set_delay(s.rawdata, s.frame_dist);
|
||||
}
|
||||
|
@ -936,10 +772,10 @@ int opm_serialize_fm_stream(opm_channel_t* chctx) {
|
|||
// then parse mask
|
||||
// dirty but should work :)
|
||||
int adsrmask = 0;
|
||||
adsrmask |= ef & ((OPM_REC_REG50 | OPM_REC_REG60 | OPM_REC_REG70 | OPM_REC_REG80) << (OPM_REC_OP_SHIFTMUL * 0)) ? 1 : 0;
|
||||
adsrmask |= ef & ((OPM_REC_REG50 | OPM_REC_REG60 | OPM_REC_REG70 | OPM_REC_REG80) << (OPM_REC_OP_SHIFTMUL * 1)) ? 2 : 0;
|
||||
adsrmask |= ef & ((OPM_REC_REG50 | OPM_REC_REG60 | OPM_REC_REG70 | OPM_REC_REG80) << (OPM_REC_OP_SHIFTMUL * 2)) ? 4 : 0;
|
||||
adsrmask |= ef & ((OPM_REC_REG50 | OPM_REC_REG60 | OPM_REC_REG70 | OPM_REC_REG80) << (OPM_REC_OP_SHIFTMUL * 3)) ? 8 : 0;
|
||||
adsrmask |= ef & ((OPM_REC_REG50 | OPM_REC_REG60 | OPM_REC_REG70 | OPM_REC_REG70) << (OPM_REC_OP_SHIFTMUL * 0)) ? 1 : 0;
|
||||
adsrmask |= ef & ((OPM_REC_REG50 | OPM_REC_REG60 | OPM_REC_REG70 | OPM_REC_REG70) << (OPM_REC_OP_SHIFTMUL * 1)) ? 2 : 0;
|
||||
adsrmask |= ef & ((OPM_REC_REG50 | OPM_REC_REG60 | OPM_REC_REG70 | OPM_REC_REG70) << (OPM_REC_OP_SHIFTMUL * 2)) ? 4 : 0;
|
||||
adsrmask |= ef & ((OPM_REC_REG50 | OPM_REC_REG60 | OPM_REC_REG70 | OPM_REC_REG70) << (OPM_REC_OP_SHIFTMUL * 3)) ? 8 : 0;
|
||||
|
||||
int cmdmask = 0;
|
||||
cmdmask |= ef & ((OPM_REC_REG30 | OPM_REC_REG40 | OPM_REC_REG90) << (OPM_REC_OP_SHIFTMUL * 0)) ? 1 : 0;
|
||||
|
@ -997,13 +833,13 @@ int opm_serialize_fm_stream(opm_channel_t* chctx) {
|
|||
s.rawdata.push_back(
|
||||
OPM_FM_FREQ_FB_PAN |
|
||||
((cmdmask == 0) && (i == s.records.size() - 1) ? OPM_FM_CMD80_EOF : 0) |
|
||||
(ef & OPM_REC_REGA4 ? OPM_FM_CMD80_REGA4 : 0) |
|
||||
(ef & OPM_REC_REGA0 ? OPM_FM_CMD80_REGA0 : 0) |
|
||||
(ef & OPM_REC_REGA4 ? OPM_FM_CMD80_REGA4 : 0) |
|
||||
(ef & OPM_REC_REGB0 ? OPM_FM_CMD80_REGB0 : 0) |
|
||||
(ef & OPM_REC_REGB4 ? OPM_FM_CMD80_REGB4 : 0)
|
||||
);
|
||||
if (ef & OPM_REC_REGA4) s.rawdata.push_back(e.block);
|
||||
if (ef & OPM_REC_REGA0) s.rawdata.push_back(e.fnum);
|
||||
if (ef & OPM_REC_REGA4) s.rawdata.push_back(e.block);
|
||||
if (ef & OPM_REC_REGB0) s.rawdata.push_back(e.fb);
|
||||
if (ef & OPM_REC_REGB4) s.rawdata.push_back(e.pan);
|
||||
}
|
||||
|
@ -1025,16 +861,14 @@ int opm_serialize_fm_stream(opm_channel_t* chctx) {
|
|||
|
||||
int opm_serialize_ssg_tone_stream(opm_channel_t* chctx) {
|
||||
int old_delay = -1;
|
||||
for (int f = 0; f < chctx->records.size(); f++) {
|
||||
auto& s = chctx->records[f];
|
||||
for (auto& s : chctx->records) {
|
||||
s.rawdata.clear();
|
||||
|
||||
// set loop point
|
||||
if (s.flags & OPM_CHAN_LOOP_POINT) s.rawdata.push_back(OPM_STREAM_LOOP);
|
||||
|
||||
// set delay
|
||||
s.frame_dist = f >= chctx->records.size() - 1 ? 0 : chctx->records[f + 1].frame - chctx->records[f].frame;
|
||||
if (s.frame_dist != old_delay || (s.flags & OPM_CHAN_LOOP_POINT)) {
|
||||
if (s.frame_dist != old_delay) {
|
||||
old_delay = s.frame_dist;
|
||||
opm_set_delay(s.rawdata, s.frame_dist);
|
||||
}
|
||||
|
@ -1048,7 +882,7 @@ int opm_serialize_ssg_tone_stream(opm_channel_t* chctx) {
|
|||
s.rawdata.push_back(OPM_STREAM_END_FRAME);
|
||||
}
|
||||
else {
|
||||
if (ef & (OPM_REC_AYTONE_PERIOD_HIGH | OPM_REC_AYTONE_PERIOD_LOW)) {
|
||||
if (ef & OPM_REC_AYTONE_PERIOD_HIGH) {
|
||||
// high period available, write it
|
||||
auto ef_cur = ef;
|
||||
ef &= ~(OPM_REC_AYTONE_PERIOD_HIGH | OPM_REC_AYTONE_PERIOD_LOW); // write both hi and lo if available
|
||||
|
@ -1057,7 +891,7 @@ int opm_serialize_ssg_tone_stream(opm_channel_t* chctx) {
|
|||
OPM_AYTONE_PERIOD |
|
||||
((ef == 0) && (i == s.records.size() - 1) ? OPM_AYTONE_CMD80_EOF : 0) |
|
||||
(ef_cur & OPM_REC_AYTONE_PERIOD_LOW ? OPM_AYTONE_CMD80_PERIOD_LOW : 0) |
|
||||
(e.aytone.period_hi & OPM_AYTONE_CMD80_PERIOD_HIGH_MASK)
|
||||
(e.aytone.period_hi & OPM_AYTONE_CMD80_PERIOD_HIGH)
|
||||
);
|
||||
if (ef_cur & OPM_REC_AYTONE_PERIOD_LOW) s.rawdata.push_back(e.aytone.period_low);
|
||||
}
|
||||
|
@ -1078,11 +912,11 @@ int opm_serialize_ssg_tone_stream(opm_channel_t* chctx) {
|
|||
|
||||
if (ef & OPM_REC_AYTONE_MASK) {
|
||||
// write mask at last
|
||||
ef &= ~OPM_REC_AYTONE_MASK;
|
||||
ef & ~OPM_REC_AYTONE_MASK;
|
||||
// write registers
|
||||
s.rawdata.push_back(
|
||||
OPM_AYTONE_MASK |
|
||||
((ef == 0) && (i == s.records.size() - 1) ? OPM_AYTONE_MASK_EOF : 0) |
|
||||
OPM_AYTONE_REGS |
|
||||
((ef == 0) && (i == s.records.size() - 1) ? OPM_AYTONE_CMD00_EOF : 0) |
|
||||
(e.aytone.mask & 1 ? OPM_AYTONE_MASK_TONE : 0) |
|
||||
(e.aytone.mask & 8 ? OPM_AYTONE_MASK_NOISE : 0)
|
||||
);
|
||||
|
@ -1095,80 +929,71 @@ int opm_serialize_ssg_tone_stream(opm_channel_t* chctx) {
|
|||
|
||||
int opm_serialize_ssg_env_stream(opm_channel_t* chctx) {
|
||||
int old_delay = -1;
|
||||
bool delay_emitted = false;
|
||||
for (int f = 0; f < chctx->records.size(); f++) {
|
||||
auto& s = chctx->records[f];
|
||||
for (auto& s : chctx->records) {
|
||||
s.rawdata.clear();
|
||||
|
||||
// set loop point
|
||||
if (s.flags & OPM_CHAN_LOOP_POINT) s.rawdata.push_back(OPM_STREAM_LOOP);
|
||||
|
||||
// set delay
|
||||
s.frame_dist = f >= chctx->records.size() - 1 ? 0 : chctx->records[f + 1].frame - chctx->records[f].frame;
|
||||
if (s.frame_dist != old_delay || (s.flags & OPM_CHAN_LOOP_POINT)) {
|
||||
if (s.frame_dist != old_delay) {
|
||||
old_delay = s.frame_dist;
|
||||
opm_set_delay(s.rawdata, s.frame_dist);
|
||||
delay_emitted = true;
|
||||
}
|
||||
|
||||
// process events
|
||||
if (s.records.size() > 0) {
|
||||
for (int i = 0; i < s.records.size(); i++) {
|
||||
auto& e = s.records[i];
|
||||
uint64_t ef = e.flags;
|
||||
if (s.records.size() > 0) for (int i = 0; i < s.records.size(); i++) {
|
||||
auto& e = s.records[i];
|
||||
uint64_t ef = e.flags;
|
||||
|
||||
if ((ef == 0) && (i == s.records.size() - 1)) {
|
||||
s.rawdata.push_back(OPM_STREAM_END_FRAME);
|
||||
if ((ef == 0) && (i == s.records.size() - 1)) {
|
||||
s.rawdata.push_back(OPM_STREAM_END_FRAME);
|
||||
}
|
||||
else {
|
||||
if (ef & OPM_REC_AYENV_PERIOD_HIGH) {
|
||||
// high period available, write it
|
||||
auto ef_cur = ef;
|
||||
ef &= ~(OPM_REC_AYENV_PERIOD_HIGH | OPM_REC_AYENV_PERIOD_LOW); // write both hi and lo if available
|
||||
// write registers
|
||||
s.rawdata.push_back(
|
||||
OPM_AYENV_PERIOD_FULL |
|
||||
((ef == 0) && (i == s.records.size() - 1) ? OPM_AYENV_CMDF0_EOF : 0) |
|
||||
(ef_cur & OPM_REC_AYENV_PERIOD_LOW ? OPM_AYENV_CMDF0_PERIOD_LOW : 0) |
|
||||
(ef_cur & OPM_REC_AYENV_PERIOD_HIGH ? OPM_AYENV_CMDF0_PERIOD_HIGH : 0)
|
||||
);
|
||||
if (ef_cur & OPM_REC_AYENV_PERIOD_LOW) s.rawdata.push_back(e.ayenv.period_low);
|
||||
if (ef_cur & OPM_REC_AYENV_PERIOD_HIGH) s.rawdata.push_back(e.ayenv.period_hi);
|
||||
}
|
||||
else {
|
||||
if (ef & (OPM_REC_AYENV_PERIOD_HIGH | OPM_REC_AYENV_PERIOD_LOW)) {
|
||||
// high period available, write it
|
||||
auto ef_cur = ef;
|
||||
ef &= ~(OPM_REC_AYENV_PERIOD_HIGH | OPM_REC_AYENV_PERIOD_LOW); // write both hi and lo if available
|
||||
// write registers
|
||||
s.rawdata.push_back(
|
||||
OPM_AYENV_PERIOD_FULL |
|
||||
((ef == 0) && (i == s.records.size() - 1) ? OPM_AYENV_CMDF0_EOF : 0) |
|
||||
(ef_cur & OPM_REC_AYENV_PERIOD_LOW ? OPM_AYENV_CMDF0_PERIOD_LOW : 0) |
|
||||
(ef_cur & OPM_REC_AYENV_PERIOD_HIGH ? OPM_AYENV_CMDF0_PERIOD_HIGH : 0)
|
||||
);
|
||||
if (ef_cur & OPM_REC_AYENV_PERIOD_LOW) s.rawdata.push_back(e.ayenv.period_low);
|
||||
if (ef_cur & OPM_REC_AYENV_PERIOD_HIGH) s.rawdata.push_back(e.ayenv.period_hi);
|
||||
}
|
||||
|
||||
if (ef & (OPM_REC_AYENV_NOISE | OPM_REC_AYENV_PERIOD_LOW)) {
|
||||
// noise available, write it
|
||||
auto ef_cur = ef;
|
||||
ef &= ~(OPM_REC_AYENV_NOISE | OPM_REC_AYENV_PERIOD_LOW); // write both noise and period low if available
|
||||
// write registers
|
||||
s.rawdata.push_back(
|
||||
OPM_AYENV_REGS |
|
||||
((ef == 0) && (i == s.records.size() - 1) ? OPM_AYENV_CMD00_EOF : 0) |
|
||||
(ef_cur & OPM_REC_AYENV_PERIOD_LOW ? OPM_AYENV_CMD00_PERIOD_LOW : 0) |
|
||||
(e.ayenv.noise & OPM_AYENV_CMD00_NOISE_MASK)
|
||||
);
|
||||
if (ef_cur & OPM_REC_AYENV_PERIOD_LOW) s.rawdata.push_back(e.ayenv.period_low);
|
||||
}
|
||||
if (ef & OPM_REC_AYENV_NOISE) {
|
||||
// noise available, write it
|
||||
auto ef_cur = ef;
|
||||
ef &= ~(OPM_REC_AYENV_NOISE | OPM_REC_AYENV_PERIOD_LOW); // write both noise and period low if available
|
||||
// write registers
|
||||
s.rawdata.push_back(
|
||||
OPM_AYENV_REGS |
|
||||
((ef == 0) && (i == s.records.size() - 1) ? OPM_AYENV_CMD00_EOF : 0) |
|
||||
(ef_cur & OPM_REC_AYENV_PERIOD_LOW ? OPM_AYENV_CMD00_PERIOD_LOW : 0) |
|
||||
(e.ayenv.noise & OPM_AYENV_CMD00_NOISE_MASK)
|
||||
);
|
||||
if (ef_cur & OPM_REC_AYENV_PERIOD_LOW) s.rawdata.push_back(e.ayenv.period_low);
|
||||
}
|
||||
|
||||
if (ef & (OPM_REC_AYENV_ENVTYPE | OPM_REC_AYENV_PERIOD_LOW)) {
|
||||
// envelope type available, write it
|
||||
auto ef_cur = ef;
|
||||
ef &= ~(OPM_REC_AYENV_ENVTYPE | OPM_REC_AYENV_PERIOD_LOW); // write both envtype and period low if available
|
||||
// write registers
|
||||
s.rawdata.push_back(
|
||||
OPM_AYENV_ENVTYPE |
|
||||
((ef == 0) && (i == s.records.size() - 1) ? OPM_AYENV_CMD80_EOF : 0) |
|
||||
(ef_cur & OPM_REC_AYENV_PERIOD_LOW ? OPM_AYENV_CMD80_PERIOD_LOW : 0) |
|
||||
(e.ayenv.envtype & OPM_AYENV_CMD80_ENV_TYPE)
|
||||
);
|
||||
if (ef_cur & OPM_REC_AYENV_PERIOD_LOW) s.rawdata.push_back(e.ayenv.period_low);
|
||||
}
|
||||
if (ef & OPM_REC_AYENV_ENVTYPE) {
|
||||
// envelope type available, write it
|
||||
auto ef_cur = ef;
|
||||
ef &= ~(OPM_REC_AYENV_ENVTYPE | OPM_REC_AYENV_PERIOD_LOW); // write both envtype and period low if available
|
||||
// write registers
|
||||
s.rawdata.push_back(
|
||||
OPM_AYENV_ENVTYPE |
|
||||
((ef == 0) && (i == s.records.size() - 1) ? OPM_AYENV_CMD80_EOF : 0) |
|
||||
(ef_cur & OPM_REC_AYENV_PERIOD_LOW ? OPM_AYENV_CMD80_PERIOD_LOW : 0) |
|
||||
(e.ayenv.envtype & OPM_AYENV_CMD80_ENV_TYPE)
|
||||
);
|
||||
if (ef_cur & OPM_REC_AYENV_PERIOD_LOW) s.rawdata.push_back(e.ayenv.period_low);
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // if (s.records.size() > 0)
|
||||
if (delay_emitted) s.rawdata.push_back(OPM_STREAM_END_FRAME);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -1179,7 +1004,7 @@ int opm_serialize_stream(opm_convert_context_t* ctx) {
|
|||
// ah fuck do it for each chip type separately
|
||||
if (ctx->chip_type <= OPM_FLAG_CHIP_OPN_DUAL) {
|
||||
int dch = 0;
|
||||
for (int chip = 0; chip < (ctx->chip_type == OPM_FLAG_CHIP_OPN_DUAL ? 2 : 1); chip++) {
|
||||
for (int chip = 0; chip < ctx->chip_type == OPM_FLAG_CHIP_OPN_DUAL ? 2 : 1; chip++) {
|
||||
opm_serialize_control_stream(dch + ctx->ch.data() + ctx->ctrl_chidx);
|
||||
for (int fmch = 0; fmch < 3; fmch++) {
|
||||
opm_serialize_fm_stream(dch + ctx->ch.data() + ctx->fm_chidx + fmch);
|
||||
|
@ -1200,7 +1025,6 @@ int opm_serialize_stream(opm_convert_context_t* ctx) {
|
|||
opm_serialize_ssg_tone_stream(ctx->ch.data() + ctx->ssg_chidx + aych);
|
||||
}
|
||||
opm_serialize_ssg_env_stream(ctx->ch.data() + ctx->ssg_chidx + 3);
|
||||
opm_serialize_rhythm_stream(ctx->ch.data() + ctx->rhy_chidx);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -1323,14 +1147,10 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
// file names
|
||||
std::string infile_str = argv[1];
|
||||
|
||||
std::string infile_str_raw = infile_str.substr(0, infile_str.find_last_of('.'));
|
||||
std::string outfile_str = infile_str.substr(0, infile_str.find(".vgm")) + ".opm";
|
||||
std::string csvfile_str = infile_str.substr(0, infile_str.find(".vgm")) + ".csv";
|
||||
std::string logfile_str = infile_str.substr(0, infile_str.find(".vgm")) + ".log";
|
||||
ctx->opmfile.filename = outfile_str;
|
||||
ctx->opmfile.foldername = infile_str_raw;
|
||||
ctx->opmfile.name_prefix = infile_str_raw.substr(infile_str_raw.find_last_of("/\\") + 1);
|
||||
ctx->logname = logfile_str;
|
||||
|
||||
// good old stdio :)
|
||||
|
@ -1384,13 +1204,21 @@ int main(int argc, char* argv[]) {
|
|||
else {
|
||||
ctx->chip_type = OPM_FLAG_CHIP_OPN;
|
||||
ctx->max_channels = 1+3+4;
|
||||
ctx->ctrl_chidx = 0;
|
||||
ctx->ssg_chidx = 3;
|
||||
ctx->fm_chidx = 1;
|
||||
ctx->rhy_chidx = 0; // not available!
|
||||
ctx->dual_chidx = 0;
|
||||
}
|
||||
}
|
||||
else if (ctx->vgm.header->YM2608_Clock != 0) {
|
||||
ctx->chip_type = OPM_FLAG_CHIP_OPNA;
|
||||
oplClockRate = ctx->vgm.header->YM2608_Clock & ((1 << 30) - 1);
|
||||
ctx->max_channels = 1+6+4+1;
|
||||
ctx->ctrl_chidx = 0;
|
||||
ctx->fm_chidx = 1;
|
||||
ctx->ssg_chidx = ctx->fm_chidx+6;
|
||||
ctx->rhy_chidx = ctx->ssg_chidx + 4;
|
||||
ctx->dual_chidx = ctx->fm_chidx+3; // for channels 4-6
|
||||
}
|
||||
else {
|
||||
printf("OPN data not found!\n");
|
||||
return 1;
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30804.86
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lxmplay", "lxmplay\lxmplay.vcxproj", "{57A21450-1B4A-4C1C-8807-735A6F2F5929}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{57A21450-1B4A-4C1C-8807-735A6F2F5929}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{57A21450-1B4A-4C1C-8807-735A6F2F5929}.Debug|x64.Build.0 = Debug|x64
|
||||
{57A21450-1B4A-4C1C-8807-735A6F2F5929}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{57A21450-1B4A-4C1C-8807-735A6F2F5929}.Debug|x86.Build.0 = Debug|Win32
|
||||
{57A21450-1B4A-4C1C-8807-735A6F2F5929}.Release|x64.ActiveCfg = Release|x64
|
||||
{57A21450-1B4A-4C1C-8807-735A6F2F5929}.Release|x64.Build.0 = Release|x64
|
||||
{57A21450-1B4A-4C1C-8807-735A6F2F5929}.Release|x86.ActiveCfg = Release|Win32
|
||||
{57A21450-1B4A-4C1C-8807-735A6F2F5929}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {E38B3B88-FC58-4459-9C7B-6A765F6EAB39}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -1,150 +0,0 @@
|
|||
#ifndef PA_ASIO_H
|
||||
#define PA_ASIO_H
|
||||
/*
|
||||
* $Id$
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* ASIO specific extensions
|
||||
*
|
||||
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
|
||||
/** @file
|
||||
@ingroup public_header
|
||||
@brief ASIO-specific PortAudio API extension header file.
|
||||
*/
|
||||
|
||||
#include "portaudio.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
/** Retrieve legal native buffer sizes for the specified device, in sample frames.
|
||||
|
||||
@param device The global index of the device about which the query is being made.
|
||||
@param minBufferSizeFrames A pointer to the location which will receive the minimum buffer size value.
|
||||
@param maxBufferSizeFrames A pointer to the location which will receive the maximum buffer size value.
|
||||
@param preferredBufferSizeFrames A pointer to the location which will receive the preferred buffer size value.
|
||||
@param granularity A pointer to the location which will receive the "granularity". This value determines
|
||||
the step size used to compute the legal values between minBufferSizeFrames and maxBufferSizeFrames.
|
||||
If granularity is -1 then available buffer size values are powers of two.
|
||||
|
||||
@see ASIOGetBufferSize in the ASIO SDK.
|
||||
|
||||
@note: this function used to be called PaAsio_GetAvailableLatencyValues. There is a
|
||||
#define that maps PaAsio_GetAvailableLatencyValues to this function for backwards compatibility.
|
||||
*/
|
||||
PaError PaAsio_GetAvailableBufferSizes( PaDeviceIndex device,
|
||||
long *minBufferSizeFrames, long *maxBufferSizeFrames, long *preferredBufferSizeFrames, long *granularity );
|
||||
|
||||
|
||||
/** Backwards compatibility alias for PaAsio_GetAvailableBufferSizes
|
||||
|
||||
@see PaAsio_GetAvailableBufferSizes
|
||||
*/
|
||||
#define PaAsio_GetAvailableLatencyValues PaAsio_GetAvailableBufferSizes
|
||||
|
||||
|
||||
/** Display the ASIO control panel for the specified device.
|
||||
|
||||
@param device The global index of the device whose control panel is to be displayed.
|
||||
@param systemSpecific On Windows, the calling application's main window handle,
|
||||
on Macintosh this value should be zero.
|
||||
*/
|
||||
PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific );
|
||||
|
||||
|
||||
|
||||
|
||||
/** Retrieve a pointer to a string containing the name of the specified
|
||||
input channel. The string is valid until Pa_Terminate is called.
|
||||
|
||||
The string will be no longer than 32 characters including the null terminator.
|
||||
*/
|
||||
PaError PaAsio_GetInputChannelName( PaDeviceIndex device, int channelIndex,
|
||||
const char** channelName );
|
||||
|
||||
|
||||
/** Retrieve a pointer to a string containing the name of the specified
|
||||
input channel. The string is valid until Pa_Terminate is called.
|
||||
|
||||
The string will be no longer than 32 characters including the null terminator.
|
||||
*/
|
||||
PaError PaAsio_GetOutputChannelName( PaDeviceIndex device, int channelIndex,
|
||||
const char** channelName );
|
||||
|
||||
|
||||
/** Set the sample rate of an open paASIO stream.
|
||||
|
||||
@param stream The stream to operate on.
|
||||
@param sampleRate The new sample rate.
|
||||
|
||||
Note that this function may fail if the stream is already running and the
|
||||
ASIO driver does not support switching the sample rate of a running stream.
|
||||
|
||||
Returns paIncompatibleStreamHostApi if stream is not a paASIO stream.
|
||||
*/
|
||||
PaError PaAsio_SetStreamSampleRate( PaStream* stream, double sampleRate );
|
||||
|
||||
|
||||
#define paAsioUseChannelSelectors (0x01)
|
||||
|
||||
typedef struct PaAsioStreamInfo{
|
||||
unsigned long size; /**< sizeof(PaAsioStreamInfo) */
|
||||
PaHostApiTypeId hostApiType; /**< paASIO */
|
||||
unsigned long version; /**< 1 */
|
||||
|
||||
unsigned long flags;
|
||||
|
||||
/* Support for opening only specific channels of an ASIO device.
|
||||
If the paAsioUseChannelSelectors flag is set, channelSelectors is a
|
||||
pointer to an array of integers specifying the device channels to use.
|
||||
When used, the length of the channelSelectors array must match the
|
||||
corresponding channelCount parameter to Pa_OpenStream() otherwise a
|
||||
crash may result.
|
||||
The values in the selectors array must specify channels within the
|
||||
range of supported channels for the device or paInvalidChannelCount will
|
||||
result.
|
||||
*/
|
||||
int *channelSelectors;
|
||||
}PaAsioStreamInfo;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* PA_ASIO_H */
|
|
@ -1,77 +0,0 @@
|
|||
#ifndef PA_JACK_H
|
||||
#define PA_JACK_H
|
||||
|
||||
/*
|
||||
* $Id:
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* JACK-specific extensions
|
||||
*
|
||||
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
/** @file
|
||||
* @ingroup public_header
|
||||
* @brief JACK-specific PortAudio API extension header file.
|
||||
*/
|
||||
|
||||
#include "portaudio.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Set the JACK client name.
|
||||
*
|
||||
* During Pa_Initialize, When PA JACK connects as a client of the JACK server, it requests a certain
|
||||
* name, which is for instance prepended to port names. By default this name is "PortAudio". The
|
||||
* JACK server may append a suffix to the client name, in order to avoid clashes among clients that
|
||||
* try to connect with the same name (e.g., different PA JACK clients).
|
||||
*
|
||||
* This function must be called before Pa_Initialize, otherwise it won't have any effect. Note that
|
||||
* the string is not copied, but instead referenced directly, so it must not be freed for as long as
|
||||
* PA might need it.
|
||||
* @sa PaJack_GetClientName
|
||||
*/
|
||||
PaError PaJack_SetClientName( const char* name );
|
||||
|
||||
/** Get the JACK client name used by PA JACK.
|
||||
*
|
||||
* The caller is responsible for freeing the returned pointer.
|
||||
*/
|
||||
PaError PaJack_GetClientName(const char** clientName);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -1,107 +0,0 @@
|
|||
#ifndef PA_LINUX_ALSA_H
|
||||
#define PA_LINUX_ALSA_H
|
||||
|
||||
/*
|
||||
* $Id$
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* ALSA-specific extensions
|
||||
*
|
||||
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
/** @file
|
||||
* @ingroup public_header
|
||||
* @brief ALSA-specific PortAudio API extension header file.
|
||||
*/
|
||||
|
||||
#include "portaudio.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct PaAlsaStreamInfo
|
||||
{
|
||||
unsigned long size;
|
||||
PaHostApiTypeId hostApiType;
|
||||
unsigned long version;
|
||||
|
||||
const char *deviceString;
|
||||
}
|
||||
PaAlsaStreamInfo;
|
||||
|
||||
/** Initialize host API specific structure, call this before setting relevant attributes. */
|
||||
void PaAlsa_InitializeStreamInfo( PaAlsaStreamInfo *info );
|
||||
|
||||
/** Instruct whether to enable real-time priority when starting the audio thread.
|
||||
*
|
||||
* If this is turned on by the stream is started, the audio callback thread will be created
|
||||
* with the FIFO scheduling policy, which is suitable for realtime operation.
|
||||
**/
|
||||
void PaAlsa_EnableRealtimeScheduling( PaStream *s, int enable );
|
||||
|
||||
#if 0
|
||||
void PaAlsa_EnableWatchdog( PaStream *s, int enable );
|
||||
#endif
|
||||
|
||||
/** Get the ALSA-lib card index of this stream's input device. */
|
||||
PaError PaAlsa_GetStreamInputCard( PaStream *s, int *card );
|
||||
|
||||
/** Get the ALSA-lib card index of this stream's output device. */
|
||||
PaError PaAlsa_GetStreamOutputCard( PaStream *s, int *card );
|
||||
|
||||
/** Set the number of periods (buffer fragments) to configure devices with.
|
||||
*
|
||||
* By default the number of periods is 4, this is the lowest number of periods that works well on
|
||||
* the author's soundcard.
|
||||
* @param numPeriods The number of periods.
|
||||
*/
|
||||
PaError PaAlsa_SetNumPeriods( int numPeriods );
|
||||
|
||||
/** Set the maximum number of times to retry opening busy device (sleeping for a
|
||||
* short interval inbetween).
|
||||
*/
|
||||
PaError PaAlsa_SetRetriesBusy( int retries );
|
||||
|
||||
/** Set the path and name of ALSA library file if PortAudio is configured to load it dynamically (see
|
||||
* PA_ALSA_DYNAMIC). This setting will overwrite the default name set by PA_ALSA_PATHNAME define.
|
||||
* @param pathName Full path with filename. Only filename can be used, but dlopen() will lookup default
|
||||
* searchable directories (/usr/lib;/usr/local/lib) then.
|
||||
*/
|
||||
void PaAlsa_SetLibraryPathName( const char *pathName );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -1,191 +0,0 @@
|
|||
#ifndef PA_MAC_CORE_H
|
||||
#define PA_MAC_CORE_H
|
||||
/*
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* Macintosh Core Audio specific extensions
|
||||
* portaudio.h should be included before this file.
|
||||
*
|
||||
* Copyright (c) 2005-2006 Bjorn Roche
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
/** @file
|
||||
* @ingroup public_header
|
||||
* @brief CoreAudio-specific PortAudio API extension header file.
|
||||
*/
|
||||
|
||||
#include "portaudio.h"
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* A pointer to a paMacCoreStreamInfo may be passed as
|
||||
* the hostApiSpecificStreamInfo in the PaStreamParameters struct
|
||||
* when opening a stream or querying the format. Use NULL, for the
|
||||
* defaults. Note that for duplex streams, flags for input and output
|
||||
* should be the same or behaviour is undefined.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
unsigned long size; /**size of whole structure including this header */
|
||||
PaHostApiTypeId hostApiType; /**host API for which this data is intended */
|
||||
unsigned long version; /**structure version */
|
||||
unsigned long flags; /** flags to modify behaviour */
|
||||
SInt32 const * channelMap; /** Channel map for HAL channel mapping , if not needed, use NULL;*/
|
||||
unsigned long channelMapSize; /** Channel map size for HAL channel mapping , if not needed, use 0;*/
|
||||
} PaMacCoreStreamInfo;
|
||||
|
||||
/**
|
||||
* Functions
|
||||
*/
|
||||
|
||||
|
||||
/** Use this function to initialize a paMacCoreStreamInfo struct
|
||||
* using the requested flags. Note that channel mapping is turned
|
||||
* off after a call to this function.
|
||||
* @param data The datastructure to initialize
|
||||
* @param flags The flags to initialize the datastructure with.
|
||||
*/
|
||||
void PaMacCore_SetupStreamInfo( PaMacCoreStreamInfo *data, unsigned long flags );
|
||||
|
||||
/** call this after pa_SetupMacCoreStreamInfo to use channel mapping as described in notes.txt.
|
||||
* @param data The stream info structure to assign a channel mapping to
|
||||
* @param channelMap The channel map array, as described in notes.txt. This array pointer will be used directly (ie the underlying data will not be copied), so the caller should not free the array until after the stream has been opened.
|
||||
* @param channelMapSize The size of the channel map array.
|
||||
*/
|
||||
void PaMacCore_SetupChannelMap( PaMacCoreStreamInfo *data, const SInt32 * const channelMap, unsigned long channelMapSize );
|
||||
|
||||
/**
|
||||
* Retrieve the AudioDeviceID of the input device assigned to an open stream
|
||||
*
|
||||
* @param s The stream to query.
|
||||
*
|
||||
* @return A valid AudioDeviceID, or NULL if an error occurred.
|
||||
*/
|
||||
AudioDeviceID PaMacCore_GetStreamInputDevice( PaStream* s );
|
||||
|
||||
/**
|
||||
* Retrieve the AudioDeviceID of the output device assigned to an open stream
|
||||
*
|
||||
* @param s The stream to query.
|
||||
*
|
||||
* @return A valid AudioDeviceID, or NULL if an error occurred.
|
||||
*/
|
||||
AudioDeviceID PaMacCore_GetStreamOutputDevice( PaStream* s );
|
||||
|
||||
/**
|
||||
* Returns a statically allocated string with the device's name
|
||||
* for the given channel. NULL will be returned on failure.
|
||||
*
|
||||
* This function's implementation is not complete!
|
||||
*
|
||||
* @param device The PortAudio device index.
|
||||
* @param channel The channel number who's name is requested.
|
||||
* @return a statically allocated string with the name of the device.
|
||||
* Because this string is statically allocated, it must be
|
||||
* copied if it is to be saved and used by the user after
|
||||
* another call to this function.
|
||||
*
|
||||
*/
|
||||
const char *PaMacCore_GetChannelName( int device, int channelIndex, bool input );
|
||||
|
||||
|
||||
/** Retrieve the range of legal native buffer sizes for the specified device, in sample frames.
|
||||
|
||||
@param device The global index of the PortAudio device about which the query is being made.
|
||||
@param minBufferSizeFrames A pointer to the location which will receive the minimum buffer size value.
|
||||
@param maxBufferSizeFrames A pointer to the location which will receive the maximum buffer size value.
|
||||
|
||||
@see kAudioDevicePropertyBufferFrameSizeRange in the CoreAudio SDK.
|
||||
*/
|
||||
PaError PaMacCore_GetBufferSizeRange( PaDeviceIndex device,
|
||||
long *minBufferSizeFrames, long *maxBufferSizeFrames );
|
||||
|
||||
|
||||
/**
|
||||
* Flags
|
||||
*/
|
||||
|
||||
/**
|
||||
* The following flags alter the behaviour of PA on the mac platform.
|
||||
* they can be ORed together. These should work both for opening and
|
||||
* checking a device.
|
||||
*/
|
||||
|
||||
/** Allows PortAudio to change things like the device's frame size,
|
||||
* which allows for much lower latency, but might disrupt the device
|
||||
* if other programs are using it, even when you are just Querying
|
||||
* the device. */
|
||||
#define paMacCoreChangeDeviceParameters (0x01)
|
||||
|
||||
/** In combination with the above flag,
|
||||
* causes the stream opening to fail, unless the exact sample rates
|
||||
* are supported by the device. */
|
||||
#define paMacCoreFailIfConversionRequired (0x02)
|
||||
|
||||
/** These flags set the SR conversion quality, if required. The weird ordering
|
||||
* allows Maximum Quality to be the default.*/
|
||||
#define paMacCoreConversionQualityMin (0x0100)
|
||||
#define paMacCoreConversionQualityMedium (0x0200)
|
||||
#define paMacCoreConversionQualityLow (0x0300)
|
||||
#define paMacCoreConversionQualityHigh (0x0400)
|
||||
#define paMacCoreConversionQualityMax (0x0000)
|
||||
|
||||
/**
|
||||
* Here are some "preset" combinations of flags (above) to get to some
|
||||
* common configurations. THIS IS OVERKILL, but if more flags are added
|
||||
* it won't be.
|
||||
*/
|
||||
|
||||
/**This is the default setting: do as much sample rate conversion as possible
|
||||
* and as little mucking with the device as possible. */
|
||||
#define paMacCorePlayNice (0x00)
|
||||
/**This setting is tuned for pro audio apps. It allows SR conversion on input
|
||||
and output, but it tries to set the appropriate SR on the device.*/
|
||||
#define paMacCorePro (0x01)
|
||||
/**This is a setting to minimize CPU usage and still play nice.*/
|
||||
#define paMacCoreMinimizeCPUButPlayNice (0x0100)
|
||||
/**This is a setting to minimize CPU usage, even if that means interrupting the device. */
|
||||
#define paMacCoreMinimizeCPU (0x0101)
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /** __cplusplus */
|
||||
|
||||
#endif /** PA_MAC_CORE_H */
|
|
@ -1,95 +0,0 @@
|
|||
#ifndef PA_WIN_DS_H
|
||||
#define PA_WIN_DS_H
|
||||
/*
|
||||
* $Id: $
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* DirectSound specific extensions
|
||||
*
|
||||
* Copyright (c) 1999-2007 Ross Bencina and Phil Burk
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
/** @file
|
||||
@ingroup public_header
|
||||
@brief DirectSound-specific PortAudio API extension header file.
|
||||
*/
|
||||
|
||||
#include "portaudio.h"
|
||||
#include "pa_win_waveformat.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
#define paWinDirectSoundUseLowLevelLatencyParameters (0x01)
|
||||
#define paWinDirectSoundUseChannelMask (0x04)
|
||||
|
||||
|
||||
typedef struct PaWinDirectSoundStreamInfo{
|
||||
unsigned long size; /**< sizeof(PaWinDirectSoundStreamInfo) */
|
||||
PaHostApiTypeId hostApiType; /**< paDirectSound */
|
||||
unsigned long version; /**< 2 */
|
||||
|
||||
unsigned long flags; /**< enable other features of this struct */
|
||||
|
||||
/**
|
||||
low-level latency setting support
|
||||
Sets the size of the DirectSound host buffer.
|
||||
When flags contains the paWinDirectSoundUseLowLevelLatencyParameters
|
||||
this size will be used instead of interpreting the generic latency
|
||||
parameters to Pa_OpenStream(). If the flag is not set this value is ignored.
|
||||
|
||||
If the stream is a full duplex stream the implementation requires that
|
||||
the values of framesPerBuffer for input and output match (if both are specified).
|
||||
*/
|
||||
unsigned long framesPerBuffer;
|
||||
|
||||
/**
|
||||
support for WAVEFORMATEXTENSIBLE channel masks. If flags contains
|
||||
paWinDirectSoundUseChannelMask this allows you to specify which speakers
|
||||
to address in a multichannel stream. Constants for channelMask
|
||||
are specified in pa_win_waveformat.h
|
||||
|
||||
*/
|
||||
PaWinWaveFormatChannelMask channelMask;
|
||||
|
||||
}PaWinDirectSoundStreamInfo;
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* PA_WIN_DS_H */
|
|
@ -1,729 +0,0 @@
|
|||
#ifndef PA_WIN_WASAPI_H
|
||||
#define PA_WIN_WASAPI_H
|
||||
/*
|
||||
* $Id: $
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* WASAPI specific extensions
|
||||
*
|
||||
* Copyright (c) 1999-2018 Ross Bencina and Phil Burk
|
||||
* Copyright (c) 2006-2010 David Viens
|
||||
* Copyright (c) 2010-2018 Dmitry Kostjuchenko
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
/** @file
|
||||
@ingroup public_header
|
||||
@brief WASAPI-specific PortAudio API extension header file.
|
||||
*/
|
||||
|
||||
#include "portaudio.h"
|
||||
#include "pa_win_waveformat.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
/* Stream setup flags. */
|
||||
typedef enum PaWasapiFlags
|
||||
{
|
||||
/* put WASAPI into exclusive mode */
|
||||
paWinWasapiExclusive = (1 << 0),
|
||||
|
||||
/* allow to skip internal PA processing completely */
|
||||
paWinWasapiRedirectHostProcessor = (1 << 1),
|
||||
|
||||
/* assign custom channel mask */
|
||||
paWinWasapiUseChannelMask = (1 << 2),
|
||||
|
||||
/* select non-Event driven method of data read/write
|
||||
Note: WASAPI Event driven core is capable of 2ms latency!!!, but Polling
|
||||
method can only provide 15-20ms latency. */
|
||||
paWinWasapiPolling = (1 << 3),
|
||||
|
||||
/* force custom thread priority setting, must be used if PaWasapiStreamInfo::threadPriority
|
||||
is set to a custom value */
|
||||
paWinWasapiThreadPriority = (1 << 4),
|
||||
|
||||
/* force explicit sample format and do not allow PA to select suitable working format, API will
|
||||
fail if provided sample format is not supported by audio hardware in Exclusive mode
|
||||
or system mixer in Shared mode */
|
||||
paWinWasapiExplicitSampleFormat = (1 << 5),
|
||||
|
||||
/* allow API to insert system-level channel matrix mixer and sample rate converter to allow
|
||||
playback formats that do not match the current configured system settings.
|
||||
this is in particular required for streams not matching the system mixer sample rate.
|
||||
only applies in Shared mode. */
|
||||
paWinWasapiAutoConvert = (1 << 6)
|
||||
}
|
||||
PaWasapiFlags;
|
||||
#define paWinWasapiExclusive (paWinWasapiExclusive)
|
||||
#define paWinWasapiRedirectHostProcessor (paWinWasapiRedirectHostProcessor)
|
||||
#define paWinWasapiUseChannelMask (paWinWasapiUseChannelMask)
|
||||
#define paWinWasapiPolling (paWinWasapiPolling)
|
||||
#define paWinWasapiThreadPriority (paWinWasapiThreadPriority)
|
||||
#define paWinWasapiExplicitSampleFormat (paWinWasapiExplicitSampleFormat)
|
||||
#define paWinWasapiAutoConvert (paWinWasapiAutoConvert)
|
||||
|
||||
|
||||
/* Stream state.
|
||||
|
||||
@note Multiple states can be united into a bitmask.
|
||||
@see PaWasapiStreamStateCallback, PaWasapi_SetStreamStateHandler
|
||||
*/
|
||||
typedef enum PaWasapiStreamState
|
||||
{
|
||||
/* state change was caused by the error:
|
||||
|
||||
Example:
|
||||
1) If thread execution stopped due to AUDCLNT_E_RESOURCES_INVALIDATED then state
|
||||
value will contain paWasapiStreamStateError|paWasapiStreamStateThreadStop.
|
||||
*/
|
||||
paWasapiStreamStateError = (1 << 0),
|
||||
|
||||
/* processing thread is preparing to start execution */
|
||||
paWasapiStreamStateThreadPrepare = (1 << 1),
|
||||
|
||||
/* processing thread started execution (enters its loop) */
|
||||
paWasapiStreamStateThreadStart = (1 << 2),
|
||||
|
||||
/* processing thread stopped execution */
|
||||
paWasapiStreamStateThreadStop = (1 << 3)
|
||||
}
|
||||
PaWasapiStreamState;
|
||||
#define paWasapiStreamStateError (paWasapiStreamStateError)
|
||||
#define paWasapiStreamStateThreadPrepare (paWasapiStreamStateThreadPrepare)
|
||||
#define paWasapiStreamStateThreadStart (paWasapiStreamStateThreadStart)
|
||||
#define paWasapiStreamStateThreadStop (paWasapiStreamStateThreadStop)
|
||||
|
||||
|
||||
/* Host processor.
|
||||
|
||||
Allows to skip internal PA processing completely. paWinWasapiRedirectHostProcessor flag
|
||||
must be set to the PaWasapiStreamInfo::flags member in order to have host processor
|
||||
redirected to this callback.
|
||||
|
||||
Use with caution! inputFrames and outputFrames depend solely on final device setup.
|
||||
To query max values of inputFrames/outputFrames use PaWasapi_GetFramesPerHostBuffer.
|
||||
*/
|
||||
typedef void (*PaWasapiHostProcessorCallback) (void *inputBuffer, long inputFrames,
|
||||
void *outputBuffer, long outputFrames, void *userData);
|
||||
|
||||
|
||||
/* Stream state handler.
|
||||
|
||||
@param pStream Pointer to PaStream object.
|
||||
@param stateFlags State flags, a collection of values from PaWasapiStreamState enum.
|
||||
@param errorId Error id provided by system API (HRESULT).
|
||||
@param userData Pointer to user data.
|
||||
|
||||
@see PaWasapiStreamState
|
||||
*/
|
||||
typedef void (*PaWasapiStreamStateCallback) (PaStream *pStream, unsigned int stateFlags,
|
||||
unsigned int errorId, void *pUserData);
|
||||
|
||||
|
||||
/* Device role. */
|
||||
typedef enum PaWasapiDeviceRole
|
||||
{
|
||||
eRoleRemoteNetworkDevice = 0,
|
||||
eRoleSpeakers,
|
||||
eRoleLineLevel,
|
||||
eRoleHeadphones,
|
||||
eRoleMicrophone,
|
||||
eRoleHeadset,
|
||||
eRoleHandset,
|
||||
eRoleUnknownDigitalPassthrough,
|
||||
eRoleSPDIF,
|
||||
eRoleHDMI,
|
||||
eRoleUnknownFormFactor
|
||||
}
|
||||
PaWasapiDeviceRole;
|
||||
|
||||
|
||||
/* Jack connection type. */
|
||||
typedef enum PaWasapiJackConnectionType
|
||||
{
|
||||
eJackConnTypeUnknown,
|
||||
eJackConnType3Point5mm,
|
||||
eJackConnTypeQuarter,
|
||||
eJackConnTypeAtapiInternal,
|
||||
eJackConnTypeRCA,
|
||||
eJackConnTypeOptical,
|
||||
eJackConnTypeOtherDigital,
|
||||
eJackConnTypeOtherAnalog,
|
||||
eJackConnTypeMultichannelAnalogDIN,
|
||||
eJackConnTypeXlrProfessional,
|
||||
eJackConnTypeRJ11Modem,
|
||||
eJackConnTypeCombination
|
||||
}
|
||||
PaWasapiJackConnectionType;
|
||||
|
||||
|
||||
/* Jack geometric location. */
|
||||
typedef enum PaWasapiJackGeoLocation
|
||||
{
|
||||
eJackGeoLocUnk = 0,
|
||||
eJackGeoLocRear = 0x1, /* matches EPcxGeoLocation::eGeoLocRear */
|
||||
eJackGeoLocFront,
|
||||
eJackGeoLocLeft,
|
||||
eJackGeoLocRight,
|
||||
eJackGeoLocTop,
|
||||
eJackGeoLocBottom,
|
||||
eJackGeoLocRearPanel,
|
||||
eJackGeoLocRiser,
|
||||
eJackGeoLocInsideMobileLid,
|
||||
eJackGeoLocDrivebay,
|
||||
eJackGeoLocHDMI,
|
||||
eJackGeoLocOutsideMobileLid,
|
||||
eJackGeoLocATAPI,
|
||||
eJackGeoLocReserved5,
|
||||
eJackGeoLocReserved6,
|
||||
}
|
||||
PaWasapiJackGeoLocation;
|
||||
|
||||
|
||||
/* Jack general location. */
|
||||
typedef enum PaWasapiJackGenLocation
|
||||
{
|
||||
eJackGenLocPrimaryBox = 0,
|
||||
eJackGenLocInternal,
|
||||
eJackGenLocSeparate,
|
||||
eJackGenLocOther
|
||||
}
|
||||
PaWasapiJackGenLocation;
|
||||
|
||||
|
||||
/* Jack's type of port. */
|
||||
typedef enum PaWasapiJackPortConnection
|
||||
{
|
||||
eJackPortConnJack = 0,
|
||||
eJackPortConnIntegratedDevice,
|
||||
eJackPortConnBothIntegratedAndJack,
|
||||
eJackPortConnUnknown
|
||||
}
|
||||
PaWasapiJackPortConnection;
|
||||
|
||||
|
||||
/* Thread priority. */
|
||||
typedef enum PaWasapiThreadPriority
|
||||
{
|
||||
eThreadPriorityNone = 0,
|
||||
eThreadPriorityAudio, //!< Default for Shared mode.
|
||||
eThreadPriorityCapture,
|
||||
eThreadPriorityDistribution,
|
||||
eThreadPriorityGames,
|
||||
eThreadPriorityPlayback,
|
||||
eThreadPriorityProAudio, //!< Default for Exclusive mode.
|
||||
eThreadPriorityWindowManager
|
||||
}
|
||||
PaWasapiThreadPriority;
|
||||
|
||||
|
||||
/* Stream descriptor. */
|
||||
typedef struct PaWasapiJackDescription
|
||||
{
|
||||
unsigned long channelMapping;
|
||||
unsigned long color; /* derived from macro: #define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16))) */
|
||||
PaWasapiJackConnectionType connectionType;
|
||||
PaWasapiJackGeoLocation geoLocation;
|
||||
PaWasapiJackGenLocation genLocation;
|
||||
PaWasapiJackPortConnection portConnection;
|
||||
unsigned int isConnected;
|
||||
}
|
||||
PaWasapiJackDescription;
|
||||
|
||||
|
||||
/** Stream category.
|
||||
Note:
|
||||
- values are equal to WASAPI AUDIO_STREAM_CATEGORY enum
|
||||
- supported since Windows 8.0, noop on earlier versions
|
||||
- values 1,2 are deprecated on Windows 10 and not included into enumeration
|
||||
|
||||
@version Available as of 19.6.0
|
||||
*/
|
||||
typedef enum PaWasapiStreamCategory
|
||||
{
|
||||
eAudioCategoryOther = 0,
|
||||
eAudioCategoryCommunications = 3,
|
||||
eAudioCategoryAlerts = 4,
|
||||
eAudioCategorySoundEffects = 5,
|
||||
eAudioCategoryGameEffects = 6,
|
||||
eAudioCategoryGameMedia = 7,
|
||||
eAudioCategoryGameChat = 8,
|
||||
eAudioCategorySpeech = 9,
|
||||
eAudioCategoryMovie = 10,
|
||||
eAudioCategoryMedia = 11
|
||||
}
|
||||
PaWasapiStreamCategory;
|
||||
|
||||
|
||||
/** Stream option.
|
||||
Note:
|
||||
- values are equal to WASAPI AUDCLNT_STREAMOPTIONS enum
|
||||
- supported since Windows 8.1, noop on earlier versions
|
||||
|
||||
@version Available as of 19.6.0
|
||||
*/
|
||||
typedef enum PaWasapiStreamOption
|
||||
{
|
||||
eStreamOptionNone = 0, //!< default
|
||||
eStreamOptionRaw = 1, //!< bypass WASAPI Audio Engine DSP effects, supported since Windows 8.1
|
||||
eStreamOptionMatchFormat = 2 //!< force WASAPI Audio Engine into a stream format, supported since Windows 10
|
||||
}
|
||||
PaWasapiStreamOption;
|
||||
|
||||
|
||||
/* Stream descriptor. */
|
||||
typedef struct PaWasapiStreamInfo
|
||||
{
|
||||
unsigned long size; /**< sizeof(PaWasapiStreamInfo) */
|
||||
PaHostApiTypeId hostApiType; /**< paWASAPI */
|
||||
unsigned long version; /**< 1 */
|
||||
|
||||
unsigned long flags; /**< collection of PaWasapiFlags */
|
||||
|
||||
/** Support for WAVEFORMATEXTENSIBLE channel masks. If flags contains
|
||||
paWinWasapiUseChannelMask this allows you to specify which speakers
|
||||
to address in a multichannel stream. Constants for channelMask
|
||||
are specified in pa_win_waveformat.h. Will be used only if
|
||||
paWinWasapiUseChannelMask flag is specified.
|
||||
*/
|
||||
PaWinWaveFormatChannelMask channelMask;
|
||||
|
||||
/** Delivers raw data to callback obtained from GetBuffer() methods skipping
|
||||
internal PortAudio processing inventory completely. userData parameter will
|
||||
be the same that was passed to Pa_OpenStream method. Will be used only if
|
||||
paWinWasapiRedirectHostProcessor flag is specified.
|
||||
*/
|
||||
PaWasapiHostProcessorCallback hostProcessorOutput;
|
||||
PaWasapiHostProcessorCallback hostProcessorInput;
|
||||
|
||||
/** Specifies thread priority explicitly. Will be used only if paWinWasapiThreadPriority flag
|
||||
is specified.
|
||||
|
||||
Please note, if Input/Output streams are opened simultaneously (Full-Duplex mode)
|
||||
you shall specify same value for threadPriority or othervise one of the values will be used
|
||||
to setup thread priority.
|
||||
*/
|
||||
PaWasapiThreadPriority threadPriority;
|
||||
|
||||
/** Stream category.
|
||||
@see PaWasapiStreamCategory
|
||||
@version Available as of 19.6.0
|
||||
*/
|
||||
PaWasapiStreamCategory streamCategory;
|
||||
|
||||
/** Stream option.
|
||||
@see PaWasapiStreamOption
|
||||
@version Available as of 19.6.0
|
||||
*/
|
||||
PaWasapiStreamOption streamOption;
|
||||
}
|
||||
PaWasapiStreamInfo;
|
||||
|
||||
|
||||
/** Returns pointer to WASAPI's IAudioClient object of the stream.
|
||||
|
||||
@param pStream Pointer to PaStream object.
|
||||
@param pAudioClient Pointer to pointer of IAudioClient.
|
||||
@param bOutput TRUE (1) for output stream, FALSE (0) for input stream.
|
||||
|
||||
@return Error code indicating success or failure.
|
||||
*/
|
||||
PaError PaWasapi_GetAudioClient( PaStream *pStream, void **pAudioClient, int bOutput );
|
||||
|
||||
|
||||
/** Update device list.
|
||||
|
||||
This function is available if PA_WASAPI_MAX_CONST_DEVICE_COUNT is defined during compile time
|
||||
with maximum constant WASAPI device count (recommended value - 32).
|
||||
If PA_WASAPI_MAX_CONST_DEVICE_COUNT is set to 0 (or not defined) during compile time the implementation
|
||||
will not define PaWasapi_UpdateDeviceList() and thus updating device list can only be possible by calling
|
||||
Pa_Terminate() and then Pa_Initialize().
|
||||
|
||||
@return Error code indicating success or failure.
|
||||
*/
|
||||
PaError PaWasapi_UpdateDeviceList();
|
||||
|
||||
|
||||
/** Get current audio format of the device assigned to the opened stream.
|
||||
|
||||
Format is represented by PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure.
|
||||
Use this function to reconfirm format if PA's processor is overridden and
|
||||
paWinWasapiRedirectHostProcessor flag is specified.
|
||||
|
||||
@param pStream Pointer to PaStream object.
|
||||
@param pFormat Pointer to PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure.
|
||||
@param formatSize Size of PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure in bytes.
|
||||
@param bOutput TRUE (1) for output stream, FALSE (0) for input stream.
|
||||
|
||||
@return Non-negative value indicating the number of bytes copied into format descriptor
|
||||
or, a PaErrorCode (which is always negative) if PortAudio is not initialized
|
||||
or an error is encountered.
|
||||
*/
|
||||
int PaWasapi_GetDeviceCurrentFormat( PaStream *pStream, void *pFormat, unsigned int formatSize, int bOutput );
|
||||
|
||||
|
||||
/** Get default audio format for the device in Shared Mode.
|
||||
|
||||
Format is represented by PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure and obtained
|
||||
by getting the device property with a PKEY_AudioEngine_DeviceFormat key.
|
||||
|
||||
@param pFormat Pointer to PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure.
|
||||
@param formatSize Size of PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure in bytes.
|
||||
@param device Device index.
|
||||
|
||||
@return Non-negative value indicating the number of bytes copied into format descriptor
|
||||
or, a PaErrorCode (which is always negative) if PortAudio is not initialized
|
||||
or an error is encountered.
|
||||
*/
|
||||
int PaWasapi_GetDeviceDefaultFormat( void *pFormat, unsigned int formatSize, PaDeviceIndex device );
|
||||
|
||||
|
||||
/** Get mix audio format for the device in Shared Mode.
|
||||
|
||||
Format is represented by PaWinWaveFormat or WAVEFORMATEXTENSIBLE structureand obtained by
|
||||
IAudioClient::GetMixFormat.
|
||||
|
||||
@param pFormat Pointer to PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure.
|
||||
@param formatSize Size of PaWinWaveFormat or WAVEFORMATEXTENSIBLE structure in bytes.
|
||||
@param device Device index.
|
||||
|
||||
@return Non-negative value indicating the number of bytes copied into format descriptor
|
||||
or, a PaErrorCode (which is always negative) if PortAudio is not initialized
|
||||
or an error is encountered.
|
||||
*/
|
||||
int PaWasapi_GetDeviceMixFormat( void *pFormat, unsigned int formatSize, PaDeviceIndex device );
|
||||
|
||||
|
||||
/** Get device role (PaWasapiDeviceRole enum).
|
||||
|
||||
@param device Device index.
|
||||
|
||||
@return Non-negative value indicating device role or, a PaErrorCode (which is always negative)
|
||||
if PortAudio is not initialized or an error is encountered.
|
||||
*/
|
||||
int/*PaWasapiDeviceRole*/ PaWasapi_GetDeviceRole( PaDeviceIndex device );
|
||||
|
||||
|
||||
/** Get device IMMDevice pointer
|
||||
|
||||
@param device Device index.
|
||||
@param pAudioClient Pointer to pointer of IMMDevice.
|
||||
|
||||
@return Error code indicating success or failure.
|
||||
*/
|
||||
PaError PaWasapi_GetIMMDevice( PaDeviceIndex device, void **pIMMDevice );
|
||||
|
||||
|
||||
/** Boost thread priority of calling thread (MMCSS).
|
||||
|
||||
Use it for Blocking Interface only inside the thread which makes calls to Pa_WriteStream/Pa_ReadStream.
|
||||
|
||||
@param pTask Handle to pointer to priority task. Must be used with PaWasapi_RevertThreadPriority
|
||||
method to revert thread priority to initial state.
|
||||
|
||||
@param priorityClass Id of thread priority of PaWasapiThreadPriority type. Specifying
|
||||
eThreadPriorityNone does nothing.
|
||||
|
||||
@return Error code indicating success or failure.
|
||||
@see PaWasapi_RevertThreadPriority
|
||||
*/
|
||||
PaError PaWasapi_ThreadPriorityBoost( void **pTask, PaWasapiThreadPriority priorityClass );
|
||||
|
||||
|
||||
/** Boost thread priority of calling thread (MMCSS).
|
||||
|
||||
Use it for Blocking Interface only inside the thread which makes calls to Pa_WriteStream/Pa_ReadStream.
|
||||
|
||||
@param pTask Task handle obtained by PaWasapi_BoostThreadPriority method.
|
||||
|
||||
@return Error code indicating success or failure.
|
||||
@see PaWasapi_BoostThreadPriority
|
||||
*/
|
||||
PaError PaWasapi_ThreadPriorityRevert( void *pTask );
|
||||
|
||||
|
||||
/** Get number of frames per host buffer.
|
||||
|
||||
It is max value of frames of WASAPI buffer which can be locked for operations.
|
||||
Use this method as helper to find out max values of inputFrames/outputFrames
|
||||
of PaWasapiHostProcessorCallback.
|
||||
|
||||
@param pStream Pointer to PaStream object.
|
||||
@param pInput Pointer to variable to receive number of input frames. Can be NULL.
|
||||
@param pOutput Pointer to variable to receive number of output frames. Can be NULL.
|
||||
|
||||
@return Error code indicating success or failure.
|
||||
@see PaWasapiHostProcessorCallback
|
||||
*/
|
||||
PaError PaWasapi_GetFramesPerHostBuffer( PaStream *pStream, unsigned int *pInput, unsigned int *pOutput );
|
||||
|
||||
|
||||
/** Get number of jacks associated with a WASAPI device.
|
||||
|
||||
Use this method to determine if there are any jacks associated with the provided WASAPI device.
|
||||
Not all audio devices will support this capability. This is valid for both input and output devices.
|
||||
|
||||
@note Not available on UWP platform.
|
||||
|
||||
@param device Device index.
|
||||
@param pJackCount Pointer to variable to receive number of jacks.
|
||||
|
||||
@return Error code indicating success or failure.
|
||||
@see PaWasapi_GetJackDescription
|
||||
*/
|
||||
PaError PaWasapi_GetJackCount( PaDeviceIndex device, int *pJackCount );
|
||||
|
||||
|
||||
/** Get the jack description associated with a WASAPI device and jack number.
|
||||
|
||||
Before this function is called, use PaWasapi_GetJackCount to determine the
|
||||
number of jacks associated with device. If jcount is greater than zero, then
|
||||
each jack from 0 to jcount can be queried with this function to get the jack
|
||||
description.
|
||||
|
||||
@note Not available on UWP platform.
|
||||
|
||||
@param device Device index.
|
||||
@param jackIndex Jack index.
|
||||
@param pJackDescription Pointer to PaWasapiJackDescription.
|
||||
|
||||
@return Error code indicating success or failure.
|
||||
@see PaWasapi_GetJackCount
|
||||
*/
|
||||
PaError PaWasapi_GetJackDescription( PaDeviceIndex device, int jackIndex, PaWasapiJackDescription *pJackDescription );
|
||||
|
||||
|
||||
/** Set stream state handler.
|
||||
|
||||
@param pStream Pointer to PaStream object.
|
||||
@param fnStateHandler Pointer to state handling function.
|
||||
@param pUserData Pointer to user data.
|
||||
|
||||
@return Error code indicating success or failure.
|
||||
*/
|
||||
PaError PaWasapi_SetStreamStateHandler( PaStream *pStream, PaWasapiStreamStateCallback fnStateHandler, void *pUserData );
|
||||
|
||||
|
||||
/** Set default device Id.
|
||||
|
||||
By default implementation will use the DEVINTERFACE_AUDIO_RENDER and
|
||||
DEVINTERFACE_AUDIO_CAPTURE Ids if device Id is not provided explicitly. These default Ids
|
||||
will not allow to use Exclusive mode on UWP/WinRT platform and thus you must provide
|
||||
device Id explicitly via this API before calling the Pa_OpenStream().
|
||||
|
||||
Device Ids on UWP platform are obtainable via:
|
||||
Windows::Media::Devices::MediaDevice::GetDefaultAudioRenderId() or
|
||||
Windows::Media::Devices::MediaDevice::GetDefaultAudioCaptureId() API.
|
||||
|
||||
After the call completes, memory referenced by pointers can be freed, as implementation keeps its own copy.
|
||||
|
||||
Call this function before calling Pa_IsFormatSupported() when Exclusive mode is requested.
|
||||
|
||||
See an example in the IMPORTANT notes.
|
||||
|
||||
@note UWP/WinRT platform only.
|
||||
|
||||
@param pId Device Id, pointer to the 16-bit Unicode string (WCHAR). If NULL then device Id
|
||||
will be reset to the default, e.g. DEVINTERFACE_AUDIO_RENDER or DEVINTERFACE_AUDIO_CAPTURE.
|
||||
@param bOutput TRUE (1) for output (render), FALSE (0) for input (capture).
|
||||
|
||||
@return Error code indicating success or failure. Will return paIncompatibleStreamHostApi if library is not compiled
|
||||
for UWP/WinRT platform. If Id is longer than PA_WASAPI_DEVICE_ID_LEN characters paBufferTooBig will
|
||||
be returned.
|
||||
*/
|
||||
PaError PaWasapiWinrt_SetDefaultDeviceId( const unsigned short *pId, int bOutput );
|
||||
|
||||
|
||||
/** Populate the device list.
|
||||
|
||||
By default the implementation will rely on DEVINTERFACE_AUDIO_RENDER and DEVINTERFACE_AUDIO_CAPTURE as
|
||||
default devices. If device Id is provided by PaWasapiWinrt_SetDefaultDeviceId() then those
|
||||
device Ids will be used as default and only devices for the device list.
|
||||
|
||||
By populating the device list you can provide an additional available audio devices of the system to PA
|
||||
which are obtainable by:
|
||||
Windows::Devices::Enumeration::DeviceInformation::FindAllAsync(selector) where selector is obtainable by
|
||||
Windows::Media::Devices::MediaDevice::GetAudioRenderSelector() or
|
||||
Windows::Media::Devices::MediaDevice::GetAudioCaptureSelector() API.
|
||||
|
||||
After the call completes, memory referenced by pointers can be freed, as implementation keeps its own copy.
|
||||
|
||||
You must call PaWasapi_UpdateDeviceList() to update the internal device list of the implementation after
|
||||
calling this function.
|
||||
|
||||
See an example in the IMPORTANT notes.
|
||||
|
||||
@note UWP/WinRT platform only.
|
||||
|
||||
@param pId Array of device Ids, pointer to the array of pointers of 16-bit Unicode string (WCHAR). If NULL
|
||||
and count is also 0 then device Ids will be reset to the default. Required.
|
||||
@param pName Array of device Names, pointer to the array of pointers of 16-bit Unicode string (WCHAR). Optional.
|
||||
@param pRole Array of device Roles, see PaWasapiDeviceRole and PaWasapi_GetDeviceRole() for more details. Optional.
|
||||
@param count Number of devices, the number of array elements (pId, pName, pRole). Maximum count of devices
|
||||
is limited by PA_WASAPI_DEVICE_MAX_COUNT.
|
||||
@param bOutput TRUE (1) for output (render), FALSE (0) for input (capture).
|
||||
|
||||
@return Error code indicating success or failure. Will return paIncompatibleStreamHostApi if library is not compiled
|
||||
for UWP/WinRT platform. If Id is longer than PA_WASAPI_DEVICE_ID_LEN characters paBufferTooBig will
|
||||
be returned. If Name is longer than PA_WASAPI_DEVICE_NAME_LEN characters paBufferTooBig will
|
||||
be returned.
|
||||
*/
|
||||
PaError PaWasapiWinrt_PopulateDeviceList( const unsigned short **pId, const unsigned short **pName,
|
||||
const PaWasapiDeviceRole *pRole, unsigned int count, int bOutput );
|
||||
|
||||
|
||||
/*
|
||||
IMPORTANT:
|
||||
|
||||
WASAPI is implemented for Callback and Blocking interfaces. It supports Shared and Exclusive
|
||||
share modes.
|
||||
|
||||
Exclusive Mode:
|
||||
|
||||
Exclusive mode allows to deliver audio data directly to hardware bypassing
|
||||
software mixing.
|
||||
Exclusive mode is specified by 'paWinWasapiExclusive' flag.
|
||||
|
||||
Callback Interface:
|
||||
|
||||
Provides best audio quality with low latency. Callback interface is implemented in
|
||||
two versions:
|
||||
|
||||
1) Event-Driven:
|
||||
This is the most powerful WASAPI implementation which provides glitch-free
|
||||
audio at around 3ms latency in Exclusive mode. Lowest possible latency for this mode is
|
||||
3 ms for HD Audio class audio chips. For the Shared mode latency can not be
|
||||
lower than 20 ms.
|
||||
|
||||
2) Poll-Driven:
|
||||
Polling is another 2-nd method to operate with WASAPI. It is less efficient than Event-Driven
|
||||
and provides latency at around 10-13ms. Polling must be used to overcome a system bug
|
||||
under Windows Vista x64 when application is WOW64(32-bit) and Event-Driven method simply
|
||||
times out (event handle is never signalled on buffer completion). Please note, such WOW64 bug
|
||||
does not exist in Vista x86 or Windows 7.
|
||||
Polling can be setup by specifying 'paWinWasapiPolling' flag. Our WASAPI implementation detects
|
||||
WOW64 bug and sets 'paWinWasapiPolling' automatically.
|
||||
|
||||
Thread priority:
|
||||
|
||||
Normally thread priority is set automatically and does not require modification. Although
|
||||
if user wants some tweaking thread priority can be modified by setting 'paWinWasapiThreadPriority'
|
||||
flag and specifying 'PaWasapiStreamInfo::threadPriority' with value from PaWasapiThreadPriority
|
||||
enum.
|
||||
|
||||
Blocking Interface:
|
||||
|
||||
Blocking interface is implemented but due to above described Poll-Driven method can not
|
||||
deliver lowest possible latency. Specifying too low latency in Shared mode will result in
|
||||
distorted audio although Exclusive mode adds stability.
|
||||
|
||||
8.24 format:
|
||||
|
||||
If paCustomFormat is specified as sample format then the implementation will understand it
|
||||
as valid 24-bits inside 32-bit container (e.g. wBitsPerSample = 32, Samples.wValidBitsPerSample = 24).
|
||||
|
||||
By using paCustomFormat there will be small optimization when samples are be copied
|
||||
with Copy_24_To_24 by PA processor instead of conversion from packed 3-byte (24-bit) data
|
||||
with Int24_To_Int32.
|
||||
|
||||
Pa_IsFormatSupported:
|
||||
|
||||
To check format with correct Share Mode (Exclusive/Shared) you must supply PaWasapiStreamInfo
|
||||
with flags paWinWasapiExclusive set through member of PaStreamParameters::hostApiSpecificStreamInfo
|
||||
structure.
|
||||
|
||||
If paWinWasapiExplicitSampleFormat flag is provided then implementation will not try to select
|
||||
suitable close format and will return an error instead of paFormatIsSupported. By specifying
|
||||
paWinWasapiExplicitSampleFormat flag it is possible to find out what sample formats are
|
||||
supported by Exclusive or Shared modes.
|
||||
|
||||
Pa_OpenStream:
|
||||
|
||||
To set desired Share Mode (Exclusive/Shared) you must supply
|
||||
PaWasapiStreamInfo with flags paWinWasapiExclusive set through member of
|
||||
PaStreamParameters::hostApiSpecificStreamInfo structure.
|
||||
|
||||
Coding style for parameters and structure members of the public API:
|
||||
|
||||
1) bXXX - boolean, [1 (TRUE), 0 (FALSE)]
|
||||
2) pXXX - pointer
|
||||
3) fnXXX - pointer to function
|
||||
4) structure members are never prefixed with a type distinguisher
|
||||
|
||||
|
||||
UWP/WinRT:
|
||||
|
||||
This platform has number of limitations which do not allow to enumerate audio devices without
|
||||
an additional external help. Enumeration is possible though from C++/CX, check the related API
|
||||
Windows::Devices::Enumeration::DeviceInformation::FindAllAsync().
|
||||
|
||||
The main limitation is an absence of the device enumeration from inside the PA's implementation.
|
||||
This problem can be solved by using the following functions:
|
||||
|
||||
PaWasapiWinrt_SetDefaultDeviceId() - to set default input/output device,
|
||||
PaWasapiWinrt_PopulateDeviceList() - to populate device list with devices.
|
||||
|
||||
Here is an example of populating the device list which can also be updated dynamically depending on
|
||||
whether device was removed from or added to the system:
|
||||
|
||||
----------------
|
||||
|
||||
std::vector<const UINT16 *> ids, names;
|
||||
std::vector<PaWasapiDeviceRole> role;
|
||||
|
||||
ids.resize(count);
|
||||
names.resize(count);
|
||||
role.resize(count);
|
||||
|
||||
for (UINT32 i = 0; i < count; ++i)
|
||||
{
|
||||
ids[i] = (const UINT16 *)device_ids[i].c_str();
|
||||
names[i] = (const UINT16 *)device_names[i].c_str();
|
||||
role[i] = eRoleUnknownFormFactor;
|
||||
}
|
||||
|
||||
PaWasapiWinrt_SetDefaultDeviceId((const UINT16 *)default_device_id.c_str(), !capture);
|
||||
PaWasapiWinrt_PopulateDeviceList(ids.data(), names.data(), role.data(), count, !capture);
|
||||
PaWasapi_UpdateDeviceList();
|
||||
|
||||
----------------
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* PA_WIN_WASAPI_H */
|
|
@ -1,199 +0,0 @@
|
|||
#ifndef PA_WIN_WAVEFORMAT_H
|
||||
#define PA_WIN_WAVEFORMAT_H
|
||||
|
||||
/*
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* Windows WAVEFORMAT* data structure utilities
|
||||
* portaudio.h should be included before this file.
|
||||
*
|
||||
* Copyright (c) 2007 Ross Bencina
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
/** @file
|
||||
@ingroup public_header
|
||||
@brief Windows specific PortAudio API extension and utilities header file.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
The following #defines for speaker channel masks are the same
|
||||
as those in ksmedia.h, except with PAWIN_ prepended, KSAUDIO_ removed
|
||||
in some cases, and casts to PaWinWaveFormatChannelMask added.
|
||||
*/
|
||||
|
||||
typedef unsigned long PaWinWaveFormatChannelMask;
|
||||
|
||||
/* Speaker Positions: */
|
||||
#define PAWIN_SPEAKER_FRONT_LEFT ((PaWinWaveFormatChannelMask)0x1)
|
||||
#define PAWIN_SPEAKER_FRONT_RIGHT ((PaWinWaveFormatChannelMask)0x2)
|
||||
#define PAWIN_SPEAKER_FRONT_CENTER ((PaWinWaveFormatChannelMask)0x4)
|
||||
#define PAWIN_SPEAKER_LOW_FREQUENCY ((PaWinWaveFormatChannelMask)0x8)
|
||||
#define PAWIN_SPEAKER_BACK_LEFT ((PaWinWaveFormatChannelMask)0x10)
|
||||
#define PAWIN_SPEAKER_BACK_RIGHT ((PaWinWaveFormatChannelMask)0x20)
|
||||
#define PAWIN_SPEAKER_FRONT_LEFT_OF_CENTER ((PaWinWaveFormatChannelMask)0x40)
|
||||
#define PAWIN_SPEAKER_FRONT_RIGHT_OF_CENTER ((PaWinWaveFormatChannelMask)0x80)
|
||||
#define PAWIN_SPEAKER_BACK_CENTER ((PaWinWaveFormatChannelMask)0x100)
|
||||
#define PAWIN_SPEAKER_SIDE_LEFT ((PaWinWaveFormatChannelMask)0x200)
|
||||
#define PAWIN_SPEAKER_SIDE_RIGHT ((PaWinWaveFormatChannelMask)0x400)
|
||||
#define PAWIN_SPEAKER_TOP_CENTER ((PaWinWaveFormatChannelMask)0x800)
|
||||
#define PAWIN_SPEAKER_TOP_FRONT_LEFT ((PaWinWaveFormatChannelMask)0x1000)
|
||||
#define PAWIN_SPEAKER_TOP_FRONT_CENTER ((PaWinWaveFormatChannelMask)0x2000)
|
||||
#define PAWIN_SPEAKER_TOP_FRONT_RIGHT ((PaWinWaveFormatChannelMask)0x4000)
|
||||
#define PAWIN_SPEAKER_TOP_BACK_LEFT ((PaWinWaveFormatChannelMask)0x8000)
|
||||
#define PAWIN_SPEAKER_TOP_BACK_CENTER ((PaWinWaveFormatChannelMask)0x10000)
|
||||
#define PAWIN_SPEAKER_TOP_BACK_RIGHT ((PaWinWaveFormatChannelMask)0x20000)
|
||||
|
||||
/* Bit mask locations reserved for future use */
|
||||
#define PAWIN_SPEAKER_RESERVED ((PaWinWaveFormatChannelMask)0x7FFC0000)
|
||||
|
||||
/* Used to specify that any possible permutation of speaker configurations */
|
||||
#define PAWIN_SPEAKER_ALL ((PaWinWaveFormatChannelMask)0x80000000)
|
||||
|
||||
/* DirectSound Speaker Config */
|
||||
#define PAWIN_SPEAKER_DIRECTOUT 0
|
||||
#define PAWIN_SPEAKER_MONO (PAWIN_SPEAKER_FRONT_CENTER)
|
||||
#define PAWIN_SPEAKER_STEREO (PAWIN_SPEAKER_FRONT_LEFT | PAWIN_SPEAKER_FRONT_RIGHT)
|
||||
#define PAWIN_SPEAKER_QUAD (PAWIN_SPEAKER_FRONT_LEFT | PAWIN_SPEAKER_FRONT_RIGHT | \
|
||||
PAWIN_SPEAKER_BACK_LEFT | PAWIN_SPEAKER_BACK_RIGHT)
|
||||
#define PAWIN_SPEAKER_SURROUND (PAWIN_SPEAKER_FRONT_LEFT | PAWIN_SPEAKER_FRONT_RIGHT | \
|
||||
PAWIN_SPEAKER_FRONT_CENTER | PAWIN_SPEAKER_BACK_CENTER)
|
||||
#define PAWIN_SPEAKER_5POINT1 (PAWIN_SPEAKER_FRONT_LEFT | PAWIN_SPEAKER_FRONT_RIGHT | \
|
||||
PAWIN_SPEAKER_FRONT_CENTER | PAWIN_SPEAKER_LOW_FREQUENCY | \
|
||||
PAWIN_SPEAKER_BACK_LEFT | PAWIN_SPEAKER_BACK_RIGHT)
|
||||
#define PAWIN_SPEAKER_7POINT1 (PAWIN_SPEAKER_FRONT_LEFT | PAWIN_SPEAKER_FRONT_RIGHT | \
|
||||
PAWIN_SPEAKER_FRONT_CENTER | PAWIN_SPEAKER_LOW_FREQUENCY | \
|
||||
PAWIN_SPEAKER_BACK_LEFT | PAWIN_SPEAKER_BACK_RIGHT | \
|
||||
PAWIN_SPEAKER_FRONT_LEFT_OF_CENTER | PAWIN_SPEAKER_FRONT_RIGHT_OF_CENTER)
|
||||
#define PAWIN_SPEAKER_5POINT1_SURROUND (PAWIN_SPEAKER_FRONT_LEFT | PAWIN_SPEAKER_FRONT_RIGHT | \
|
||||
PAWIN_SPEAKER_FRONT_CENTER | PAWIN_SPEAKER_LOW_FREQUENCY | \
|
||||
PAWIN_SPEAKER_SIDE_LEFT | PAWIN_SPEAKER_SIDE_RIGHT)
|
||||
#define PAWIN_SPEAKER_7POINT1_SURROUND (PAWIN_SPEAKER_FRONT_LEFT | PAWIN_SPEAKER_FRONT_RIGHT | \
|
||||
PAWIN_SPEAKER_FRONT_CENTER | PAWIN_SPEAKER_LOW_FREQUENCY | \
|
||||
PAWIN_SPEAKER_BACK_LEFT | PAWIN_SPEAKER_BACK_RIGHT | \
|
||||
PAWIN_SPEAKER_SIDE_LEFT | PAWIN_SPEAKER_SIDE_RIGHT)
|
||||
/*
|
||||
According to the Microsoft documentation:
|
||||
The following are obsolete 5.1 and 7.1 settings (they lack side speakers). Note this means
|
||||
that the default 5.1 and 7.1 settings (KSAUDIO_SPEAKER_5POINT1 and KSAUDIO_SPEAKER_7POINT1 are
|
||||
similarly obsolete but are unchanged for compatibility reasons).
|
||||
*/
|
||||
#define PAWIN_SPEAKER_5POINT1_BACK PAWIN_SPEAKER_5POINT1
|
||||
#define PAWIN_SPEAKER_7POINT1_WIDE PAWIN_SPEAKER_7POINT1
|
||||
|
||||
/* DVD Speaker Positions */
|
||||
#define PAWIN_SPEAKER_GROUND_FRONT_LEFT PAWIN_SPEAKER_FRONT_LEFT
|
||||
#define PAWIN_SPEAKER_GROUND_FRONT_CENTER PAWIN_SPEAKER_FRONT_CENTER
|
||||
#define PAWIN_SPEAKER_GROUND_FRONT_RIGHT PAWIN_SPEAKER_FRONT_RIGHT
|
||||
#define PAWIN_SPEAKER_GROUND_REAR_LEFT PAWIN_SPEAKER_BACK_LEFT
|
||||
#define PAWIN_SPEAKER_GROUND_REAR_RIGHT PAWIN_SPEAKER_BACK_RIGHT
|
||||
#define PAWIN_SPEAKER_TOP_MIDDLE PAWIN_SPEAKER_TOP_CENTER
|
||||
#define PAWIN_SPEAKER_SUPER_WOOFER PAWIN_SPEAKER_LOW_FREQUENCY
|
||||
|
||||
|
||||
/*
|
||||
PaWinWaveFormat is defined here to provide compatibility with
|
||||
compilation environments which don't have headers defining
|
||||
WAVEFORMATEXTENSIBLE (e.g. older versions of MSVC, Borland C++ etc.
|
||||
|
||||
The fields for WAVEFORMATEX and WAVEFORMATEXTENSIBLE are declared as an
|
||||
unsigned char array here to avoid clients who include this file having
|
||||
a dependency on windows.h and mmsystem.h, and also to to avoid having
|
||||
to write separate packing pragmas for each compiler.
|
||||
*/
|
||||
#define PAWIN_SIZEOF_WAVEFORMATEX 18
|
||||
#define PAWIN_SIZEOF_WAVEFORMATEXTENSIBLE (PAWIN_SIZEOF_WAVEFORMATEX + 22)
|
||||
|
||||
typedef struct{
|
||||
unsigned char fields[ PAWIN_SIZEOF_WAVEFORMATEXTENSIBLE ];
|
||||
unsigned long extraLongForAlignment; /* ensure that compiler aligns struct to DWORD */
|
||||
} PaWinWaveFormat;
|
||||
|
||||
/*
|
||||
WAVEFORMATEXTENSIBLE fields:
|
||||
|
||||
union {
|
||||
WORD wValidBitsPerSample;
|
||||
WORD wSamplesPerBlock;
|
||||
WORD wReserved;
|
||||
} Samples;
|
||||
DWORD dwChannelMask;
|
||||
GUID SubFormat;
|
||||
*/
|
||||
|
||||
#define PAWIN_INDEXOF_WVALIDBITSPERSAMPLE (PAWIN_SIZEOF_WAVEFORMATEX+0)
|
||||
#define PAWIN_INDEXOF_DWCHANNELMASK (PAWIN_SIZEOF_WAVEFORMATEX+2)
|
||||
#define PAWIN_INDEXOF_SUBFORMAT (PAWIN_SIZEOF_WAVEFORMATEX+6)
|
||||
|
||||
|
||||
/*
|
||||
Valid values to pass for the waveFormatTag PaWin_InitializeWaveFormatEx and
|
||||
PaWin_InitializeWaveFormatExtensible functions below. These must match
|
||||
the standard Windows WAVE_FORMAT_* values.
|
||||
*/
|
||||
#define PAWIN_WAVE_FORMAT_PCM (1)
|
||||
#define PAWIN_WAVE_FORMAT_IEEE_FLOAT (3)
|
||||
#define PAWIN_WAVE_FORMAT_DOLBY_AC3_SPDIF (0x0092)
|
||||
#define PAWIN_WAVE_FORMAT_WMA_SPDIF (0x0164)
|
||||
|
||||
|
||||
/*
|
||||
returns PAWIN_WAVE_FORMAT_PCM or PAWIN_WAVE_FORMAT_IEEE_FLOAT
|
||||
depending on the sampleFormat parameter.
|
||||
*/
|
||||
int PaWin_SampleFormatToLinearWaveFormatTag( PaSampleFormat sampleFormat );
|
||||
|
||||
/*
|
||||
Use the following two functions to initialize the waveformat structure.
|
||||
*/
|
||||
|
||||
void PaWin_InitializeWaveFormatEx( PaWinWaveFormat *waveFormat,
|
||||
int numChannels, PaSampleFormat sampleFormat, int waveFormatTag, double sampleRate );
|
||||
|
||||
|
||||
void PaWin_InitializeWaveFormatExtensible( PaWinWaveFormat *waveFormat,
|
||||
int numChannels, PaSampleFormat sampleFormat, int waveFormatTag, double sampleRate,
|
||||
PaWinWaveFormatChannelMask channelMask );
|
||||
|
||||
|
||||
/* Map a channel count to a speaker channel mask */
|
||||
PaWinWaveFormatChannelMask PaWin_DefaultChannelMask( int numChannels );
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* PA_WIN_WAVEFORMAT_H */
|
|
@ -1,137 +0,0 @@
|
|||
#ifndef PA_WIN_WDMKS_H
|
||||
#define PA_WIN_WDMKS_H
|
||||
/*
|
||||
* $Id$
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* WDM/KS specific extensions
|
||||
*
|
||||
* Copyright (c) 1999-2007 Ross Bencina and Phil Burk
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
/** @file
|
||||
@ingroup public_header
|
||||
@brief WDM Kernel Streaming-specific PortAudio API extension header file.
|
||||
*/
|
||||
|
||||
|
||||
#include "portaudio.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/** Flags to indicate valid fields in PaWinWDMKSInfo.
|
||||
@see PaWinWDMKSInfo
|
||||
@version Available as of 19.5.0.
|
||||
*/
|
||||
typedef enum PaWinWDMKSFlags
|
||||
{
|
||||
/** Makes WDMKS use the supplied latency figures instead of relying on the frame size reported
|
||||
by the WaveCyclic device. Use at own risk!
|
||||
*/
|
||||
paWinWDMKSOverrideFramesize = (1 << 0),
|
||||
|
||||
/** Makes WDMKS (output stream) use the given channelMask instead of the default.
|
||||
@version Available as of 19.5.0.
|
||||
*/
|
||||
paWinWDMKSUseGivenChannelMask = (1 << 1),
|
||||
|
||||
} PaWinWDMKSFlags;
|
||||
|
||||
typedef struct PaWinWDMKSInfo{
|
||||
unsigned long size; /**< sizeof(PaWinWDMKSInfo) */
|
||||
PaHostApiTypeId hostApiType; /**< paWDMKS */
|
||||
unsigned long version; /**< 1 */
|
||||
|
||||
/** Flags indicate which fields are valid.
|
||||
@see PaWinWDMKSFlags
|
||||
@version Available as of 19.5.0.
|
||||
*/
|
||||
unsigned long flags;
|
||||
|
||||
/** The number of packets to use for WaveCyclic devices, range is [2, 8]. Set to zero for default value of 2. */
|
||||
unsigned noOfPackets;
|
||||
|
||||
/** If paWinWDMKSUseGivenChannelMask bit is set in flags, use this as channelMask instead of default.
|
||||
@see PaWinWDMKSFlags
|
||||
@version Available as of 19.5.0.
|
||||
*/
|
||||
unsigned channelMask;
|
||||
} PaWinWDMKSInfo;
|
||||
|
||||
typedef enum PaWDMKSType
|
||||
{
|
||||
Type_kNotUsed,
|
||||
Type_kWaveCyclic,
|
||||
Type_kWaveRT,
|
||||
Type_kCnt,
|
||||
} PaWDMKSType;
|
||||
|
||||
typedef enum PaWDMKSSubType
|
||||
{
|
||||
SubType_kUnknown,
|
||||
SubType_kNotification,
|
||||
SubType_kPolled,
|
||||
SubType_kCnt,
|
||||
} PaWDMKSSubType;
|
||||
|
||||
typedef struct PaWinWDMKSDeviceInfo {
|
||||
wchar_t filterPath[MAX_PATH]; /**< KS filter path in Unicode! */
|
||||
wchar_t topologyPath[MAX_PATH]; /**< Topology filter path in Unicode! */
|
||||
PaWDMKSType streamingType;
|
||||
GUID deviceProductGuid; /**< The product GUID of the device (if supported) */
|
||||
} PaWinWDMKSDeviceInfo;
|
||||
|
||||
typedef struct PaWDMKSDirectionSpecificStreamInfo
|
||||
{
|
||||
PaDeviceIndex device;
|
||||
unsigned channels; /**< No of channels the device is opened with */
|
||||
unsigned framesPerHostBuffer; /**< No of frames of the device buffer */
|
||||
int endpointPinId; /**< Endpoint pin ID (on topology filter if topologyName is not empty) */
|
||||
int muxNodeId; /**< Only valid for input */
|
||||
PaWDMKSSubType streamingSubType; /**< Not known until device is opened for streaming */
|
||||
} PaWDMKSDirectionSpecificStreamInfo;
|
||||
|
||||
typedef struct PaWDMKSSpecificStreamInfo {
|
||||
PaWDMKSDirectionSpecificStreamInfo input;
|
||||
PaWDMKSDirectionSpecificStreamInfo output;
|
||||
} PaWDMKSSpecificStreamInfo;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* PA_WIN_DS_H */
|
|
@ -1,185 +0,0 @@
|
|||
#ifndef PA_WIN_WMME_H
|
||||
#define PA_WIN_WMME_H
|
||||
/*
|
||||
* $Id$
|
||||
* PortAudio Portable Real-Time Audio Library
|
||||
* MME specific extensions
|
||||
*
|
||||
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortAudio license; however,
|
||||
* the PortAudio community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
/** @file
|
||||
@ingroup public_header
|
||||
@brief WMME-specific PortAudio API extension header file.
|
||||
*/
|
||||
|
||||
#include "portaudio.h"
|
||||
#include "pa_win_waveformat.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
/* The following are flags which can be set in
|
||||
PaWinMmeStreamInfo's flags field.
|
||||
*/
|
||||
|
||||
#define paWinMmeUseLowLevelLatencyParameters (0x01)
|
||||
#define paWinMmeUseMultipleDevices (0x02) /* use mme specific multiple device feature */
|
||||
#define paWinMmeUseChannelMask (0x04)
|
||||
|
||||
/* By default, the mme implementation drops the processing thread's priority
|
||||
to THREAD_PRIORITY_NORMAL and sleeps the thread if the CPU load exceeds 100%
|
||||
This flag disables any priority throttling. The processing thread will always
|
||||
run at THREAD_PRIORITY_TIME_CRITICAL.
|
||||
*/
|
||||
#define paWinMmeDontThrottleOverloadedProcessingThread (0x08)
|
||||
|
||||
/* Flags for non-PCM spdif passthrough.
|
||||
*/
|
||||
#define paWinMmeWaveFormatDolbyAc3Spdif (0x10)
|
||||
#define paWinMmeWaveFormatWmaSpdif (0x20)
|
||||
|
||||
|
||||
typedef struct PaWinMmeDeviceAndChannelCount{
|
||||
PaDeviceIndex device;
|
||||
int channelCount;
|
||||
}PaWinMmeDeviceAndChannelCount;
|
||||
|
||||
|
||||
typedef struct PaWinMmeStreamInfo{
|
||||
unsigned long size; /**< sizeof(PaWinMmeStreamInfo) */
|
||||
PaHostApiTypeId hostApiType; /**< paMME */
|
||||
unsigned long version; /**< 1 */
|
||||
|
||||
unsigned long flags;
|
||||
|
||||
/* low-level latency setting support
|
||||
These settings control the number and size of host buffers in order
|
||||
to set latency. They will be used instead of the generic parameters
|
||||
to Pa_OpenStream() if flags contains the PaWinMmeUseLowLevelLatencyParameters
|
||||
flag.
|
||||
|
||||
If PaWinMmeStreamInfo structures with PaWinMmeUseLowLevelLatencyParameters
|
||||
are supplied for both input and output in a full duplex stream, then the
|
||||
input and output framesPerBuffer must be the same, or the larger of the
|
||||
two must be a multiple of the smaller, otherwise a
|
||||
paIncompatibleHostApiSpecificStreamInfo error will be returned from
|
||||
Pa_OpenStream().
|
||||
*/
|
||||
unsigned long framesPerBuffer;
|
||||
unsigned long bufferCount; /* formerly numBuffers */
|
||||
|
||||
/* multiple devices per direction support
|
||||
If flags contains the PaWinMmeUseMultipleDevices flag,
|
||||
this functionality will be used, otherwise the device parameter to
|
||||
Pa_OpenStream() will be used instead.
|
||||
If devices are specified here, the corresponding device parameter
|
||||
to Pa_OpenStream() should be set to paUseHostApiSpecificDeviceSpecification,
|
||||
otherwise an paInvalidDevice error will result.
|
||||
The total number of channels across all specified devices
|
||||
must agree with the corresponding channelCount parameter to
|
||||
Pa_OpenStream() otherwise a paInvalidChannelCount error will result.
|
||||
*/
|
||||
PaWinMmeDeviceAndChannelCount *devices;
|
||||
unsigned long deviceCount;
|
||||
|
||||
/*
|
||||
support for WAVEFORMATEXTENSIBLE channel masks. If flags contains
|
||||
paWinMmeUseChannelMask this allows you to specify which speakers
|
||||
to address in a multichannel stream. Constants for channelMask
|
||||
are specified in pa_win_waveformat.h
|
||||
|
||||
*/
|
||||
PaWinWaveFormatChannelMask channelMask;
|
||||
|
||||
}PaWinMmeStreamInfo;
|
||||
|
||||
|
||||
/** Retrieve the number of wave in handles used by a PortAudio WinMME stream.
|
||||
Returns zero if the stream is output only.
|
||||
|
||||
@return A non-negative value indicating the number of wave in handles
|
||||
or, a PaErrorCode (which are always negative) if PortAudio is not initialized
|
||||
or an error is encountered.
|
||||
|
||||
@see PaWinMME_GetStreamInputHandle
|
||||
*/
|
||||
int PaWinMME_GetStreamInputHandleCount( PaStream* stream );
|
||||
|
||||
|
||||
/** Retrieve a wave in handle used by a PortAudio WinMME stream.
|
||||
|
||||
@param stream The stream to query.
|
||||
@param handleIndex The zero based index of the wave in handle to retrieve. This
|
||||
should be in the range [0, PaWinMME_GetStreamInputHandleCount(stream)-1].
|
||||
|
||||
@return A valid wave in handle, or NULL if an error occurred.
|
||||
|
||||
@see PaWinMME_GetStreamInputHandle
|
||||
*/
|
||||
HWAVEIN PaWinMME_GetStreamInputHandle( PaStream* stream, int handleIndex );
|
||||
|
||||
|
||||
/** Retrieve the number of wave out handles used by a PortAudio WinMME stream.
|
||||
Returns zero if the stream is input only.
|
||||
|
||||
@return A non-negative value indicating the number of wave out handles
|
||||
or, a PaErrorCode (which are always negative) if PortAudio is not initialized
|
||||
or an error is encountered.
|
||||
|
||||
@see PaWinMME_GetStreamOutputHandle
|
||||
*/
|
||||
int PaWinMME_GetStreamOutputHandleCount( PaStream* stream );
|
||||
|
||||
|
||||
/** Retrieve a wave out handle used by a PortAudio WinMME stream.
|
||||
|
||||
@param stream The stream to query.
|
||||
@param handleIndex The zero based index of the wave out handle to retrieve.
|
||||
This should be in the range [0, PaWinMME_GetStreamOutputHandleCount(stream)-1].
|
||||
|
||||
@return A valid wave out handle, or NULL if an error occurred.
|
||||
|
||||
@see PaWinMME_GetStreamOutputHandleCount
|
||||
*/
|
||||
HWAVEOUT PaWinMME_GetStreamOutputHandle( PaStream* stream, int handleIndex );
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* PA_WIN_WMME_H */
|
File diff suppressed because it is too large
Load diff
|
@ -1,543 +0,0 @@
|
|||
/*
|
||||
This data is derived from the chip's output - internal ROM can't be read.
|
||||
It was verified, using real YM2608, that this ADPCM stream produces 100% correct output signal.
|
||||
*/
|
||||
|
||||
const unsigned char YM2608_ADPCM_ROM[0x2000] = {
|
||||
|
||||
/* Source: 01BD.ROM */
|
||||
/* Length: 448 / 0x000001C0 */
|
||||
|
||||
0x88,0x08,0x08,0x08,0x00,0x88,0x16,0x76,0x99,0xB8,0x22,0x3A,0x84,0x3C,0xB1,0x54,
|
||||
0x10,0xA9,0x98,0x32,0x80,0x33,0x9A,0xA7,0x4A,0xB4,0x58,0xBC,0x15,0x29,0x8A,0x97,
|
||||
0x9B,0x44,0xAC,0x80,0x12,0xDE,0x13,0x1B,0xC0,0x58,0xC8,0x11,0x0A,0xA2,0x1A,0xA0,
|
||||
0x00,0x98,0x0B,0x93,0x9E,0x92,0x0A,0x88,0xBE,0x14,0x1B,0x98,0x08,0xA1,0x4A,0xC1,
|
||||
0x30,0xD9,0x33,0x98,0x10,0x89,0x17,0x1A,0x82,0x29,0x37,0x0C,0x83,0x50,0x9A,0x24,
|
||||
0x1A,0x83,0x10,0x23,0x19,0xB3,0x72,0x8A,0x16,0x10,0x0A,0x93,0x70,0x99,0x23,0x99,
|
||||
0x02,0x20,0x91,0x18,0x02,0x41,0xAB,0x24,0x18,0x81,0x99,0x4A,0xE8,0x28,0x9A,0x99,
|
||||
0xA1,0x2F,0xA8,0x9D,0x90,0x08,0xCC,0xA3,0x1D,0xCA,0x82,0x0B,0xD8,0x08,0xB9,0x09,
|
||||
0xBC,0xB8,0x00,0xBE,0x90,0x1B,0xCA,0x00,0x9B,0x8A,0xA8,0x91,0x0F,0xB3,0x3D,0xB8,
|
||||
0x31,0x0B,0xA5,0x0A,0x11,0xA1,0x48,0x92,0x10,0x50,0x91,0x30,0x23,0x09,0x37,0x39,
|
||||
0xA2,0x72,0x89,0x92,0x30,0x83,0x1C,0x96,0x28,0xB9,0x24,0x8C,0xA1,0x31,0xAD,0xA9,
|
||||
0x13,0x9C,0xBA,0xA8,0x0B,0xBF,0xB8,0x9B,0xCA,0x88,0xDB,0xB8,0x19,0xFC,0x92,0x0A,
|
||||
0xBA,0x89,0xAB,0xB8,0xAB,0xD8,0x08,0xAD,0xBA,0x33,0x9D,0xAA,0x83,0x3A,0xC0,0x40,
|
||||
0xB9,0x15,0x39,0xA2,0x52,0x89,0x02,0x63,0x88,0x13,0x23,0x03,0x52,0x02,0x54,0x00,
|
||||
0x11,0x23,0x23,0x35,0x20,0x01,0x44,0x41,0x80,0x24,0x40,0xA9,0x45,0x19,0x81,0x12,
|
||||
0x81,0x02,0x11,0x21,0x19,0x02,0x61,0x8A,0x13,0x3A,0x10,0x12,0x23,0x8B,0x37,0x18,
|
||||
0x91,0x24,0x10,0x81,0x34,0x20,0x05,0x32,0x82,0x53,0x20,0x14,0x33,0x31,0x34,0x52,
|
||||
0x00,0x43,0x32,0x13,0x52,0x22,0x13,0x52,0x11,0x43,0x11,0x32,0x32,0x32,0x22,0x02,
|
||||
0x13,0x12,0x89,0x22,0x19,0x81,0x81,0x08,0xA8,0x08,0x8B,0x90,0x1B,0xBA,0x8A,0x9B,
|
||||
0xB9,0x89,0xCA,0xB9,0xAB,0xCA,0x9B,0xCA,0xB9,0xAB,0xDA,0x99,0xAC,0xBB,0x9B,0xAC,
|
||||
0xAA,0xBA,0xAC,0xAB,0x9A,0xAA,0xAA,0xBA,0xB8,0xA9,0xBA,0x99,0xA9,0x9A,0xA0,0x8A,
|
||||
0xA9,0x08,0x8A,0xA9,0x00,0x99,0x89,0x88,0x98,0x08,0x99,0x00,0x89,0x80,0x08,0x98,
|
||||
0x00,0x88,0x88,0x80,0x90,0x80,0x90,0x80,0x81,0x99,0x08,0x88,0x99,0x09,0x00,0x1A,
|
||||
0xA8,0x10,0x9A,0x88,0x08,0x0A,0x8A,0x89,0x99,0xA8,0x98,0xA9,0x99,0x99,0xA9,0x99,
|
||||
0xAA,0x8A,0xAA,0x9B,0x8A,0x9A,0xA9,0x9A,0xBA,0x99,0x9A,0xAA,0x99,0x89,0xA9,0x99,
|
||||
0x98,0x9A,0x98,0x88,0x09,0x89,0x09,0x08,0x08,0x09,0x18,0x18,0x00,0x12,0x00,0x11,
|
||||
0x11,0x11,0x12,0x12,0x21,0x21,0x22,0x22,0x22,0x22,0x22,0x22,0x32,0x31,0x32,0x31,
|
||||
0x32,0x32,0x21,0x31,0x21,0x32,0x21,0x12,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
|
||||
|
||||
/* Source: 02SD.ROM */
|
||||
/* Length: 640 / 0x00000280 */
|
||||
|
||||
0x0A,0xDC,0x14,0x0B,0xBA,0xBC,0x01,0x0F,0xF5,0x2F,0x87,0x19,0xC9,0x24,0x1B,0xA1,
|
||||
0x31,0x99,0x90,0x32,0x32,0xFE,0x83,0x48,0xA8,0xA9,0x23,0x19,0xBC,0x91,0x02,0x41,
|
||||
0xDE,0x81,0x28,0xA8,0x0A,0xB1,0x72,0xDA,0x23,0xBC,0x04,0x19,0xB8,0x21,0x8A,0x03,
|
||||
0x29,0xBA,0x14,0x21,0x0B,0xC0,0x43,0x08,0x91,0x50,0x93,0x0F,0x86,0x1A,0x91,0x18,
|
||||
0x21,0xCB,0x27,0x0A,0xA1,0x42,0x8C,0xA9,0x21,0x10,0x08,0xAB,0x94,0x2A,0xDA,0x02,
|
||||
0x8B,0x91,0x09,0x98,0xAE,0x80,0xA9,0x02,0x0A,0xE9,0x21,0xBB,0x15,0x20,0xBE,0x92,
|
||||
0x42,0x09,0xA9,0x11,0x34,0x08,0x12,0x0A,0x27,0x29,0xA1,0x52,0x12,0x8E,0x92,0x28,
|
||||
0x92,0x2B,0xD1,0x23,0xBF,0x81,0x10,0x99,0xA8,0x0A,0xC4,0x3B,0xB9,0xB0,0x00,0x62,
|
||||
0xCF,0x92,0x29,0x92,0x2B,0xB1,0x1C,0xB2,0x72,0xAA,0x88,0x11,0x18,0x80,0x13,0x9E,
|
||||
0x03,0x18,0xB0,0x60,0xA1,0x28,0x88,0x08,0x04,0x10,0x8F,0x96,0x19,0x90,0x01,0x09,
|
||||
0xC8,0x50,0x91,0x8A,0x01,0xAB,0x03,0x50,0xBA,0x9D,0x93,0x68,0xBA,0x80,0x22,0xCB,
|
||||
0x41,0xBC,0x92,0x60,0xB9,0x1A,0x95,0x4A,0xC8,0x20,0x88,0x33,0xAC,0x92,0x38,0x83,
|
||||
0x09,0x80,0x16,0x09,0x29,0xD0,0x54,0x8C,0xA2,0x28,0x91,0x89,0x93,0x60,0xCD,0x85,
|
||||
0x1B,0xA1,0x49,0x90,0x8A,0x80,0x34,0x0C,0xC9,0x14,0x19,0x98,0xA0,0x40,0xA9,0x21,
|
||||
0xD9,0x34,0x0A,0xA9,0x10,0x23,0xCB,0x25,0xAA,0x25,0x9B,0x13,0xCD,0x16,0x09,0xA0,
|
||||
0x80,0x01,0x19,0x90,0x88,0x21,0xAC,0x33,0x8B,0xD8,0x27,0x3B,0xB8,0x81,0x31,0x80,
|
||||
0xAF,0x97,0x0A,0x82,0x0A,0xA0,0x21,0x89,0x8A,0xA2,0x32,0x8D,0xBB,0x87,0x19,0x21,
|
||||
0xC9,0xBC,0x45,0x09,0x90,0x09,0xA1,0x24,0x1A,0xD0,0x10,0x08,0x11,0xA9,0x21,0xE8,
|
||||
0x60,0xA9,0x14,0x0C,0xD1,0x32,0xAB,0x04,0x0C,0x81,0x90,0x29,0x83,0x9B,0x01,0x8F,
|
||||
0x97,0x0B,0x82,0x18,0x88,0xBA,0x06,0x39,0xC8,0x23,0xBC,0x04,0x09,0x92,0x08,0x1A,
|
||||
0xBB,0x74,0x8C,0x81,0x18,0x81,0x9D,0x83,0x41,0xCD,0x81,0x40,0x9A,0x90,0x10,0x12,
|
||||
0x9C,0xA1,0x68,0xD8,0x33,0x9C,0x91,0x01,0x12,0xBE,0x02,0x09,0x12,0x99,0x9A,0x36,
|
||||
0x0A,0xB0,0x30,0x88,0xA3,0x2D,0x12,0xBC,0x03,0x3A,0x11,0xBD,0x08,0xC8,0x62,0x80,
|
||||
0x8B,0xD8,0x23,0x38,0xF9,0x12,0x08,0x99,0x91,0x21,0x99,0x85,0x2F,0xB2,0x30,0x90,
|
||||
0x88,0xD9,0x53,0xAC,0x82,0x19,0x91,0x20,0xCC,0x96,0x29,0xC9,0x24,0x89,0x80,0x99,
|
||||
0x12,0x08,0x18,0x88,0x99,0x23,0xAB,0x73,0xCB,0x33,0x9F,0x04,0x2B,0xB1,0x08,0x03,
|
||||
0x1B,0xC9,0x21,0x32,0xFA,0x33,0xDB,0x02,0x33,0xAE,0xB9,0x54,0x8B,0xA1,0x20,0x89,
|
||||
0x90,0x11,0x88,0x09,0x98,0x23,0xBE,0x37,0x8D,0x81,0x20,0xAA,0x34,0xBB,0x13,0x18,
|
||||
0xB9,0x40,0xB1,0x18,0x83,0x8E,0xB2,0x72,0xBC,0x82,0x30,0xA9,0x9A,0x24,0x8B,0x27,
|
||||
0x0E,0x91,0x20,0x90,0x08,0xB0,0x32,0xB9,0x21,0xB0,0xAC,0x45,0x9A,0xA1,0x50,0xA9,
|
||||
0x80,0x0A,0x26,0x9B,0x11,0xBB,0x23,0x71,0xCB,0x12,0x10,0xB8,0x40,0xA9,0xA5,0x39,
|
||||
0xC0,0x30,0xB2,0x20,0xAA,0xBA,0x76,0x1C,0xC1,0x48,0x98,0x80,0x18,0x81,0xAA,0x23,
|
||||
0x9C,0xA2,0x32,0xAC,0x9A,0x43,0x9C,0x12,0xAD,0x82,0x72,0xBC,0x00,0x82,0x39,0xD1,
|
||||
0x3A,0xB8,0x35,0x9B,0x10,0x40,0xF9,0x22,0x0A,0xC0,0x51,0xB9,0x82,0x18,0x98,0xA3,
|
||||
0x79,0xD0,0x20,0x88,0x09,0x01,0x99,0x82,0x11,0x38,0xFC,0x33,0x09,0xC8,0x40,0xA9,
|
||||
0x11,0x29,0xAA,0x94,0x3A,0xC2,0x4A,0xC0,0x89,0x52,0xBC,0x11,0x08,0x09,0xB8,0x71,
|
||||
0xA9,0x08,0xA8,0x62,0x8D,0x92,0x10,0x00,0x9E,0x94,0x38,0xBA,0x13,0x88,0x90,0x4A,
|
||||
0xE2,0x30,0xBA,0x02,0x00,0x19,0xD9,0x62,0xBB,0x04,0x0B,0xA3,0x68,0xB9,0x21,0x88,
|
||||
0x9D,0x04,0x10,0x8C,0xC8,0x62,0x99,0xAA,0x24,0x1A,0x80,0x9A,0x14,0x9B,0x26,0x8C,
|
||||
0x92,0x30,0xB9,0x09,0xA3,0x71,0xBB,0x10,0x19,0x82,0x39,0xDB,0x02,0x44,0x9F,0x10,
|
||||
|
||||
/* Source: 04TOP.ROM */
|
||||
/* Length: 5952 / 0x00001740 */
|
||||
|
||||
0x07,0xFF,0x7C,0x3C,0x31,0xC6,0xC4,0xBB,0x7F,0x7F,0x7B,0x82,0x8A,0x4D,0x5F,0x7C,
|
||||
0x3E,0x44,0xD2,0xB3,0xA0,0x19,0x1B,0x6C,0x81,0x28,0xC4,0xA1,0x1C,0x4B,0x18,0x00,
|
||||
0x2A,0xA2,0x0A,0x7C,0x2A,0x00,0x01,0x89,0x98,0x48,0x8A,0x3C,0x28,0x2A,0x5B,0x3E,
|
||||
0x3A,0x1A,0x3B,0x3D,0x4B,0x3B,0x4A,0x08,0x2A,0x1A,0x2C,0x4A,0x3B,0x82,0x99,0x3C,
|
||||
0x5D,0x29,0x2B,0x39,0x0B,0x23,0xAB,0x1A,0x4C,0x79,0xA3,0x01,0xC1,0x2A,0x0A,0x38,
|
||||
0xA7,0xB9,0x12,0x1F,0x29,0x08,0x82,0xA1,0x08,0xA9,0x42,0xAA,0x95,0xB3,0x90,0x81,
|
||||
0x09,0xD4,0x1A,0x80,0x1B,0x07,0xB8,0x12,0x8E,0x49,0x81,0x92,0xD3,0x90,0xA1,0x2A,
|
||||
0x02,0xE1,0xA3,0x99,0x02,0xB3,0x94,0xB3,0xB0,0xF4,0x98,0x93,0x90,0x13,0xE1,0x81,
|
||||
0x99,0x38,0x91,0xA6,0xD3,0x99,0x94,0xC1,0x83,0xB1,0x92,0x98,0x49,0xC4,0xB2,0xA4,
|
||||
0xA3,0xD0,0x1A,0x30,0xBA,0x59,0x02,0xD4,0xA0,0xA4,0xA2,0x8A,0x01,0x00,0xB7,0xA8,
|
||||
0x18,0x2A,0x2B,0x1E,0x23,0xC8,0x1A,0x00,0x39,0xA0,0x18,0x92,0x4F,0x2D,0x5A,0x10,
|
||||
0x89,0x81,0x2A,0x8B,0x6A,0x02,0x09,0xB3,0x8D,0x48,0x1B,0x80,0x19,0x34,0xF8,0x29,
|
||||
0x0A,0x7B,0x2A,0x28,0x81,0x0C,0x02,0x1E,0x29,0x09,0x12,0xC2,0x94,0xE1,0x18,0x98,
|
||||
0x02,0xC4,0x89,0x91,0x1A,0x20,0xA9,0x02,0x1B,0x48,0x8E,0x20,0x88,0x2D,0x08,0x59,
|
||||
0x1B,0x02,0xA3,0xB1,0x8A,0x1E,0x58,0x80,0xC2,0xB6,0x88,0x91,0x88,0x11,0xA1,0xA3,
|
||||
0xE2,0x01,0xB0,0x19,0x11,0x09,0xF4,0x88,0x09,0x88,0x19,0x89,0x12,0xF1,0x2A,0x28,
|
||||
0x8C,0x25,0x99,0xA4,0x98,0x39,0xA1,0x00,0xD0,0x58,0xAA,0x59,0x01,0x0C,0x00,0x2B,
|
||||
0x00,0x08,0x89,0x6B,0x69,0x90,0x01,0x90,0x98,0x12,0xB3,0xF3,0xA0,0x89,0x02,0x3B,
|
||||
0x0C,0x50,0xA9,0x4E,0x6B,0x19,0x28,0x09,0xA2,0x08,0x2F,0x20,0x88,0x92,0x8A,0x11,
|
||||
0xC4,0x93,0xF1,0x18,0x88,0x11,0xF2,0x80,0x92,0xA8,0x02,0xA8,0xB7,0xB3,0xA3,0xA0,
|
||||
0x88,0x1A,0x40,0xE2,0x91,0x19,0x88,0x18,0x91,0x83,0xC1,0xB5,0x92,0xA9,0xC6,0x90,
|
||||
0x01,0xC2,0x81,0x98,0x03,0xF0,0x00,0x2C,0x2A,0x92,0x2C,0x83,0x1F,0x3A,0x29,0x00,
|
||||
0xB8,0x70,0xAB,0x69,0x18,0x89,0x10,0x0D,0x12,0x0B,0x88,0x4A,0x3A,0x9B,0x70,0xA8,
|
||||
0x28,0x2F,0x2A,0x3A,0x1B,0x85,0x88,0x8B,0x6A,0x29,0x00,0x91,0x91,0x1B,0x7C,0x29,
|
||||
0x01,0x88,0x90,0x19,0x2B,0x2B,0x00,0x39,0xA8,0x5E,0x21,0x89,0x91,0x09,0x3A,0x6F,
|
||||
0x2A,0x18,0x18,0x8B,0x50,0x89,0x2B,0x19,0x49,0x88,0x29,0xF5,0x89,0x08,0x09,0x12,
|
||||
0xAA,0x15,0xB0,0x82,0xAC,0x38,0x00,0x3F,0x81,0x10,0xB0,0x49,0xA2,0x81,0x3A,0xC8,
|
||||
0x87,0x90,0xC4,0xA3,0x99,0x19,0x83,0xE1,0x84,0xE2,0xA2,0x90,0x80,0x93,0xB5,0xC4,
|
||||
0xB3,0xA1,0x0A,0x18,0x92,0xC4,0xA0,0x93,0x0C,0x3A,0x18,0x01,0x1E,0x20,0xB1,0x82,
|
||||
0x8C,0x03,0xB5,0x2E,0x82,0x19,0xB2,0x1B,0x1B,0x6B,0x4C,0x19,0x12,0x8B,0x5A,0x11,
|
||||
0x0C,0x3A,0x2C,0x18,0x3D,0x08,0x2A,0x5C,0x18,0x00,0x88,0x3D,0x29,0x80,0x2A,0x09,
|
||||
0x00,0x7A,0x0A,0x10,0x0B,0x69,0x98,0x10,0x81,0x3F,0x00,0x18,0x19,0x91,0xB7,0x9A,
|
||||
0x28,0x8A,0x48,0x92,0xF3,0xA2,0x88,0x98,0x87,0xA1,0x88,0x80,0x81,0x95,0xD1,0xA3,
|
||||
0x1B,0x1C,0x39,0x10,0xA1,0x2A,0x0B,0x7A,0x4B,0x80,0x13,0xC1,0xD1,0x2B,0x2A,0x85,
|
||||
0xB2,0xA2,0x93,0xB2,0xD3,0x80,0xD1,0x18,0x08,0x08,0xB7,0x98,0x81,0x3F,0x01,0x88,
|
||||
0x01,0xE2,0x00,0x9A,0x59,0x08,0x10,0xC3,0x99,0x84,0xA9,0xA5,0x91,0x91,0x91,0x80,
|
||||
0xB5,0x94,0xC0,0x01,0x98,0x09,0x84,0xB0,0x80,0x7A,0x08,0x18,0x90,0xA8,0x6A,0x1C,
|
||||
0x39,0x2A,0xB7,0x98,0x19,0x10,0x2A,0xA1,0x10,0xBD,0x39,0x18,0x2D,0x39,0x3F,0x10,
|
||||
0x3F,0x01,0x09,0x19,0x0A,0x38,0x8C,0x40,0xB3,0xB4,0x93,0xAD,0x20,0x2B,0xD4,0x81,
|
||||
0xC3,0xB0,0x39,0xA0,0x23,0xD8,0x04,0xB1,0x9B,0xA7,0x1A,0x92,0x08,0xA5,0x88,0x81,
|
||||
0xE2,0x01,0xB8,0x01,0x81,0xC1,0xC7,0x90,0x92,0x80,0xA1,0x97,0xA0,0xA2,0x82,0xB8,
|
||||
0x18,0x00,0x9C,0x78,0x98,0x83,0x0B,0x0B,0x32,0x7D,0x19,0x10,0xA1,0x19,0x09,0x0A,
|
||||
0x78,0xA8,0x10,0x1B,0x29,0x29,0x1A,0x14,0x2F,0x88,0x4A,0x1B,0x10,0x10,0xAB,0x79,
|
||||
0x0D,0x49,0x18,0xA0,0x02,0x1F,0x19,0x3A,0x2B,0x11,0x8A,0x88,0x79,0x8A,0x20,0x49,
|
||||
0x9B,0x58,0x0B,0x28,0x18,0xA9,0x3A,0x7D,0x00,0x29,0x88,0x82,0x3D,0x1A,0x38,0xBA,
|
||||
0x15,0x09,0xAA,0x51,0x8B,0x83,0x3C,0x8A,0x58,0x1B,0xB5,0x01,0xBB,0x50,0x19,0x99,
|
||||
0x24,0xCA,0x21,0x1B,0xA2,0x87,0xA8,0xB1,0x68,0xA1,0xA6,0xA2,0xA8,0x29,0x8B,0x24,
|
||||
0xB4,0xE2,0x92,0x8A,0x00,0x19,0x93,0xB5,0xB4,0xB1,0x81,0xB1,0x03,0x9A,0x82,0xA7,
|
||||
0x90,0xD6,0xA0,0x80,0x1B,0x29,0x01,0xA4,0xE1,0x18,0x0A,0x2A,0x29,0x92,0xC7,0xA8,
|
||||
0x81,0x19,0x89,0x30,0x10,0xE0,0x30,0xB8,0x10,0x0C,0x1A,0x79,0x1B,0xA7,0x80,0xA0,
|
||||
0x00,0x0B,0x28,0x18,0xB1,0x85,0x1E,0x00,0x20,0xA9,0x18,0x18,0x1C,0x13,0xBC,0x15,
|
||||
0x99,0x2E,0x12,0x00,0xE1,0x00,0x0B,0x3B,0x21,0x90,0x06,0xC9,0x2A,0x49,0x0A,0x18,
|
||||
0x20,0xD1,0x3C,0x08,0x00,0x83,0xC9,0x41,0x8E,0x18,0x08,0x02,0xA0,0x09,0xA4,0x7B,
|
||||
0x90,0x19,0x2A,0x10,0x2A,0xA8,0x71,0xBA,0x10,0x4A,0x0E,0x22,0xB2,0xB2,0x1B,0x8C,
|
||||
0x78,0x1A,0xB5,0x93,0xA9,0x1B,0x49,0x19,0x29,0xA3,0xC6,0x88,0xAA,0x32,0x0D,0x1B,
|
||||
0x22,0x08,0xC2,0x18,0xB9,0x79,0x3F,0x01,0x10,0xA9,0x84,0x1C,0x09,0x21,0xB0,0xA7,
|
||||
0x0A,0x99,0x50,0x0C,0x81,0x28,0x8B,0x48,0x2E,0x00,0x08,0x99,0x38,0x5B,0x88,0x14,
|
||||
0xA9,0x08,0x11,0xAA,0x72,0xC1,0xB3,0x09,0x8A,0x05,0x91,0xF2,0x81,0xA1,0x09,0x02,
|
||||
0xF2,0x92,0x99,0x1A,0x49,0x80,0xC5,0x90,0x90,0x18,0x09,0x12,0xA1,0xF2,0x81,0x98,
|
||||
0xC6,0x91,0xA0,0x11,0xA0,0x94,0xB4,0xF2,0x81,0x8B,0x03,0x80,0xD2,0x93,0xA8,0x88,
|
||||
0x69,0xA0,0x03,0xB8,0x88,0x32,0xBC,0x97,0x80,0xB1,0x3B,0x1A,0xA6,0x00,0xD1,0x01,
|
||||
0x0B,0x3B,0x30,0x9B,0x31,0x3E,0x92,0x19,0x8A,0xD3,0x5C,0x1B,0x41,0xA0,0x93,0xA2,
|
||||
0xAF,0x39,0x4C,0x01,0x92,0xA8,0x81,0x3C,0x0D,0x78,0x98,0x00,0x19,0x0A,0x20,0x2D,
|
||||
0x29,0x3C,0x1B,0x48,0x88,0x99,0x7A,0x2D,0x29,0x2A,0x82,0x80,0xA8,0x49,0x3E,0x19,
|
||||
0x11,0x98,0x82,0x9A,0x3B,0x28,0x2F,0x20,0x4C,0x90,0x29,0x19,0x9A,0x7A,0x29,0x28,
|
||||
0x98,0x88,0x33,0xCD,0x11,0x3A,0xC1,0xA4,0xA0,0xC4,0x82,0xC8,0x50,0x98,0xB2,0x21,
|
||||
0xC0,0xB6,0x98,0x82,0x80,0x9C,0x23,0x00,0xF8,0x30,0xA8,0x1A,0x68,0xA8,0x86,0x9A,
|
||||
0x01,0x2A,0x0A,0x97,0x91,0xC1,0x18,0x89,0x02,0x83,0xE0,0x01,0x8B,0x29,0x30,0xE2,
|
||||
0x91,0x0B,0x18,0x3B,0x1C,0x11,0x28,0xAC,0x78,0x80,0x93,0x91,0xA9,0x49,0x8B,0x87,
|
||||
0x90,0x99,0x3D,0x5A,0x81,0x08,0xA1,0x11,0x2F,0x1A,0x21,0x9B,0x15,0xA2,0xB0,0x11,
|
||||
0xC0,0x91,0x5B,0x98,0x24,0xA2,0xF2,0x92,0x8B,0x6A,0x18,0x81,0xB5,0xB1,0x88,0x4C,
|
||||
0x00,0x00,0xA4,0xC1,0x2B,0x1A,0x59,0x0A,0x02,0x80,0x1E,0x02,0x08,0xB3,0x80,0x9A,
|
||||
0x23,0xB8,0xF2,0x84,0xAB,0x01,0x48,0x90,0xA7,0x90,0x0A,0x29,0x09,0x95,0x99,0xA0,
|
||||
0x59,0x2B,0x00,0x97,0xB0,0x29,0x89,0x2A,0x03,0xD0,0xB7,0x1B,0x81,0x00,0xA6,0xB1,
|
||||
0x90,0x09,0x48,0xC0,0x11,0x00,0x8A,0x00,0x5B,0x83,0x9A,0x18,0x2F,0x3C,0x18,0x11,
|
||||
0xA9,0x04,0x1A,0x4F,0x01,0x98,0x81,0x09,0x09,0x4A,0x18,0xB4,0xA2,0x0B,0x59,0x90,
|
||||
0x3B,0x49,0xBC,0x40,0x6A,0x88,0x3A,0x08,0x3E,0x3A,0x80,0x93,0xB0,0xE1,0x5A,0x00,
|
||||
0xA4,0xB3,0xE3,0x90,0x0D,0x38,0x09,0x82,0xC4,0xA1,0xB1,0x4C,0x18,0x10,0x91,0xB2,
|
||||
0x13,0xEA,0x34,0x99,0x88,0xA6,0x89,0x92,0x91,0xC1,0x20,0xB2,0xC2,0x86,0xD2,0xB3,
|
||||
0x80,0xB2,0x08,0x09,0x87,0x91,0xC0,0x11,0x89,0x90,0x28,0xB9,0x79,0x19,0xA4,0x82,
|
||||
0xD0,0x03,0x0C,0xA3,0xA5,0xB2,0xB2,0x1B,0x29,0x13,0xF1,0xB4,0x81,0x9D,0x38,0x00,
|
||||
0xC4,0xA1,0x89,0x59,0x1A,0x81,0xA4,0xA9,0x1C,0x6A,0x19,0x02,0xB1,0x1A,0x4A,0x0B,
|
||||
0x78,0x89,0x81,0x1C,0x2A,0x29,0x4A,0xA3,0x3E,0x1C,0x49,0x1A,0x08,0x21,0xAE,0x28,
|
||||
0x4B,0x19,0x20,0x8C,0x10,0x3A,0xAB,0x26,0x8B,0x18,0x59,0x99,0x13,0xA2,0xAB,0x79,
|
||||
0x2F,0x18,0x10,0xB2,0x80,0x1B,0x4D,0x5A,0x80,0x82,0x98,0x81,0x80,0x09,0xA5,0x90,
|
||||
0x91,0x03,0xC2,0xE2,0x81,0xA8,0x82,0x09,0xC6,0xA3,0xB1,0x08,0x5B,0x08,0x05,0xD1,
|
||||
0xA2,0x89,0x2A,0x28,0x91,0xA6,0x88,0xB0,0x49,0x80,0x09,0x08,0x88,0x07,0xB8,0x05,
|
||||
0x99,0x81,0x88,0x18,0xE2,0x00,0xC3,0x18,0x0D,0x10,0x30,0xD0,0x93,0x8A,0x09,0x10,
|
||||
0x2F,0x11,0x90,0xA1,0x20,0x9B,0xB1,0x73,0xC8,0x94,0x98,0x3B,0x01,0x0C,0x30,0x19,
|
||||
0xF8,0x12,0x90,0xBA,0x78,0x0A,0x11,0x98,0xA0,0x79,0x8A,0x30,0x2B,0xC2,0x11,0x0D,
|
||||
0x09,0x7A,0x00,0x82,0xB9,0x01,0x7A,0x89,0x21,0x09,0xA1,0x0A,0x7C,0x10,0x88,0xB5,
|
||||
0x88,0x0A,0x2B,0x69,0x1A,0x10,0xA0,0x5B,0x19,0x1A,0x10,0x19,0x1A,0x6C,0x20,0x90,
|
||||
0xA5,0x98,0x1B,0x0A,0x69,0x82,0xD1,0x18,0x09,0x19,0x2A,0x93,0xD4,0x9A,0x01,0x49,
|
||||
0xA2,0xA2,0x82,0xD8,0x22,0xAA,0x97,0xA9,0x2D,0x38,0x2A,0xB6,0x80,0x90,0x0A,0x3C,
|
||||
0x82,0x94,0xB8,0x21,0x0E,0x2A,0x22,0xB8,0x00,0x4F,0x2B,0x3A,0x81,0xA1,0x29,0x2C,
|
||||
0x6A,0x13,0xD1,0xA2,0x98,0x28,0x0C,0x01,0xD5,0x08,0xA9,0x31,0xB3,0xB0,0xA7,0xB0,
|
||||
0x29,0x1B,0x87,0xA2,0xA1,0xB2,0x4A,0x89,0x11,0xC3,0xF3,0x98,0x08,0x03,0xA0,0xA3,
|
||||
0xC5,0x90,0xB3,0xB5,0xB4,0xB8,0x02,0x91,0x91,0xD3,0xA4,0xC1,0x1B,0x82,0x28,0xA4,
|
||||
0xD1,0x94,0x8A,0x28,0x08,0x03,0xE0,0x80,0xD4,0x90,0x91,0xA1,0x3B,0x3D,0x02,0xE4,
|
||||
0xA1,0x92,0x89,0x1A,0x4B,0x95,0xB3,0x90,0x99,0x6A,0x0A,0x30,0xA1,0x93,0xA6,0xA9,
|
||||
0x85,0x8B,0x82,0x10,0xB1,0xA3,0x94,0xF8,0x38,0x9A,0x30,0x1A,0x8B,0xA7,0x89,0x01,
|
||||
0x5B,0x19,0x18,0x11,0xF0,0x18,0x1C,0x39,0x19,0x0C,0x12,0x1C,0x2A,0x7B,0x3A,0x88,
|
||||
0x2B,0x18,0x2B,0x5C,0x20,0x92,0x8D,0x38,0x8A,0x3A,0x5B,0x2E,0x3A,0x2B,0x10,0x12,
|
||||
0xBB,0x6A,0x4D,0x18,0x10,0xB1,0x81,0x2A,0x8B,0x79,0x80,0x01,0x0A,0x09,0x5B,0x2D,
|
||||
0x84,0x8A,0x08,0x02,0xA2,0x91,0x82,0xE8,0x50,0x9B,0x85,0xA3,0xB0,0xA3,0x1B,0x02,
|
||||
0x18,0xF3,0xA2,0x88,0xAB,0x53,0xD1,0xB4,0xA3,0x09,0x09,0x18,0xD4,0x08,0xB0,0x09,
|
||||
0x58,0xD1,0x82,0x89,0x81,0x1A,0x18,0x05,0xB9,0xC3,0x30,0xC0,0x95,0x80,0xC3,0x89,
|
||||
0x89,0x13,0x88,0xF2,0x93,0x0E,0x18,0x01,0x92,0xA5,0xB8,0x2A,0x39,0xAA,0x33,0x9A,
|
||||
0xB1,0x11,0xF5,0xA1,0xA1,0x0A,0x50,0xB8,0x03,0xC4,0xA0,0x4E,0x29,0x10,0x88,0xC2,
|
||||
0x1A,0x39,0x1D,0x28,0x98,0x94,0x0E,0x10,0x2A,0x3C,0x02,0x2D,0x1B,0x4B,0x3B,0x49,
|
||||
0x19,0xA9,0x48,0x2F,0x29,0x10,0x89,0x02,0x0C,0x10,0x09,0xB9,0x70,0x1B,0x8A,0x50,
|
||||
0xA8,0x2B,0x49,0x89,0x69,0x88,0x95,0x89,0x90,0x92,0x4C,0x19,0x82,0xC1,0x01,0x80,
|
||||
0xA0,0x2B,0x7A,0x81,0x10,0xC2,0xB7,0x98,0x88,0x19,0x2C,0x03,0xB1,0xA4,0xA1,0x0C,
|
||||
0x3B,0x78,0x88,0x85,0xB1,0xA0,0x1B,0x3A,0x4A,0x08,0x94,0x81,0xF1,0x80,0x00,0x0C,
|
||||
0x59,0x09,0x18,0x90,0xA6,0x92,0x8C,0x1A,0x79,0x92,0xA8,0x00,0x81,0x2E,0x2A,0x13,
|
||||
0xA2,0xB0,0xA5,0x88,0x88,0x89,0x11,0x19,0xA0,0xF3,0x82,0xB0,0x83,0x5F,0x2A,0x01,
|
||||
0xA1,0x94,0xB0,0x09,0x78,0x98,0xA3,0xA6,0xA0,0x91,0x80,0x93,0x98,0xC1,0x12,0x18,
|
||||
0xC9,0x17,0xA0,0xA0,0x1A,0x21,0x80,0x99,0xD4,0x30,0x9D,0x00,0x10,0x2F,0x08,0x1C,
|
||||
0x21,0x08,0xB4,0xC3,0x2B,0xA9,0x52,0xD2,0xA3,0xD1,0x09,0x10,0x8B,0x24,0x92,0xD1,
|
||||
0x80,0x19,0xA0,0x2C,0x12,0x49,0xAA,0xB6,0x95,0xB8,0x08,0x3A,0x2B,0x01,0xF3,0xB3,
|
||||
0x0B,0x09,0x79,0x18,0xA2,0xA4,0xA0,0x18,0x0C,0x20,0x08,0xA9,0x16,0x0C,0x00,0x1B,
|
||||
0x08,0x2B,0x7B,0x01,0x01,0xB9,0x59,0x19,0x8B,0x45,0xA8,0x80,0x0C,0x1A,0x41,0x1E,
|
||||
0x00,0x28,0xA8,0x5A,0x00,0xC1,0x49,0x99,0x21,0x1D,0x08,0x85,0x99,0x95,0x89,0x90,
|
||||
0x11,0x90,0xD1,0x28,0xB2,0xA7,0x99,0x81,0x02,0xAC,0x13,0x81,0xB2,0xA6,0xA9,0x28,
|
||||
0x1C,0xB1,0x33,0xD1,0xC1,0x58,0xA8,0x14,0xB0,0xB7,0x91,0xA0,0x82,0x89,0xC2,0x28,
|
||||
0xA1,0xB2,0x49,0xD2,0x94,0xC8,0x12,0x80,0x99,0x85,0x08,0xD3,0x09,0xA2,0xB3,0x1E,
|
||||
0x08,0x21,0xB9,0x23,0xB4,0xAB,0x41,0xAC,0x87,0x09,0xA2,0xC5,0x0B,0x2A,0x5A,0x91,
|
||||
0x20,0x9A,0x89,0x78,0x9B,0x31,0x89,0x80,0x29,0x0A,0xB7,0x3C,0x98,0x48,0x1D,0x00,
|
||||
0x01,0xB0,0x20,0x2F,0x29,0x4A,0x89,0x94,0x1C,0x88,0x28,0x2B,0x10,0x88,0x9A,0x71,
|
||||
0x9A,0x08,0x4A,0x2F,0x18,0x2B,0x18,0x02,0xA8,0x4B,0x7A,0x99,0x48,0x80,0xA8,0x20,
|
||||
0x1D,0x40,0xA8,0x10,0x08,0xA8,0xC5,0x88,0xC2,0x18,0x88,0x2A,0x12,0xF3,0x82,0xD8,
|
||||
0x20,0x0A,0x09,0xA6,0x98,0x04,0xB9,0x11,0x18,0xC3,0xE1,0x29,0xA1,0x11,0xC1,0x03,
|
||||
0xE2,0x9A,0x33,0xA9,0xB5,0x98,0x92,0xA1,0x02,0xF8,0x21,0xA8,0x10,0x02,0xC1,0xB7,
|
||||
0x1B,0x90,0x5B,0x3C,0x83,0x93,0xE0,0x19,0x1A,0x11,0x11,0xF1,0x92,0x89,0x19,0x2C,
|
||||
0x2C,0x41,0x99,0x92,0x90,0x3F,0x18,0x4B,0x00,0x08,0xD2,0x01,0xB2,0xAA,0x78,0x09,
|
||||
0x01,0x91,0xA2,0x98,0x2F,0x3A,0x2C,0x01,0x00,0x93,0xE0,0x28,0x2C,0x2B,0x01,0x12,
|
||||
0xE1,0x80,0xB3,0x3D,0x3A,0x0A,0x50,0x98,0xC2,0xA0,0x11,0xAA,0x30,0x87,0x90,0xC2,
|
||||
0x29,0x88,0x38,0xC8,0xB5,0x90,0xBA,0x70,0x1A,0x02,0x94,0xD0,0x80,0x1A,0x82,0xA6,
|
||||
0xB0,0x91,0x18,0xB3,0x00,0x13,0xF1,0xA2,0xC1,0x82,0xB0,0x00,0x15,0x0B,0xD3,0x02,
|
||||
0xA8,0x91,0x2B,0x1F,0x49,0x88,0xA6,0x80,0x88,0x08,0x1B,0xA5,0x80,0xB9,0x06,0x0B,
|
||||
0x90,0x21,0x9D,0x48,0x18,0xA0,0x15,0xC9,0x82,0x2B,0x1A,0x42,0x9A,0xC4,0x39,0xBC,
|
||||
0x69,0x00,0xA0,0x29,0x8C,0x39,0x59,0x08,0x09,0x49,0xA9,0x6B,0x81,0x00,0x98,0xB0,
|
||||
0x68,0x3D,0x81,0x88,0x18,0x19,0x1D,0x12,0x80,0xB2,0x3A,0x3F,0x85,0x92,0xD0,0x00,
|
||||
0x0A,0x19,0x12,0xF1,0x02,0x9B,0x19,0x40,0xB9,0x11,0x02,0xF2,0x1A,0x08,0x94,0x0A,
|
||||
0xC2,0x83,0x0B,0xB4,0xA4,0xC0,0x32,0xD8,0x86,0x98,0x90,0x95,0x89,0xA3,0x83,0xC2,
|
||||
0x92,0xE1,0x92,0x82,0xD9,0x03,0x08,0xA9,0x85,0x92,0xA2,0x80,0xE0,0x30,0x8B,0xB3,
|
||||
0x87,0x89,0x90,0x83,0xA0,0x08,0x92,0x93,0x3E,0xAB,0x43,0x89,0xE3,0x80,0x83,0x2F,
|
||||
0x00,0xA3,0x80,0xC9,0x22,0x3F,0x08,0x81,0x0B,0x33,0x9A,0xA3,0x7B,0x0C,0x29,0x4A,
|
||||
0x1B,0x21,0xAA,0x70,0x1B,0x0D,0x48,0x1A,0x81,0x88,0xB1,0x39,0x3F,0x08,0x58,0xA0,
|
||||
0x81,0x1A,0x1A,0x2B,0x6D,0x11,0x0A,0x91,0x01,0x1A,0x98,0x5A,0x0C,0x03,0xB1,0x84,
|
||||
0xA3,0xAD,0x58,0x2A,0xA1,0x84,0xB1,0xA0,0x5C,0x2B,0x13,0xA8,0x95,0x83,0xE8,0x10,
|
||||
0x81,0xB0,0x00,0xC2,0x96,0xA0,0x91,0x00,0x2C,0x90,0x30,0xF2,0x80,0xA8,0x39,0x21,
|
||||
0xC1,0x03,0xAC,0x39,0x7C,0x29,0x91,0x1A,0x00,0x19,0x2C,0x3A,0x93,0xB0,0x29,0x8F,
|
||||
0x28,0x02,0x93,0xF3,0xA9,0x01,0x03,0xE0,0x08,0x09,0x1D,0x58,0xA1,0x83,0xA9,0x6B,
|
||||
0x2A,0x3C,0x21,0x89,0xC2,0x2C,0x4B,0x8A,0x50,0x81,0x98,0xA8,0x32,0x0C,0x8E,0x24,
|
||||
0x0B,0x1A,0x81,0x92,0xA1,0x4F,0x18,0x3A,0x0A,0xB4,0x18,0x2E,0x39,0x82,0x19,0xD3,
|
||||
0xD0,0x28,0x1B,0x11,0x98,0x07,0xAA,0x28,0x00,0x88,0xB4,0x89,0x1B,0x1F,0x22,0x00,
|
||||
0xB3,0xC9,0x33,0xAB,0x2B,0xB5,0x48,0x98,0x98,0xA7,0x10,0xD2,0xC1,0x23,0xCA,0x93,
|
||||
0xC6,0x80,0xA1,0x88,0x02,0x89,0xE2,0x09,0x38,0xBA,0x40,0x89,0x21,0xD8,0x49,0x10,
|
||||
0x8D,0x02,0x90,0xC3,0x9A,0x24,0x89,0x08,0x84,0xA5,0x9C,0x10,0x11,0x9C,0x88,0x30,
|
||||
0x3C,0xA1,0x94,0x58,0x8C,0x0B,0x69,0x29,0x9A,0x81,0x12,0x2B,0x8B,0x79,0x94,0xB0,
|
||||
0xC1,0x84,0xC2,0x99,0x25,0x99,0x11,0xA2,0x93,0xE4,0x99,0x80,0x0A,0x00,0x10,0xB7,
|
||||
0xB0,0x31,0xBA,0x3C,0x21,0xB3,0xF1,0x18,0xA0,0x2A,0x20,0xA3,0x06,0xE8,0x28,0xA1,
|
||||
0xB4,0x08,0x0B,0x11,0x4B,0xB7,0x90,0xA5,0x98,0x3D,0x19,0x02,0xA1,0xC4,0xB2,0x19,
|
||||
0x28,0xC0,0xA5,0x92,0xB1,0xA3,0x0A,0x0A,0x08,0x2B,0x70,0xC4,0xB3,0x00,0xBC,0x4B,
|
||||
0x39,0x12,0xE3,0xA0,0x00,0x3F,0x18,0x29,0x94,0xD1,0x19,0x09,0x00,0xA1,0x83,0x99,
|
||||
0x9B,0x35,0x80,0xC4,0xB1,0x6A,0x1A,0x1C,0x29,0x38,0x0E,0x19,0x5A,0x1A,0x82,0x8A,
|
||||
0x59,0x2A,0x2E,0x20,0x88,0xA8,0x3A,0x38,0x3D,0x00,0xB3,0x29,0xAD,0x49,0x10,0x0C,
|
||||
0x01,0x01,0xA3,0x8F,0x85,0x09,0x1B,0x88,0x10,0xA3,0xD2,0x90,0x3C,0x5C,0x39,0x03,
|
||||
0xD1,0xA0,0x00,0x2A,0x0B,0x04,0xA7,0x90,0xA0,0x11,0x90,0x99,0x83,0xB4,0xB1,0xF1,
|
||||
0x84,0x88,0x90,0x18,0x18,0xD3,0xD2,0xB3,0xA0,0x1A,0x21,0xA7,0xB2,0xB3,0x92,0x9A,
|
||||
0x22,0xB9,0x28,0x38,0xBD,0x87,0x2A,0xB1,0x13,0x0D,0x0A,0x38,0xC9,0x24,0xC0,0x19,
|
||||
0x23,0x0F,0x01,0x88,0xC0,0x2A,0x82,0x18,0x28,0xF0,0x18,0x2A,0x29,0x4B,0x35,0xB8,
|
||||
0xA3,0x9D,0x18,0x1B,0x40,0x00,0x9A,0x5C,0x3A,0x09,0x2F,0x38,0x8A,0x3B,0x3B,0x11,
|
||||
0x5C,0x19,0x2B,0x4A,0x08,0x0A,0x3D,0x20,0x4F,0x3A,0x19,0x2A,0x18,0x4D,0x1B,0x3A,
|
||||
0x11,0x0D,0x3A,0x3C,0x4B,0x93,0x81,0xAA,0x6B,0x4A,0x18,0x00,0xC3,0xC3,0x9A,0x59,
|
||||
0x2A,0x1B,0xA7,0xA1,0x81,0x88,0x88,0x58,0xB2,0xB1,0x2B,0x83,0xD4,0x81,0x08,0x0F,
|
||||
0x00,0x20,0xC2,0xE2,0x80,0x08,0x1C,0x29,0x04,0xB1,0xA2,0x01,0x1C,0x91,0x00,0x0C,
|
||||
0x49,0xB0,0x43,0xF2,0x99,0x39,0x3F,0x00,0x81,0x94,0xC1,0x09,0x1A,0x69,0x90,0x80,
|
||||
0x94,0xAA,0x20,0x2A,0x91,0xB1,0x39,0x7A,0x38,0xD1,0x10,0x8A,0x8C,0x5A,0x01,0xB5,
|
||||
0x98,0x80,0x2A,0x0B,0x32,0x92,0xF1,0x81,0x9A,0x23,0x8A,0xA3,0xB7,0x09,0x03,0x08,
|
||||
0xD0,0x94,0x9A,0x09,0x01,0x93,0xB7,0xC2,0x8C,0x3A,0x83,0x99,0x05,0xA0,0x0B,0x29,
|
||||
0x93,0xE5,0x80,0x89,0x38,0x90,0x8A,0xD7,0xA1,0x19,0x1B,0x48,0x98,0x92,0xC3,0xA1,
|
||||
0x09,0x3F,0x02,0x0C,0x22,0xC3,0xB2,0xA1,0x01,0x9F,0x4A,0x01,0xA3,0xD3,0xB0,0x28,
|
||||
0x3F,0x29,0x20,0xA2,0xC2,0xB1,0x08,0x5A,0x98,0x13,0xD2,0xC1,0x01,0xB2,0x80,0x3D,
|
||||
0x03,0xC1,0x89,0x96,0x90,0x90,0x3A,0x1A,0x9A,0x32,0xB6,0xA2,0x8E,0x4A,0x28,0x8A,
|
||||
0x84,0xA2,0x8A,0x2D,0x49,0x09,0x88,0x18,0x30,0x9D,0x2C,0x23,0xB1,0x0C,0x92,0x2D,
|
||||
0x39,0x82,0xC4,0x2E,0x10,0x1A,0x10,0xB9,0x48,0x19,0x39,0xBA,0x34,0xDA,0x2D,0x48,
|
||||
0x1A,0xA6,0x98,0x83,0x9A,0x1D,0x38,0x04,0xD0,0x18,0x90,0x2C,0x11,0x93,0xD3,0x9A,
|
||||
0x11,0x08,0x82,0xF1,0x01,0xA0,0x2A,0x93,0xD3,0xB4,0xB8,0x82,0x2F,0x11,0xA3,0xB3,
|
||||
0xA8,0x3B,0x09,0x23,0x96,0xC8,0x3B,0x3F,0x93,0x82,0xA1,0x90,0x3F,0x28,0x81,0xD1,
|
||||
0x93,0x08,0x2D,0x18,0x91,0xB3,0xB5,0x98,0x2A,0x2B,0x84,0xB1,0x5B,0x8A,0x31,0x18,
|
||||
0x80,0x8B,0x7E,0x39,0x2B,0x02,0xC1,0x8B,0x6C,0x49,0x09,0x10,0xA1,0x08,0x01,0x0C,
|
||||
0x20,0xA1,0x09,0x4F,0x18,0x00,0x01,0xA0,0x5C,0x1B,0x5B,0x10,0x92,0x90,0x2B,0x5A,
|
||||
0x3D,0x18,0x91,0x19,0x98,0x2D,0x39,0x89,0x2D,0x3A,0x48,0x2C,0x11,0xB5,0x9A,0x19,
|
||||
0x5B,0x28,0x90,0x95,0x98,0x89,0x2B,0x40,0x08,0x90,0xF3,0x0A,0x08,0xA6,0x80,0x91,
|
||||
0xB2,0xA0,0x02,0xF2,0xA1,0xB7,0x89,0x81,0x82,0x91,0xB1,0x21,0xAB,0x32,0xE9,0x04,
|
||||
0xA2,0x8D,0x12,0x91,0xA3,0xA3,0xD2,0x8B,0x39,0xD1,0x84,0xE2,0x90,0x00,0x2B,0x29,
|
||||
0xA3,0xD4,0xA1,0x91,0x1D,0x5A,0x08,0x19,0x11,0x99,0x08,0x18,0x49,0x0F,0x18,0x10,
|
||||
0x82,0xF1,0x00,0x89,0x2F,0x3A,0x01,0xB3,0xC2,0x81,0x3F,0x29,0x08,0x10,0xA1,0xA1,
|
||||
0x3B,0x5D,0x19,0x28,0x0B,0x38,0x82,0x91,0x19,0xBD,0x3B,0x7A,0x80,0x12,0xB3,0xE0,
|
||||
0x0B,0x6A,0x01,0x88,0xA4,0x08,0x0B,0x08,0x59,0x80,0x80,0x1D,0x49,0x89,0x00,0x84,
|
||||
0x99,0x1A,0x2B,0x32,0xE3,0xB4,0xA9,0x3A,0x99,0x31,0xE3,0xAA,0x58,0x3B,0x88,0x95,
|
||||
0xC0,0x18,0x4A,0x09,0x30,0xF2,0xA3,0x1C,0x1B,0x49,0x00,0xD3,0xB2,0xA0,0x18,0x11,
|
||||
0x92,0xD3,0xB2,0x91,0x80,0xE7,0xA1,0x91,0x98,0x19,0x22,0xC2,0xD2,0x18,0x8D,0x3B,
|
||||
0x10,0xA5,0x91,0x98,0x02,0x3E,0x80,0x01,0x90,0xAA,0x13,0xF1,0x02,0xD1,0x08,0x19,
|
||||
0x49,0xB4,0x91,0xB4,0x99,0x2A,0x0C,0x32,0xC0,0x05,0x88,0x0B,0x80,0x2C,0x81,0x10,
|
||||
0x0B,0x51,0xA9,0x19,0x05,0xBF,0x28,0x20,0xE1,0x90,0x80,0x28,0x19,0x08,0x26,0xB1,
|
||||
0xA1,0x18,0x88,0x2A,0xF0,0x12,0x8A,0xB3,0x14,0x1B,0xD4,0xD8,0x10,0x08,0x8A,0x17,
|
||||
0xA0,0x98,0x2B,0x3A,0x29,0x48,0xA4,0x99,0x0E,0x4A,0x12,0x8B,0x31,0x8B,0x4E,0x1A,
|
||||
0x11,0xB5,0x89,0x91,0x29,0x89,0xC2,0x97,0x90,0x0A,0x19,0x11,0x91,0xC1,0xD5,0x08,
|
||||
0x89,0x20,0x91,0xB1,0x1A,0x2D,0x18,0x29,0xD2,0x3B,0x3E,0x3A,0x2A,0x90,0x82,0x1C,
|
||||
0x49,0x3B,0x93,0xB6,0xC8,0x4C,0x02,0x91,0x93,0xF2,0x88,0x2D,0x28,0x81,0x82,0xC1,
|
||||
0x89,0x2D,0x6B,0x19,0x82,0x80,0x18,0x8B,0x39,0x39,0xC8,0x3A,0x6A,0x0A,0x22,0xD2,
|
||||
0x09,0x2C,0x1A,0x68,0x92,0xE2,0x89,0x2A,0x2A,0x30,0xC2,0xA3,0xB4,0x1D,0x2A,0x09,
|
||||
0x93,0x18,0xF2,0x89,0x28,0xB3,0x01,0x8F,0x18,0x11,0xA1,0x93,0x90,0xD1,0x7A,0x20,
|
||||
0xC3,0xA2,0xA8,0x88,0x1D,0x28,0xA5,0xA2,0xA2,0x0B,0x29,0x2B,0x87,0xC1,0x80,0x0A,
|
||||
0x19,0x01,0x12,0xF1,0x10,0x80,0x0A,0x18,0x08,0x2F,0x4A,0x02,0x89,0x1B,0x29,0x5D,
|
||||
0x4C,0x08,0x82,0xA1,0x0A,0x3A,0x4B,0x29,0xC6,0xC3,0x09,0x09,0x88,0x39,0x98,0x82,
|
||||
0xA5,0x1A,0x30,0x11,0xBD,0x3F,0x12,0x8B,0x28,0xC3,0x88,0x3F,0x2B,0x3B,0x48,0xA1,
|
||||
0x80,0x8A,0x4D,0x39,0x01,0x93,0xA2,0xF1,0x19,0x19,0x0A,0x02,0xB2,0x8B,0x24,0xD2,
|
||||
0x4B,0x12,0xC8,0x2E,0x10,0xB5,0x89,0x01,0x09,0x1C,0x2A,0x03,0xD4,0x91,0x98,0x99,
|
||||
0x11,0x2B,0xE4,0x00,0x00,0x01,0xE0,0xA5,0x89,0x99,0x31,0x18,0xD0,0xB7,0x98,0x18,
|
||||
0x0A,0x10,0x94,0xC2,0x90,0x18,0x00,0x99,0x87,0xA0,0x90,0x2A,0x3C,0x02,0xB8,0xC1,
|
||||
0x79,0x1A,0x20,0x08,0xA1,0xD2,0x1C,0x29,0x03,0xD1,0x29,0x99,0x2C,0x50,0xB3,0xD1,
|
||||
0x08,0x09,0x3C,0x10,0x04,0xB2,0x0D,0x2B,0x59,0x80,0x90,0x01,0x0F,0x3A,0x18,0x01,
|
||||
0xA2,0x9B,0x5B,0x3D,0x81,0x03,0xD2,0x98,0x59,0x90,0x81,0x92,0xB4,0x8B,0x1B,0x40,
|
||||
0xB2,0xB5,0x08,0x4B,0x01,0x09,0xD1,0x91,0x8B,0x7A,0x10,0xB3,0xC3,0x99,0x49,0x1A,
|
||||
0x29,0xB5,0xA2,0xAB,0x40,0x81,0x19,0xB7,0xB0,0x20,0x2B,0xD4,0x88,0xA1,0x91,0x3C,
|
||||
0x82,0x37,0xD3,0xB1,0x8A,0x1B,0x30,0xB3,0xF4,0xA1,0x91,0x09,0x10,0x03,0xD0,0x83,
|
||||
0xA9,0x8F,0x10,0x01,0x90,0x18,0x80,0x20,0x2B,0xF1,0x28,0x99,0x2A,0x41,0xF0,0x12,
|
||||
0xAA,0x83,0x82,0xD1,0xC1,0x08,0x89,0x59,0x09,0x83,0x87,0xB0,0x2A,0x4D,0x18,0x09,
|
||||
0x19,0xB3,0x4B,0x3F,0x39,0x19,0x09,0x01,0x89,0x03,0x1F,0x00,0x1A,0x0B,0x10,0x68,
|
||||
0xA0,0x18,0x8C,0x6A,0x09,0x08,0x97,0xA1,0x81,0x1B,0x2B,0x4C,0x03,0xB4,0xA8,0x92,
|
||||
0x4B,0x3C,0xA1,0x81,0x95,0xA8,0x81,0x12,0xBB,0x92,0x45,0xB9,0x93,0xF4,0x88,0x0A,
|
||||
0x2D,0x28,0x00,0xA3,0xA3,0x8A,0x3F,0x48,0xB1,0x92,0xB4,0xA8,0x30,0x80,0xD3,0x80,
|
||||
0xD1,0x19,0x3B,0xC4,0x81,0xC1,0x29,0x0D,0x20,0x13,0xC8,0xB4,0x4C,0x09,0x00,0x82,
|
||||
0xC2,0x3B,0x0D,0x30,0x0B,0x12,0xF0,0x1B,0x20,0x0A,0xA6,0x80,0x0A,0x4A,0x4A,0x80,
|
||||
0x94,0xB1,0x2E,0x3B,0x1A,0x10,0x93,0x10,0x4C,0x3D,0x08,0x82,0xC9,0x19,0x6A,0x2B,
|
||||
0x38,0xD1,0x08,0x19,0x2A,0x5A,0x82,0xB1,0x8D,0x29,0x78,0x09,0x82,0x0A,0x2C,0x1B,
|
||||
0x19,0x41,0xB8,0x8C,0x79,0x2B,0x11,0x88,0x82,0x91,0xDC,0x28,0x11,0xB0,0x11,0x18,
|
||||
0xC9,0x62,0xA1,0x91,0x98,0x3B,0x3A,0xB0,0xF4,0x01,0xC0,0x29,0x39,0xF8,0x95,0x91,
|
||||
0x88,0x88,0x91,0x03,0xA1,0xE2,0x18,0x82,0xD1,0xA2,0xD1,0x80,0x19,0x20,0x83,0xB1,
|
||||
0xE3,0x80,0x91,0x4D,0x1A,0x03,0xB2,0x09,0x18,0xD1,0x19,0x09,0x92,0xA6,0xA0,0xB6,
|
||||
0xB2,0x8B,0x38,0x10,0x42,0xD3,0xD0,0xA8,0x20,0x2C,0x10,0x01,0xB1,0xB4,0xAB,0x5B,
|
||||
0x79,0x80,0x10,0x1A,0xA8,0x3D,0x18,0x20,0xB3,0x8F,0x18,0x01,0x00,0x09,0xF3,0x89,
|
||||
0x69,0x88,0x81,0x91,0x08,0xE1,0x1A,0x08,0x11,0x81,0x1E,0x29,0xA0,0x01,0x00,0x90,
|
||||
0x3E,0x7B,0x18,0x82,0xC3,0xA1,0x2A,0x2C,0x5B,0x81,0xA5,0x90,0x81,0x00,0x0B,0x1A,
|
||||
0x1C,0x2C,0x32,0xC0,0xF3,0x80,0x2D,0x2A,0x10,0x02,0xE4,0xC1,0x89,0x4A,0x09,0x01,
|
||||
0x03,0xD2,0x98,0x2A,0x39,0x8A,0x89,0x26,0xB1,0xB2,0x12,0xC0,0x0A,0x5A,0x18,0x98,
|
||||
0xF3,0x92,0x99,0x99,0x79,0x01,0xB5,0xA1,0x80,0x80,0x90,0x83,0xA0,0xE2,0x81,0x29,
|
||||
0x93,0x8A,0x0A,0x6A,0x1F,0x18,0x02,0xC8,0x01,0x19,0x3B,0x4A,0x98,0x17,0xA8,0x0D,
|
||||
0x38,0xA1,0x91,0x10,0xA2,0x2B,0x4C,0xA6,0x81,0xBA,0x21,0x4C,0x80,0x21,0xD1,0x92,
|
||||
0x2C,0x08,0x30,0x9F,0x93,0x2A,0x89,0x03,0x8B,0x87,0x0A,0x0D,0x12,0x98,0xA4,0x93,
|
||||
0xBB,0x59,0x18,0xA1,0x32,0xE9,0x84,0x08,0x8A,0x02,0xA1,0x91,0x4B,0xB4,0x20,0x88,
|
||||
0xF0,0x3A,0x1A,0x88,0x87,0xB1,0x92,0x0A,0x08,0x6B,0x83,0xC3,0x91,0xC0,0x2B,0x79,
|
||||
0x08,0x8A,0x84,0xA0,0x89,0x40,0x1B,0xA1,0x39,0x98,0x17,0xC2,0xA2,0x12,0xCD,0x20,
|
||||
0x89,0x92,0x25,0xB0,0x2D,0x3A,0x8B,0x58,0x2A,0xA0,0x4C,0x08,0x30,0xAE,0x82,0x59,
|
||||
0x89,0x1A,0x10,0xC2,0x18,0x2C,0x40,0x1E,0x01,0xA3,0x8A,0x81,0x2C,0x29,0x29,0xA9,
|
||||
0x13,0x51,0xAD,0x12,0x89,0x8F,0x18,0x2C,0x39,0x00,0xC1,0x10,0x3C,0x2A,0x41,0xC8,
|
||||
0xA2,0x91,0x0A,0x6C,0x10,0x12,0x88,0xE8,0x30,0x91,0x81,0xD8,0x01,0x1B,0x0D,0x07,
|
||||
0x00,0xA8,0x92,0x0A,0x28,0xD2,0xC3,0x02,0xAA,0x94,0x81,0xB4,0xB3,0x1A,0x0B,0x13,
|
||||
0xF9,0x16,0xA1,0x8A,0x59,0x19,0x02,0xC1,0x91,0x8B,0x3D,0x18,0x3B,0xA4,0x94,0x80,
|
||||
0x99,0x88,0x1C,0x79,0x0A,0x02,0x03,0xF8,0x90,0x39,0x5B,0x19,0x02,0xC3,0x90,0xBB,
|
||||
0x58,0x6A,0x09,0x02,0x89,0x91,0x88,0x1A,0x69,0x8A,0x19,0x15,0xA0,0xA2,0x00,0x9A,
|
||||
0x6B,0x49,0x88,0xA3,0x92,0xBB,0x6B,0x3D,0x38,0x01,0x98,0x91,0x3F,0x09,0x18,0x20,
|
||||
0x90,0x80,0xAC,0x70,0x91,0x9B,0x51,0x09,0x88,0x99,0x14,0x8B,0x98,0x83,0x79,0xA0,
|
||||
0x99,0x13,0x01,0x19,0xE0,0x83,0x0B,0xB0,0x0C,0x31,0x95,0xB5,0xC2,0x8A,0x39,0x20,
|
||||
0x80,0x39,0xF3,0xB1,0x10,0x88,0x5E,0x18,0x94,0xA1,0x88,0xA1,0x98,0x15,0xAA,0x39,
|
||||
0xD4,0x84,0xC0,0xA2,0xA2,0x0C,0x81,0x86,0xB5,0xA1,0xB1,0x14,0x1B,0xB1,0x02,0x92,
|
||||
0xC3,0xE0,0x88,0x11,0xAA,0x69,0x18,0x81,0xA3,0xB0,0x01,0xBF,0x2A,0x31,0x93,0xF1,
|
||||
0x00,0x89,0x18,0x19,0x11,0xD3,0xE0,0x10,0x18,0xB1,0x18,0x24,0x9A,0x2B,0xA4,0xC0,
|
||||
0xB0,0x31,0x6C,0x19,0xB4,0x12,0xA8,0xEA,0x58,0x10,0x8B,0x93,0x82,0x88,0x9A,0x41,
|
||||
0x10,0xC3,0xEA,0x41,0xA9,0x9C,0x34,0xA1,0x2A,0x79,0xA2,0x01,0xA8,0xB3,0x28,0xCC,
|
||||
0x41,0x9A,0xB3,0x4B,0xB3,0x27,0x8B,0x83,0x2B,0x2F,0x08,0x28,0xB2,0x80,0x2C,0x30,
|
||||
0x5E,0x09,0x12,0x9B,0x09,0x22,0x5B,0x19,0x8A,0x11,0x59,0x99,0xA4,0x32,0xCD,0x18,
|
||||
0x08,0x10,0x85,0xB3,0xB4,0x1E,0x88,0x28,0x8A,0x11,0x09,0xC0,0x79,0x80,0x91,0x3B,
|
||||
0x80,0x10,0x0F,0x01,0x80,0x91,0x19,0x3D,0x92,0x28,0xA8,0x37,0x9A,0x0A,0x3A,0x8A,
|
||||
0x45,0xA9,0xA4,0x00,0xAA,0x09,0x3D,0x59,0x20,0xE1,0x08,0x98,0x90,0x59,0x10,0x09,
|
||||
0xA3,0xC3,0x93,0x99,0x2B,0x69,0x11,0xD1,0xB1,0xA4,0x91,0x3C,0x89,0x83,0xF0,0x10,
|
||||
0x91,0xA1,0x89,0x59,0x05,0x99,0x93,0x94,0xC8,0x08,0x0A,0x09,0x17,0xB1,0x83,0xC1,
|
||||
0x91,0x40,0xA2,0xC2,0x98,0xC3,0xBA,0x28,0x23,0x0F,0x80,0x50,0xB8,0x19,0x10,0x96,
|
||||
0x98,0x8C,0x05,0x98,0x19,0x29,0x2B,0x3B,0x0A,0xE2,0x01,0x0F,0x3C,0x38,0x08,0x09,
|
||||
0x81,0x4A,0x6C,0x08,0x00,0x88,0x98,0x38,0x2C,0x5A,0x1B,0x20,0x1A,0x39,0xB0,0x09,
|
||||
0xCB,0x5B,0x49,0x09,0x71,0x00,0xC1,0x0E,0x08,0x38,0x0C,0x02,0x10,0x0E,0x10,0x8A,
|
||||
0x48,0x19,0x90,0x92,0x0D,0xA3,0x98,0x3B,0x79,0x19,0x01,0x10,0xE1,0x80,0x19,0x2B,
|
||||
0x10,0xF2,0x02,0xAB,0x84,0x9A,0x29,0xB4,0x80,0x92,0x03,0x88,0x95,0xD0,0x03,0x90,
|
||||
0xA0,0xC7,0xA1,0xB0,0xA2,0x02,0x18,0xB5,0xD4,0x01,0xC0,0x08,0xA2,0x93,0xA8,0xA0,
|
||||
0xC3,0x20,0xF3,0x90,0x00,0xD5,0x08,0x89,0xA5,0x80,0xA0,0x81,0x82,0xC2,0x09,0xD1,
|
||||
0x13,0xCB,0x03,0x84,0x91,0xE1,0x1B,0x12,0x08,0xAB,0x87,0x18,0xAB,0x58,0x89,0x28,
|
||||
0x81,0xC9,0x33,0xA9,0x80,0x2E,0x20,0x83,0xB9,0x20,0x3B,0x9E,0x7A,0x08,0x81,0x18,
|
||||
0x0B,0x88,0x79,0x80,0x8B,0x00,0x12,0x0E,0x89,0x51,0x1B,0x81,0xA0,0x3A,0x01,0xAF,
|
||||
0x11,0x28,0xBA,0x35,0x98,0x88,0x52,0xC0,0x83,0x2F,0xA9,0x11,0x0A,0x19,0x25,0xD0,
|
||||
0x30,0x9C,0x08,0x21,0x98,0x81,0x2A,0xF3,0x2A,0x80,0xB6,0x2B,0x08,0x93,0xE9,0x02,
|
||||
0x81,0x8C,0x21,0x00,0xA6,0xA9,0x94,0x01,0x8F,0x80,0x94,0x98,0x93,0xB4,0x00,0x08,
|
||||
0xC0,0x14,0x98,0xB3,0xB4,0xC1,0x09,0x18,0xA7,0x00,0xA3,0xC8,0x0A,0x3C,0x19,0x96,
|
||||
0x83,0xC1,0x99,0x19,0x4A,0x85,0x80,0xC1,0x91,0x99,0x90,0x2A,0x17,0x95,0x99,0x88,
|
||||
0x12,0xAE,0x39,0x08,0x92,0x84,0xB0,0xA8,0x79,0x09,0x19,0x01,0xB2,0xA3,0x8F,0x28,
|
||||
0x2B,0xA2,0x40,0x82,0xA0,0x4C,0xA9,0x39,0x8D,0x81,0x70,0x88,0xA0,0x1A,0x49,0x2D,
|
||||
0x1A,0x26,0xA8,0x98,0x08,0x29,0x0B,0x12,0x96,0xB1,0xB2,0x3A,0x13,0x9B,0x60,0xA0,
|
||||
0x88,0xB2,0x34,0xEA,0x1A,0x2A,0x79,0x98,0x10,0x04,0x8C,0x1C,0x81,0x04,0x8C,0x83,
|
||||
0x19,0x2F,0x81,0x93,0x98,0x10,0x08,0x30,0x2A,0xFA,0x05,0x08,0x2A,0x89,0x91,0xA3,
|
||||
0xFA,0x11,0x11,0x00,0x8C,0x04,0x8A,0x2A,0xB5,0x10,0xA9,0xC2,0x3D,0x1B,0x32,0x04,
|
||||
0x0A,0x1A,0x09,0x40,0x1F,0x92,0x1D,0x2A,0x91,0x10,0x30,0x2F,0x0B,0x68,0x99,0xA2,
|
||||
0x92,0x88,0x78,0xA9,0x20,0x28,0xE2,0x92,0x1A,0x99,0x4B,0x19,0x22,0xA1,0xE2,0x21,
|
||||
0x2F,0x98,0x29,0x18,0x91,0x08,0xB0,0x79,0x1A,0x82,0x3B,0xB1,0xA7,0x8A,0xB3,0x98,
|
||||
0x5B,0x23,0xCA,0x42,0x83,0xF0,0x90,0x18,0x98,0x08,0xB4,0x20,0xA3,0xC0,0x43,0xD8,
|
||||
0x80,0x81,0xA3,0x99,0xD9,0xA7,0x19,0x90,0x10,0x05,0xB1,0x8B,0x02,0xA4,0xBD,0x23,
|
||||
0x93,0x8A,0x99,0x4B,0x03,0xC1,0xF8,0x38,0x09,0x2B,0x14,0xD0,0x03,0x8A,0x2A,0x39,
|
||||
0xB9,0x97,0x90,0xAA,0x50,0x01,0x99,0x51,0xD1,0x09,0x1A,0xB5,0x00,0x8B,0x93,0x08,
|
||||
0x98,0x11,0xF9,0x85,0x2B,0x08,0x96,0x89,0x90,0x2A,0x12,0x4A,0xD8,0x85,0x2B,0x0E,
|
||||
0x10,0x00,0x01,0xB1,0x9B,0x69,0x1A,0x90,0x40,0xB8,0x01,0x08,0x0A,0x2C,0x09,0x14,
|
||||
0x4B,0xE2,0x82,0x88,0xB1,0x78,0x0A,0x01,0xC2,0x93,0x19,0xCE,0x20,0x3C,0x82,0xB4,
|
||||
0x1B,0x20,0x8C,0x3B,0x29,0xAB,0x86,0x23,0xD8,0x81,0x9A,0x5A,0x49,0xB0,0x16,0xA0,
|
||||
0xB0,0x28,0x1B,0x13,0x93,0xE4,0xA2,0xA9,0x08,0x5A,0xB3,0x12,0xC1,0xE1,0x10,0x88,
|
||||
0x01,0x0C,0x92,0x08,0x89,0xB7,0x88,0x81,0x10,0x9A,0x17,0xA0,0xB0,0x13,0x99,0xE0,
|
||||
0x39,0x31,0xD2,0xB2,0x80,0x0B,0x2D,0x49,0x80,0x01,0xB0,0x06,0x09,0x0C,0x3A,0x69,
|
||||
0xA0,0x08,0xB2,0xA1,0x69,0x2B,0x5A,0x81,0x92,0xBA,0x21,0xB1,0x7D,0x10,0x80,0x08,
|
||||
0x88,0x82,0x32,0x0D,0xB0,0x1A,0x1C,0x21,0x94,0xA9,0x58,0xB9,0x5A,0x4A,0xA0,0x13,
|
||||
0xA9,0x80,0x7C,0x00,0x20,0x8A,0x04,0x0C,0x00,0x82,0x2A,0xB2,0xAC,0x4B,0x69,0xA0,
|
||||
0xA6,0x81,0x9B,0x19,0x38,0x8B,0x17,0xB2,0x81,0x2A,0xBB,0x94,0x29,0xA2,0x15,0xBA,
|
||||
0x97,0xA3,0xB9,0x79,0x01,0xB2,0x02,0xF1,0x90,0x0A,0x29,0x11,0x88,0xE5,0xA0,0x81,
|
||||
0x19,0x91,0x90,0x28,0xB3,0x14,0xD0,0xB5,0x91,0x9A,0x29,0x0B,0x07,0xA2,0xB3,0x01,
|
||||
0x9D,0x28,0x41,0xD0,0x91,0x90,0x82,0x1A,0xA8,0x44,0x9A,0xA9,0x21,0xE3,0xA9,0x4B,
|
||||
0x19,0x78,0x89,0x83,0xA3,0xB9,0x5A,0x3D,0x80,0x82,0xA2,0xA0,0x6C,0x10,0x20,0x8B,
|
||||
0x93,0x8B,0x0E,0x33,0xA9,0xB1,0x68,0x8A,0x31,0xAC,0x94,0xB4,0x8B,0x32,0x0B,0xB4,
|
||||
0x81,0x91,0x1D,0x33,0xD9,0x31,0xE1,0x8B,0x3B,0x30,0x12,0x49,0xD2,0x8E,0x29,0x18,
|
||||
0x8A,0x92,0x02,0xAA,0x59,0x1C,0x32,0x88,0x01,0x23,0xFB,0x83,0x29,0xDA,0x59,0x01,
|
||||
0x81,0x92,0xE1,0x18,0x8A,0x1D,0x30,0x93,0xF1,0x00,0x01,0x0B,0x39,0x92,0x89,0xA0,
|
||||
0x11,0x5B,0xE0,0x82,0x09,0x13,0xAA,0xB4,0x16,0xD8,0x91,0x2A,0x29,0x84,0x1B,0xC5,
|
||||
0x98,0x98,0x31,0x98,0x99,0x17,0xA9,0x20,0x92,0xC3,0x18,0x9D,0x20,0x3D,0x89,0x94,
|
||||
0xA2,0x1C,0x5C,0x29,0x39,0xA0,0xB3,0x00,0x0C,0x4C,0x48,0x92,0x0A,0x91,0x85,0x9A,
|
||||
0x01,0x82,0x1F,0x10,0x99,0x15,0xC1,0xA0,0x39,0x1A,0x1D,0x85,0xB4,0x90,0x1A,0x2A,
|
||||
0x4B,0x01,0xB2,0x93,0xBE,0x12,0x83,0xC9,0x18,0x09,0x20,0x78,0xF1,0x08,0x19,0x88,
|
||||
0x3A,0x83,0xB3,0xA9,0x93,0x7A,0x0A,0x96,0x98,0x00,0xA8,0x3A,0x30,0x92,0xF2,0x9B,
|
||||
0x3D,0x38,0x92,0x92,0xC3,0xB8,0x6B,0x29,0x01,0x01,0xB2,0x2F,0x09,0x19,0x18,0x01,
|
||||
0x3B,0x7B,0x10,0xA1,0x90,0x39,0x0F,0x38,0x0A,0xB5,0xA4,0x89,0x8B,0x6A,0x2B,0x12,
|
||||
0xC8,0x90,0x40,0x2A,0x9E,0x22,0x88,0x18,0x09,0x3A,0xC3,0xE8,0x09,0x59,0x08,0x12,
|
||||
0x94,0xD0,0x1A,0x2C,0x38,0x00,0xA1,0x83,0xE8,0x08,0x3A,0x08,0x10,0x9E,0x83,0x1D,
|
||||
0x92,0x19,0x2C,0x39,0x3B,0x59,0x04,0xE1,0x80,0x08,0x8D,0x21,0x81,0xB2,0xB2,0x02,
|
||||
0x99,0x91,0xA4,0xD6,0x98,0x99,0x03,0x80,0x98,0xA7,0x91,0x09,0xA1,0xB2,0xB3,0xE1,
|
||||
0x12,0x92,0xB1,0x81,0x06,0x99,0x0A,0x23,0xC4,0xB1,0xF2,0x89,0x19,0x3A,0x94,0x82,
|
||||
0xE0,0x89,0x38,0x0B,0xA4,0xA5,0x80,0x80,0x8C,0x34,0xB9,0xA9,0x23,0x13,0xB9,0xC1,
|
||||
0xC7,0x1B,0x89,0x10,0x20,0x11,0xE3,0xA8,0x4B,0x0B,0x40,0x91,0x90,0x1B,0x5F,0x2A,
|
||||
0x18,0x82,0x91,0x0B,0x4A,0x28,0xCA,0x40,0x80,0x5B,0x2C,0x13,0xB0,0x8A,0xA9,0x5A,
|
||||
0x58,0x89,0x82,0x88,0x2E,0x3B,0x31,0xA1,0x9B,0x01,0x7A,0x2C,0x01,0x91,0x93,0x3F,
|
||||
0x88,0x39,0x10,0xF1,0x91,0x8B,0x48,0x0A,0x12,0xE3,0xA8,0x18,0x28,0x92,0x97,0x98,
|
||||
0x99,0x19,0xA1,0x11,0xB6,0x88,0x3B,0x10,0xD3,0xC3,0xA1,0x2A,0x8A,0x49,0x04,0xF1,
|
||||
0x91,0x02,0x8A,0x89,0x04,0xF1,0x98,0x80,0x18,0x12,0xE3,0x81,0x98,0x80,0x01,0xB3,
|
||||
0xF2,0x99,0x12,0x2A,0xB5,0xB3,0x92,0xAA,0x19,0x50,0xB2,0xC3,0x92,0xD0,0x2B,0x68,
|
||||
0x93,0x99,0xC0,0x2C,0x3E,0x80,0x20,0x08,0x93,0x0D,0x2A,0x31,0x8D,0x02,0x2B,0x91,
|
||||
0x08,0x0A,0x03,0x2C,0x3C,0x52,0xB9,0xA0,0x12,0xBF,0x3A,0x29,0x01,0x88,0xC0,0x6A,
|
||||
0x3C,0x0A,0x49,0x18,0x0B,0x39,0x2B,0x69,0x0A,0x84,0x2A,0x2A,0x1C,0x2A,0xC3,0x8C,
|
||||
0x19,0x50,0x09,0x91,0xA7,0x8D,0x18,0x1A,0x28,0x00,0xA0,0x94,0x10,0x1F,0x20,0x90,
|
||||
0x8A,0x12,0xD0,0x1A,0x5A,0x81,0x04,0xBC,0x23,0x10,0xE0,0x90,0x90,0x18,0x1A,0xA6,
|
||||
0x12,0xB1,0xD0,0x4A,0x08,0x82,0x92,0xB6,0x9A,0x0A,0x12,0x88,0xC3,0xC5,0x8A,0x89,
|
||||
0x20,0xB5,0x93,0x0B,0x18,0x00,0x09,0xF2,0x88,0x2A,0x4A,0x08,0x05,0xB2,0xA9,0x3B,
|
||||
0x5D,0x28,0xA4,0xB1,0x00,0x19,0x19,0x7A,0xA3,0xB3,0x0A,0x90,0xA1,0xC4,0x80,0xBA,
|
||||
0x50,0x13,0xC1,0xC2,0x9A,0x2A,0x7B,0x28,0x84,0xC1,0x09,0x3B,0x4E,0x20,0x91,0xA1,
|
||||
0x18,0xAB,0x79,0x10,0xB4,0x08,0x9A,0x11,0x2B,0xF0,0x93,0xAA,0x01,0x6A,0x01,0x93,
|
||||
0x80,0xB8,0x2A,0x5B,0x10,0x80,0x89,0x4A,0x5B,0x92,0x15,0xB2,0xA0,0x2F,0x19,0x93,
|
||||
0xB8,0x95,0x80,0x1C,0x21,0xA9,0x02,0x0B,0xA0,0x5A,0x18,0x98,0x39,0x1B,0x68,0x00,
|
||||
0x91,0x91,0x9C,0x39,0x3E,0x18,0x84,0xB3,0x9B,0x7A,0x08,0x18,0x0A,0xB5,0x91,0x0B,
|
||||
0x28,0x39,0x19,0x90,0x0A,0x50,0xAC,0x11,0x01,0xAB,0x88,0x52,0x1B,0x83,0xC4,0xA2,
|
||||
0x9A,0xAB,0x03,0x90,0x19,0x93,0x81,0x08,0x92,0x9A,0x68,0x98,0x19,0x39,0xC1,0x92,
|
||||
0x8A,0x38,0x4E,0x02,0xB1,0x90,0xC3,0x18,0x2B,0x04,0xC3,0xD2,0x91,0x90,0x81,0x89,
|
||||
0x13,0xF1,0x88,0x93,0xA2,0x00,0x91,0xC0,0x5B,0x21,0x99,0x93,0x06,0x9A,0x1B,0x48,
|
||||
0x99,0xB7,0x90,0x89,0x18,0x1B,0x11,0xA4,0xB2,0x81,0x9A,0x08,0x97,0x98,0x91,0x10,
|
||||
0xB8,0x06,0xA2,0xA0,0x29,0x2B,0x21,0xC2,0xD1,0x10,0x1A,0x4A,0x29,0xF1,0x98,0x29,
|
||||
0x1B,0x31,0x10,0xA0,0xA1,0x1D,0x5A,0x29,0xB2,0x82,0xA8,0x0F,0x28,0x21,0x09,0x91,
|
||||
0x82,0x4D,0x10,0xA3,0xB0,0x89,0x4C,0x39,0xA0,0xA4,0xA1,0x89,0x1E,0x28,0x29,0xA3,
|
||||
0xC3,0x2D,0x19,0x01,0x49,0x01,0x9B,0x0C,0x21,0xC2,0xA2,0x93,0x7C,0x2A,0x10,0x90,
|
||||
|
||||
/* Source: 08HH.ROM */
|
||||
/* Length: 384 / 0x00000180 */
|
||||
|
||||
0x75,0xF2,0xAB,0x7D,0x7E,0x5C,0x3B,0x4B,0x3C,0x4D,0x4A,0x02,0xB3,0xC5,0xE7,0xE3,
|
||||
0x92,0xB3,0xC4,0xB3,0xC3,0x8A,0x3B,0x5D,0x5C,0x3A,0x84,0xC2,0x91,0xA4,0xE7,0xF7,
|
||||
0xF7,0xF4,0xA1,0x1B,0x49,0xA5,0xB1,0x1E,0x7F,0x5A,0x00,0x89,0x39,0xB7,0xA8,0x3D,
|
||||
0x4A,0x84,0xE7,0xF7,0xE2,0x2D,0x4C,0x3A,0x4E,0x7D,0x04,0xB0,0x2D,0x4B,0x10,0x80,
|
||||
0xA3,0x99,0x10,0x0E,0x59,0x93,0xC4,0xB1,0x81,0xC4,0xA2,0xB2,0x88,0x08,0x3F,0x3B,
|
||||
0x28,0xA6,0xC3,0xA2,0xA2,0xC5,0xC1,0x3F,0x7E,0x39,0x81,0x93,0xC2,0xA3,0xE5,0xD2,
|
||||
0x80,0x93,0xB8,0x6D,0x49,0x82,0xD4,0xA1,0x90,0x01,0xA0,0x09,0x04,0xE3,0xB2,0x91,
|
||||
0xB7,0xB3,0xA8,0x2A,0x03,0xF3,0xA1,0x92,0xC5,0xC3,0xB2,0x0B,0x30,0xB3,0x8E,0x6D,
|
||||
0x4A,0x01,0xB4,0xB4,0xC4,0xC3,0x99,0x3B,0x12,0xE3,0xA1,0x88,0x82,0xB4,0x9A,0x5C,
|
||||
0x3A,0x18,0x93,0xC3,0xB3,0xB4,0xA8,0x19,0x04,0xF3,0xA8,0x3B,0x10,0xA2,0x88,0xA5,
|
||||
0xB2,0x0B,0x6D,0x4B,0x10,0x91,0x89,0x3C,0x18,0x18,0xA6,0xC4,0xC3,0x98,0x19,0x2B,
|
||||
0x20,0x91,0xA0,0x4E,0x28,0x93,0xB3,0xC2,0x92,0xA9,0x5A,0x96,0xC4,0xC2,0x09,0x01,
|
||||
0xC4,0xA1,0x92,0xC4,0xA1,0x89,0x10,0xA3,0xA1,0x90,0x1C,0x5A,0x01,0xC5,0xA1,0x92,
|
||||
0xD4,0xB3,0xC4,0xC4,0xC3,0xA1,0x88,0x1A,0x28,0x89,0x3C,0x3A,0x3D,0x29,0x00,0x93,
|
||||
0xB0,0x3D,0x28,0x80,0x91,0x82,0xE3,0x99,0x2A,0x11,0xD6,0xC3,0x99,0x29,0x82,0xC4,
|
||||
0xC3,0xA1,0x0A,0x3B,0x3D,0x3A,0x02,0xC3,0xA2,0x99,0x3B,0x2C,0x7C,0x28,0x81,0xA3,
|
||||
0xB2,0xA3,0xB1,0x08,0x1A,0x3C,0x18,0x2E,0x4C,0x39,0xA5,0xB3,0xB4,0xC2,0x88,0x08,
|
||||
0x19,0x0A,0x49,0xB7,0xB3,0xA2,0xA1,0x92,0xA1,0x93,0xB1,0x0C,0x7D,0x39,0x93,0xB3,
|
||||
0xB1,0x1A,0x19,0x5D,0x28,0xA6,0xC4,0xB2,0x90,0x09,0x2A,0x18,0x1B,0x5B,0x28,0x88,
|
||||
0x2C,0x29,0x82,0xA0,0x18,0x91,0x2D,0x29,0x2B,0x5C,0x4C,0x3B,0x4C,0x28,0x80,0x92,
|
||||
0x90,0x09,0x2B,0x28,0x1D,0x6B,0x11,0xC5,0xB2,0x0B,0x39,0x09,0x4D,0x28,0x88,0x00,
|
||||
0x1B,0x28,0x94,0xE3,0xA0,0x1A,0x28,0xB5,0xB4,0xB3,0xB2,0x93,0xE2,0x91,0x92,0xD4,
|
||||
0xA0,0x1B,0x4A,0x01,0xA1,0x88,0x2D,0x5C,0x3B,0x28,0x08,0x93,0xD4,0xB2,0x91,0xB4,
|
||||
0xA0,0x3E,0x3B,0x4B,0x3B,0x29,0x08,0x93,0x9B,0x7B,0x3A,0x19,0x00,0x80,0x80,0xA0,
|
||||
|
||||
/* Source: 10TOM.ROM */
|
||||
/* Length: 640 / 0x00000280 */
|
||||
|
||||
0x77,0x27,0x87,0x01,0x2D,0x4F,0xC3,0xC1,0x92,0x91,0x89,0x59,0x83,0x1A,0x32,0xC2,
|
||||
0x95,0xB1,0x81,0x88,0x81,0x4A,0x3D,0x11,0x9E,0x0B,0x88,0x0C,0x18,0x3B,0x11,0x11,
|
||||
0x91,0x00,0xA0,0xE2,0x0A,0x48,0x13,0x24,0x81,0x48,0x1B,0x39,0x1C,0x83,0x84,0xA1,
|
||||
0xD1,0x8E,0x8A,0x0B,0xC0,0x98,0x92,0xB8,0x39,0x90,0x10,0x92,0xF0,0xB5,0x88,0x32,
|
||||
0x49,0x51,0x21,0x03,0x82,0x10,0x8A,0x7A,0x09,0x00,0xA2,0xCA,0x1B,0xCC,0x1C,0xB9,
|
||||
0x8E,0x89,0x89,0xA1,0x89,0x92,0x29,0x11,0x60,0x40,0x14,0x22,0x32,0x78,0x40,0x01,
|
||||
0x02,0x90,0x81,0xAB,0x0B,0x00,0xAF,0x99,0xCC,0xAB,0xDA,0xA9,0x99,0x1B,0x30,0x14,
|
||||
0x92,0x22,0x19,0x68,0x32,0x14,0x26,0x13,0x23,0x23,0x20,0x12,0x9A,0xA8,0xB9,0xFA,
|
||||
0xAA,0xCA,0xCC,0x0C,0xA8,0xAE,0x88,0xB9,0x88,0xA0,0x02,0x21,0x50,0x43,0x03,0x81,
|
||||
0x2A,0x11,0x34,0x63,0x24,0x33,0x22,0x38,0x8B,0xEA,0xAE,0x99,0xA0,0x90,0x82,0x00,
|
||||
0x89,0xBF,0x8A,0xE8,0xA9,0x90,0x01,0x12,0x13,0x12,0x08,0xA9,0xAA,0xC9,0x22,0x63,
|
||||
0x63,0x12,0x44,0x00,0x10,0x88,0x9C,0x98,0xA1,0x85,0x03,0x32,0x36,0x80,0x89,0xDB,
|
||||
0xDB,0xBB,0xB9,0xBA,0x01,0x81,0x28,0x19,0xCB,0xFA,0xBC,0x09,0x13,0x37,0x34,0x34,
|
||||
0x23,0x31,0x20,0x10,0x00,0x00,0x28,0x38,0x10,0x88,0xEC,0x8D,0xCB,0xBC,0xCC,0xBB,
|
||||
0xBB,0xC9,0x99,0x00,0x00,0x33,0x11,0x22,0x81,0x07,0x41,0x54,0x34,0x34,0x22,0x31,
|
||||
0x00,0x88,0x9A,0x9B,0x98,0xAB,0x8E,0x9B,0xBD,0x9C,0xBC,0xBB,0xDA,0xAA,0xA9,0x99,
|
||||
0x18,0x38,0x60,0x20,0x31,0x13,0x13,0x51,0x14,0x31,0x53,0x33,0x35,0x22,0x01,0x8A,
|
||||
0x9C,0xA9,0xCA,0xC9,0xA8,0x00,0x10,0x81,0x9C,0x9E,0xAB,0xCC,0xAB,0xBA,0x98,0x30,
|
||||
0x52,0x03,0x81,0x08,0x9C,0xAC,0xAC,0x18,0x11,0x03,0x51,0x61,0x41,0x31,0x31,0x02,
|
||||
0x01,0x20,0x24,0x43,0x44,0x40,0x30,0x10,0xBC,0xBE,0xCB,0xDB,0xAB,0xBA,0x99,0x98,
|
||||
0x99,0xAA,0xBD,0xAA,0xC8,0x90,0x11,0x53,0x37,0x23,0x43,0x34,0x33,0x33,0x33,0x11,
|
||||
0x28,0x00,0x19,0xA9,0x9A,0xCB,0xCE,0xBB,0xEB,0xBC,0xBB,0xCA,0xBA,0xA8,0x88,0x11,
|
||||
0x12,0x21,0x20,0x22,0x26,0x26,0x23,0x23,0x43,0x24,0x22,0x32,0x20,0x31,0x81,0x9A,
|
||||
0xBC,0xBC,0xCB,0xBD,0x9A,0xA9,0x90,0x98,0xBA,0xCC,0xCB,0xBC,0x8B,0x88,0x22,0x35,
|
||||
0x23,0x12,0x99,0x8B,0xAA,0xAA,0x89,0x82,0x93,0x31,0x42,0x23,0x23,0x21,0x32,0x11,
|
||||
0x20,0x13,0x13,0x24,0x24,0x24,0x22,0x11,0x8A,0x9E,0xAC,0xAC,0xAA,0xBA,0xAA,0xAB,
|
||||
0xBD,0xBC,0xCB,0xCB,0xA9,0xA8,0x91,0x12,0x44,0x43,0x44,0x34,0x34,0x42,0x33,0x42,
|
||||
0x21,0x11,0x11,0x88,0x80,0xAA,0x0B,0xAC,0xCB,0xEC,0xAC,0xBA,0xCA,0xAB,0x9A,0x99,
|
||||
0x80,0x91,0x09,0x08,0x10,0x22,0x44,0x43,0x44,0x33,0x43,0x22,0x13,0x21,0x22,0x20,
|
||||
0x09,0x88,0xB9,0xC8,0xBB,0xAB,0xAB,0xA9,0xA9,0x9B,0x9B,0x99,0x90,0x90,0x00,0x81,
|
||||
0x00,0x08,0x09,0x8A,0x9A,0xAA,0xA9,0xA9,0x99,0x90,0x80,0x01,0x80,0x00,0x09,0x31,
|
||||
0x32,0x44,0x33,0x43,0x34,0x33,0x24,0x22,0x23,0x12,0x10,0x09,0x9B,0xAB,0xCA,0xCC,
|
||||
0xBB,0xCB,0xDA,0xCA,0xAB,0xCA,0xAB,0xA9,0xA8,0x92,0x12,0x43,0x53,0x35,0x23,0x33,
|
||||
0x43,0x43,0x52,0x22,0x22,0x21,0x01,0x09,0x89,0xA9,0xBB,0xBD,0xBC,0xCB,0xDA,0xAB,
|
||||
0xAB,0xAB,0xAA,0xA9,0x99,0xA8,0x09,0x01,0x11,0x34,0x25,0x23,0x33,0x51,0x22,0x31,
|
||||
0x12,0x20,0x21,0x12,0x10,0x80,0x99,0x9A,0x99,0x99,0x88,0x08,0x00,0x88,0xA9,0x99,
|
||||
0x99,0x80,0x80,0x10,0x01,0x00,0x9A,0xAA,0xBB,0xBA,0xBA,0xA9,0x99,0x99,0x89,0x99,
|
||||
0x99,0x00,0x01,0x33,0x35,0x24,0x23,0x34,0x23,0x33,0x34,0x33,0x43,0x32,0x21,0x88,
|
||||
0xAB,0xBD,0xBB,0xDB,0xAB,0xBA,0xBB,0xDA,0xBB,0xCB,0xBB,0xBC,0xA8,0x90,0x01,0x12,
|
||||
0x23,0x43,0x53,0x34,0x34,0x39,0x80,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00,
|
||||
|
||||
/* Source: 20RIM.ROM */
|
||||
/* Length: 128 / 0x00000080 */
|
||||
|
||||
0x0F,0xFF,0x73,0x8E,0x71,0xCD,0x00,0x49,0x10,0x90,0x21,0x49,0xA0,0xDB,0x02,0x3A,
|
||||
0xE3,0x0A,0x50,0x98,0xC0,0x59,0xA2,0x99,0x09,0x22,0xA2,0x80,0x10,0xA8,0x5B,0xD2,
|
||||
0x88,0x21,0x09,0x96,0xA8,0x10,0x0A,0xE0,0x08,0x48,0x19,0xAB,0x52,0xA8,0x92,0x0C,
|
||||
0x03,0x19,0xE2,0x0A,0x12,0xC2,0x81,0x1E,0x01,0xD0,0x48,0x88,0x98,0x01,0x49,0x91,
|
||||
0xAA,0x2C,0x25,0x89,0x88,0xB5,0x81,0xA2,0x9A,0x12,0x9E,0x38,0x3B,0x81,0x9B,0x59,
|
||||
0x01,0x93,0xCA,0x4A,0x21,0xA0,0x3D,0x0A,0x39,0x3D,0x12,0xA8,0x3F,0x18,0x01,0x92,
|
||||
0x1C,0x00,0xB2,0x48,0xB9,0x94,0xA3,0x19,0x4F,0x19,0xB2,0x32,0x90,0xBA,0x01,0xE6,
|
||||
0x91,0x80,0xC1,0xA4,0x2A,0x08,0xA1,0xB1,0x25,0xD2,0x88,0x99,0x21,0x80,0x88,0x80,
|
||||
};
|
|
@ -1,118 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct VGMHeader {
|
||||
char id[4]; // "Vgm/x20"
|
||||
uint32_t eofOffset; // filesize - 4
|
||||
uint32_t version; // in BCD, latest is 0x170 -> 1.70
|
||||
|
||||
// any "#clock" fields - 0 means chip is unused
|
||||
uint32_t SN76489_Clock;
|
||||
uint32_t YM2413_Clock;
|
||||
|
||||
uint32_t GD3_Offset; // GD3 tags offset (ignored)
|
||||
|
||||
uint32_t samplesNum; // Total of all wait values in the file. (umm?)
|
||||
uint32_t loopOffset; // relative from this field offset
|
||||
uint32_t loopLength; // 0 if no loop
|
||||
|
||||
uint32_t frameRate; // NOT the chip sampling rate (it's 44100hz) but rather (in most cases) a player routine calling rate (usually 60hz for NTSC and 50hz for PAL systems), DON'T RELY ON!
|
||||
|
||||
uint32_t SN76489_Feedback:16; // The white noise feedback pattern for the SN76489 PSG
|
||||
uint32_t SN76489_ShiftWidth:8; // The noise feedback shift register width, in bits
|
||||
|
||||
uint32_t SN76489_Flags:8; // Misc flags for the SN76489. Most of them don't make audible changes and can be ignored, if the SN76489 emulator lacks the features.
|
||||
// bit 0 frequency 0 is 0x400
|
||||
// bit 1 output negate flag
|
||||
// bit 2 stereo on / off(on when bit clear)
|
||||
// bit 3 / 8 Clock Divider on / off(on when bit clear)
|
||||
// bit 4 - 7 reserved(must be zero)
|
||||
uint32_t YM2612_Clock;
|
||||
uint32_t YM2151_Clock;
|
||||
|
||||
uint32_t dataOffset; // relative from this field offset - use it for data fetching!
|
||||
|
||||
uint32_t unused[3];
|
||||
uint32_t YM2203_Clock;
|
||||
uint32_t YM2608_Clock;
|
||||
uint32_t unused_2[1];
|
||||
uint32_t YM3812_Clock;
|
||||
uint32_t unused_3[2];
|
||||
uint32_t YMF262_Clock;
|
||||
};
|
||||
|
||||
|
||||
// stream opcodes descriptions
|
||||
|
||||
/*
|
||||
general opcode groups (for faster and easier parsing):
|
||||
|
||||
0x30..0x3F - one operand
|
||||
0x40..0x4F, 0x50..0x5F,
|
||||
0xA0..0xAF, 0xB0..0xBF - two operands (except for 0x4F/0x50, which are one operand)
|
||||
|
||||
0xC0..0xCF, 0xD0..0xDF - three operands
|
||||
0xE0..0xEF, 0xF0..0xFF - four operands
|
||||
*/
|
||||
|
||||
enum class VGM_Stream_Opcode : uint8_t {
|
||||
|
||||
PSG_STEREO_WRITE = 0x4F, // 0x4F dd : Game Gear PSG stereo, write dd to port 0x06
|
||||
PSG_WRITE = 0x50, // 0x50 dd : PSG(SN76489 / SN76496) write value dd
|
||||
YM2413_WRITE = 0x51, // 0x51 aa dd : YM2413, write value dd to register aa
|
||||
YM2612_PORT0_WRITE = 0x52, // 0x52 aa dd : YM2612 port 0, write value dd to register aa
|
||||
YM2612_PORT1_WRITE = 0x53, // 0x53 aa dd : YM2612 port 1, write value dd to register aa
|
||||
YM2151_WRITE = 0x54, // 0x54 aa dd : YM2151, write value dd to register aa
|
||||
YM2203_WRITE = 0x55, // 0x55 aa dd : YM2203, write value dd to register aa
|
||||
YM2608_PORT0_WRITE = 0x56, // 0x56 aa dd : YM2608 port 0, write value dd to register aa
|
||||
YM2608_PORT1_WRITE = 0x57, // 0x57 aa dd : YM2608 port 1, write value dd to register aa
|
||||
YM2610_PORT0_WRITE = 0x58, // 0x58 aa dd : YM2610 port 0, write value dd to register aa
|
||||
YM2610_PORT1_WRITE = 0x59, // 0x59 aa dd : YM2610 port 1, write value dd to register aa
|
||||
YM3812_WRITE = 0x5a, // 0x5A aa dd : YM3812, write value dd to register aa
|
||||
YM3526_WRITE = 0x5b, // 0x5B aa dd : YM3526, write value dd to register aa
|
||||
Y8950_WRITE = 0x5c, // 0x5C aa dd : Y8950, write value dd to register aa
|
||||
YMZ280B_WRITE = 0x5d, // 0x5D aa dd : YMZ280B, write value dd to register aa
|
||||
YMF262_PORT0_WRITE = 0x5e, // 0x5E aa dd : YMF262 port 0, write value dd to register aa
|
||||
YMF262_PORT1_WRITE = 0x5f, // 0x5F aa dd : YMF262 port 1, write value dd to register aa
|
||||
|
||||
DELAY_LONG = 0x61, // 0x61 nn nn : Wait n samples, n can range from 0 to 65535 (approx 1.49 seconds). Longer pauses than this are represented by multiple wait commands.
|
||||
DELAY_60HZ = 0x62, // 0x62 : wait 735 samples(60th of a second), a shortcut for 0x61 0xdf 0x02
|
||||
DELAY_50HZ = 0x63, // 0x63 : wait 882 samples(50th of a second), a shortcut for 0x61 0x72 0x03
|
||||
SET_DELAY_LENGTH = 0x64, // 0x64 : cc nn nn : override length of 0x62 / 0x63: cc - command(0x62 / 0x63), nn - delay in samples [Note:Not yet implemented.Am I really sure about this ? ]
|
||||
END_OF_DATA = 0x66, // 0x66 : end of sound data
|
||||
DATA_BLOCK = 0x67, // 0x67 ... : data block : see below
|
||||
PCM_RAW_WRITE = 0x68, // 0x68 ... : PCM RAM write : see below
|
||||
|
||||
AY_WRITE = 0xa0, // 0xA0 aa dd : AY8910, write value dd to register aa
|
||||
YM2203_CHIP2_WRITE = 0xA5, // 0xA5 aa dd : YM2203 chip 2, write value dd to register aa
|
||||
RF5C68_WRITE = 0xb0, // 0xB0 aa dd : RF5C68, write value dd to register aa
|
||||
RF5C164_WRITE = 0xb1, // 0xB1 aa dd : RF5C164, write value dd to register aa
|
||||
PWM_WRITE = 0xb2, // 0xB2 ad dd : PWM, write value ddd to register a(d is MSB, dd is LSB)
|
||||
DMG_WRITE = 0xb3, // 0xB3 aa dd : GameBoy DMG, write value dd to register aa
|
||||
APU_WRITE = 0xb4, // 0xB4 aa dd : NES APU, write value dd to register aa
|
||||
MULTIPCM_WRITE = 0xb5, // 0xB5 aa dd : MultiPCM, write value dd to register aa
|
||||
UPD7759_WRITE = 0xb6, // 0xB6 aa dd : uPD7759, write value dd to register aa
|
||||
OKIM6258_WRITE = 0xb7, // 0xB7 aa dd : OKIM6258, write value dd to register aa
|
||||
OKIM6295_WRITE = 0xb8, // 0xB8 aa dd : OKIM6295, write value dd to register aa
|
||||
HUC6280_WRITE = 0xb9, // 0xB9 aa dd : HuC6280, write value dd to register aa
|
||||
K053260_WRITE = 0xba, // 0xBA aa dd : K053260, write value dd to register aa
|
||||
POKEY_WRITE = 0xbb, // 0xBB aa dd : Pokey, write value dd to register aa
|
||||
SEGAPCM_WRITE = 0xc0, // 0xC0 aaaa dd : Sega PCM, write value dd to memory offset aaaa
|
||||
RF5C68_MEM_WRITE = 0xc1, // 0xC1 aaaa dd : RF5C68, write value dd to memory offset aaaa
|
||||
RF5C164_MEM_WRITE = 0xc2, // 0xC2 aaaa dd : RF5C164, write value dd to memory offset aaaa
|
||||
MULTIPCM_SET_BANK = 0xc3, // 0xC3 cc aaaa : MultiPCM, write set bank offset aaaa to channel cc
|
||||
QSOUND3_WRITE = 0xc4, // 0xC4 mmll rr : QSound, write value mmll to register rr (mm - data MSB, ll - data LSB)
|
||||
YMF278B_WRITE = 0xd0, // 0xD0 pp aa dd : YMF278B port pp, write value dd to register aa
|
||||
YMF271_WRITE = 0xd1, // 0xD1 pp aa dd : YMF271 port pp, write value dd to register aa
|
||||
SCC1_WRITE = 0xd2, // 0xD2 pp aa dd : SCC1 port pp, write value dd to register aa
|
||||
K054539_WRITE = 0xd3, // 0xD3 pp aa dd : K054539 write value dd to register ppaa
|
||||
C140_WRITE = 0xd4, // 0xD4 pp aa dd : C140 write value dd to register ppaa
|
||||
SEEK_TO_PCM_DATA = 0xe0, // 0xE0 dddddddd : seek to offset dddddddd(Intel byte order) in PCM data bank
|
||||
|
||||
// ranged opcodes
|
||||
DELAY_SHORT = 0x70, // 0x7n : wait n + 1 samples, n can range from 0 to 15.
|
||||
YM2612_PCM_OUT = 0x80, // 0x8n : YM2612 port 0 address 2A write from the data bank, then wait n samples; n can range from 0 to 15. Note that the wait is n, NOT n + 1. (Note: Written to first chip instance only.)
|
||||
DAC_STREAM_CONTROL = 0x90, // 0x90 - 0x95 : DAC Stream Control Write : see below
|
||||
|
||||
};
|
Binary file not shown.
|
@ -1,34 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
// RIFF header
|
||||
struct RIFF_Header {
|
||||
char id[4]; // "RIFF"
|
||||
uint32_t size;
|
||||
char fourcc[4]; // "WAVE"
|
||||
};
|
||||
|
||||
struct chunk_Header {
|
||||
char id[4];
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
// wave format header
|
||||
struct fmt_Header {
|
||||
char id[4]; // "fmt "
|
||||
uint32_t size; // size of chunk!
|
||||
|
||||
uint16_t wFormatTag; // Format code
|
||||
uint16_t nChannels; // Number of interleaved channels
|
||||
uint32_t nSamplesPerSec; // Sampling rate (blocks per second)
|
||||
uint32_t nAvgBytesPerSec; // Data rate
|
||||
uint16_t nBlockAlign; // Data block size (bytes)
|
||||
uint16_t wBitsPerSample; // Bits per sample
|
||||
|
||||
// enough :)
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
|
@ -1,9 +0,0 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
tab_width = 4
|
||||
trim_trailing_whitespace = true
|
40
vgmplay/lxmplay/ymfm/.gitignore
vendored
40
vgmplay/lxmplay/ymfm/.gitignore
vendored
|
@ -1,40 +0,0 @@
|
|||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# Symbol/intermediate files
|
||||
*.pdb
|
||||
*.ilk
|
||||
|
||||
# VS Code stuff
|
||||
.vs/
|
||||
reference/
|
|
@ -1,282 +0,0 @@
|
|||
# ymfm: One FM core to rule them all
|
||||
|
||||
The ymfm emulator ws written from the ground-up using the analysis and deduction by Nemesis as a starting point, particularly in [this thread](https://gendev.spritesmind.net/forum/viewtopic.php?f=24&t=386).
|
||||
|
||||
The core assumption is that these details apply to all FM variants unless otherwise proven incorrect.
|
||||
|
||||
The fine details of this implementation have also been cross-checked against Nemesis' implementation in his [Exodus emulator](https://www.exodusemulator.com/), as well as Alexey Khokholov's ["Nuked" implementations](https://github.com/nukeykt/Nuked-OPN2) based off die shots.
|
||||
|
||||
Operator and channel summing/mixing code for OPM and OPN is largely based off of research done by [David Viens](https://twitter.com/plgDavid) and Hubert Lamontagne.
|
||||
|
||||
## Families
|
||||
|
||||
The Yamaha FM chips can be broadly categoried into families:
|
||||
|
||||
* OPM (YM2151)
|
||||
* OPP (YM2164)
|
||||
* OPN (YM2203)
|
||||
* OPNA/OPNB/OPN2 (YM2608, YM2610, YM2610B, YM2612, YM3438, YMF276, YMF288)
|
||||
* OPL (YM3526)
|
||||
* OPL2 (YM3812)
|
||||
* OPLL (YM2413, YM2423, YMF281, DS1001, and others)
|
||||
* OPL3 (YMF262, YMF289B)
|
||||
* OPL4 (YMF278)
|
||||
|
||||
Additionally, several lesser-documented variants exist exclusively in the employ of Yamaha synthesizers:
|
||||
|
||||
* OPQ (YM3608)
|
||||
* OPZ (YM2414)
|
||||
|
||||
All of these families are very closely related, and the ymfm engine is designed to be universal to work across all of
|
||||
these families.
|
||||
|
||||
Of course, each variant has its own register maps, features, and implementation details which need to be sorted out.
|
||||
Thus, each significant variant listed above is represented by a register class.
|
||||
The register class contains:
|
||||
|
||||
* constants describing core parameters and features
|
||||
* mappers between operators and channels
|
||||
* generic fetchers that return normalized values across families
|
||||
* family-specific implementations of LFO and phase calculations
|
||||
|
||||
## Family History
|
||||
|
||||
This history outlines the progress of adding/removing features across the three main families (OPM, OPN, OPL):
|
||||
|
||||
OPM started it all off, featuring:
|
||||
* 8 FM channels, 4 operators each
|
||||
* LFO and noise support
|
||||
* Stereo output
|
||||
|
||||
OPM -> OPN changes:
|
||||
* Reduced to 3 FM channels, 4 operators each
|
||||
* Removed LFO and noise support
|
||||
* Mono output
|
||||
* Integrated AY-8910 compatible PSG
|
||||
* Added SSG-EG envelope mode
|
||||
* Added multi-frequency mode: ch. 3 operators can have separate frequencies
|
||||
* Software controlled clock divider
|
||||
|
||||
OPN -> OPNA changes:
|
||||
* Increased to 6 FM channels, 4 operators each
|
||||
* Added back (a cut-down) LFO
|
||||
* Stereo output again
|
||||
* Removed software controlled divider on later versions (OPNB/OPN2)
|
||||
* Removed PSG on OPN2 models
|
||||
|
||||
OPNA -> OPL changes:
|
||||
* Increased to 9 FM channels, but only 2 operators each
|
||||
* Even more simplified LFO
|
||||
* Mono output
|
||||
* Removed PSG
|
||||
* Removed SSG-EG envelope modes
|
||||
* Removed multi-frequency modes
|
||||
* Fixed clock divider
|
||||
* Built-in ryhthm generation
|
||||
|
||||
OPL -> OPL2 changes:
|
||||
* Added 4 selectable waveforms
|
||||
|
||||
OPL2 -> OPLL changes:
|
||||
* Vastly simplified register map
|
||||
* 15 built-in instruments, plus built-in rhythm instruments
|
||||
* 1 user-controlled instrument
|
||||
|
||||
OPL2 -> OPL3 changes:
|
||||
* Increased to 18 FM channels, 2 operators each
|
||||
* 4 output channels
|
||||
* Increased to 8 selectable waveforms
|
||||
* 6 channels can be configured to use 4 operators
|
||||
|
||||
## Channels and Operators
|
||||
|
||||
The polyphony of a given chip is determined by the number of channels it supports.
|
||||
This number ranges from as low as 3 to as high as 18.
|
||||
Each channel has either 2 or 4 operators that can be combined in a myriad of ways.
|
||||
On most chips the number of operators per channel is fixed; however, some later OPL chips allow this to be toggled between 2 and 4 at runtime.
|
||||
|
||||
The base ymfm engine class maintains an array of channels and operators, while the relationship between the two is described by the register class.
|
||||
|
||||
## Registers
|
||||
|
||||
Registers on the Yamaha chips are generally write-only, and can be divided into three distinct categories:
|
||||
|
||||
* system-wide registers
|
||||
* channel-specific registers
|
||||
* operator-specific registers
|
||||
|
||||
For maximum flexibility, most parameters can be configured at the operator level, with channel-level registers controlling details such as how to combine the operators into the final output.
|
||||
System-wide registers are used to control chip-wide modes and manage onboard timer functions.
|
||||
|
||||
Note that since registers are write-only, some ymfm register classes will use "holes" in the register space to store additional values that may be needed.
|
||||
|
||||
## Attenuation
|
||||
|
||||
Most of the computations of the FM engines are done in terms of attenuation, and thus are logarithmic in nature.
|
||||
The maximum resolution used internally is 12 bits, as returned by the sin table:
|
||||
|
||||
Bit | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
|
||||
----|----|----|----|----|----|----|-----|------|-------|--------|---------|---------
|
||||
dB | -96| -48| -24| -12| -6| -3| -1.5| -0.75| -0.375| -0.1875| -0.09375| -0.046875
|
||||
|
||||
The envelope generator internally uses 10 bits:
|
||||
|
||||
Bit | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||
----|----|----|----|----|----|-----|------|-------|--------|---------|
|
||||
dB | -48| -24| -12| -6| -3| -1.5| -0.75| -0.375| -0.1875| -0.09375|
|
||||
|
||||
Total level for operators is usually represented by 7 bits:
|
||||
|
||||
Bit | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||
----|----|----|----|----|----|-----|------|
|
||||
dB | -48| -24| -12| -6| -3| -1.5| -0.75|
|
||||
|
||||
Sustain level in the envelope generator is usually represented by 4 bits:
|
||||
|
||||
Bit | 3 | 2 | 1 | 0 |
|
||||
----|----|----|----|----|
|
||||
dB | -24| -12| -6| -3|
|
||||
|
||||
## Status and Timers
|
||||
|
||||
Generically, all chips (except OPLL) support two timers that can be programmed to fire and signal IRQs.
|
||||
These timers also set bits in the status register.
|
||||
The behavior of these bits is shared across all implementations, even if the exact bit positions shift (this is controlled by constants in the registers class).
|
||||
|
||||
In addition, several chips incorporate ADPCM decoders which also may set bits in the same status register.
|
||||
For this reason, it is possible to control various bits in the status register via the `set_reset_status()` function directly.
|
||||
Any active bits that are set and which are not masked (mask is controlled by `set_irq_mask()`), lead to an IRQ being signalled.
|
||||
|
||||
Thus, it is possible for the chip-specific implementations to set the mask and control the status register bits such that IRQs are signalled via the same mechanism as timer signals.
|
||||
|
||||
In addition, the OPM and OPN families have a "busy" flag, which is set after each write, indicating that another write should not be performed.
|
||||
Historically, the duration of this flag was constant and had nothing to do with the internals of the chip.
|
||||
However, since the details can potentially vary chip-to-chip, it is the chip's responsibility to insert the busy flag into the status before returning it to the caller.
|
||||
|
||||
## Clocking
|
||||
|
||||
Each of the Yamaha chips works by cycling through all operators one at a time.
|
||||
Thus, the effective output rate of the chips is related to the input clock divided by the number of operators.
|
||||
In addition, the input clock is prescaled by an amount.
|
||||
Generally, this is a fixed value, though some early OPN chips allow this to be selected at runtime from a small
|
||||
number of values.
|
||||
|
||||
## Channel Frequencies
|
||||
|
||||
One major difference between OPM and later families is in how frequencies are specified.
|
||||
OPM specifies frequency via a 3-bit 'block' (aka octave), combined with a 4-bit 'key code' (note number) and a 6-bit 'key fraction'.
|
||||
The key code and fraction are converted on the chip into an x.11 fixed-point value and then shifted by the block to produce the final step value for the phase.
|
||||
|
||||
Later families, on the other hand, specify frequencies via a 3-bit 'block' just as on OPM, but combined with a 9-12-bit 'frequency number' or 'fnum', which is directly shifted by the block to produce the step value.
|
||||
So essentially, later chips make the user do the conversion from note value to phase increment, while OPM is programmed in a more 'musical' way, specifying notes and cents.
|
||||
|
||||
Internally, this is abstracted away into a 'block_freq' value, which is a 16-bit value containing the block and frequency info concatenated together as follows:
|
||||
|
||||
* OPM: `[3-bit block]:[4-bit keycode]:[6-bit fraction] = 13 bits total`
|
||||
|
||||
* OPZ: `[3-bit block]:[12-bit fnum] = 15 bits total`
|
||||
* OPN: `[3-bit block]:[11-bit fnum] 0 = 15 bits total`
|
||||
* OPL: `[3-bit block]:[10-bit fnum]:00 = 15 bits total`
|
||||
* OPLL: `[3-bit block]:[ 9-bit fnum]:000 = 15 bits total`
|
||||
|
||||
The register classes handle the raw format directly and convert it into a phase increment which can be used by the generic engine.
|
||||
|
||||
## Low Frequency Oscillator (LFO)
|
||||
|
||||
The LFO engines are different in several key ways.
|
||||
The OPM LFO engine is fairly intricate.
|
||||
It has a 4.4 floating-point rate which allows for a huge range of frequencies, and can select between four different waveforms (sawtooth, square, triangle, or noise).
|
||||
Separate 7-bit depth controls for AM and PM control the amount of modulation applied in each case.
|
||||
This global LFO value is then further controlled at the channel level by a 2-bit AM sensitivity and a 3-bit PM sensitivity, and each operator has a 1-bit AM on/off switch.
|
||||
|
||||
For OPN the LFO engine was removed entirely, but a limited version was put back in OPNA and later chips.
|
||||
This stripped-down version offered only a 3-bit rate setting (versus the 4.4 floating-point rate in OPN), and no
|
||||
global depth control.
|
||||
It did bring back the channel-level sensitivity controls and the operator-level on/off control.
|
||||
|
||||
For OPL, the LFO is simplified again, with AM and PM running at fixed frequencies, and simple enable flags at the operator level for each controlling their application.
|
||||
|
||||
## Differences Between Families
|
||||
|
||||
The table below provides some high level functional differences between the differnet families:
|
||||
|
||||
subfamily: | OPM | OPN | OPNA | OPL | OPL2 | OPLL | OPL3 |
|
||||
------------:|:------:|:------:|:------:|:------:|:------:|:------:|:------:|
|
||||
outputs: | 2 | 1 | 2 | 1 | 1 | 1 | 4 |
|
||||
channels: | 8 | 3 | 6 | 9 | 9 | 9 | 18 |
|
||||
operators: | 32 | 12 | 24 | 18 | 18 | 18 | 36 |
|
||||
waveforms: | 1 | 1 | 1 | 1 | 4 | 2 | 8 |
|
||||
instruments: | no | no | no | yes | yes | yes | yes |
|
||||
ryhthm: | no | no | no | no | no | yes | no |
|
||||
dynamic ops: | no | no | no | no | no | no | yes |
|
||||
prescale: | 2 | 2/3/6 | 2/3/6 | 4 | 4 | 4 | 8 |
|
||||
EG divider: | 3 | 3 | 3 | 1 | 1 | 1 | 1 |
|
||||
EG DP: | no | no | no | no | no | yes | no |
|
||||
EG SSG: | no | yes | yes | no | no | no | no |
|
||||
mod delay: | no | no | no | yes | yes | yes? | no |
|
||||
CSM: | yes | ch 2 | ch 2 | yes | yes | yes | no |
|
||||
LFO: | yes | no | yes | yes | yes | yes | yes |
|
||||
noise: | yes | no | no | no | no | no | no |
|
||||
|
||||
* Outputs represents the number of output channels: 1=mono, 2=stereo, 4=stereo+.
|
||||
* Channels represents the number of independent FM channels.
|
||||
* Operators represents the number of operators, or "slots" which are assembled into the channels.
|
||||
* Waveforms represents the number of different sine-derived waveforms available.
|
||||
* Instruments indicates whether the family has built-in instruments.
|
||||
* Rhythm indicates whether the family has a built-in rhythm
|
||||
* Dynamic ops indicates whether it is possible to switch between 2-operator and 4-operator modes dynamically.
|
||||
* Prescale specifies the default clock divider; some chips allow this to be controlled via register writes.
|
||||
* EG divider represents the divider applied to the envelope generator clock.
|
||||
* EG DP indicates whether the envelope generator includes a DP (depress?) phase at the beginning of each key on.
|
||||
* SSG EG indicates whether the envelope generator has SSG-style support.
|
||||
* Mod delay indicates whether the connection to the first modulator's input is delayed by 1 sample.
|
||||
* CSM indicates whether CSM mode is supported, triggered by timer A.
|
||||
* LFO indicates whether LFO is supported.
|
||||
* Noise indicates whether one of the operators can be replaced with a noise source.
|
||||
|
||||
## Chip Specifics
|
||||
|
||||
While OPM is its own thing, the OPN and OPL families have quite a few specific
|
||||
implementations, with many differing details beyond the core FM parts. Here are
|
||||
some details on the OPN family:
|
||||
|
||||
chip ID: | YM2203 | YM2608 | YMF288 | YM2610 | YM2610B | YM2612 | YM3438 | YMF276 |
|
||||
---------:|:------:|:------:|:------:|:------:|:-------:|:------:|:------:|:------:|
|
||||
aka: | OPN | OPNA | OPN3L | OPNB | OPNB2 | OPN2 | OPN2C | OPN2L |
|
||||
FM: | 3 | 6 | 6 | 4 | 6 | 6 | 6 | 6 |
|
||||
AY-8910: | 3 | 1 | 1 | 1 | 1 | - | - | - |
|
||||
ADPCM-A: | - | 6 int | 6 int | 6 ext | 6 ext | - | - | - |
|
||||
ADPCM-B: | - | 1 ext | - | 1 ext | 1 ext | - | - | - |
|
||||
DAC: | no | no | no | no | no | yes | yes | yes |
|
||||
output: | 10.3fp | 16-bit | 16-bit | 16-bit | 16-bit | 9-bit | 9-bit | 16-bit |
|
||||
summing: | adder | adder | adder | adder | adder | muxer | muxer | adder |
|
||||
|
||||
* FM represents the number of FM channels available.
|
||||
* AY-8910 represents the number of AY-8910-compatible outputs.
|
||||
* ADPCM-A represents the number of internal/external ADPCM-A channels present.
|
||||
* ADPCM-B represents the number of internal/external ADPCM-B channels present.
|
||||
* DAC indicates if a directly-accessible DAC output exists, replacing one channel.
|
||||
* Output indicates the output format to the final DAC.
|
||||
* Summing indicates whether channels are added or time divided in the output.
|
||||
|
||||
OPL has a similar trove of chip variants:
|
||||
|
||||
chip ID: | YM3526 | Y8950 | YM3812 | YM2413 | YMF262 | YMF289B | YMF278B |
|
||||
------------:|:------:|:-------:|:------:|:------:|:------:|:-------:|:-------:|
|
||||
aka: | OPL |MSX-AUDIO| OPL2 | OPLL | OPL3 | OPL3L | OPL4 |
|
||||
FM: | 9 | 9 | 9 | 9 | 18 | 18 | 18 |
|
||||
ADPCM-B: | - | 1 ext | - | - | - | - | - |
|
||||
wavetable: | - | - | - | - | - | - | 24 |
|
||||
instruments: | no | no | no | yes | no | no | no |
|
||||
output: | 10.3fp | 10.3fp | 10.3fp | 9-bit | 16-bit | 16-bit | 16-bit |
|
||||
summing: | adder | adder | adder | muxer | adder | adder | adder |
|
||||
|
||||
* FM represents the number of FM channels available.
|
||||
* ADPCM-B represents the number of external ADPCM-B channels present.
|
||||
* Wavetable indicates the number of wavetable channels present.
|
||||
* Instruments indicates that the chip has built-in instrument selection.
|
||||
* Output indicates the output format to the final DAC.
|
||||
* Summing indicates whether channels are added or time divided in the output.
|
||||
|
||||
There are several close variants of the YM2413 with different sets of built-in instruments.
|
||||
These include the YM2423, YMF281, and DS1001 (aka Konami VRC7).
|
|
@ -1,29 +0,0 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2021, Aaron Giles
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,125 +0,0 @@
|
|||
# ymfm
|
||||
|
||||
<div style='text-align:center;margin:auto'>
|
||||
<img src='https://aarongiles.com/img/icon-ymfm.png' width='128px'>
|
||||
</div>
|
||||
|
||||
[ymfm](https://github.com/aaronsgiles/ymfm) is a collection of BSD-licensed Yamaha FM sound cores (OPM, OPN, OPL, and others), written by [Aaron Giles](https://aarongiles.com)
|
||||
|
||||
## Supported environments
|
||||
|
||||
This code should compile cleanly in any environment that has C++14 support.
|
||||
It has been tested on gcc, clang, and Microsoft Visual C++ 2019.
|
||||
|
||||
## Supported chip families
|
||||
|
||||
Currently, support is present for the following chips (organized by header file):
|
||||
|
||||
* ymfm_misc.h:
|
||||
* YM2149 (SSG) [1983: MSX; Atari ST]
|
||||
* ymfm_opm.h:
|
||||
* YM2151 (OPM) [1983: Sharp X1, X68000; MSX; synths: DX21, DX27, DX100]
|
||||
* YM2164 (OPP) [1985: FB-01 MIDI Expander; IBM Music Feature Card; MSX; synths: Korg DS-8, 707]
|
||||
* ymfm_opn.h:
|
||||
* YM2203 (OPN) [1984: NEC PC-88, PC-98, NEC PC-6001mkII SR, PC-6601 SR]
|
||||
* YM2608 (OPNA) [1985: NEC PC-88, PC-98]
|
||||
* YM2610 (OPNB) [1987: Neo Geo]
|
||||
* YM2610B (OPNB2)
|
||||
* YM2612 (OPN2) [1988: Sega Mega Drive/Genesis; FM Towns]
|
||||
* YM3438 (OPN2C)
|
||||
* YMF276 (OPN2L)
|
||||
* YMF288 (OPN3L) [1995: NEC PC-98]
|
||||
* ymfm_opl.h:
|
||||
* YM3526 (OPL) [1984: C64 SFX Sound Expander]
|
||||
* Y8950 (MSX-Audio) [1984: MSX]
|
||||
* YM3812 (OPL2) [1985: AdLib, Sound Blaster; synths: some Portasound keyboards]
|
||||
* YMF262 (OPL3) [1988: Sound Blaster Pro 2.0, SB16]
|
||||
* YMF289B (OPL3L)
|
||||
* YMF278B (OPL4) [1993: MSX Moonsound cartridge]
|
||||
* YM2413 (OPLL) [1986: Sega Master System, Mark III; MSX; synths: Portasound PSS-140, PSS-170, PSS-270]
|
||||
* YM2423 (OPLL-X)
|
||||
* YMF281 (OPLLP)
|
||||
* DS1001 (Konami 053982/VRC7) [1991: Famicom cartridge Lagrange Point]
|
||||
* ymfm_opq.h:
|
||||
* YM3806 (OPQ) [synths: PSR-60/70]
|
||||
* ymfm_opz.h:
|
||||
* YM2414 (OPZ) [1987: synths: TX81Z, DX11, YS200; Korg Z3 guitar synth]
|
||||
|
||||
There are some obviously-related chips that also are on my horizon but have no implementation as yet:
|
||||
|
||||
* YMW-258-F 'GEW8' (aka Sega 315-5560 aka Sega Multi-PCM)
|
||||
* YMF271 (OPX)
|
||||
* YM21280 (OPS) / YM21290 (EGS) [synths: DX7, DX1, DX5, DX9, TX7, TX216, TX416, TX816]
|
||||
* OPK?
|
||||
|
||||
## History
|
||||
|
||||
These cores were originally written during the summer and fall of 2020 as part of the [MAME](https://mamedev.org/) project.
|
||||
As such, their design started off heavily based on how MAME works.
|
||||
|
||||
The OPM/OPN cores first appeared in MAME 0.230.
|
||||
The OPL cores were added in MAME 0.231.
|
||||
A further rewrite to abstract MAME dependencies is planned for MAME 0.232.
|
||||
|
||||
The goal was threefold:
|
||||
1. provide BSD-licensed emulation cores that are more compatible with MAME's core licensing
|
||||
1. modernize and unify the code around a common implementation of shared features
|
||||
1. improve accuracy where possible based on discoveries made by others
|
||||
|
||||
## Accuracy
|
||||
|
||||
The goal of these cores is not 100% digital accuracy.
|
||||
To achieve that would require full emulation of the pipelines, which would make the code extremely difficult to comprehend.
|
||||
It would also make it much harder to share common implementations of features, or to add support for less well-known chip types.
|
||||
If you want that level of accuracy, there are [several](https://github.com/nukeykt/Nuked-OPN2) [decap-based](https://github.com/nukeykt/Nuked-OPM) [emulation cores](https://github.com/nukeykt/Nuked-OPLL) out there.
|
||||
|
||||
Instead, the main goals are:
|
||||
1. Extremely high (audibly indistinguishable) accuracy
|
||||
1. Reasonable performance
|
||||
1. Clean design with readable code
|
||||
1. Clear documentation of the various chips
|
||||
|
||||
## General approach
|
||||
|
||||
Check out the [examples directory](https://github.com/aaronsgiles/ymfm/tree/main/examples) for some example usage patterns.
|
||||
I'm not a big fan of makefiles for simple things, so instructions on how to compile each example are provided at the top.
|
||||
|
||||
# IMPORTANT
|
||||
|
||||
As of May 2021, the interface to these is still a bit in flux.
|
||||
Be prepared when syncing with upstream to make some adjustments.
|
||||
|
||||
### Clocking
|
||||
|
||||
The general philosophy of the emulators provided here is that they are clock-independent.
|
||||
Much like the actual chips, you (the consumer) control the clock; the chips themselves have no idea what time it is.
|
||||
They just tick forward each time you ask them to.
|
||||
|
||||
The way you move things along is via the `generate()` function, which ticks the internal system forward one or more samples, and writes out an array out chip-specific `output_data`.
|
||||
But what, exactly, is a "sample", and how long is it?
|
||||
|
||||
This is where the external clock comes in.
|
||||
Most of the Yamaha chips are externally clocked in the MHz range.
|
||||
They then divide that clock by a factor (sometimes dynamically controllable), and then the internal operators are pipelined to further divide the clock.
|
||||
|
||||
For example, the YM2151 internally divides the clock by 2, and has 32 operators to iterate through.
|
||||
Thus, for a nominal input lock of 3.58MHz, you end up at around a 55.9kHz sample rate.
|
||||
Fortunately, all the chip implementations can compute this for you; just pass the raw external clock value to the `sample_rate()` method and it will hand you back the output sample rate you want.
|
||||
|
||||
Then call `generate()` that many times per second to output the results.
|
||||
|
||||
But what if I want to output at a "normal" rate, like 44.1kHz?
|
||||
Sorry, you'll have to rate convert as needed.
|
||||
|
||||
### Reading and Writing
|
||||
|
||||
To read or write to the chips, you can call the `read()` and `write()` methods.
|
||||
The offset provided corresponds to the addressing input lines in a (hopefully) logical way.
|
||||
|
||||
For reads, almost all chips have a status register, which you can read via `read_status()`.
|
||||
Some chips have a data port that can be read via `read_data()`.
|
||||
And chips with extended addressing may also have `read_status_hi()` and `read_data_hi()`.
|
||||
|
||||
For writes, almost all chips have an address register and a data register, and so you can reliably count on there being a `write_address()` and `write_data()` method as well.
|
||||
If the chip supports extended addressing, it may also have `write_address_hi()` and `write_data_hi()`.
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
//
|
||||
// Simple program that touches all the existing cores to help ensure
|
||||
// that everything builds cleanly.
|
||||
//
|
||||
// Compile with:
|
||||
//
|
||||
// g++ --std=c++14 -I../../src buildall.cpp ../../src/ymfm_misc.cpp ../../src/ymfm_opl.cpp ../../src/ymfm_opm.cpp ../../src/ymfm_opn.cpp ../../src/ymfm_opq.cpp ../../src/ymfm_opz.cpp ../../src/ymfm_adpcm.cpp ../../src/ymfm_pcm.cpp ../../src/ymfm_ssg.cpp -o buildall.exe
|
||||
//
|
||||
// or:
|
||||
//
|
||||
// clang --std=c++14 -I../../src buildall.cpp ../../src/ymfm_misc.cpp ../../src/ymfm_opl.cpp ../../src/ymfm_opm.cpp ../../src/ymfm_opn.cpp ../../src/ymfm_opq.cpp ../../src/ymfm_opz.cpp ../../src/ymfm_adpcm.cpp ../../src/ymfm_pcm.cpp ../../src/ymfm_ssg.cpp -o buildall.exe
|
||||
//
|
||||
// or:
|
||||
//
|
||||
// cl -I..\..\src buildall.cpp ..\..\src\ymfm_misc.cpp ..\..\src\ymfm_opl.cpp ..\..\src\ymfm_opm.cpp ..\..\src\ymfm_opn.cpp ..\..\src\ymfm_opq.cpp ..\..\src\ymfm_opz.cpp ..\..\src\ymfm_adpcm.cpp ..\..\src\ymfm_pcm.cpp ..\..\src\ymfm_ssg.cpp /Od /Zi /std:c++14 /EHsc
|
||||
//
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "ymfm_misc.h"
|
||||
#include "ymfm_opl.h"
|
||||
#include "ymfm_opm.h"
|
||||
#include "ymfm_opn.h"
|
||||
#include "ymfm_opq.h"
|
||||
#include "ymfm_opz.h"
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// main - program entry point
|
||||
//-------------------------------------------------
|
||||
|
||||
template<typename ChipType>
|
||||
class chip_wrapper : public ymfm::ymfm_interface
|
||||
{
|
||||
public:
|
||||
chip_wrapper() :
|
||||
m_chip(*this)
|
||||
{
|
||||
// reset
|
||||
m_chip.reset();
|
||||
|
||||
// save/restore
|
||||
std::vector<uint8_t> buffer;
|
||||
{
|
||||
ymfm::ymfm_saved_state saver(buffer, true);
|
||||
m_chip.save_restore(saver);
|
||||
}
|
||||
{
|
||||
ymfm::ymfm_saved_state restorer(buffer, false);
|
||||
m_chip.save_restore(restorer);
|
||||
}
|
||||
|
||||
// dummy read/write
|
||||
m_chip.read(0);
|
||||
m_chip.write(0, 0);
|
||||
|
||||
// generate
|
||||
typename ChipType::output_data output[20];
|
||||
m_chip.generate(&output[0], ymfm::array_size(output));
|
||||
}
|
||||
|
||||
private:
|
||||
ChipType m_chip;
|
||||
};
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// main - program entry point
|
||||
//-------------------------------------------------
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// just keep adding chip variants here as they are implemented
|
||||
|
||||
// ymfm_misc.h:
|
||||
chip_wrapper<ymfm::ym2149> test2149;
|
||||
|
||||
// ymfm_opl.h:
|
||||
chip_wrapper<ymfm::ym3526> test3526;
|
||||
chip_wrapper<ymfm::y8950> test8950;
|
||||
chip_wrapper<ymfm::ym3812> test3812;
|
||||
chip_wrapper<ymfm::ymf262> test262;
|
||||
chip_wrapper<ymfm::ymf289b> test289b;
|
||||
chip_wrapper<ymfm::ymf278b> test278b;
|
||||
chip_wrapper<ymfm::ym2413> test2413;
|
||||
chip_wrapper<ymfm::ym2423> test2423;
|
||||
chip_wrapper<ymfm::ymf281> test281;
|
||||
chip_wrapper<ymfm::ds1001> test1001;
|
||||
|
||||
// ymfm_opm.h:
|
||||
chip_wrapper<ymfm::ym2151> test2151;
|
||||
chip_wrapper<ymfm::ym2164> test2164;
|
||||
|
||||
// ymfm_opn.h:
|
||||
chip_wrapper<ymfm::ym2203> test2203;
|
||||
chip_wrapper<ymfm::ym2608> test2608;
|
||||
chip_wrapper<ymfm::ymf288> test288;
|
||||
chip_wrapper<ymfm::ym2610> test2610;
|
||||
chip_wrapper<ymfm::ym2610b> test2610b;
|
||||
chip_wrapper<ymfm::ym2612> test2612;
|
||||
chip_wrapper<ymfm::ym3438> test3438;
|
||||
chip_wrapper<ymfm::ymf276> test276;
|
||||
|
||||
// ymfm_opq.h:
|
||||
chip_wrapper<ymfm::ym3806> test3806;
|
||||
chip_wrapper<ymfm::ym3533> test3533;
|
||||
|
||||
// ymfm_opz.h:
|
||||
chip_wrapper<ymfm::ym2414> test2414;
|
||||
|
||||
printf("Done\n");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
test
|
File diff suppressed because it is too large
Load diff
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* em_inflate.h - fast in-memory inflate (gzip/zlib decompressor) definitions
|
||||
*
|
||||
* Copyright (C) 2019 Emmanuel Marty
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef _EM_INFLATE_H
|
||||
#define _EM_INFLATE_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Inflate gzip or zlib data
|
||||
*
|
||||
* @param pCompressedData pointer to start of zlib data
|
||||
* @param nCompressedDataSize size of zlib data, in bytes
|
||||
* @param pOutData pointer to start of decompression buffer
|
||||
* @param nMaxOutDataSize maximum size of decompression buffer, in bytes
|
||||
*
|
||||
* @return number of bytes decompressed, or -1 in case of an error
|
||||
*/
|
||||
size_t em_inflate(const void *pCompressedData, size_t nCompressedDataSize, unsigned char *pOutData, size_t nMaxOutDataSize);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _EM_INFLATE_H */
|
File diff suppressed because it is too large
Load diff
|
@ -1,566 +0,0 @@
|
|||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2021, Aaron Giles
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef YMFM_H
|
||||
#define YMFM_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
||||
//*********************************************************
|
||||
// DEBUGGING
|
||||
//*********************************************************
|
||||
|
||||
class debug
|
||||
{
|
||||
public:
|
||||
// masks to help isolate specific channels
|
||||
static constexpr uint32_t GLOBAL_FM_CHANNEL_MASK = 0xffffffff;
|
||||
static constexpr uint32_t GLOBAL_ADPCM_A_CHANNEL_MASK = 0xffffffff;
|
||||
static constexpr uint32_t GLOBAL_ADPCM_B_CHANNEL_MASK = 0xffffffff;
|
||||
static constexpr uint32_t GLOBAL_PCM_CHANNEL_MASK = 0xffffffff;
|
||||
|
||||
// types of logging
|
||||
static constexpr bool LOG_FM_WRITES = false;
|
||||
static constexpr bool LOG_KEYON_EVENTS = false;
|
||||
static constexpr bool LOG_UNEXPECTED_READ_WRITES = false;
|
||||
|
||||
// helpers to write based on the log type
|
||||
template<typename... Params> static void log_fm_write(Params &&... args) { if (LOG_FM_WRITES) log(args...); }
|
||||
template<typename... Params> static void log_keyon(Params &&... args) { if (LOG_KEYON_EVENTS) log(args...); }
|
||||
template<typename... Params> static void log_unexpected_read_write(Params &&... args) { if (LOG_UNEXPECTED_READ_WRITES) log(args...); }
|
||||
|
||||
// downstream helper to output log data; defaults to printf
|
||||
template<typename... Params> static void log(Params &&... args) { printf(args...); }
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// GLOBAL HELPERS
|
||||
//*********************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// bitfield - extract a bitfield from the given
|
||||
// value, starting at bit 'start' for a length of
|
||||
// 'length' bits
|
||||
//-------------------------------------------------
|
||||
|
||||
inline uint32_t bitfield(uint32_t value, int start, int length = 1)
|
||||
{
|
||||
return (value >> start) & ((1 << length) - 1);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// clamp - clamp between the minimum and maximum
|
||||
// values provided
|
||||
//-------------------------------------------------
|
||||
|
||||
inline int32_t clamp(int32_t value, int32_t minval, int32_t maxval)
|
||||
{
|
||||
if (value < minval)
|
||||
return minval;
|
||||
if (value > maxval)
|
||||
return maxval;
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// count_leading_zeros - return the number of
|
||||
// leading zeros in a 32-bit value; CPU-optimized
|
||||
// versions for various architectures are included
|
||||
// below
|
||||
//-------------------------------------------------
|
||||
|
||||
#if defined(__GNUC__)
|
||||
|
||||
inline uint8_t count_leading_zeros(uint32_t value)
|
||||
{
|
||||
if (value == 0)
|
||||
return 32;
|
||||
return __builtin_clz(value);
|
||||
}
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
|
||||
inline uint8_t count_leading_zeros(uint32_t value)
|
||||
{
|
||||
unsigned long index;
|
||||
return _BitScanReverse(&index, value) ? uint8_t(31U - index) : 32U;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
inline uint8_t count_leading_zeros(uint32_t value)
|
||||
{
|
||||
if (value == 0)
|
||||
return 32;
|
||||
uint8_t count;
|
||||
for (count = 0; int32_t(value) >= 0; count++)
|
||||
value <<= 1;
|
||||
return count;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// Many of the Yamaha FM chips emit a floating-point value, which is sent to
|
||||
// a DAC for processing. The exact format of this floating-point value is
|
||||
// documented below. This description only makes sense if the "internal"
|
||||
// format treats sign as 1=positive and 0=negative, so the helpers below
|
||||
// presume that.
|
||||
//
|
||||
// Internal OPx data 16-bit signed data Exp Sign Mantissa
|
||||
// ================= ================= === ==== ========
|
||||
// 1 1xxxxxxxx------ -> 0 1xxxxxxxx------ -> 111 1 1xxxxxxx
|
||||
// 1 01xxxxxxxx----- -> 0 01xxxxxxxx----- -> 110 1 1xxxxxxx
|
||||
// 1 001xxxxxxxx---- -> 0 001xxxxxxxx---- -> 101 1 1xxxxxxx
|
||||
// 1 0001xxxxxxxx--- -> 0 0001xxxxxxxx--- -> 100 1 1xxxxxxx
|
||||
// 1 00001xxxxxxxx-- -> 0 00001xxxxxxxx-- -> 011 1 1xxxxxxx
|
||||
// 1 000001xxxxxxxx- -> 0 000001xxxxxxxx- -> 010 1 1xxxxxxx
|
||||
// 1 000000xxxxxxxxx -> 0 000000xxxxxxxxx -> 001 1 xxxxxxxx
|
||||
// 0 111111xxxxxxxxx -> 1 111111xxxxxxxxx -> 001 0 xxxxxxxx
|
||||
// 0 111110xxxxxxxx- -> 1 111110xxxxxxxx- -> 010 0 0xxxxxxx
|
||||
// 0 11110xxxxxxxx-- -> 1 11110xxxxxxxx-- -> 011 0 0xxxxxxx
|
||||
// 0 1110xxxxxxxx--- -> 1 1110xxxxxxxx--- -> 100 0 0xxxxxxx
|
||||
// 0 110xxxxxxxx---- -> 1 110xxxxxxxx---- -> 101 0 0xxxxxxx
|
||||
// 0 10xxxxxxxx----- -> 1 10xxxxxxxx----- -> 110 0 0xxxxxxx
|
||||
// 0 0xxxxxxxx------ -> 1 0xxxxxxxx------ -> 111 0 0xxxxxxx
|
||||
|
||||
//-------------------------------------------------
|
||||
// encode_fp - given a 32-bit signed input value
|
||||
// convert it to a signed 3.10 floating-point
|
||||
// value
|
||||
//-------------------------------------------------
|
||||
|
||||
inline int16_t encode_fp(int32_t value)
|
||||
{
|
||||
// handle overflows first
|
||||
if (value < -32768)
|
||||
return (7 << 10) | 0x000;
|
||||
if (value > 32767)
|
||||
return (7 << 10) | 0x3ff;
|
||||
|
||||
// we need to count the number of leading sign bits after the sign
|
||||
// we can use count_leading_zeros if we invert negative values
|
||||
int32_t scanvalue = value ^ (int32_t(value) >> 31);
|
||||
|
||||
// exponent is related to the number of leading bits starting from bit 14
|
||||
int exponent = 7 - count_leading_zeros(scanvalue << 17);
|
||||
|
||||
// smallest exponent value allowed is 1
|
||||
exponent = std::max(exponent, 1);
|
||||
|
||||
// mantissa
|
||||
int32_t mantissa = value >> (exponent - 1);
|
||||
|
||||
// assemble into final form, inverting the sign
|
||||
return ((exponent << 10) | (mantissa & 0x3ff)) ^ 0x200;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// decode_fp - given a 3.10 floating-point value,
|
||||
// convert it to a signed 16-bit value
|
||||
//-------------------------------------------------
|
||||
|
||||
inline int16_t decode_fp(int16_t value)
|
||||
{
|
||||
// invert the sign and the exponent
|
||||
value ^= 0x1e00;
|
||||
|
||||
// shift mantissa up to 16 bits then apply inverted exponent
|
||||
return int16_t(value << 6) >> bitfield(value, 10, 3);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// roundtrip_fp - compute the result of a round
|
||||
// trip through the encode/decode process above
|
||||
//-------------------------------------------------
|
||||
|
||||
inline int16_t roundtrip_fp(int32_t value)
|
||||
{
|
||||
// handle overflows first
|
||||
if (value < -32768)
|
||||
return -32768;
|
||||
if (value > 32767)
|
||||
return 32767;
|
||||
|
||||
// we need to count the number of leading sign bits after the sign
|
||||
// we can use count_leading_zeros if we invert negative values
|
||||
int32_t scanvalue = value ^ (int32_t(value) >> 31);
|
||||
|
||||
// exponent is related to the number of leading bits starting from bit 14
|
||||
int exponent = 7 - count_leading_zeros(scanvalue << 17);
|
||||
|
||||
// smallest exponent value allowed is 1
|
||||
exponent = std::max(exponent, 1);
|
||||
|
||||
// apply the shift back and forth to zero out bits that are lost
|
||||
exponent -= 1;
|
||||
int32_t mask = (1 << exponent) - 1;
|
||||
return value & ~mask;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// HELPER CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// various envelope states
|
||||
enum envelope_state : uint32_t
|
||||
{
|
||||
EG_DEPRESS = 0, // OPLL only; set EG_HAS_DEPRESS to enable
|
||||
EG_ATTACK = 1,
|
||||
EG_DECAY = 2,
|
||||
EG_SUSTAIN = 3,
|
||||
EG_RELEASE = 4,
|
||||
EG_REVERB = 5, // OPQ/OPZ only; set EG_HAS_REVERB to enable
|
||||
EG_STATES = 6
|
||||
};
|
||||
|
||||
// external I/O access classes
|
||||
enum access_class : uint32_t
|
||||
{
|
||||
ACCESS_IO = 0,
|
||||
ACCESS_ADPCM_A,
|
||||
ACCESS_ADPCM_B,
|
||||
ACCESS_PCM,
|
||||
ACCESS_CLASSES
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// HELPER CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> ymfm_output
|
||||
|
||||
// struct containing an array of output values
|
||||
template<int NumOutputs>
|
||||
struct ymfm_output
|
||||
{
|
||||
// clear all outputs to 0
|
||||
ymfm_output &clear()
|
||||
{
|
||||
for (uint32_t index = 0; index < NumOutputs; index++)
|
||||
data[index] = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// clamp all outputs to a 16-bit signed value
|
||||
ymfm_output &clamp16()
|
||||
{
|
||||
for (uint32_t index = 0; index < NumOutputs; index++)
|
||||
data[index] = clamp(data[index], -32768, 32767);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// run each output value through the floating-point processor
|
||||
ymfm_output &roundtrip_fp()
|
||||
{
|
||||
for (uint32_t index = 0; index < NumOutputs; index++)
|
||||
data[index] = ymfm::roundtrip_fp(data[index]);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// internal state
|
||||
int32_t data[NumOutputs];
|
||||
};
|
||||
|
||||
|
||||
// ======================> ymfm_wavfile
|
||||
|
||||
// this class is a debugging helper that accumulates data and writes it to wav files
|
||||
template<int Channels>
|
||||
class ymfm_wavfile
|
||||
{
|
||||
public:
|
||||
// construction
|
||||
ymfm_wavfile(uint32_t samplerate = 44100) :
|
||||
m_samplerate(samplerate)
|
||||
{
|
||||
}
|
||||
|
||||
// configuration
|
||||
ymfm_wavfile &set_index(uint32_t index) { m_index = index; return *this; }
|
||||
ymfm_wavfile &set_samplerate(uint32_t samplerate) { m_samplerate = samplerate; return *this; }
|
||||
|
||||
// destruction
|
||||
~ymfm_wavfile()
|
||||
{
|
||||
if (!m_buffer.empty())
|
||||
{
|
||||
// create file
|
||||
char name[20];
|
||||
snprintf(&name[0], sizeof(name), "wavlog-%02d.wav", m_index);
|
||||
FILE *out = fopen(name, "wb");
|
||||
|
||||
// make the wav file header
|
||||
uint8_t header[44];
|
||||
memcpy(&header[0], "RIFF", 4);
|
||||
*(uint32_t *)&header[4] = m_buffer.size() * 2 + 44 - 8;
|
||||
memcpy(&header[8], "WAVE", 4);
|
||||
memcpy(&header[12], "fmt ", 4);
|
||||
*(uint32_t *)&header[16] = 16;
|
||||
*(uint16_t *)&header[20] = 1;
|
||||
*(uint16_t *)&header[22] = Channels;
|
||||
*(uint32_t *)&header[24] = m_samplerate;
|
||||
*(uint32_t *)&header[28] = m_samplerate * 2 * Channels;
|
||||
*(uint16_t *)&header[32] = 2 * Channels;
|
||||
*(uint16_t *)&header[34] = 16;
|
||||
memcpy(&header[36], "data", 4);
|
||||
*(uint32_t *)&header[40] = m_buffer.size() * 2 + 44 - 44;
|
||||
|
||||
// write header then data
|
||||
fwrite(&header[0], 1, sizeof(header), out);
|
||||
fwrite(&m_buffer[0], 2, m_buffer.size(), out);
|
||||
fclose(out);
|
||||
}
|
||||
}
|
||||
|
||||
// add data to the file
|
||||
template<int Outputs>
|
||||
void add(ymfm_output<Outputs> output)
|
||||
{
|
||||
int16_t sum[Channels] = { 0 };
|
||||
for (int index = 0; index < Outputs; index++)
|
||||
sum[index % Channels] += output.data[index];
|
||||
for (int index = 0; index < Channels; index++)
|
||||
m_buffer.push_back(sum[index]);
|
||||
}
|
||||
|
||||
// add data to the file, using a reference
|
||||
template<int Outputs>
|
||||
void add(ymfm_output<Outputs> output, ymfm_output<Outputs> const &ref)
|
||||
{
|
||||
int16_t sum[Channels] = { 0 };
|
||||
for (int index = 0; index < Outputs; index++)
|
||||
sum[index % Channels] += output.data[index] - ref.data[index];
|
||||
for (int index = 0; index < Channels; index++)
|
||||
m_buffer.push_back(sum[index]);
|
||||
}
|
||||
|
||||
private:
|
||||
// internal state
|
||||
uint32_t m_index;
|
||||
uint32_t m_samplerate;
|
||||
std::vector<int16_t> m_buffer;
|
||||
};
|
||||
|
||||
|
||||
// ======================> ymfm_saved_state
|
||||
|
||||
// this class contains a managed vector of bytes that is used to save and
|
||||
// restore state
|
||||
class ymfm_saved_state
|
||||
{
|
||||
public:
|
||||
// construction
|
||||
ymfm_saved_state(std::vector<uint8_t> &buffer, bool saving) :
|
||||
m_buffer(buffer),
|
||||
m_offset(saving ? -1 : 0)
|
||||
{
|
||||
if (saving)
|
||||
buffer.resize(0);
|
||||
}
|
||||
|
||||
// are we saving or restoring?
|
||||
bool saving() const { return (m_offset < 0); }
|
||||
|
||||
// generic save/restore
|
||||
template<typename DataType>
|
||||
void save_restore(DataType &data)
|
||||
{
|
||||
if (saving())
|
||||
save(data);
|
||||
else
|
||||
restore(data);
|
||||
}
|
||||
|
||||
public:
|
||||
// save data to the buffer
|
||||
void save(bool &data) { write(data ? 1 : 0); }
|
||||
void save(int8_t &data) { write(data); }
|
||||
void save(uint8_t &data) { write(data); }
|
||||
void save(int16_t &data) { write(uint8_t(data)).write(data >> 8); }
|
||||
void save(uint16_t &data) { write(uint8_t(data)).write(data >> 8); }
|
||||
void save(int32_t &data) { write(data).write(data >> 8).write(data >> 16).write(data >> 24); }
|
||||
void save(uint32_t &data) { write(data).write(data >> 8).write(data >> 16).write(data >> 24); }
|
||||
void save(envelope_state &data) { write(uint8_t(data)); }
|
||||
template<typename DataType, int Count>
|
||||
void save(DataType (&data)[Count]) { for (uint32_t index = 0; index < Count; index++) save(data[index]); }
|
||||
|
||||
// restore data from the buffer
|
||||
void restore(bool &data) { data = read() ? true : false; }
|
||||
void restore(int8_t &data) { data = read(); }
|
||||
void restore(uint8_t &data) { data = read(); }
|
||||
void restore(int16_t &data) { data = read(); data |= read() << 8; }
|
||||
void restore(uint16_t &data) { data = read(); data |= read() << 8; }
|
||||
void restore(int32_t &data) { data = read(); data |= read() << 8; data |= read() << 16; data |= read() << 24; }
|
||||
void restore(uint32_t &data) { data = read(); data |= read() << 8; data |= read() << 16; data |= read() << 24; }
|
||||
void restore(envelope_state &data) { data = envelope_state(read()); }
|
||||
template<typename DataType, int Count>
|
||||
void restore(DataType (&data)[Count]) { for (uint32_t index = 0; index < Count; index++) restore(data[index]); }
|
||||
|
||||
// internal helper
|
||||
ymfm_saved_state &write(uint8_t data) { m_buffer.push_back(data); return *this; }
|
||||
uint8_t read() { return (m_offset < int32_t(m_buffer.size())) ? m_buffer[m_offset++] : 0; }
|
||||
|
||||
// internal state
|
||||
std::vector<uint8_t> &m_buffer;
|
||||
int32_t m_offset;
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// INTERFACE CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> ymfm_engine_callbacks
|
||||
|
||||
// this class represents functions in the engine that the ymfm_interface
|
||||
// needs to be able to call; it is represented here as a separate interface
|
||||
// that is independent of the actual engine implementation
|
||||
class ymfm_engine_callbacks
|
||||
{
|
||||
public:
|
||||
virtual ~ymfm_engine_callbacks() = default;
|
||||
|
||||
// timer callback; called by the interface when a timer fires
|
||||
virtual void engine_timer_expired(uint32_t tnum) = 0;
|
||||
|
||||
// check interrupts; called by the interface after synchronization
|
||||
virtual void engine_check_interrupts() = 0;
|
||||
|
||||
// mode register write; called by the interface after synchronization
|
||||
virtual void engine_mode_write(uint8_t data) = 0;
|
||||
};
|
||||
|
||||
|
||||
// ======================> ymfm_interface
|
||||
|
||||
// this class represents the interface between the fm_engine and the outside
|
||||
// world; it provides hooks for timers, synchronization, and I/O
|
||||
class ymfm_interface
|
||||
{
|
||||
// the engine is our friend
|
||||
template<typename RegisterType> friend class fm_engine_base;
|
||||
|
||||
public:
|
||||
virtual ~ymfm_interface() = default;
|
||||
|
||||
// the following functions must be implemented by any derived classes; the
|
||||
// default implementations are sufficient for some minimal operation, but will
|
||||
// likely need to be overridden to integrate with the outside world; they are
|
||||
// all prefixed with ymfm_ to reduce the likelihood of namespace collisions
|
||||
|
||||
//
|
||||
// timing and synchronizaton
|
||||
//
|
||||
|
||||
// the chip implementation calls this when a write happens to the mode
|
||||
// register, which could affect timers and interrupts; our responsibility
|
||||
// is to ensure the system is up to date before calling the engine's
|
||||
// engine_mode_write() method
|
||||
virtual void ymfm_sync_mode_write(uint8_t data) { m_engine->engine_mode_write(data); }
|
||||
|
||||
// the chip implementation calls this when the chip's status has changed,
|
||||
// which may affect the interrupt state; our responsibility is to ensure
|
||||
// the system is up to date before calling the engine's
|
||||
// engine_check_interrupts() method
|
||||
virtual void ymfm_sync_check_interrupts() { m_engine->engine_check_interrupts(); }
|
||||
|
||||
// the chip implementation calls this when one of the two internal timers
|
||||
// has changed state; our responsibility is to arrange to call the engine's
|
||||
// engine_timer_expired() method after the provided number of clocks; if
|
||||
// duration_in_clocks is negative, we should cancel any outstanding timers
|
||||
virtual void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) { }
|
||||
|
||||
// the chip implementation calls this to indicate that the chip should be
|
||||
// considered in a busy state until the given number of clocks has passed;
|
||||
// our responsibility is to compute and remember the ending time based on
|
||||
// the chip's clock for later checking
|
||||
virtual void ymfm_set_busy_end(uint32_t clocks) { }
|
||||
|
||||
// the chip implementation calls this to see if the chip is still currently
|
||||
// is a busy state, as specified by a previous call to ymfm_set_busy_end();
|
||||
// our responsibility is to compare the current time against the previously
|
||||
// noted busy end time and return true if we haven't yet passed it
|
||||
virtual bool ymfm_is_busy() { return false; }
|
||||
|
||||
//
|
||||
// I/O functions
|
||||
//
|
||||
|
||||
// the chip implementation calls this when the state of the IRQ signal has
|
||||
// changed due to a status change; our responsibility is to respond as
|
||||
// needed to the change in IRQ state, signaling any consumers
|
||||
virtual void ymfm_update_irq(bool asserted) { }
|
||||
|
||||
// the chip implementation calls this whenever data is read from outside
|
||||
// of the chip; our responsibility is to provide the data requested
|
||||
virtual uint8_t ymfm_external_read(access_class type, uint32_t address) { return 0; }
|
||||
|
||||
// the chip implementation calls this whenever data is written outside
|
||||
// of the chip; our responsibility is to pass the written data on to any consumers
|
||||
virtual void ymfm_external_write(access_class type, uint32_t address, uint8_t data) { }
|
||||
|
||||
protected:
|
||||
// pointer to engine callbacks -- this is set directly by the engine at
|
||||
// construction time
|
||||
ymfm_engine_callbacks *m_engine;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // YMFM_H
|
|
@ -1,807 +0,0 @@
|
|||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2021, Aaron Giles
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "ymfm_adpcm.h"
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
||||
//*********************************************************
|
||||
// ADPCM "A" REGISTERS
|
||||
//*********************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// reset - reset the register state
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_a_registers::reset()
|
||||
{
|
||||
std::fill_n(&m_regdata[0], REGISTERS, 0);
|
||||
|
||||
// initialize the pans to on by default, and max instrument volume;
|
||||
// some neogeo homebrews (for example ffeast) rely on this
|
||||
m_regdata[0x08] = m_regdata[0x09] = m_regdata[0x0a] =
|
||||
m_regdata[0x0b] = m_regdata[0x0c] = m_regdata[0x0d] = 0xdf;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// save_restore - save or restore the data
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_a_registers::save_restore(ymfm_saved_state &state)
|
||||
{
|
||||
state.save_restore(m_regdata);
|
||||
}
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// ADPCM "A" CHANNEL
|
||||
//*********************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// adpcm_a_channel - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
adpcm_a_channel::adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift) :
|
||||
m_choffs(choffs),
|
||||
m_address_shift(addrshift),
|
||||
m_playing(0),
|
||||
m_curnibble(0),
|
||||
m_curbyte(0),
|
||||
m_curaddress(0),
|
||||
m_accumulator(0),
|
||||
m_step_index(0),
|
||||
m_regs(owner.regs()),
|
||||
m_owner(owner)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// reset - reset the channel state
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_a_channel::reset()
|
||||
{
|
||||
m_playing = 0;
|
||||
m_curnibble = 0;
|
||||
m_curbyte = 0;
|
||||
m_curaddress = 0;
|
||||
m_accumulator = 0;
|
||||
m_step_index = 0;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// save_restore - save or restore the data
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_a_channel::save_restore(ymfm_saved_state &state)
|
||||
{
|
||||
state.save_restore(m_playing);
|
||||
state.save_restore(m_curnibble);
|
||||
state.save_restore(m_curbyte);
|
||||
state.save_restore(m_curaddress);
|
||||
state.save_restore(m_accumulator);
|
||||
state.save_restore(m_step_index);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// keyonoff - signal key on/off
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_a_channel::keyonoff(bool on)
|
||||
{
|
||||
// QUESTION: repeated key ons restart the sample?
|
||||
m_playing = on;
|
||||
if (m_playing)
|
||||
{
|
||||
m_curaddress = m_regs.ch_start(m_choffs) << m_address_shift;
|
||||
m_curnibble = 0;
|
||||
m_curbyte = 0;
|
||||
m_accumulator = 0;
|
||||
m_step_index = 0;
|
||||
|
||||
// don't log masked channels
|
||||
if (((debug::GLOBAL_ADPCM_A_CHANNEL_MASK >> m_choffs) & 1) != 0)
|
||||
debug::log_keyon("KeyOn ADPCM-A%d: pan=%d%d start=%04X end=%04X level=%02X\n",
|
||||
m_choffs,
|
||||
m_regs.ch_pan_left(m_choffs),
|
||||
m_regs.ch_pan_right(m_choffs),
|
||||
m_regs.ch_start(m_choffs),
|
||||
m_regs.ch_end(m_choffs),
|
||||
m_regs.ch_instrument_level(m_choffs));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// clock - master clocking function
|
||||
//-------------------------------------------------
|
||||
|
||||
bool adpcm_a_channel::clock()
|
||||
{
|
||||
// if not playing, just output 0
|
||||
if (m_playing == 0)
|
||||
{
|
||||
m_accumulator = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we're about to read nibble 0, fetch the data
|
||||
uint8_t data;
|
||||
if (m_curnibble == 0)
|
||||
{
|
||||
// stop when we hit the end address; apparently only low 20 bits are used for
|
||||
// comparison on the YM2610: this affects sample playback in some games, for
|
||||
// example twinspri character select screen music will skip some samples if
|
||||
// this is not correct
|
||||
//
|
||||
// note also: end address is inclusive, so wait until we are about to fetch
|
||||
// the sample just after the end before stopping; this is needed for nitd's
|
||||
// jump sound, for example
|
||||
uint32_t end = (m_regs.ch_end(m_choffs) + 1) << m_address_shift;
|
||||
if (((m_curaddress ^ end) & 0xfffff) == 0)
|
||||
{
|
||||
m_playing = m_accumulator = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_A, m_curaddress++);
|
||||
data = m_curbyte >> 4;
|
||||
m_curnibble = 1;
|
||||
}
|
||||
|
||||
// otherwise just extract from the previosuly-fetched byte
|
||||
else
|
||||
{
|
||||
data = m_curbyte & 0xf;
|
||||
m_curnibble = 0;
|
||||
}
|
||||
|
||||
// compute the ADPCM delta
|
||||
static uint16_t const s_steps[49] =
|
||||
{
|
||||
16, 17, 19, 21, 23, 25, 28,
|
||||
31, 34, 37, 41, 45, 50, 55,
|
||||
60, 66, 73, 80, 88, 97, 107,
|
||||
118, 130, 143, 157, 173, 190, 209,
|
||||
230, 253, 279, 307, 337, 371, 408,
|
||||
449, 494, 544, 598, 658, 724, 796,
|
||||
876, 963, 1060, 1166, 1282, 1411, 1552
|
||||
};
|
||||
int32_t delta = (2 * bitfield(data, 0, 3) + 1) * s_steps[m_step_index] / 8;
|
||||
if (bitfield(data, 3))
|
||||
delta = -delta;
|
||||
|
||||
// the 12-bit accumulator wraps on the ym2610 and ym2608 (like the msm5205)
|
||||
m_accumulator = (m_accumulator + delta) & 0xfff;
|
||||
|
||||
// adjust ADPCM step
|
||||
static int8_t const s_step_inc[8] = { -1, -1, -1, -1, 2, 5, 7, 9 };
|
||||
m_step_index = clamp(m_step_index + s_step_inc[bitfield(data, 0, 3)], 0, 48);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// output - return the computed output value, with
|
||||
// panning applied
|
||||
//-------------------------------------------------
|
||||
|
||||
template<int NumOutputs>
|
||||
void adpcm_a_channel::output(ymfm_output<NumOutputs> &output) const
|
||||
{
|
||||
// volume combines instrument and total levels
|
||||
int vol = (m_regs.ch_instrument_level(m_choffs) ^ 0x1f) + (m_regs.total_level() ^ 0x3f);
|
||||
|
||||
// if combined is maximum, don't add to outputs
|
||||
if (vol >= 63)
|
||||
return;
|
||||
|
||||
// convert into a shift and a multiplier
|
||||
// QUESTION: verify this from other sources
|
||||
int8_t mul = 15 - (vol & 7);
|
||||
uint8_t shift = 4 + 1 + (vol >> 3);
|
||||
|
||||
// m_accumulator is a 12-bit value; shift up to sign-extend;
|
||||
// the downshift is incorporated into 'shift'
|
||||
int16_t value = ((int16_t(m_accumulator << 4) * mul) >> shift) & ~3;
|
||||
|
||||
// apply to left/right as appropriate
|
||||
if (NumOutputs == 1 || m_regs.ch_pan_left(m_choffs))
|
||||
output.data[0] += value;
|
||||
if (NumOutputs > 1 && m_regs.ch_pan_right(m_choffs))
|
||||
output.data[1] += value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// ADPCM "A" ENGINE
|
||||
//*********************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// adpcm_a_engine - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
adpcm_a_engine::adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift) :
|
||||
m_intf(intf)
|
||||
{
|
||||
// create the channels
|
||||
for (int chnum = 0; chnum < CHANNELS; chnum++)
|
||||
m_channel[chnum] = std::make_unique<adpcm_a_channel>(*this, chnum, addrshift);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// reset - reset the engine state
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_a_engine::reset()
|
||||
{
|
||||
// reset register state
|
||||
m_regs.reset();
|
||||
|
||||
// reset each channel
|
||||
for (auto &chan : m_channel)
|
||||
chan->reset();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// save_restore - save or restore the data
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_a_engine::save_restore(ymfm_saved_state &state)
|
||||
{
|
||||
// save register state
|
||||
m_regs.save_restore(state);
|
||||
|
||||
// save channel state
|
||||
for (int chnum = 0; chnum < CHANNELS; chnum++)
|
||||
m_channel[chnum]->save_restore(state);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// clock - master clocking function
|
||||
//-------------------------------------------------
|
||||
|
||||
uint32_t adpcm_a_engine::clock(uint32_t chanmask)
|
||||
{
|
||||
// clock each channel, setting a bit in result if it finished
|
||||
uint32_t result = 0;
|
||||
for (int chnum = 0; chnum < CHANNELS; chnum++)
|
||||
if (bitfield(chanmask, chnum))
|
||||
if (m_channel[chnum]->clock())
|
||||
result |= 1 << chnum;
|
||||
|
||||
// return the bitmask of completed samples
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// update - master update function
|
||||
//-------------------------------------------------
|
||||
|
||||
template<int NumOutputs>
|
||||
void adpcm_a_engine::output(ymfm_output<NumOutputs> &output, uint32_t chanmask)
|
||||
{
|
||||
// mask out some channels for debug purposes
|
||||
chanmask &= debug::GLOBAL_ADPCM_A_CHANNEL_MASK;
|
||||
|
||||
// compute the output of each channel
|
||||
for (int chnum = 0; chnum < CHANNELS; chnum++)
|
||||
if (bitfield(chanmask, chnum))
|
||||
m_channel[chnum]->output(output);
|
||||
}
|
||||
|
||||
template void adpcm_a_engine::output<1>(ymfm_output<1> &output, uint32_t chanmask);
|
||||
template void adpcm_a_engine::output<2>(ymfm_output<2> &output, uint32_t chanmask);
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// write - handle writes to the ADPCM-A registers
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_a_engine::write(uint32_t regnum, uint8_t data)
|
||||
{
|
||||
// store the raw value to the register array;
|
||||
// most writes are passive, consumed only when needed
|
||||
m_regs.write(regnum, data);
|
||||
|
||||
// actively handle writes to the control register
|
||||
if (regnum == 0x00)
|
||||
for (int chnum = 0; chnum < CHANNELS; chnum++)
|
||||
if (bitfield(data, chnum))
|
||||
m_channel[chnum]->keyonoff(bitfield(~data, 7));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// ADPCM "B" REGISTERS
|
||||
//*********************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// reset - reset the register state
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_b_registers::reset()
|
||||
{
|
||||
std::fill_n(&m_regdata[0], REGISTERS, 0);
|
||||
|
||||
// default limit to wide open
|
||||
m_regdata[0x0c] = m_regdata[0x0d] = 0xff;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// save_restore - save or restore the data
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_b_registers::save_restore(ymfm_saved_state &state)
|
||||
{
|
||||
state.save_restore(m_regdata);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// ADPCM "B" CHANNEL
|
||||
//*********************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// adpcm_b_channel - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
adpcm_b_channel::adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift) :
|
||||
m_address_shift(addrshift),
|
||||
m_status(STATUS_BRDY),
|
||||
m_curnibble(0),
|
||||
m_curbyte(0),
|
||||
m_dummy_read(0),
|
||||
m_position(0),
|
||||
m_curaddress(0),
|
||||
m_accumulator(0),
|
||||
m_prev_accum(0),
|
||||
m_adpcm_step(STEP_MIN),
|
||||
m_regs(owner.regs()),
|
||||
m_owner(owner)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// reset - reset the channel state
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_b_channel::reset()
|
||||
{
|
||||
m_status = STATUS_BRDY;
|
||||
m_curnibble = 0;
|
||||
m_curbyte = 0;
|
||||
m_dummy_read = 0;
|
||||
m_position = 0;
|
||||
m_curaddress = 0;
|
||||
m_accumulator = 0;
|
||||
m_prev_accum = 0;
|
||||
m_adpcm_step = STEP_MIN;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// save_restore - save or restore the data
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_b_channel::save_restore(ymfm_saved_state &state)
|
||||
{
|
||||
state.save_restore(m_status);
|
||||
state.save_restore(m_curnibble);
|
||||
state.save_restore(m_curbyte);
|
||||
state.save_restore(m_dummy_read);
|
||||
state.save_restore(m_position);
|
||||
state.save_restore(m_curaddress);
|
||||
state.save_restore(m_accumulator);
|
||||
state.save_restore(m_prev_accum);
|
||||
state.save_restore(m_adpcm_step);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// clock - master clocking function
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_b_channel::clock()
|
||||
{
|
||||
// only process if active and not recording (which we don't support)
|
||||
if (!m_regs.execute() || m_regs.record() || (m_status & STATUS_PLAYING) == 0)
|
||||
{
|
||||
m_status &= ~STATUS_PLAYING;
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, advance the step
|
||||
uint32_t position = m_position + m_regs.delta_n();
|
||||
m_position = uint16_t(position);
|
||||
if (position < 0x10000)
|
||||
return;
|
||||
|
||||
// if we're about to process nibble 0, fetch sample
|
||||
if (m_curnibble == 0)
|
||||
{
|
||||
// playing from RAM/ROM
|
||||
if (m_regs.external())
|
||||
m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress);
|
||||
}
|
||||
|
||||
// extract the nibble from our current byte
|
||||
uint8_t data = uint8_t(m_curbyte << (4 * m_curnibble)) >> 4;
|
||||
m_curnibble ^= 1;
|
||||
|
||||
// we just processed the last nibble
|
||||
if (m_curnibble == 0)
|
||||
{
|
||||
// if playing from RAM/ROM, check the end/limit address or advance
|
||||
if (m_regs.external())
|
||||
{
|
||||
// handle the sample end, either repeating or stopping
|
||||
if (at_end())
|
||||
{
|
||||
// if repeating, go back to the start
|
||||
if (m_regs.repeat())
|
||||
load_start();
|
||||
|
||||
// otherwise, done; set the EOS bit
|
||||
else
|
||||
{
|
||||
m_accumulator = 0;
|
||||
m_prev_accum = 0;
|
||||
m_status = (m_status & ~STATUS_PLAYING) | STATUS_EOS;
|
||||
debug::log_keyon("%s\n", "ADPCM EOS");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// wrap at the limit address
|
||||
else if (at_limit())
|
||||
m_curaddress = 0;
|
||||
|
||||
// otherwise, advance the current address
|
||||
else
|
||||
{
|
||||
m_curaddress++;
|
||||
m_curaddress &= 0xffffff;
|
||||
}
|
||||
}
|
||||
|
||||
// if CPU-driven, copy the next byte and request more
|
||||
else
|
||||
{
|
||||
m_curbyte = m_regs.cpudata();
|
||||
m_status |= STATUS_BRDY;
|
||||
}
|
||||
}
|
||||
|
||||
// remember previous value for interpolation
|
||||
m_prev_accum = m_accumulator;
|
||||
|
||||
// forecast to next forecast: 1/8, 3/8, 5/8, 7/8, 9/8, 11/8, 13/8, 15/8
|
||||
int32_t delta = (2 * bitfield(data, 0, 3) + 1) * m_adpcm_step / 8;
|
||||
if (bitfield(data, 3))
|
||||
delta = -delta;
|
||||
|
||||
// add and clamp to 16 bits
|
||||
m_accumulator = clamp(m_accumulator + delta, -32768, 32767);
|
||||
|
||||
// scale the ADPCM step: 0.9, 0.9, 0.9, 0.9, 1.2, 1.6, 2.0, 2.4
|
||||
static uint8_t const s_step_scale[8] = { 57, 57, 57, 57, 77, 102, 128, 153 };
|
||||
m_adpcm_step = clamp((m_adpcm_step * s_step_scale[bitfield(data, 0, 3)]) / 64, STEP_MIN, STEP_MAX);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// output - return the computed output value, with
|
||||
// panning applied
|
||||
//-------------------------------------------------
|
||||
|
||||
template<int NumOutputs>
|
||||
void adpcm_b_channel::output(ymfm_output<NumOutputs> &output, uint32_t rshift) const
|
||||
{
|
||||
// mask out some channels for debug purposes
|
||||
if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) == 0)
|
||||
return;
|
||||
|
||||
// do a linear interpolation between samples
|
||||
int32_t result = (m_prev_accum * int32_t((m_position ^ 0xffff) + 1) + m_accumulator * int32_t(m_position)) >> 16;
|
||||
|
||||
// apply volume (level) in a linear fashion and reduce
|
||||
result = (result * int32_t(m_regs.level())) >> (8 + rshift);
|
||||
|
||||
// apply to left/right
|
||||
if (NumOutputs == 1 || m_regs.pan_left())
|
||||
output.data[0] += result;
|
||||
if (NumOutputs > 1 && m_regs.pan_right())
|
||||
output.data[1] += result;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// read - handle special register reads
|
||||
//-------------------------------------------------
|
||||
|
||||
uint8_t adpcm_b_channel::read(uint32_t regnum)
|
||||
{
|
||||
uint8_t result = 0;
|
||||
|
||||
// register 8 reads over the bus under some conditions
|
||||
if (regnum == 0x08 && !m_regs.execute() && !m_regs.record() && m_regs.external())
|
||||
{
|
||||
// two dummy reads are consumed first
|
||||
if (m_dummy_read != 0)
|
||||
{
|
||||
load_start();
|
||||
m_dummy_read--;
|
||||
}
|
||||
|
||||
// read the data
|
||||
else
|
||||
{
|
||||
// read from outside of the chip
|
||||
result = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress++);
|
||||
|
||||
// did we hit the end? if so, signal EOS
|
||||
if (at_end())
|
||||
{
|
||||
m_status = STATUS_EOS | STATUS_BRDY;
|
||||
debug::log_keyon("%s\n", "ADPCM EOS");
|
||||
}
|
||||
else
|
||||
{
|
||||
// signal ready
|
||||
m_status = STATUS_BRDY;
|
||||
}
|
||||
|
||||
// wrap at the limit address
|
||||
if (at_limit())
|
||||
m_curaddress = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// write - handle special register writes
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_b_channel::write(uint32_t regnum, uint8_t value)
|
||||
{
|
||||
// register 0 can do a reset; also use writes here to reset the
|
||||
// dummy read counter
|
||||
if (regnum == 0x00)
|
||||
{
|
||||
if (m_regs.execute())
|
||||
{
|
||||
load_start();
|
||||
|
||||
// don't log masked channels
|
||||
if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) != 0)
|
||||
debug::log_keyon("KeyOn ADPCM-B: rep=%d spk=%d pan=%d%d dac=%d 8b=%d rom=%d ext=%d rec=%d start=%04X end=%04X pre=%04X dn=%04X lvl=%02X lim=%04X\n",
|
||||
m_regs.repeat(),
|
||||
m_regs.speaker(),
|
||||
m_regs.pan_left(),
|
||||
m_regs.pan_right(),
|
||||
m_regs.dac_enable(),
|
||||
m_regs.dram_8bit(),
|
||||
m_regs.rom_ram(),
|
||||
m_regs.external(),
|
||||
m_regs.record(),
|
||||
m_regs.start(),
|
||||
m_regs.end(),
|
||||
m_regs.prescale(),
|
||||
m_regs.delta_n(),
|
||||
m_regs.level(),
|
||||
m_regs.limit());
|
||||
}
|
||||
else
|
||||
m_status &= ~STATUS_EOS;
|
||||
if (m_regs.resetflag())
|
||||
reset();
|
||||
if (m_regs.external())
|
||||
m_dummy_read = 2;
|
||||
}
|
||||
|
||||
// register 8 writes over the bus under some conditions
|
||||
else if (regnum == 0x08)
|
||||
{
|
||||
// if writing from the CPU during execute, clear the ready flag
|
||||
if (m_regs.execute() && !m_regs.record() && !m_regs.external())
|
||||
m_status &= ~STATUS_BRDY;
|
||||
|
||||
// if writing during "record", pass through as data
|
||||
else if (!m_regs.execute() && m_regs.record() && m_regs.external())
|
||||
{
|
||||
// clear out dummy reads and set start address
|
||||
if (m_dummy_read != 0)
|
||||
{
|
||||
load_start();
|
||||
m_dummy_read = 0;
|
||||
}
|
||||
|
||||
// did we hit the end? if so, signal EOS
|
||||
if (at_end())
|
||||
{
|
||||
debug::log_keyon("%s\n", "ADPCM EOS");
|
||||
m_status = STATUS_EOS | STATUS_BRDY;
|
||||
}
|
||||
|
||||
// otherwise, write the data and signal ready
|
||||
else
|
||||
{
|
||||
m_owner.intf().ymfm_external_write(ACCESS_ADPCM_B, m_curaddress++, value);
|
||||
m_status = STATUS_BRDY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// address_shift - compute the current address
|
||||
// shift amount based on register settings
|
||||
//-------------------------------------------------
|
||||
|
||||
uint32_t adpcm_b_channel::address_shift() const
|
||||
{
|
||||
// if a constant address shift, just provide that
|
||||
if (m_address_shift != 0)
|
||||
return m_address_shift;
|
||||
|
||||
// if ROM or 8-bit DRAM, shift is 5 bits
|
||||
if (m_regs.rom_ram())
|
||||
return 5;
|
||||
if (m_regs.dram_8bit())
|
||||
return 5;
|
||||
|
||||
// otherwise, shift is 2 bits
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// load_start - load the start address and
|
||||
// initialize the state
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_b_channel::load_start()
|
||||
{
|
||||
m_status = (m_status & ~STATUS_EOS) | STATUS_PLAYING;
|
||||
m_curaddress = m_regs.external() ? (m_regs.start() << address_shift()) : 0;
|
||||
m_curnibble = 0;
|
||||
m_curbyte = 0;
|
||||
m_position = 0;
|
||||
m_accumulator = 0;
|
||||
m_prev_accum = 0;
|
||||
m_adpcm_step = STEP_MIN;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// ADPCM "B" ENGINE
|
||||
//*********************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// adpcm_b_engine - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
adpcm_b_engine::adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift) :
|
||||
m_intf(intf)
|
||||
{
|
||||
// create the channel (only one supported for now, but leaving possibilities open)
|
||||
m_channel = std::make_unique<adpcm_b_channel>(*this, addrshift);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// reset - reset the engine state
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_b_engine::reset()
|
||||
{
|
||||
// reset registers
|
||||
m_regs.reset();
|
||||
|
||||
// reset each channel
|
||||
m_channel->reset();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// save_restore - save or restore the data
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_b_engine::save_restore(ymfm_saved_state &state)
|
||||
{
|
||||
// save our state
|
||||
m_regs.save_restore(state);
|
||||
|
||||
// save channel state
|
||||
m_channel->save_restore(state);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// clock - master clocking function
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_b_engine::clock()
|
||||
{
|
||||
// clock each channel, setting a bit in result if it finished
|
||||
m_channel->clock();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// output - master output function
|
||||
//-------------------------------------------------
|
||||
|
||||
template<int NumOutputs>
|
||||
void adpcm_b_engine::output(ymfm_output<NumOutputs> &output, uint32_t rshift)
|
||||
{
|
||||
// compute the output of each channel
|
||||
m_channel->output(output, rshift);
|
||||
}
|
||||
|
||||
template void adpcm_b_engine::output<1>(ymfm_output<1> &output, uint32_t rshift);
|
||||
template void adpcm_b_engine::output<2>(ymfm_output<2> &output, uint32_t rshift);
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// write - handle writes to the ADPCM-B registers
|
||||
//-------------------------------------------------
|
||||
|
||||
void adpcm_b_engine::write(uint32_t regnum, uint8_t data)
|
||||
{
|
||||
// store the raw value to the register array;
|
||||
// most writes are passive, consumed only when needed
|
||||
m_regs.write(regnum, data);
|
||||
|
||||
// let the channel handle any special writes
|
||||
m_channel->write(regnum, data);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,411 +0,0 @@
|
|||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2021, Aaron Giles
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef YMFM_ADPCM_H
|
||||
#define YMFM_ADPCM_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ymfm.h"
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
||||
//*********************************************************
|
||||
// INTERFACE CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// forward declarations
|
||||
class adpcm_a_engine;
|
||||
class adpcm_b_engine;
|
||||
|
||||
|
||||
// ======================> adpcm_a_registers
|
||||
|
||||
//
|
||||
// ADPCM-A register map:
|
||||
//
|
||||
// System-wide registers:
|
||||
// 00 x------- Dump (disable=1) or keyon (0) control
|
||||
// --xxxxxx Mask of channels to dump or keyon
|
||||
// 01 --xxxxxx Total level
|
||||
// 02 xxxxxxxx Test register
|
||||
// 08-0D x------- Pan left
|
||||
// -x------ Pan right
|
||||
// ---xxxxx Instrument level
|
||||
// 10-15 xxxxxxxx Start address (low)
|
||||
// 18-1D xxxxxxxx Start address (high)
|
||||
// 20-25 xxxxxxxx End address (low)
|
||||
// 28-2D xxxxxxxx End address (high)
|
||||
//
|
||||
class adpcm_a_registers
|
||||
{
|
||||
public:
|
||||
// constants
|
||||
static constexpr uint32_t OUTPUTS = 2;
|
||||
static constexpr uint32_t CHANNELS = 6;
|
||||
static constexpr uint32_t REGISTERS = 0x30;
|
||||
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
|
||||
|
||||
// constructor
|
||||
adpcm_a_registers() { }
|
||||
|
||||
// reset to initial state
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// map channel number to register offset
|
||||
static constexpr uint32_t channel_offset(uint32_t chnum)
|
||||
{
|
||||
assert(chnum < CHANNELS);
|
||||
return chnum;
|
||||
}
|
||||
|
||||
// direct read/write access
|
||||
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
|
||||
|
||||
// system-wide registers
|
||||
uint32_t dump() const { return bitfield(m_regdata[0x00], 7); }
|
||||
uint32_t dump_mask() const { return bitfield(m_regdata[0x00], 0, 6); }
|
||||
uint32_t total_level() const { return bitfield(m_regdata[0x01], 0, 6); }
|
||||
uint32_t test() const { return m_regdata[0x02]; }
|
||||
|
||||
// per-channel registers
|
||||
uint32_t ch_pan_left(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 7); }
|
||||
uint32_t ch_pan_right(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 6); }
|
||||
uint32_t ch_instrument_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 0, 5); }
|
||||
uint32_t ch_start(uint32_t choffs) const { return m_regdata[choffs + 0x10] | (m_regdata[choffs + 0x18] << 8); }
|
||||
uint32_t ch_end(uint32_t choffs) const { return m_regdata[choffs + 0x20] | (m_regdata[choffs + 0x28] << 8); }
|
||||
|
||||
// per-channel writes
|
||||
void write_start(uint32_t choffs, uint32_t address)
|
||||
{
|
||||
write(choffs + 0x10, address);
|
||||
write(choffs + 0x18, address >> 8);
|
||||
}
|
||||
void write_end(uint32_t choffs, uint32_t address)
|
||||
{
|
||||
write(choffs + 0x20, address);
|
||||
write(choffs + 0x28, address >> 8);
|
||||
}
|
||||
|
||||
private:
|
||||
// internal state
|
||||
uint8_t m_regdata[REGISTERS]; // register data
|
||||
};
|
||||
|
||||
|
||||
// ======================> adpcm_a_channel
|
||||
|
||||
class adpcm_a_channel
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift);
|
||||
|
||||
// reset the channel state
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// signal key on/off
|
||||
void keyonoff(bool on);
|
||||
|
||||
// master clockingfunction
|
||||
bool clock();
|
||||
|
||||
// return the computed output value, with panning applied
|
||||
template<int NumOutputs>
|
||||
void output(ymfm_output<NumOutputs> &output) const;
|
||||
|
||||
private:
|
||||
// internal state
|
||||
uint32_t const m_choffs; // channel offset
|
||||
uint32_t const m_address_shift; // address bits shift-left
|
||||
uint32_t m_playing; // currently playing?
|
||||
uint32_t m_curnibble; // index of the current nibble
|
||||
uint32_t m_curbyte; // current byte of data
|
||||
uint32_t m_curaddress; // current address
|
||||
int32_t m_accumulator; // accumulator
|
||||
int32_t m_step_index; // index in the stepping table
|
||||
adpcm_a_registers &m_regs; // reference to registers
|
||||
adpcm_a_engine &m_owner; // reference to our owner
|
||||
};
|
||||
|
||||
|
||||
// ======================> adpcm_a_engine
|
||||
|
||||
class adpcm_a_engine
|
||||
{
|
||||
public:
|
||||
static constexpr int CHANNELS = adpcm_a_registers::CHANNELS;
|
||||
|
||||
// constructor
|
||||
adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift);
|
||||
|
||||
// reset our status
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// master clocking function
|
||||
uint32_t clock(uint32_t chanmask);
|
||||
|
||||
// compute sum of channel outputs
|
||||
template<int NumOutputs>
|
||||
void output(ymfm_output<NumOutputs> &output, uint32_t chanmask);
|
||||
|
||||
// write to the ADPCM-A registers
|
||||
void write(uint32_t regnum, uint8_t data);
|
||||
|
||||
// set the start/end address for a channel (for hardcoded YM2608 percussion)
|
||||
void set_start_end(uint8_t chnum, uint16_t start, uint16_t end)
|
||||
{
|
||||
uint32_t choffs = adpcm_a_registers::channel_offset(chnum);
|
||||
m_regs.write_start(choffs, start);
|
||||
m_regs.write_end(choffs, end);
|
||||
}
|
||||
|
||||
// return a reference to our interface
|
||||
ymfm_interface &intf() { return m_intf; }
|
||||
|
||||
// return a reference to our registers
|
||||
adpcm_a_registers ®s() { return m_regs; }
|
||||
|
||||
private:
|
||||
// internal state
|
||||
ymfm_interface &m_intf; // reference to the interface
|
||||
std::unique_ptr<adpcm_a_channel> m_channel[CHANNELS]; // array of channels
|
||||
adpcm_a_registers m_regs; // registers
|
||||
};
|
||||
|
||||
|
||||
// ======================> adpcm_b_registers
|
||||
|
||||
//
|
||||
// ADPCM-B register map:
|
||||
//
|
||||
// System-wide registers:
|
||||
// 00 x------- Start of synthesis/analysis
|
||||
// -x------ Record
|
||||
// --x----- External/manual driving
|
||||
// ---x---- Repeat playback
|
||||
// ----x--- Speaker off
|
||||
// -------x Reset
|
||||
// 01 x------- Pan left
|
||||
// -x------ Pan right
|
||||
// ----x--- Start conversion
|
||||
// -----x-- DAC enable
|
||||
// ------x- DRAM access (1=8-bit granularity; 0=1-bit)
|
||||
// -------x RAM/ROM (1=ROM, 0=RAM)
|
||||
// 02 xxxxxxxx Start address (low)
|
||||
// 03 xxxxxxxx Start address (high)
|
||||
// 04 xxxxxxxx End address (low)
|
||||
// 05 xxxxxxxx End address (high)
|
||||
// 06 xxxxxxxx Prescale value (low)
|
||||
// 07 -----xxx Prescale value (high)
|
||||
// 08 xxxxxxxx CPU data/buffer
|
||||
// 09 xxxxxxxx Delta-N frequency scale (low)
|
||||
// 0a xxxxxxxx Delta-N frequency scale (high)
|
||||
// 0b xxxxxxxx Level control
|
||||
// 0c xxxxxxxx Limit address (low)
|
||||
// 0d xxxxxxxx Limit address (high)
|
||||
// 0e xxxxxxxx DAC data [YM2608/10]
|
||||
// 0f xxxxxxxx PCM data [YM2608/10]
|
||||
// 0e xxxxxxxx DAC data high [Y8950]
|
||||
// 0f xx------ DAC data low [Y8950]
|
||||
// 10 -----xxx DAC data exponent [Y8950]
|
||||
//
|
||||
class adpcm_b_registers
|
||||
{
|
||||
public:
|
||||
// constants
|
||||
static constexpr uint32_t REGISTERS = 0x11;
|
||||
|
||||
// constructor
|
||||
adpcm_b_registers() { }
|
||||
|
||||
// reset to initial state
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// direct read/write access
|
||||
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
|
||||
|
||||
// system-wide registers
|
||||
uint32_t execute() const { return bitfield(m_regdata[0x00], 7); }
|
||||
uint32_t record() const { return bitfield(m_regdata[0x00], 6); }
|
||||
uint32_t external() const { return bitfield(m_regdata[0x00], 5); }
|
||||
uint32_t repeat() const { return bitfield(m_regdata[0x00], 4); }
|
||||
uint32_t speaker() const { return bitfield(m_regdata[0x00], 3); }
|
||||
uint32_t resetflag() const { return bitfield(m_regdata[0x00], 0); }
|
||||
uint32_t pan_left() const { return bitfield(m_regdata[0x01], 7); }
|
||||
uint32_t pan_right() const { return bitfield(m_regdata[0x01], 6); }
|
||||
uint32_t start_conversion() const { return bitfield(m_regdata[0x01], 3); }
|
||||
uint32_t dac_enable() const { return bitfield(m_regdata[0x01], 2); }
|
||||
uint32_t dram_8bit() const { return bitfield(m_regdata[0x01], 1); }
|
||||
uint32_t rom_ram() const { return bitfield(m_regdata[0x01], 0); }
|
||||
uint32_t start() const { return m_regdata[0x02] | (m_regdata[0x03] << 8); }
|
||||
uint32_t end() const { return m_regdata[0x04] | (m_regdata[0x05] << 8); }
|
||||
uint32_t prescale() const { return m_regdata[0x06] | (bitfield(m_regdata[0x07], 0, 3) << 8); }
|
||||
uint32_t cpudata() const { return m_regdata[0x08]; }
|
||||
uint32_t delta_n() const { return m_regdata[0x09] | (m_regdata[0x0a] << 8); }
|
||||
uint32_t level() const { return m_regdata[0x0b]; }
|
||||
uint32_t limit() const { return m_regdata[0x0c] | (m_regdata[0x0d] << 8); }
|
||||
uint32_t dac() const { return m_regdata[0x0e]; }
|
||||
uint32_t pcm() const { return m_regdata[0x0f]; }
|
||||
|
||||
private:
|
||||
// internal state
|
||||
uint8_t m_regdata[REGISTERS]; // register data
|
||||
};
|
||||
|
||||
|
||||
// ======================> adpcm_b_channel
|
||||
|
||||
class adpcm_b_channel
|
||||
{
|
||||
static constexpr int32_t STEP_MIN = 127;
|
||||
static constexpr int32_t STEP_MAX = 24576;
|
||||
|
||||
public:
|
||||
static constexpr uint8_t STATUS_EOS = 0x01;
|
||||
static constexpr uint8_t STATUS_BRDY = 0x02;
|
||||
static constexpr uint8_t STATUS_PLAYING = 0x04;
|
||||
|
||||
// constructor
|
||||
adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift);
|
||||
|
||||
// reset the channel state
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// signal key on/off
|
||||
void keyonoff(bool on);
|
||||
|
||||
// master clocking function
|
||||
void clock();
|
||||
|
||||
// return the computed output value, with panning applied
|
||||
template<int NumOutputs>
|
||||
void output(ymfm_output<NumOutputs> &output, uint32_t rshift) const;
|
||||
|
||||
// return the status register
|
||||
uint8_t status() const { return m_status; }
|
||||
|
||||
// handle special register reads
|
||||
uint8_t read(uint32_t regnum);
|
||||
|
||||
// handle special register writes
|
||||
void write(uint32_t regnum, uint8_t value);
|
||||
|
||||
private:
|
||||
// helper - return the current address shift
|
||||
uint32_t address_shift() const;
|
||||
|
||||
// load the start address
|
||||
void load_start();
|
||||
|
||||
// limit checker; stops at the last byte of the chunk described by address_shift()
|
||||
bool at_limit() const { return (m_curaddress == (((m_regs.limit() + 1) << address_shift()) - 1)); }
|
||||
|
||||
// end checker; stops at the last byte of the chunk described by address_shift()
|
||||
bool at_end() const { return (m_curaddress == (((m_regs.end() + 1) << address_shift()) - 1)); }
|
||||
|
||||
// internal state
|
||||
uint32_t const m_address_shift; // address bits shift-left
|
||||
uint32_t m_status; // currently playing?
|
||||
uint32_t m_curnibble; // index of the current nibble
|
||||
uint32_t m_curbyte; // current byte of data
|
||||
uint32_t m_dummy_read; // dummy read tracker
|
||||
uint32_t m_position; // current fractional position
|
||||
uint32_t m_curaddress; // current address
|
||||
int32_t m_accumulator; // accumulator
|
||||
int32_t m_prev_accum; // previous accumulator (for linear interp)
|
||||
int32_t m_adpcm_step; // next forecast
|
||||
adpcm_b_registers &m_regs; // reference to registers
|
||||
adpcm_b_engine &m_owner; // reference to our owner
|
||||
};
|
||||
|
||||
|
||||
// ======================> adpcm_b_engine
|
||||
|
||||
class adpcm_b_engine
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift = 0);
|
||||
|
||||
// reset our status
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// master clocking function
|
||||
void clock();
|
||||
|
||||
// compute sum of channel outputs
|
||||
template<int NumOutputs>
|
||||
void output(ymfm_output<NumOutputs> &output, uint32_t rshift);
|
||||
|
||||
// read from the ADPCM-B registers
|
||||
uint32_t read(uint32_t regnum) { return m_channel->read(regnum); }
|
||||
|
||||
// write to the ADPCM-B registers
|
||||
void write(uint32_t regnum, uint8_t data);
|
||||
|
||||
// status
|
||||
uint8_t status() const { return m_channel->status(); }
|
||||
|
||||
// return a reference to our interface
|
||||
ymfm_interface &intf() { return m_intf; }
|
||||
|
||||
// return a reference to our registers
|
||||
adpcm_b_registers ®s() { return m_regs; }
|
||||
|
||||
private:
|
||||
// internal state
|
||||
ymfm_interface &m_intf; // reference to our interface
|
||||
std::unique_ptr<adpcm_b_channel> m_channel; // channel pointer
|
||||
adpcm_b_registers m_regs; // registers
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // YMFM_ADPCM_H
|
|
@ -1,463 +0,0 @@
|
|||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2021, Aaron Giles
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef YMFM_FM_H
|
||||
#define YMFM_FM_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#define YMFM_DEBUG_LOG_WAVFILES (0)
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
||||
//*********************************************************
|
||||
// GLOBAL ENUMERATORS
|
||||
//*********************************************************
|
||||
|
||||
// three different keyon sources; actual keyon is an OR over all of these
|
||||
enum keyon_type : uint32_t
|
||||
{
|
||||
KEYON_NORMAL = 0,
|
||||
KEYON_RHYTHM = 1,
|
||||
KEYON_CSM = 2
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// CORE IMPLEMENTATION
|
||||
//*********************************************************
|
||||
|
||||
// ======================> opdata_cache
|
||||
|
||||
// this class holds data that is computed once at the start of clocking
|
||||
// and remains static during subsequent sound generation
|
||||
struct opdata_cache
|
||||
{
|
||||
// set phase_step to this value to recalculate it each sample; needed
|
||||
// in the case of PM LFO changes
|
||||
static constexpr uint32_t PHASE_STEP_DYNAMIC = 1;
|
||||
|
||||
uint16_t const *waveform; // base of sine table
|
||||
uint32_t phase_step; // phase step, or PHASE_STEP_DYNAMIC if PM is active
|
||||
uint32_t total_level; // total level * 8 + KSL
|
||||
uint32_t block_freq; // raw block frequency value (used to compute phase_step)
|
||||
int32_t detune; // detuning value (used to compute phase_step)
|
||||
uint32_t multiple; // multiple value (x.1, used to compute phase_step)
|
||||
uint32_t eg_sustain; // sustain level, shifted up to envelope values
|
||||
uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR
|
||||
uint8_t eg_shift = 0; // envelope shift amount
|
||||
};
|
||||
|
||||
|
||||
// ======================> fm_registers_base
|
||||
|
||||
// base class for family-specific register classes; this provides a few
|
||||
// constants, common defaults, and helpers, but mostly each derived class is
|
||||
// responsible for defining all commonly-called methods
|
||||
class fm_registers_base
|
||||
{
|
||||
public:
|
||||
// this value is returned from the write() function for rhythm channels
|
||||
static constexpr uint32_t RHYTHM_CHANNEL = 0xff;
|
||||
|
||||
// this is the size of a full sin waveform
|
||||
static constexpr uint32_t WAVEFORM_LENGTH = 0x400;
|
||||
|
||||
//
|
||||
// the following constants need to be defined per family:
|
||||
// uint32_t OUTPUTS: The number of outputs exposed (1-4)
|
||||
// uint32_t CHANNELS: The number of channels on the chip
|
||||
// uint32_t ALL_CHANNELS: A bitmask of all channels
|
||||
// uint32_t OPERATORS: The number of operators on the chip
|
||||
// uint32_t WAVEFORMS: The number of waveforms offered
|
||||
// uint32_t REGISTERS: The number of 8-bit registers allocated
|
||||
// uint32_t DEFAULT_PRESCALE: The starting clock prescale
|
||||
// uint32_t EG_CLOCK_DIVIDER: The clock divider of the envelope generator
|
||||
// uint32_t CSM_TRIGGER_MASK: Mask of channels to trigger in CSM mode
|
||||
// uint32_t REG_MODE: The address of the "mode" register controlling timers
|
||||
// uint8_t STATUS_TIMERA: Status bit to set when timer A fires
|
||||
// uint8_t STATUS_TIMERB: Status bit to set when tiemr B fires
|
||||
// uint8_t STATUS_BUSY: Status bit to set when the chip is busy
|
||||
// uint8_t STATUS_IRQ: Status bit to set when an IRQ is signalled
|
||||
//
|
||||
// the following constants are uncommon:
|
||||
// bool DYNAMIC_OPS: True if ops/channel can be changed at runtime (OPL3+)
|
||||
// bool EG_HAS_DEPRESS: True if the chip has a DP ("depress"?) envelope stage (OPLL)
|
||||
// bool EG_HAS_REVERB: True if the chip has a faux reverb envelope stage (OPQ/OPZ)
|
||||
// bool EG_HAS_SSG: True if the chip has SSG envelope support (OPN)
|
||||
// bool MODULATOR_DELAY: True if the modulator is delayed by 1 sample (OPL pre-OPL3)
|
||||
//
|
||||
static constexpr bool DYNAMIC_OPS = false;
|
||||
static constexpr bool EG_HAS_DEPRESS = false;
|
||||
static constexpr bool EG_HAS_REVERB = false;
|
||||
static constexpr bool EG_HAS_SSG = false;
|
||||
static constexpr bool MODULATOR_DELAY = false;
|
||||
|
||||
// system-wide register defaults
|
||||
uint32_t status_mask() const { return 0; } // OPL only
|
||||
uint32_t irq_reset() const { return 0; } // OPL only
|
||||
uint32_t noise_enable() const { return 0; } // OPM only
|
||||
uint32_t rhythm_enable() const { return 0; } // OPL only
|
||||
|
||||
// per-operator register defaults
|
||||
uint32_t op_ssg_eg_enable(uint32_t opoffs) const { return 0; } // OPN(A) only
|
||||
uint32_t op_ssg_eg_mode(uint32_t opoffs) const { return 0; } // OPN(A) only
|
||||
|
||||
protected:
|
||||
// helper to encode four operator numbers into a 32-bit value in the
|
||||
// operator maps for each register class
|
||||
static constexpr uint32_t operator_list(uint8_t o1 = 0xff, uint8_t o2 = 0xff, uint8_t o3 = 0xff, uint8_t o4 = 0xff)
|
||||
{
|
||||
return o1 | (o2 << 8) | (o3 << 16) | (o4 << 24);
|
||||
}
|
||||
|
||||
// helper to apply KSR to the raw ADSR rate, ignoring ksr if the
|
||||
// raw value is 0, and clamping to 63
|
||||
static constexpr uint32_t effective_rate(uint32_t rawrate, uint32_t ksr)
|
||||
{
|
||||
return (rawrate == 0) ? 0 : std::min<uint32_t>(rawrate + ksr, 63);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// CORE ENGINE CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// forward declarations
|
||||
template<class RegisterType> class fm_engine_base;
|
||||
|
||||
// ======================> fm_operator
|
||||
|
||||
// fm_operator represents an FM operator (or "slot" in FM parlance), which
|
||||
// produces an output sine wave modulated by an envelope
|
||||
template<class RegisterType>
|
||||
class fm_operator
|
||||
{
|
||||
// "quiet" value, used to optimize when we can skip doing work
|
||||
static constexpr uint32_t EG_QUIET = 0x380;
|
||||
|
||||
public:
|
||||
// constructor
|
||||
fm_operator(fm_engine_base<RegisterType> &owner, uint32_t opoffs);
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// reset the operator state
|
||||
void reset();
|
||||
|
||||
// return the operator/channel offset
|
||||
uint32_t opoffs() const { return m_opoffs; }
|
||||
uint32_t choffs() const { return m_choffs; }
|
||||
|
||||
// set the current channel
|
||||
void set_choffs(uint32_t choffs) { m_choffs = choffs; }
|
||||
|
||||
// prepare prior to clocking
|
||||
bool prepare();
|
||||
|
||||
// master clocking function
|
||||
void clock(uint32_t env_counter, int32_t lfo_raw_pm);
|
||||
|
||||
// return the current phase value
|
||||
uint32_t phase() const { return m_phase >> 10; }
|
||||
|
||||
// compute operator volume
|
||||
int32_t compute_volume(uint32_t phase, uint32_t am_offset) const;
|
||||
|
||||
// compute volume for the OPM noise channel
|
||||
int32_t compute_noise_volume(uint32_t am_offset) const;
|
||||
|
||||
// key state control
|
||||
void keyonoff(uint32_t on, keyon_type type);
|
||||
|
||||
// return a reference to our registers
|
||||
RegisterType ®s() const { return m_regs; }
|
||||
|
||||
// simple getters for debugging
|
||||
envelope_state debug_eg_state() const { return m_env_state; }
|
||||
uint16_t debug_eg_attenuation() const { return m_env_attenuation; }
|
||||
uint8_t debug_ssg_inverted() const { return m_ssg_inverted; }
|
||||
opdata_cache &debug_cache() { return m_cache; }
|
||||
|
||||
private:
|
||||
// start the attack phase
|
||||
void start_attack(bool is_restart = false);
|
||||
|
||||
// start the release phase
|
||||
void start_release();
|
||||
|
||||
// clock phases
|
||||
void clock_keystate(uint32_t keystate);
|
||||
void clock_ssg_eg_state();
|
||||
void clock_envelope(uint32_t env_counter);
|
||||
void clock_phase(int32_t lfo_raw_pm);
|
||||
|
||||
// return effective attenuation of the envelope
|
||||
uint32_t envelope_attenuation(uint32_t am_offset) const;
|
||||
|
||||
// internal state
|
||||
uint32_t m_choffs; // channel offset in registers
|
||||
uint32_t m_opoffs; // operator offset in registers
|
||||
uint32_t m_phase; // current phase value (10.10 format)
|
||||
uint16_t m_env_attenuation; // computed envelope attenuation (4.6 format)
|
||||
envelope_state m_env_state; // current envelope state
|
||||
uint8_t m_ssg_inverted; // non-zero if the output should be inverted (bit 0)
|
||||
uint8_t m_key_state; // current key state: on or off (bit 0)
|
||||
uint8_t m_keyon_live; // live key on state (bit 0 = direct, bit 1 = rhythm, bit 2 = CSM)
|
||||
opdata_cache m_cache; // cached values for performance
|
||||
RegisterType &m_regs; // direct reference to registers
|
||||
fm_engine_base<RegisterType> &m_owner; // reference to the owning engine
|
||||
};
|
||||
|
||||
|
||||
// ======================> fm_channel
|
||||
|
||||
// fm_channel represents an FM channel which combines the output of 2 or 4
|
||||
// operators into a final result
|
||||
template<class RegisterType>
|
||||
class fm_channel
|
||||
{
|
||||
using output_data = ymfm_output<RegisterType::OUTPUTS>;
|
||||
|
||||
public:
|
||||
// constructor
|
||||
fm_channel(fm_engine_base<RegisterType> &owner, uint32_t choffs);
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// reset the channel state
|
||||
void reset();
|
||||
|
||||
// return the channel offset
|
||||
uint32_t choffs() const { return m_choffs; }
|
||||
|
||||
// assign operators
|
||||
void assign(uint32_t index, fm_operator<RegisterType> *op)
|
||||
{
|
||||
assert(index < m_op.size());
|
||||
m_op[index] = op;
|
||||
if (op != nullptr)
|
||||
op->set_choffs(m_choffs);
|
||||
}
|
||||
|
||||
// signal key on/off to our operators
|
||||
void keyonoff(uint32_t states, keyon_type type, uint32_t chnum);
|
||||
|
||||
// prepare prior to clocking
|
||||
bool prepare();
|
||||
|
||||
// master clocking function
|
||||
void clock(uint32_t env_counter, int32_t lfo_raw_pm);
|
||||
|
||||
// specific 2-operator and 4-operator output handlers
|
||||
void output_2op(output_data &output, uint32_t rshift, int32_t clipmax) const;
|
||||
void output_4op(output_data &output, uint32_t rshift, int32_t clipmax) const;
|
||||
|
||||
// compute the special OPL rhythm channel outputs
|
||||
void output_rhythm_ch6(output_data &output, uint32_t rshift, int32_t clipmax) const;
|
||||
void output_rhythm_ch7(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const;
|
||||
void output_rhythm_ch8(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const;
|
||||
|
||||
// are we a 4-operator channel or a 2-operator one?
|
||||
bool is4op() const
|
||||
{
|
||||
if (RegisterType::DYNAMIC_OPS)
|
||||
return (m_op[2] != nullptr);
|
||||
return (RegisterType::OPERATORS / RegisterType::CHANNELS == 4);
|
||||
}
|
||||
|
||||
// return a reference to our registers
|
||||
RegisterType ®s() const { return m_regs; }
|
||||
|
||||
// simple getters for debugging
|
||||
fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_op[index]; }
|
||||
|
||||
private:
|
||||
// helper to add values to the outputs based on channel enables
|
||||
void add_to_output(uint32_t choffs, output_data &output, int32_t value) const
|
||||
{
|
||||
// create these constants to appease overzealous compilers checking array
|
||||
// bounds in unreachable code (looking at you, clang)
|
||||
constexpr int out0_index = 0;
|
||||
constexpr int out1_index = 1 % RegisterType::OUTPUTS;
|
||||
constexpr int out2_index = 2 % RegisterType::OUTPUTS;
|
||||
constexpr int out3_index = 3 % RegisterType::OUTPUTS;
|
||||
|
||||
if (RegisterType::OUTPUTS == 1 || m_regs.ch_output_0(choffs))
|
||||
output.data[out0_index] += value;
|
||||
if (RegisterType::OUTPUTS >= 2 && m_regs.ch_output_1(choffs))
|
||||
output.data[out1_index] += value;
|
||||
if (RegisterType::OUTPUTS >= 3 && m_regs.ch_output_2(choffs))
|
||||
output.data[out2_index] += value;
|
||||
if (RegisterType::OUTPUTS >= 4 && m_regs.ch_output_3(choffs))
|
||||
output.data[out3_index] += value;
|
||||
}
|
||||
|
||||
// internal state
|
||||
uint32_t m_choffs; // channel offset in registers
|
||||
int16_t m_feedback[2]; // feedback memory for operator 1
|
||||
mutable int16_t m_feedback_in; // next input value for op 1 feedback (set in output)
|
||||
std::array<fm_operator<RegisterType> *, 4> m_op; // up to 4 operators
|
||||
RegisterType &m_regs; // direct reference to registers
|
||||
fm_engine_base<RegisterType> &m_owner; // reference to the owning engine
|
||||
};
|
||||
|
||||
|
||||
// ======================> fm_engine_base
|
||||
|
||||
// fm_engine_base represents a set of operators and channels which together
|
||||
// form a Yamaha FM core; chips that implement other engines (ADPCM, wavetable,
|
||||
// etc) take this output and combine it with the others externally
|
||||
template<class RegisterType>
|
||||
class fm_engine_base : public ymfm_engine_callbacks
|
||||
{
|
||||
public:
|
||||
// expose some constants from the registers
|
||||
static constexpr uint32_t OUTPUTS = RegisterType::OUTPUTS;
|
||||
static constexpr uint32_t CHANNELS = RegisterType::CHANNELS;
|
||||
static constexpr uint32_t ALL_CHANNELS = RegisterType::ALL_CHANNELS;
|
||||
static constexpr uint32_t OPERATORS = RegisterType::OPERATORS;
|
||||
|
||||
// also expose status flags for consumers that inject additional bits
|
||||
static constexpr uint8_t STATUS_TIMERA = RegisterType::STATUS_TIMERA;
|
||||
static constexpr uint8_t STATUS_TIMERB = RegisterType::STATUS_TIMERB;
|
||||
static constexpr uint8_t STATUS_BUSY = RegisterType::STATUS_BUSY;
|
||||
static constexpr uint8_t STATUS_IRQ = RegisterType::STATUS_IRQ;
|
||||
|
||||
// expose the correct output class
|
||||
using output_data = ymfm_output<OUTPUTS>;
|
||||
|
||||
// constructor
|
||||
fm_engine_base(ymfm_interface &intf);
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// reset the overall state
|
||||
void reset();
|
||||
|
||||
// master clocking function
|
||||
uint32_t clock(uint32_t chanmask);
|
||||
|
||||
// compute sum of channel outputs
|
||||
void output(output_data &output, uint32_t rshift, int32_t clipmax, uint32_t chanmask) const;
|
||||
|
||||
// write to the OPN registers
|
||||
void write(uint16_t regnum, uint8_t data);
|
||||
|
||||
// return the current status
|
||||
uint8_t status() const;
|
||||
|
||||
// set/reset bits in the status register, updating the IRQ status
|
||||
uint8_t set_reset_status(uint8_t set, uint8_t reset)
|
||||
{
|
||||
m_status = (m_status | set) & ~(reset | STATUS_BUSY);
|
||||
m_intf.ymfm_sync_check_interrupts();
|
||||
return m_status & ~m_regs.status_mask();
|
||||
}
|
||||
|
||||
// set the IRQ mask
|
||||
void set_irq_mask(uint8_t mask) { m_irq_mask = mask; m_intf.ymfm_sync_check_interrupts(); }
|
||||
|
||||
// return the current clock prescale
|
||||
uint32_t clock_prescale() const { return m_clock_prescale; }
|
||||
|
||||
// set prescale factor (2/3/6)
|
||||
void set_clock_prescale(uint32_t prescale) { m_clock_prescale = prescale; }
|
||||
|
||||
// compute sample rate
|
||||
uint32_t sample_rate(uint32_t baseclock) const
|
||||
{
|
||||
#if (YMFM_DEBUG_LOG_WAVFILES)
|
||||
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
|
||||
m_wavfile[chnum].set_samplerate(baseclock / (m_clock_prescale * OPERATORS));
|
||||
#endif
|
||||
return baseclock / (m_clock_prescale * OPERATORS);
|
||||
}
|
||||
|
||||
// return the owning device
|
||||
ymfm_interface &intf() const { return m_intf; }
|
||||
|
||||
// return a reference to our registers
|
||||
RegisterType ®s() { return m_regs; }
|
||||
|
||||
// invalidate any caches
|
||||
void invalidate_caches() { m_modified_channels = RegisterType::ALL_CHANNELS; }
|
||||
|
||||
// simple getters for debugging
|
||||
fm_channel<RegisterType> *debug_channel(uint32_t index) const { return m_channel[index].get(); }
|
||||
fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_operator[index].get(); }
|
||||
|
||||
public:
|
||||
// timer callback; called by the interface when a timer fires
|
||||
virtual void engine_timer_expired(uint32_t tnum) override;
|
||||
|
||||
// check interrupts; called by the interface after synchronization
|
||||
virtual void engine_check_interrupts() override;
|
||||
|
||||
// mode register write; called by the interface after synchronization
|
||||
virtual void engine_mode_write(uint8_t data) override;
|
||||
|
||||
protected:
|
||||
// assign the current set of operators to channels
|
||||
void assign_operators();
|
||||
|
||||
// update the state of the given timer
|
||||
void update_timer(uint32_t which, uint32_t enable, int32_t delta_clocks);
|
||||
|
||||
// internal state
|
||||
ymfm_interface &m_intf; // reference to the system interface
|
||||
uint32_t m_env_counter; // envelope counter; low 2 bits are sub-counter
|
||||
uint8_t m_status; // current status register
|
||||
uint8_t m_clock_prescale; // prescale factor (2/3/6)
|
||||
uint8_t m_irq_mask; // mask of which bits signal IRQs
|
||||
uint8_t m_irq_state; // current IRQ state
|
||||
uint8_t m_timer_running[2]; // current timer running state
|
||||
uint8_t m_total_clocks; // low 8 bits of the total number of clocks processed
|
||||
uint32_t m_active_channels; // mask of active channels (computed by prepare)
|
||||
uint32_t m_modified_channels; // mask of channels that have been modified
|
||||
uint32_t m_prepare_count; // counter to do periodic prepare sweeps
|
||||
RegisterType m_regs; // register accessor
|
||||
std::unique_ptr<fm_channel<RegisterType>> m_channel[CHANNELS]; // channel pointers
|
||||
std::unique_ptr<fm_operator<RegisterType>> m_operator[OPERATORS]; // operator pointers
|
||||
#if (YMFM_DEBUG_LOG_WAVFILES)
|
||||
mutable ymfm_wavfile<1> m_wavfile[CHANNELS]; // for debugging
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // YMFM_FM_H
|
File diff suppressed because it is too large
Load diff
|
@ -1,175 +0,0 @@
|
|||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2021, Aaron Giles
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "ymfm_misc.h"
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
||||
//*********************************************************
|
||||
// YM2149
|
||||
//*********************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// ym2149 - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
ym2149::ym2149(ymfm_interface &intf) :
|
||||
m_address(0),
|
||||
m_ssg(intf)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// reset - reset the system
|
||||
//-------------------------------------------------
|
||||
|
||||
void ym2149::reset()
|
||||
{
|
||||
// reset the engines
|
||||
m_ssg.reset();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// save_restore - save or restore the data
|
||||
//-------------------------------------------------
|
||||
|
||||
void ym2149::save_restore(ymfm_saved_state &state)
|
||||
{
|
||||
state.save_restore(m_address);
|
||||
m_ssg.save_restore(state);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// read_data - read the data register
|
||||
//-------------------------------------------------
|
||||
|
||||
uint8_t ym2149::read_data()
|
||||
{
|
||||
return m_ssg.read(m_address & 0x0f);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// read - handle a read from the device
|
||||
//-------------------------------------------------
|
||||
|
||||
uint8_t ym2149::read(uint32_t offset)
|
||||
{
|
||||
uint8_t result = 0xff;
|
||||
switch (offset & 3) // BC2,BC1
|
||||
{
|
||||
case 0: // inactive
|
||||
break;
|
||||
|
||||
case 1: // address
|
||||
break;
|
||||
|
||||
case 2: // inactive
|
||||
break;
|
||||
|
||||
case 3: // read
|
||||
result = read_data();
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// write_address - handle a write to the address
|
||||
// register
|
||||
//-------------------------------------------------
|
||||
|
||||
void ym2149::write_address(uint8_t data)
|
||||
{
|
||||
// just set the address
|
||||
m_address = data;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// write - handle a write to the register
|
||||
// interface
|
||||
//-------------------------------------------------
|
||||
|
||||
void ym2149::write_data(uint8_t data)
|
||||
{
|
||||
m_ssg.write(m_address & 0x0f, data);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// write - handle a write to the register
|
||||
// interface
|
||||
//-------------------------------------------------
|
||||
|
||||
void ym2149::write(uint32_t offset, uint8_t data)
|
||||
{
|
||||
switch (offset & 3) // BC2,BC1
|
||||
{
|
||||
case 0: // address
|
||||
write_address(data);
|
||||
break;
|
||||
|
||||
case 1: // inactive
|
||||
break;
|
||||
|
||||
case 2: // write
|
||||
write_data(data);
|
||||
break;
|
||||
|
||||
case 3: // address
|
||||
write_address(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// generate - generate samples of SSG sound
|
||||
//-------------------------------------------------
|
||||
|
||||
void ym2149::generate(output_data *output, uint32_t numsamples)
|
||||
{
|
||||
for (uint32_t samp = 0; samp < numsamples; samp++, output++)
|
||||
{
|
||||
// clock the SSG
|
||||
m_ssg.clock();
|
||||
|
||||
// YM2149 keeps the three SSG outputs independent
|
||||
m_ssg.output(*output);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2021, Aaron Giles
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef YMFM_MISC_H
|
||||
#define YMFM_MISC_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ymfm.h"
|
||||
#include "ymfm_adpcm.h"
|
||||
#include "ymfm_ssg.h"
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
||||
//*********************************************************
|
||||
// SSG IMPLEMENTATION CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> ym2149
|
||||
|
||||
// ym2149 is just an SSG with no FM part, but we expose FM-like parts so that it
|
||||
// integrates smoothly with everything else; they just don't do anything
|
||||
class ym2149
|
||||
{
|
||||
public:
|
||||
static constexpr uint32_t OUTPUTS = ssg_engine::OUTPUTS;
|
||||
static constexpr uint32_t SSG_OUTPUTS = ssg_engine::OUTPUTS;
|
||||
using output_data = ymfm_output<OUTPUTS>;
|
||||
|
||||
// constructor
|
||||
ym2149(ymfm_interface &intf);
|
||||
|
||||
// configuration
|
||||
void ssg_override(ssg_override &intf) { m_ssg.override(intf); }
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return input_clock / ssg_engine::CLOCK_DIVIDER / 8; }
|
||||
|
||||
// read access
|
||||
uint8_t read_data();
|
||||
uint8_t read(uint32_t offset);
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate one sample of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
|
||||
protected:
|
||||
// internal state
|
||||
uint8_t m_address; // address register
|
||||
ssg_engine m_ssg; // SSG engine
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // YMFM_MISC_H
|
File diff suppressed because it is too large
Load diff
|
@ -1,902 +0,0 @@
|
|||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2021, Aaron Giles
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef YMFM_OPL_H
|
||||
#define YMFM_OPL_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ymfm.h"
|
||||
#include "ymfm_adpcm.h"
|
||||
#include "ymfm_fm.h"
|
||||
#include "ymfm_pcm.h"
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
||||
//*********************************************************
|
||||
// REGISTER CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> opl_registers_base
|
||||
|
||||
//
|
||||
// OPL/OPL2/OPL3/OPL4 register map:
|
||||
//
|
||||
// System-wide registers:
|
||||
// 01 xxxxxxxx Test register
|
||||
// --x----- Enable OPL compatibility mode [OPL2 only] (1 = enable)
|
||||
// 02 xxxxxxxx Timer A value (4 * OPN)
|
||||
// 03 xxxxxxxx Timer B value
|
||||
// 04 x------- RST
|
||||
// -x------ Mask timer A
|
||||
// --x----- Mask timer B
|
||||
// ------x- Load timer B
|
||||
// -------x Load timer A
|
||||
// 08 x------- CSM mode [OPL/OPL2 only]
|
||||
// -x------ Note select
|
||||
// BD x------- AM depth
|
||||
// -x------ PM depth
|
||||
// --x----- Rhythm enable
|
||||
// ---x---- Bass drum key on
|
||||
// ----x--- Snare drum key on
|
||||
// -----x-- Tom key on
|
||||
// ------x- Top cymbal key on
|
||||
// -------x High hat key on
|
||||
// 101 --xxxxxx Test register 2 [OPL3 only]
|
||||
// 104 --x----- Channel 6 4-operator mode [OPL3 only]
|
||||
// ---x---- Channel 5 4-operator mode [OPL3 only]
|
||||
// ----x--- Channel 4 4-operator mode [OPL3 only]
|
||||
// -----x-- Channel 3 4-operator mode [OPL3 only]
|
||||
// ------x- Channel 2 4-operator mode [OPL3 only]
|
||||
// -------x Channel 1 4-operator mode [OPL3 only]
|
||||
// 105 -------x New [OPL3 only]
|
||||
// ------x- New2 [OPL4 only]
|
||||
//
|
||||
// Per-channel registers (channel in address bits 0-3)
|
||||
// Note that all these apply to address+100 as well on OPL3+
|
||||
// A0-A8 xxxxxxxx F-number (low 8 bits)
|
||||
// B0-B8 --x----- Key on
|
||||
// ---xxx-- Block (octvate, 0-7)
|
||||
// ------xx F-number (high two bits)
|
||||
// C0-C8 x------- CHD output (to DO0 pin) [OPL3+ only]
|
||||
// -x------ CHC output (to DO0 pin) [OPL3+ only]
|
||||
// --x----- CHB output (mixed right, to DO2 pin) [OPL3+ only]
|
||||
// ---x---- CHA output (mixed left, to DO2 pin) [OPL3+ only]
|
||||
// ----xxx- Feedback level for operator 1 (0-7)
|
||||
// -------x Operator connection algorithm
|
||||
//
|
||||
// Per-operator registers (operator in bits 0-5)
|
||||
// Note that all these apply to address+100 as well on OPL3+
|
||||
// 20-35 x------- AM enable
|
||||
// -x------ PM enable (VIB)
|
||||
// --x----- EG type
|
||||
// ---x---- Key scale rate
|
||||
// ----xxxx Multiple value (0-15)
|
||||
// 40-55 xx------ Key scale level (0-3)
|
||||
// --xxxxxx Total level (0-63)
|
||||
// 60-75 xxxx---- Attack rate (0-15)
|
||||
// ----xxxx Decay rate (0-15)
|
||||
// 80-95 xxxx---- Sustain level (0-15)
|
||||
// ----xxxx Release rate (0-15)
|
||||
// E0-F5 ------xx Wave select (0-3) [OPL2 only]
|
||||
// -----xxx Wave select (0-7) [OPL3+ only]
|
||||
//
|
||||
|
||||
template<int Revision>
|
||||
class opl_registers_base : public fm_registers_base
|
||||
{
|
||||
static constexpr bool IsOpl2 = (Revision == 2);
|
||||
static constexpr bool IsOpl2Plus = (Revision >= 2);
|
||||
static constexpr bool IsOpl3Plus = (Revision >= 3);
|
||||
static constexpr bool IsOpl4Plus = (Revision >= 4);
|
||||
|
||||
public:
|
||||
// constants
|
||||
static constexpr uint32_t OUTPUTS = IsOpl3Plus ? 4 : 1;
|
||||
static constexpr uint32_t CHANNELS = IsOpl3Plus ? 18 : 9;
|
||||
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
|
||||
static constexpr uint32_t OPERATORS = CHANNELS * 2;
|
||||
static constexpr uint32_t WAVEFORMS = IsOpl3Plus ? 8 : (IsOpl2Plus ? 4 : 1);
|
||||
static constexpr uint32_t REGISTERS = IsOpl3Plus ? 0x200 : 0x100;
|
||||
static constexpr uint32_t REG_MODE = 0x04;
|
||||
static constexpr uint32_t DEFAULT_PRESCALE = IsOpl4Plus ? 19 : (IsOpl3Plus ? 8 : 4);
|
||||
static constexpr uint32_t EG_CLOCK_DIVIDER = 1;
|
||||
static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
|
||||
static constexpr bool DYNAMIC_OPS = IsOpl3Plus;
|
||||
static constexpr bool MODULATOR_DELAY = !IsOpl3Plus;
|
||||
static constexpr uint8_t STATUS_TIMERA = 0x40;
|
||||
static constexpr uint8_t STATUS_TIMERB = 0x20;
|
||||
static constexpr uint8_t STATUS_BUSY = 0;
|
||||
static constexpr uint8_t STATUS_IRQ = 0x80;
|
||||
|
||||
// constructor
|
||||
opl_registers_base();
|
||||
|
||||
// reset to initial state
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// map channel number to register offset
|
||||
static constexpr uint32_t channel_offset(uint32_t chnum)
|
||||
{
|
||||
assert(chnum < CHANNELS);
|
||||
if (!IsOpl3Plus)
|
||||
return chnum;
|
||||
else
|
||||
return (chnum % 9) + 0x100 * (chnum / 9);
|
||||
}
|
||||
|
||||
// map operator number to register offset
|
||||
static constexpr uint32_t operator_offset(uint32_t opnum)
|
||||
{
|
||||
assert(opnum < OPERATORS);
|
||||
if (!IsOpl3Plus)
|
||||
return opnum + 2 * (opnum / 6);
|
||||
else
|
||||
return (opnum % 18) + 2 * ((opnum % 18) / 6) + 0x100 * (opnum / 18);
|
||||
}
|
||||
|
||||
// return an array of operator indices for each channel
|
||||
struct operator_mapping { uint32_t chan[CHANNELS]; };
|
||||
void operator_map(operator_mapping &dest) const;
|
||||
|
||||
// OPL4 apparently can read back FM registers?
|
||||
uint8_t read(uint16_t index) const { return m_regdata[index]; }
|
||||
|
||||
// handle writes to the register array
|
||||
bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
|
||||
|
||||
// clock the noise and LFO, if present, returning LFO PM value
|
||||
int32_t clock_noise_and_lfo();
|
||||
|
||||
// reset the LFO
|
||||
void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; }
|
||||
|
||||
// return the AM offset from LFO for the given channel
|
||||
// on OPL this is just a fixed value
|
||||
uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; }
|
||||
|
||||
// return LFO/noise states
|
||||
uint32_t noise_state() const { return m_noise_lfsr >> 23; }
|
||||
|
||||
// caching helpers
|
||||
void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
|
||||
|
||||
// compute the phase step, given a PM value
|
||||
uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
|
||||
|
||||
// log a key-on event
|
||||
std::string log_keyon(uint32_t choffs, uint32_t opoffs);
|
||||
|
||||
// system-wide registers
|
||||
uint32_t test() const { return byte(0x01, 0, 8); }
|
||||
uint32_t waveform_enable() const { return IsOpl2 ? byte(0x01, 5, 1) : (IsOpl3Plus ? 1 : 0); }
|
||||
uint32_t timer_a_value() const { return byte(0x02, 0, 8) * 4; } // 8->10 bits
|
||||
uint32_t timer_b_value() const { return byte(0x03, 0, 8); }
|
||||
uint32_t status_mask() const { return byte(0x04, 0, 8) & 0x78; }
|
||||
uint32_t irq_reset() const { return byte(0x04, 7, 1); }
|
||||
uint32_t reset_timer_b() const { return byte(0x04, 7, 1) | byte(0x04, 5, 1); }
|
||||
uint32_t reset_timer_a() const { return byte(0x04, 7, 1) | byte(0x04, 6, 1); }
|
||||
uint32_t enable_timer_b() const { return 1; }
|
||||
uint32_t enable_timer_a() const { return 1; }
|
||||
uint32_t load_timer_b() const { return byte(0x04, 1, 1); }
|
||||
uint32_t load_timer_a() const { return byte(0x04, 0, 1); }
|
||||
uint32_t csm() const { return IsOpl3Plus ? 0 : byte(0x08, 7, 1); }
|
||||
uint32_t note_select() const { return byte(0x08, 6, 1); }
|
||||
uint32_t lfo_am_depth() const { return byte(0xbd, 7, 1); }
|
||||
uint32_t lfo_pm_depth() const { return byte(0xbd, 6, 1); }
|
||||
uint32_t rhythm_enable() const { return byte(0xbd, 5, 1); }
|
||||
uint32_t rhythm_keyon() const { return byte(0xbd, 4, 0); }
|
||||
uint32_t newflag() const { return IsOpl3Plus ? byte(0x105, 0, 1) : 0; }
|
||||
uint32_t new2flag() const { return IsOpl4Plus ? byte(0x105, 1, 1) : 0; }
|
||||
uint32_t fourop_enable() const { return IsOpl3Plus ? byte(0x104, 0, 6) : 0; }
|
||||
|
||||
// per-channel registers
|
||||
uint32_t ch_block_freq(uint32_t choffs) const { return word(0xb0, 0, 5, 0xa0, 0, 8, choffs); }
|
||||
uint32_t ch_feedback(uint32_t choffs) const { return byte(0xc0, 1, 3, choffs); }
|
||||
uint32_t ch_algorithm(uint32_t choffs) const { return byte(0xc0, 0, 1, choffs) | (IsOpl3Plus ? (8 | (byte(0xc3, 0, 1, choffs) << 1)) : 0); }
|
||||
uint32_t ch_output_any(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 4) : 1; }
|
||||
uint32_t ch_output_0(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 1) : 1; }
|
||||
uint32_t ch_output_1(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 5, 1) : (IsOpl3Plus ? 1 : 0); }
|
||||
uint32_t ch_output_2(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 6, 1) : 0; }
|
||||
uint32_t ch_output_3(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 7, 1) : 0; }
|
||||
|
||||
// per-operator registers
|
||||
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0x20, 7, 1, opoffs); }
|
||||
uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return byte(0x20, 6, 1, opoffs); }
|
||||
uint32_t op_eg_sustain(uint32_t opoffs) const { return byte(0x20, 5, 1, opoffs); }
|
||||
uint32_t op_ksr(uint32_t opoffs) const { return byte(0x20, 4, 1, opoffs); }
|
||||
uint32_t op_multiple(uint32_t opoffs) const { return byte(0x20, 0, 4, opoffs); }
|
||||
uint32_t op_ksl(uint32_t opoffs) const { uint32_t temp = byte(0x40, 6, 2, opoffs); return bitfield(temp, 1) | (bitfield(temp, 0) << 1); }
|
||||
uint32_t op_total_level(uint32_t opoffs) const { return byte(0x40, 0, 6, opoffs); }
|
||||
uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x60, 4, 4, opoffs); }
|
||||
uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0x60, 0, 4, opoffs); }
|
||||
uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0x80, 4, 4, opoffs); }
|
||||
uint32_t op_release_rate(uint32_t opoffs) const { return byte(0x80, 0, 4, opoffs); }
|
||||
uint32_t op_waveform(uint32_t opoffs) const { return IsOpl2Plus ? byte(0xe0, 0, newflag() ? 3 : 2, opoffs) : 0; }
|
||||
|
||||
protected:
|
||||
// return a bitfield extracted from a byte
|
||||
uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
|
||||
{
|
||||
return bitfield(m_regdata[offset + extra_offset], start, count);
|
||||
}
|
||||
|
||||
// return a bitfield extracted from a pair of bytes, MSBs listed first
|
||||
uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
|
||||
{
|
||||
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
|
||||
}
|
||||
|
||||
// helper to determine if the this channel is an active rhythm channel
|
||||
bool is_rhythm(uint32_t choffs) const
|
||||
{
|
||||
return rhythm_enable() && (choffs >= 6 && choffs <= 8);
|
||||
}
|
||||
|
||||
// internal state
|
||||
uint16_t m_lfo_am_counter; // LFO AM counter
|
||||
uint16_t m_lfo_pm_counter; // LFO PM counter
|
||||
uint32_t m_noise_lfsr; // noise LFSR state
|
||||
uint8_t m_lfo_am; // current LFO AM value
|
||||
uint8_t m_regdata[REGISTERS]; // register data
|
||||
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
|
||||
};
|
||||
|
||||
using opl_registers = opl_registers_base<1>;
|
||||
using opl2_registers = opl_registers_base<2>;
|
||||
using opl3_registers = opl_registers_base<3>;
|
||||
using opl4_registers = opl_registers_base<4>;
|
||||
|
||||
|
||||
|
||||
// ======================> opll_registers
|
||||
|
||||
//
|
||||
// OPLL register map:
|
||||
//
|
||||
// System-wide registers:
|
||||
// 0E --x----- Rhythm enable
|
||||
// ---x---- Bass drum key on
|
||||
// ----x--- Snare drum key on
|
||||
// -----x-- Tom key on
|
||||
// ------x- Top cymbal key on
|
||||
// -------x High hat key on
|
||||
// 0F xxxxxxxx Test register
|
||||
//
|
||||
// Per-channel registers (channel in address bits 0-3)
|
||||
// 10-18 xxxxxxxx F-number (low 8 bits)
|
||||
// 20-28 --x----- Sustain on
|
||||
// ---x---- Key on
|
||||
// --- xxx- Block (octvate, 0-7)
|
||||
// -------x F-number (high bit)
|
||||
// 30-38 xxxx---- Instrument selection
|
||||
// ----xxxx Volume
|
||||
//
|
||||
// User instrument registers (for carrier, modulator operators)
|
||||
// 00-01 x------- AM enable
|
||||
// -x------ PM enable (VIB)
|
||||
// --x----- EG type
|
||||
// ---x---- Key scale rate
|
||||
// ----xxxx Multiple value (0-15)
|
||||
// 02 xx------ Key scale level (carrier, 0-3)
|
||||
// --xxxxxx Total level (modulator, 0-63)
|
||||
// 03 xx------ Key scale level (modulator, 0-3)
|
||||
// ---x---- Rectified wave (carrier)
|
||||
// ----x--- Rectified wave (modulator)
|
||||
// -----xxx Feedback level for operator 1 (0-7)
|
||||
// 04-05 xxxx---- Attack rate (0-15)
|
||||
// ----xxxx Decay rate (0-15)
|
||||
// 06-07 xxxx---- Sustain level (0-15)
|
||||
// ----xxxx Release rate (0-15)
|
||||
//
|
||||
// Internal (fake) registers:
|
||||
// 40-48 xxxxxxxx Current instrument base address
|
||||
// 4E-5F xxxxxxxx Current instrument base address + operator slot (0/1)
|
||||
// 70-FF xxxxxxxx Data for instruments (1-16 plus 3 drums)
|
||||
//
|
||||
|
||||
class opll_registers : public fm_registers_base
|
||||
{
|
||||
public:
|
||||
static constexpr uint32_t OUTPUTS = 2;
|
||||
static constexpr uint32_t CHANNELS = 9;
|
||||
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
|
||||
static constexpr uint32_t OPERATORS = CHANNELS * 2;
|
||||
static constexpr uint32_t WAVEFORMS = 2;
|
||||
static constexpr uint32_t REGISTERS = 0x40;
|
||||
static constexpr uint32_t REG_MODE = 0x3f;
|
||||
static constexpr uint32_t DEFAULT_PRESCALE = 4;
|
||||
static constexpr uint32_t EG_CLOCK_DIVIDER = 1;
|
||||
static constexpr uint32_t CSM_TRIGGER_MASK = 0;
|
||||
static constexpr bool EG_HAS_DEPRESS = true;
|
||||
static constexpr bool MODULATOR_DELAY = true;
|
||||
static constexpr uint8_t STATUS_TIMERA = 0;
|
||||
static constexpr uint8_t STATUS_TIMERB = 0;
|
||||
static constexpr uint8_t STATUS_BUSY = 0;
|
||||
static constexpr uint8_t STATUS_IRQ = 0;
|
||||
|
||||
// OPLL-specific constants
|
||||
static constexpr uint32_t INSTDATA_SIZE = 0x90;
|
||||
|
||||
// constructor
|
||||
opll_registers();
|
||||
|
||||
// reset to initial state
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// map channel number to register offset
|
||||
static constexpr uint32_t channel_offset(uint32_t chnum)
|
||||
{
|
||||
assert(chnum < CHANNELS);
|
||||
return chnum;
|
||||
}
|
||||
|
||||
// map operator number to register offset
|
||||
static constexpr uint32_t operator_offset(uint32_t opnum)
|
||||
{
|
||||
assert(opnum < OPERATORS);
|
||||
return opnum;
|
||||
}
|
||||
|
||||
// return an array of operator indices for each channel
|
||||
struct operator_mapping { uint32_t chan[CHANNELS]; };
|
||||
void operator_map(operator_mapping &dest) const;
|
||||
|
||||
// read a register value
|
||||
uint8_t read(uint16_t index) const { return m_regdata[index]; }
|
||||
|
||||
// handle writes to the register array
|
||||
bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
|
||||
|
||||
// clock the noise and LFO, if present, returning LFO PM value
|
||||
int32_t clock_noise_and_lfo();
|
||||
|
||||
// reset the LFO
|
||||
void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; }
|
||||
|
||||
// return the AM offset from LFO for the given channel
|
||||
// on OPL this is just a fixed value
|
||||
uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; }
|
||||
|
||||
// return LFO/noise states
|
||||
uint32_t noise_state() const { return m_noise_lfsr >> 23; }
|
||||
|
||||
// caching helpers
|
||||
void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
|
||||
|
||||
// compute the phase step, given a PM value
|
||||
uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
|
||||
|
||||
// log a key-on event
|
||||
std::string log_keyon(uint32_t choffs, uint32_t opoffs);
|
||||
|
||||
// set the instrument data
|
||||
void set_instrument_data(uint8_t const *data)
|
||||
{
|
||||
std::copy_n(data, INSTDATA_SIZE, &m_instdata[0]);
|
||||
}
|
||||
|
||||
// system-wide registers
|
||||
uint32_t rhythm_enable() const { return byte(0x0e, 5, 1); }
|
||||
uint32_t rhythm_keyon() const { return byte(0x0e, 4, 0); }
|
||||
uint32_t test() const { return byte(0x0f, 0, 8); }
|
||||
uint32_t waveform_enable() const { return 1; }
|
||||
uint32_t timer_a_value() const { return 0; }
|
||||
uint32_t timer_b_value() const { return 0; }
|
||||
uint32_t status_mask() const { return 0; }
|
||||
uint32_t irq_reset() const { return 0; }
|
||||
uint32_t reset_timer_b() const { return 0; }
|
||||
uint32_t reset_timer_a() const { return 0; }
|
||||
uint32_t enable_timer_b() const { return 0; }
|
||||
uint32_t enable_timer_a() const { return 0; }
|
||||
uint32_t load_timer_b() const { return 0; }
|
||||
uint32_t load_timer_a() const { return 0; }
|
||||
uint32_t csm() const { return 0; }
|
||||
|
||||
// per-channel registers
|
||||
uint32_t ch_block_freq(uint32_t choffs) const { return word(0x20, 0, 4, 0x10, 0, 8, choffs); }
|
||||
uint32_t ch_sustain(uint32_t choffs) const { return byte(0x20, 5, 1, choffs); }
|
||||
uint32_t ch_total_level(uint32_t choffs) const { return instchbyte(0x02, 0, 6, choffs); }
|
||||
uint32_t ch_feedback(uint32_t choffs) const { return instchbyte(0x03, 0, 3, choffs); }
|
||||
uint32_t ch_algorithm(uint32_t choffs) const { return 0; }
|
||||
uint32_t ch_instrument(uint32_t choffs) const { return byte(0x30, 4, 4, choffs); }
|
||||
uint32_t ch_output_any(uint32_t choffs) const { return 1; }
|
||||
uint32_t ch_output_0(uint32_t choffs) const { return !is_rhythm(choffs); }
|
||||
uint32_t ch_output_1(uint32_t choffs) const { return is_rhythm(choffs); }
|
||||
uint32_t ch_output_2(uint32_t choffs) const { return 0; }
|
||||
uint32_t ch_output_3(uint32_t choffs) const { return 0; }
|
||||
|
||||
// per-operator registers
|
||||
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return instopbyte(0x00, 7, 1, opoffs); }
|
||||
uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return instopbyte(0x00, 6, 1, opoffs); }
|
||||
uint32_t op_eg_sustain(uint32_t opoffs) const { return instopbyte(0x00, 5, 1, opoffs); }
|
||||
uint32_t op_ksr(uint32_t opoffs) const { return instopbyte(0x00, 4, 1, opoffs); }
|
||||
uint32_t op_multiple(uint32_t opoffs) const { return instopbyte(0x00, 0, 4, opoffs); }
|
||||
uint32_t op_ksl(uint32_t opoffs) const { return instopbyte(0x02, 6, 2, opoffs); }
|
||||
uint32_t op_waveform(uint32_t opoffs) const { return instchbyte(0x03, 3 + bitfield(opoffs, 0), 1, opoffs >> 1); }
|
||||
uint32_t op_attack_rate(uint32_t opoffs) const { return instopbyte(0x04, 4, 4, opoffs); }
|
||||
uint32_t op_decay_rate(uint32_t opoffs) const { return instopbyte(0x04, 0, 4, opoffs); }
|
||||
uint32_t op_sustain_level(uint32_t opoffs) const { return instopbyte(0x06, 4, 4, opoffs); }
|
||||
uint32_t op_release_rate(uint32_t opoffs) const { return instopbyte(0x06, 0, 4, opoffs); }
|
||||
uint32_t op_volume(uint32_t opoffs) const { return byte(0x30, 4 * bitfield(~opoffs, 0), 4, opoffs >> 1); }
|
||||
|
||||
private:
|
||||
// return a bitfield extracted from a byte
|
||||
uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
|
||||
{
|
||||
return bitfield(m_regdata[offset + extra_offset], start, count);
|
||||
}
|
||||
|
||||
// return a bitfield extracted from a pair of bytes, MSBs listed first
|
||||
uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
|
||||
{
|
||||
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
|
||||
}
|
||||
|
||||
// helpers to read from instrument channel/operator data
|
||||
uint32_t instchbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t choffs) const { return bitfield(m_chinst[choffs][offset], start, count); }
|
||||
uint32_t instopbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t opoffs) const { return bitfield(m_opinst[opoffs][offset], start, count); }
|
||||
|
||||
// helper to determine if the this channel is an active rhythm channel
|
||||
bool is_rhythm(uint32_t choffs) const
|
||||
{
|
||||
return rhythm_enable() && choffs >= 6;
|
||||
}
|
||||
|
||||
// internal state
|
||||
uint16_t m_lfo_am_counter; // LFO AM counter
|
||||
uint16_t m_lfo_pm_counter; // LFO PM counter
|
||||
uint32_t m_noise_lfsr; // noise LFSR state
|
||||
uint8_t m_lfo_am; // current LFO AM value
|
||||
uint8_t const *m_chinst[CHANNELS]; // pointer to instrument data for each channel
|
||||
uint8_t const *m_opinst[OPERATORS]; // pointer to instrument data for each operator
|
||||
uint8_t m_regdata[REGISTERS]; // register data
|
||||
uint8_t m_instdata[INSTDATA_SIZE]; // instrument data
|
||||
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// OPL IMPLEMENTATION CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> ym3526
|
||||
|
||||
class ym3526
|
||||
{
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opl_registers>;
|
||||
using output_data = fm_engine::output_data;
|
||||
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
|
||||
|
||||
// constructor
|
||||
ym3526(ymfm_interface &intf);
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access
|
||||
uint8_t read_status();
|
||||
uint8_t read(uint32_t offset);
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
protected:
|
||||
// internal state
|
||||
uint8_t m_address; // address register
|
||||
fm_engine m_fm; // core FM engine
|
||||
};
|
||||
|
||||
|
||||
// ======================> y8950
|
||||
|
||||
class y8950
|
||||
{
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opl_registers>;
|
||||
using output_data = fm_engine::output_data;
|
||||
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
|
||||
|
||||
static constexpr uint8_t STATUS_ADPCM_B_PLAYING = 0x01;
|
||||
static constexpr uint8_t STATUS_ADPCM_B_BRDY = 0x08;
|
||||
static constexpr uint8_t STATUS_ADPCM_B_EOS = 0x10;
|
||||
static constexpr uint8_t ALL_IRQS = STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_EOS | fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB;
|
||||
|
||||
// constructor
|
||||
y8950(ymfm_interface &intf);
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access
|
||||
uint8_t read_status();
|
||||
uint8_t read_data();
|
||||
uint8_t read(uint32_t offset);
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
|
||||
protected:
|
||||
// internal state
|
||||
uint8_t m_address; // address register
|
||||
uint8_t m_io_ddr; // data direction register for I/O
|
||||
fm_engine m_fm; // core FM engine
|
||||
adpcm_b_engine m_adpcm_b; // ADPCM-B engine
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// OPL2 IMPLEMENTATION CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> ym3812
|
||||
|
||||
class ym3812
|
||||
{
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opl2_registers>;
|
||||
using output_data = fm_engine::output_data;
|
||||
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
|
||||
|
||||
// constructor
|
||||
ym3812(ymfm_interface &intf);
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access
|
||||
uint8_t read_status();
|
||||
uint8_t read(uint32_t offset);
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
|
||||
protected:
|
||||
// internal state
|
||||
uint8_t m_address; // address register
|
||||
fm_engine m_fm; // core FM engine
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// OPL3 IMPLEMENTATION CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> ymf262
|
||||
|
||||
class ymf262
|
||||
{
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opl3_registers>;
|
||||
using output_data = fm_engine::output_data;
|
||||
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
|
||||
|
||||
// constructor
|
||||
ymf262(ymfm_interface &intf);
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access
|
||||
uint8_t read_status();
|
||||
uint8_t read(uint32_t offset);
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write_address_hi(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
|
||||
protected:
|
||||
// internal state
|
||||
uint16_t m_address; // address register
|
||||
fm_engine m_fm; // core FM engine
|
||||
};
|
||||
|
||||
|
||||
// ======================> ymf289b
|
||||
|
||||
class ymf289b
|
||||
{
|
||||
static constexpr uint8_t STATUS_BUSY_FLAGS = 0x05;
|
||||
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opl3_registers>;
|
||||
using output_data = fm_engine::output_data;
|
||||
static constexpr uint32_t OUTPUTS = 2;
|
||||
|
||||
// constructor
|
||||
ymf289b(ymfm_interface &intf);
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access
|
||||
uint8_t read_status();
|
||||
uint8_t read_data();
|
||||
uint8_t read(uint32_t offset);
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write_address_hi(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
|
||||
protected:
|
||||
// internal helpers
|
||||
bool ymf289b_mode() { return ((m_fm.regs().read(0x105) & 0x04) != 0); }
|
||||
|
||||
// internal state
|
||||
uint16_t m_address; // address register
|
||||
fm_engine m_fm; // core FM engine
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// OPL4 IMPLEMENTATION CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> ymf278b
|
||||
|
||||
class ymf278b
|
||||
{
|
||||
// Using the nominal datasheet frequency of 33.868MHz, the output of the
|
||||
// chip will be clock/768 = 44.1kHz. However, the FM engine is clocked
|
||||
// internally at clock/(19*36), or 49.515kHz, so the FM output needs to
|
||||
// be downsampled. We treat this as needing to clock the FM engine an
|
||||
// extra tick every few samples. The exact ratio is 768/(19*36) or
|
||||
// 768/684 = 192/171. So if we always clock the FM once, we'll have
|
||||
// 192/171 - 1 = 21/171 left. Thus we count 21 for each sample and when
|
||||
// it gets above 171, we tick an extra time.
|
||||
static constexpr uint32_t FM_EXTRA_SAMPLE_THRESH = 171;
|
||||
static constexpr uint32_t FM_EXTRA_SAMPLE_STEP = 192 - FM_EXTRA_SAMPLE_THRESH;
|
||||
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opl4_registers>;
|
||||
static constexpr uint32_t OUTPUTS = 6;
|
||||
using output_data = ymfm_output<OUTPUTS>;
|
||||
|
||||
static constexpr uint8_t STATUS_BUSY = 0x01;
|
||||
static constexpr uint8_t STATUS_LD = 0x02;
|
||||
|
||||
// constructor
|
||||
ymf278b(ymfm_interface &intf);
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return input_clock / 768; }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access
|
||||
uint8_t read_status();
|
||||
uint8_t read_data_pcm();
|
||||
uint8_t read(uint32_t offset);
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write_address_hi(uint8_t data);
|
||||
void write_address_pcm(uint8_t data);
|
||||
void write_data_pcm(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
|
||||
protected:
|
||||
// internal state
|
||||
uint16_t m_address; // address register
|
||||
uint32_t m_fm_pos; // FM resampling position
|
||||
uint32_t m_load_remaining; // how many more samples until LD flag clears
|
||||
bool m_next_status_id; // flag to track which status ID to return
|
||||
fm_engine m_fm; // core FM engine
|
||||
pcm_engine m_pcm; // core PCM engine
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// OPLL IMPLEMENTATION CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> opll_base
|
||||
|
||||
class opll_base
|
||||
{
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opll_registers>;
|
||||
using output_data = fm_engine::output_data;
|
||||
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
|
||||
|
||||
// constructor
|
||||
opll_base(ymfm_interface &intf, uint8_t const *data);
|
||||
|
||||
// configuration
|
||||
void set_instrument_data(uint8_t const *data) { m_fm.regs().set_instrument_data(data); }
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access -- doesn't really have any, but provide these for consistency
|
||||
uint8_t read_status() { return 0x00; }
|
||||
uint8_t read(uint32_t offset) { return 0x00; }
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
|
||||
protected:
|
||||
// internal state
|
||||
uint8_t m_address; // address register
|
||||
fm_engine m_fm; // core FM engine
|
||||
};
|
||||
|
||||
|
||||
// ======================> ym2413
|
||||
|
||||
class ym2413 : public opll_base
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
ym2413(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
|
||||
|
||||
private:
|
||||
// internal state
|
||||
static uint8_t const s_default_instruments[];
|
||||
};
|
||||
|
||||
|
||||
// ======================> ym2413
|
||||
|
||||
class ym2423 : public opll_base
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
ym2423(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
|
||||
|
||||
private:
|
||||
// internal state
|
||||
static uint8_t const s_default_instruments[];
|
||||
};
|
||||
|
||||
|
||||
// ======================> ymf281
|
||||
|
||||
class ymf281 : public opll_base
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
ymf281(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
|
||||
|
||||
private:
|
||||
// internal state
|
||||
static uint8_t const s_default_instruments[];
|
||||
};
|
||||
|
||||
|
||||
// ======================> ds1001
|
||||
|
||||
class ds1001 : public opll_base
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
ds1001(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
|
||||
|
||||
private:
|
||||
// internal state
|
||||
static uint8_t const s_default_instruments[];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // YMFM_OPL_H
|
|
@ -1,539 +0,0 @@
|
|||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2021, Aaron Giles
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "ymfm_opm.h"
|
||||
#include "ymfm_fm.ipp"
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
||||
//*********************************************************
|
||||
// OPM REGISTERS
|
||||
//*********************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// opm_registers - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
opm_registers::opm_registers() :
|
||||
m_lfo_counter(0),
|
||||
m_noise_lfsr(1),
|
||||
m_noise_counter(0),
|
||||
m_noise_state(0),
|
||||
m_noise_lfo(0),
|
||||
m_lfo_am(0)
|
||||
{
|
||||
// create the waveforms
|
||||
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
|
||||
m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
|
||||
|
||||
// create the LFO waveforms; AM in the low 8 bits, PM in the upper 8
|
||||
// waveforms are adjusted to match the pictures in the application manual
|
||||
for (uint32_t index = 0; index < LFO_WAVEFORM_LENGTH; index++)
|
||||
{
|
||||
// waveform 0 is a sawtooth
|
||||
uint8_t am = index ^ 0xff;
|
||||
uint8_t pm = index;
|
||||
m_lfo_waveform[0][index] = am | (pm << 8);
|
||||
|
||||
// waveform 1 is a square wave
|
||||
am = bitfield(index, 7) ? 0 : 0xff;
|
||||
pm = am ^ 0x80;
|
||||
m_lfo_waveform[1][index] = am | (pm << 8);
|
||||
|
||||
// waveform 2 is a triangle wave
|
||||
am = bitfield(index, 7) ? (index << 1) : ((index ^ 0xff) << 1);
|
||||
pm = bitfield(index, 6) ? am : ~am;
|
||||
m_lfo_waveform[2][index] = am | (pm << 8);
|
||||
|
||||
// waveform 3 is noise; it is filled in dynamically
|
||||
m_lfo_waveform[3][index] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// reset - reset to initial state
|
||||
//-------------------------------------------------
|
||||
|
||||
void opm_registers::reset()
|
||||
{
|
||||
std::fill_n(&m_regdata[0], REGISTERS, 0);
|
||||
|
||||
// enable output on both channels by default
|
||||
m_regdata[0x20] = m_regdata[0x21] = m_regdata[0x22] = m_regdata[0x23] = 0xc0;
|
||||
m_regdata[0x24] = m_regdata[0x25] = m_regdata[0x26] = m_regdata[0x27] = 0xc0;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// save_restore - save or restore the data
|
||||
//-------------------------------------------------
|
||||
|
||||
void opm_registers::save_restore(ymfm_saved_state &state)
|
||||
{
|
||||
state.save_restore(m_lfo_counter);
|
||||
state.save_restore(m_lfo_am);
|
||||
state.save_restore(m_noise_lfsr);
|
||||
state.save_restore(m_noise_counter);
|
||||
state.save_restore(m_noise_state);
|
||||
state.save_restore(m_noise_lfo);
|
||||
state.save_restore(m_regdata);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// operator_map - return an array of operator
|
||||
// indices for each channel; for OPM this is fixed
|
||||
//-------------------------------------------------
|
||||
|
||||
void opm_registers::operator_map(operator_mapping &dest) const
|
||||
{
|
||||
// Note that the channel index order is 0,2,1,3, so we bitswap the index.
|
||||
//
|
||||
// This is because the order in the map is:
|
||||
// carrier 1, carrier 2, modulator 1, modulator 2
|
||||
//
|
||||
// But when wiring up the connections, the more natural order is:
|
||||
// carrier 1, modulator 1, carrier 2, modulator 2
|
||||
static const operator_mapping s_fixed_map =
|
||||
{ {
|
||||
operator_list( 0, 16, 8, 24 ), // Channel 0 operators
|
||||
operator_list( 1, 17, 9, 25 ), // Channel 1 operators
|
||||
operator_list( 2, 18, 10, 26 ), // Channel 2 operators
|
||||
operator_list( 3, 19, 11, 27 ), // Channel 3 operators
|
||||
operator_list( 4, 20, 12, 28 ), // Channel 4 operators
|
||||
operator_list( 5, 21, 13, 29 ), // Channel 5 operators
|
||||
operator_list( 6, 22, 14, 30 ), // Channel 6 operators
|
||||
operator_list( 7, 23, 15, 31 ), // Channel 7 operators
|
||||
} };
|
||||
dest = s_fixed_map;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// write - handle writes to the register array
|
||||
//-------------------------------------------------
|
||||
|
||||
bool opm_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
|
||||
{
|
||||
assert(index < REGISTERS);
|
||||
|
||||
// LFO AM/PM depth are written to the same register (0x19);
|
||||
// redirect the PM depth to an unused neighbor (0x1a)
|
||||
if (index == 0x19)
|
||||
m_regdata[index + bitfield(data, 7)] = data;
|
||||
else if (index != 0x1a)
|
||||
m_regdata[index] = data;
|
||||
|
||||
// handle writes to the key on index
|
||||
if (index == 0x08)
|
||||
{
|
||||
channel = bitfield(data, 0, 3);
|
||||
opmask = bitfield(data, 3, 4);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// clock_noise_and_lfo - clock the noise and LFO,
|
||||
// handling clock division, depth, and waveform
|
||||
// computations
|
||||
//-------------------------------------------------
|
||||
|
||||
int32_t opm_registers::clock_noise_and_lfo()
|
||||
{
|
||||
// base noise frequency is measured at 2x 1/2 FM frequency; this
|
||||
// means each tick counts as two steps against the noise counter
|
||||
uint32_t freq = noise_frequency();
|
||||
for (int rep = 0; rep < 2; rep++)
|
||||
{
|
||||
// evidence seems to suggest the LFSR is clocked continually and just
|
||||
// sampled at the noise frequency for output purposes; note that the
|
||||
// low 8 bits are the most recent 8 bits of history while bits 8-24
|
||||
// contain the 17 bit LFSR state
|
||||
m_noise_lfsr <<= 1;
|
||||
m_noise_lfsr |= bitfield(m_noise_lfsr, 17) ^ bitfield(m_noise_lfsr, 14) ^ 1;
|
||||
|
||||
// compare against the frequency and latch when we exceed it
|
||||
if (m_noise_counter++ >= freq)
|
||||
{
|
||||
m_noise_counter = 0;
|
||||
m_noise_state = bitfield(m_noise_lfsr, 17);
|
||||
}
|
||||
}
|
||||
|
||||
// treat the rate as a 4.4 floating-point step value with implied
|
||||
// leading 1; this matches exactly the frequencies in the application
|
||||
// manual, though it might not be implemented exactly this way on chip
|
||||
uint32_t rate = lfo_rate();
|
||||
m_lfo_counter += (0x10 | bitfield(rate, 0, 4)) << bitfield(rate, 4, 4);
|
||||
|
||||
// bit 1 of the test register is officially undocumented but has been
|
||||
// discovered to hold the LFO in reset while active
|
||||
if (lfo_reset())
|
||||
m_lfo_counter = 0;
|
||||
|
||||
// now pull out the non-fractional LFO value
|
||||
uint32_t lfo = bitfield(m_lfo_counter, 22, 8);
|
||||
|
||||
// fill in the noise entry 1 ahead of our current position; this
|
||||
// ensures the current value remains stable for a full LFO clock
|
||||
// and effectively latches the running value when the LFO advances
|
||||
uint32_t lfo_noise = bitfield(m_noise_lfsr, 17, 8);
|
||||
m_lfo_waveform[3][(lfo + 1) & 0xff] = lfo_noise | (lfo_noise << 8);
|
||||
|
||||
// fetch the AM/PM values based on the waveform; AM is unsigned and
|
||||
// encoded in the low 8 bits, while PM signed and encoded in the upper
|
||||
// 8 bits
|
||||
int32_t ampm = m_lfo_waveform[lfo_waveform()][lfo];
|
||||
|
||||
// apply depth to the AM value and store for later
|
||||
m_lfo_am = ((ampm & 0xff) * lfo_am_depth()) >> 7;
|
||||
|
||||
// apply depth to the PM value and return it
|
||||
return ((ampm >> 8) * int32_t(lfo_pm_depth())) >> 7;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// lfo_am_offset - return the AM offset from LFO
|
||||
// for the given channel
|
||||
//-------------------------------------------------
|
||||
|
||||
uint32_t opm_registers::lfo_am_offset(uint32_t choffs) const
|
||||
{
|
||||
// OPM maps AM quite differently from OPN
|
||||
|
||||
// shift value for AM sensitivity is [*, 0, 1, 2],
|
||||
// mapping to values of [0, 23.9, 47.8, and 95.6dB]
|
||||
uint32_t am_sensitivity = ch_lfo_am_sens(choffs);
|
||||
if (am_sensitivity == 0)
|
||||
return 0;
|
||||
|
||||
// QUESTION: see OPN note below for the dB range mapping; it applies
|
||||
// here as well
|
||||
|
||||
// raw LFO AM value on OPM is 0-FF, which is already a factor of 2
|
||||
// larger than the OPN below, putting our staring point at 2x theirs;
|
||||
// this works out since our minimum is 2x their maximum
|
||||
return m_lfo_am << (am_sensitivity - 1);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// cache_operator_data - fill the operator cache
|
||||
// with prefetched data
|
||||
//-------------------------------------------------
|
||||
|
||||
void opm_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
|
||||
{
|
||||
// set up the easy stuff
|
||||
cache.waveform = &m_waveform[0][0];
|
||||
|
||||
// get frequency from the channel
|
||||
uint32_t block_freq = cache.block_freq = ch_block_freq(choffs);
|
||||
|
||||
// compute the keycode: block_freq is:
|
||||
//
|
||||
// BBBCCCCFFFFFF
|
||||
// ^^^^^
|
||||
//
|
||||
// the 5-bit keycode is just the top 5 bits (block + top 2 bits
|
||||
// of the key code)
|
||||
uint32_t keycode = bitfield(block_freq, 8, 5);
|
||||
|
||||
// detune adjustment
|
||||
cache.detune = detune_adjustment(op_detune(opoffs), keycode);
|
||||
|
||||
// multiple value, as an x.1 value (0 means 0.5)
|
||||
cache.multiple = op_multiple(opoffs) * 2;
|
||||
if (cache.multiple == 0)
|
||||
cache.multiple = 1;
|
||||
|
||||
// phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on
|
||||
// block_freq, detune, and multiple, so compute it after we've done those
|
||||
if (lfo_pm_depth() == 0 || ch_lfo_pm_sens(choffs) == 0)
|
||||
cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
|
||||
else
|
||||
cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
|
||||
|
||||
// total level, scaled by 8
|
||||
cache.total_level = op_total_level(opoffs) << 3;
|
||||
|
||||
// 4-bit sustain level, but 15 means 31 so effectively 5 bits
|
||||
cache.eg_sustain = op_sustain_level(opoffs);
|
||||
cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
|
||||
cache.eg_sustain <<= 5;
|
||||
|
||||
// determine KSR adjustment for enevlope rates
|
||||
uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3);
|
||||
cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval);
|
||||
cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval);
|
||||
cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval);
|
||||
cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// compute_phase_step - compute the phase step
|
||||
//-------------------------------------------------
|
||||
|
||||
uint32_t opm_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
|
||||
{
|
||||
// OPM logic is rather unique here, due to extra detune
|
||||
// and the use of key codes (not to be confused with keycode)
|
||||
|
||||
// start with coarse detune delta; table uses cents value from
|
||||
// manual, converted into 1/64ths
|
||||
static const int16_t s_detune2_delta[4] = { 0, (600*64+50)/100, (781*64+50)/100, (950*64+50)/100 };
|
||||
int32_t delta = s_detune2_delta[op_detune2(opoffs)];
|
||||
|
||||
// add in the PM delta
|
||||
uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs);
|
||||
if (pm_sensitivity != 0)
|
||||
{
|
||||
// raw PM value is -127..128 which is +/- 200 cents
|
||||
// manual gives these magnitudes in cents:
|
||||
// 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700
|
||||
// this roughly corresponds to shifting the 200-cent value:
|
||||
// 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2
|
||||
if (pm_sensitivity < 6)
|
||||
delta += lfo_raw_pm >> (6 - pm_sensitivity);
|
||||
else
|
||||
delta += uint32_t(lfo_raw_pm) << (pm_sensitivity - 5);
|
||||
}
|
||||
|
||||
// apply delta and convert to a frequency number
|
||||
uint32_t phase_step = opm_key_code_to_phase_step(cache.block_freq, delta);
|
||||
|
||||
// apply detune based on the keycode
|
||||
phase_step += cache.detune;
|
||||
|
||||
// apply frequency multiplier (which is cached as an x.1 value)
|
||||
return (phase_step * cache.multiple) >> 1;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// log_keyon - log a key-on event
|
||||
//-------------------------------------------------
|
||||
|
||||
std::string opm_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
|
||||
{
|
||||
uint32_t chnum = choffs;
|
||||
uint32_t opnum = opoffs;
|
||||
|
||||
char buffer[256];
|
||||
int end = 0;
|
||||
|
||||
end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X dt2=%u dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
|
||||
chnum, opnum,
|
||||
ch_block_freq(choffs),
|
||||
op_detune2(opoffs),
|
||||
op_detune(opoffs),
|
||||
ch_feedback(choffs),
|
||||
ch_algorithm(choffs),
|
||||
op_multiple(opoffs),
|
||||
op_total_level(opoffs),
|
||||
op_ksr(opoffs),
|
||||
op_attack_rate(opoffs),
|
||||
op_decay_rate(opoffs),
|
||||
op_sustain_rate(opoffs),
|
||||
op_release_rate(opoffs),
|
||||
op_sustain_level(opoffs),
|
||||
ch_output_0(choffs) ? 'L' : '-',
|
||||
ch_output_1(choffs) ? 'R' : '-');
|
||||
|
||||
bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0);
|
||||
if (am)
|
||||
end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth());
|
||||
bool pm = (lfo_pm_depth() != 0 && ch_lfo_pm_sens(choffs) != 0);
|
||||
if (pm)
|
||||
end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth());
|
||||
if (am || pm)
|
||||
end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]);
|
||||
if (noise_enable() && opoffs == 31)
|
||||
end += snprintf(&buffer[end], sizeof(buffer) - end, " noise=1");
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// YM2151
|
||||
//*********************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// ym2151 - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
ym2151::ym2151(ymfm_interface &intf, opm_variant variant) :
|
||||
m_variant(variant),
|
||||
m_address(0),
|
||||
m_fm(intf)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// reset - reset the system
|
||||
//-------------------------------------------------
|
||||
|
||||
void ym2151::reset()
|
||||
{
|
||||
// reset the engines
|
||||
m_fm.reset();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// save_restore - save or restore the data
|
||||
//-------------------------------------------------
|
||||
|
||||
void ym2151::save_restore(ymfm_saved_state &state)
|
||||
{
|
||||
m_fm.save_restore(state);
|
||||
state.save_restore(m_address);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// read_status - read the status register
|
||||
//-------------------------------------------------
|
||||
|
||||
uint8_t ym2151::read_status()
|
||||
{
|
||||
uint8_t result = m_fm.status();
|
||||
if (m_fm.intf().ymfm_is_busy())
|
||||
result |= fm_engine::STATUS_BUSY;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// read - handle a read from the device
|
||||
//-------------------------------------------------
|
||||
|
||||
uint8_t ym2151::read(uint32_t offset)
|
||||
{
|
||||
uint8_t result = 0xff;
|
||||
switch (offset & 1)
|
||||
{
|
||||
case 0: // data port (unused)
|
||||
debug::log_unexpected_read_write("Unexpected read from YM2151 offset %d\n", offset & 3);
|
||||
break;
|
||||
|
||||
case 1: // status port, YM2203 compatible
|
||||
result = read_status();
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// write_address - handle a write to the address
|
||||
// register
|
||||
//-------------------------------------------------
|
||||
|
||||
void ym2151::write_address(uint8_t data)
|
||||
{
|
||||
// just set the address
|
||||
m_address = data;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// write - handle a write to the register
|
||||
// interface
|
||||
//-------------------------------------------------
|
||||
|
||||
void ym2151::write_data(uint8_t data)
|
||||
{
|
||||
// write the FM register
|
||||
m_fm.write(m_address, data);
|
||||
|
||||
// special cases
|
||||
if (m_address == 0x1b)
|
||||
{
|
||||
// writes to register 0x1B send the upper 2 bits to the output lines
|
||||
m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data >> 6);
|
||||
}
|
||||
|
||||
// mark busy for a bit
|
||||
m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// write - handle a write to the register
|
||||
// interface
|
||||
//-------------------------------------------------
|
||||
|
||||
void ym2151::write(uint32_t offset, uint8_t data)
|
||||
{
|
||||
switch (offset & 1)
|
||||
{
|
||||
case 0: // address port
|
||||
write_address(data);
|
||||
break;
|
||||
|
||||
case 1: // data port
|
||||
write_data(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// generate - generate one sample of sound
|
||||
//-------------------------------------------------
|
||||
|
||||
void ym2151::generate(output_data *output, uint32_t numsamples)
|
||||
{
|
||||
for (uint32_t samp = 0; samp < numsamples; samp++, output++)
|
||||
{
|
||||
// clock the system
|
||||
m_fm.clock(fm_engine::ALL_CHANNELS);
|
||||
|
||||
// update the FM content; OPM is full 14-bit with no intermediate clipping
|
||||
m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS);
|
||||
|
||||
// YM2151 uses an external DAC (YM3012) with mantissa/exponent format
|
||||
// convert to 10.3 floating point value and back to simulate truncation
|
||||
output->roundtrip_fp();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue