Merge branch 'master' into ymf289b
This commit is contained in:
commit
25eb720631
|
@ -24,7 +24,7 @@ jobs:
|
|||
#- { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 }
|
||||
- { name: 'macOS x86_64', os: macos-latest, arch: x86_64 }
|
||||
- { name: 'macOS ARM', os: macos-latest, arch: arm64 }
|
||||
- { name: 'Linux x86_64', os: ubuntu-18.04, arch: x86_64 }
|
||||
- { name: 'Linux x86_64', os: ubuntu-20.04, arch: x86_64 }
|
||||
#- { name: 'Linux ARM', os: ubuntu-18.04, arch: armhf }
|
||||
fail-fast: false
|
||||
|
||||
|
|
|
@ -22,3 +22,5 @@ android/app/.cxx/
|
|||
CMakeSettings.json
|
||||
CMakePresets.json
|
||||
extern/imgui_patched/examples/
|
||||
src/asm/68k/amigatest/*.bin
|
||||
src/asm/68k/amigatest/player
|
||||
|
|
|
@ -459,6 +459,8 @@ src/engine/platform/sound/ga20/iremga20.cpp
|
|||
|
||||
src/engine/platform/sound/sm8521.c
|
||||
|
||||
src/engine/platform/sound/d65modified.c
|
||||
|
||||
src/engine/platform/oplAInterface.cpp
|
||||
src/engine/platform/ym2608Interface.cpp
|
||||
src/engine/platform/ym2610Interface.cpp
|
||||
|
@ -467,10 +469,12 @@ src/engine/blip_buf.c
|
|||
src/engine/brrUtils.c
|
||||
src/engine/safeReader.cpp
|
||||
src/engine/safeWriter.cpp
|
||||
src/engine/cmdStream.cpp
|
||||
src/engine/config.cpp
|
||||
src/engine/configEngine.cpp
|
||||
src/engine/dispatchContainer.cpp
|
||||
src/engine/engine.cpp
|
||||
src/engine/export.cpp
|
||||
src/engine/fileOps.cpp
|
||||
src/engine/fileOpsIns.cpp
|
||||
src/engine/filter.cpp
|
||||
|
@ -486,6 +490,7 @@ src/engine/waveSynth.cpp
|
|||
src/engine/vgmOps.cpp
|
||||
src/engine/zsmOps.cpp
|
||||
src/engine/zsm.cpp
|
||||
|
||||
src/engine/platform/abstract.cpp
|
||||
src/engine/platform/genesis.cpp
|
||||
src/engine/platform/genesisext.cpp
|
||||
|
@ -528,6 +533,7 @@ src/engine/platform/t6w28.cpp
|
|||
src/engine/platform/vb.cpp
|
||||
src/engine/platform/vera.cpp
|
||||
src/engine/platform/zxbeeper.cpp
|
||||
src/engine/platform/zxbeeperquadtone.cpp
|
||||
src/engine/platform/bubsyswsg.cpp
|
||||
src/engine/platform/n163.cpp
|
||||
src/engine/platform/pet.cpp
|
||||
|
@ -544,8 +550,12 @@ src/engine/platform/snes.cpp
|
|||
src/engine/platform/k007232.cpp
|
||||
src/engine/platform/ga20.cpp
|
||||
src/engine/platform/sm8521.cpp
|
||||
src/engine/platform/pv1000.cpp
|
||||
src/engine/platform/pcmdac.cpp
|
||||
src/engine/platform/dummy.cpp
|
||||
|
||||
src/engine/export/abstract.cpp
|
||||
src/engine/export/amigaValidation.cpp
|
||||
)
|
||||
|
||||
if (USE_SNDFILE)
|
||||
|
@ -615,6 +625,7 @@ src/gui/editing.cpp
|
|||
src/gui/editControls.cpp
|
||||
src/gui/effectList.cpp
|
||||
src/gui/findReplace.cpp
|
||||
src/gui/fmPreview.cpp
|
||||
src/gui/gradient.cpp
|
||||
src/gui/grooves.cpp
|
||||
src/gui/insEdit.cpp
|
||||
|
@ -780,6 +791,8 @@ endif()
|
|||
|
||||
if(ANDROID AND NOT TERMUX)
|
||||
add_library(furnace SHARED ${USED_SOURCES})
|
||||
elseif(WIN32)
|
||||
add_executable(furnace WIN32 ${USED_SOURCES})
|
||||
else()
|
||||
add_executable(furnace ${USED_SOURCES})
|
||||
endif()
|
||||
|
|
|
@ -47,6 +47,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
|
|||
- OKI MSM6258 and MSM6295
|
||||
- Konami K007232
|
||||
- Irem GA20
|
||||
- Ensoniq ES5506
|
||||
- wavetable chips:
|
||||
- HuC6280 used in PC Engine
|
||||
- Konami Bubble System WSG
|
||||
|
@ -54,6 +55,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
|
|||
- Namco arcade chips (WSG/C15/C30)
|
||||
- WonderSwan
|
||||
- Seta/Allumer X1-010
|
||||
- Sharp SM8521 used in Tiger Game.com
|
||||
- NES (Ricoh 2A03/2A07), with additional expansion sound support:
|
||||
- Konami VRC6
|
||||
- Konami VRC7
|
||||
|
@ -78,7 +80,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
|
|||
- over 200 ready to use presets from computers, game consoles and arcade boards...
|
||||
- ...or create your own - up to 32 of them or a total of 128 channels!
|
||||
- DefleMask compatibility
|
||||
- loads .dmf modules from all versions (beta 1 to 1.1.5)
|
||||
- loads .dmf modules from all versions (beta 1 to 1.1.7)
|
||||
- saves .dmf modules - both modern and legacy
|
||||
- Furnace doubles as a module downgrader
|
||||
- loads/saves .dmp instruments and .dmw wavetables as well
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# to-do for 0.6pre5
|
||||
|
||||
- tutorial
|
||||
- ease-of-use improvements... ideas:
|
||||
- preset compat flags
|
||||
- setting to toggle the Choose a System screen on new project
|
||||
- maybe reduced set of presets for the sake of simplicity
|
||||
- a more preferable highlight/drag system
|
||||
- some speed/intuitive workflow improvements that go a long way
|
||||
- Had a hard time finding the docs on github and in Furnace's folder.
|
||||
- make .pdf manual out of papers/doc/
|
||||
- you're going too fast; please slow down
|
||||
- break compatibility if it relieves complexity
|
||||
- ins/wave/sample organization (folders and all)
|
||||
- multi-key binds
|
||||
- bug fixes
|
|
@ -15,8 +15,8 @@ android {
|
|||
}
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 26
|
||||
versionCode 136
|
||||
versionName "dev136"
|
||||
versionCode 143
|
||||
versionName "0.6pre4"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.tildearrow.furnace"
|
||||
android:versionCode="133"
|
||||
android:versionName="0.6pre3"
|
||||
android:versionCode="143"
|
||||
android:versionName="0.6pre4"
|
||||
android:installLocation="auto">
|
||||
|
||||
<!-- OpenGL ES 2.0 -->
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -26,6 +26,7 @@ SOFTWARE.
|
|||
*/
|
||||
|
||||
#include "ImGuiFileDialog.h"
|
||||
#include "../../src/ta-log.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
|
@ -132,8 +133,11 @@ namespace IGFD
|
|||
#define resetButtonString ICON_FA_REPEAT
|
||||
#endif // resetButtonString
|
||||
#ifndef drivesButtonString
|
||||
#define drivesButtonString "Drives"
|
||||
#define drivesButtonString ICON_FA_HDD_O
|
||||
#endif // drivesButtonString
|
||||
#ifndef parentDirString
|
||||
#define parentDirString ICON_FA_CHEVRON_UP
|
||||
#endif // parentDirString
|
||||
#ifndef editPathButtonString
|
||||
#define editPathButtonString ICON_FA_PENCIL
|
||||
#endif // editPathButtonString
|
||||
|
@ -167,6 +171,9 @@ namespace IGFD
|
|||
#ifndef buttonResetPathString
|
||||
#define buttonResetPathString "Reset to current directory"
|
||||
#endif // buttonResetPathString
|
||||
#ifndef buttonParentDirString
|
||||
#define buttonParentDirString "Go to parent directory"
|
||||
#endif
|
||||
#ifndef buttonCreateDirString
|
||||
#define buttonCreateDirString "Create Directory"
|
||||
#endif // buttonCreateDirString
|
||||
|
@ -602,6 +609,10 @@ namespace IGFD
|
|||
res.ext = pfn.substr(lastPoint + 1);
|
||||
IGFD::Utils::ReplaceString(res.name, "." + res.ext, "");
|
||||
}
|
||||
|
||||
if (res.path.empty()) {
|
||||
res.path=separator;
|
||||
}
|
||||
|
||||
if (!res.isOk)
|
||||
{
|
||||
|
@ -1191,12 +1202,14 @@ namespace IGFD
|
|||
if (puDLGDirectoryMode) // directory mode
|
||||
SetDefaultFileName(".");
|
||||
else
|
||||
SetDefaultFileName(puDLGDefaultFileName);
|
||||
SetDefaultFileName("");
|
||||
logV("IGFD: OpenCurrentPath()");
|
||||
ScanDir(vFileDialogInternal, GetCurrentPath());
|
||||
}
|
||||
|
||||
void IGFD::FileManager::SortFields(const FileDialogInternal& vFileDialogInternal, const SortingFieldEnum& vSortingField, const bool vCanChangeOrder)
|
||||
{
|
||||
logV("IGFD: SortFields()");
|
||||
if (vSortingField != SortingFieldEnum::FIELD_NONE)
|
||||
{
|
||||
puHeaderFileName = tableHeaderFileNameString;
|
||||
|
@ -1206,10 +1219,17 @@ namespace IGFD
|
|||
#ifdef USE_THUMBNAILS
|
||||
puHeaderFileThumbnails = tableHeaderFileThumbnailsString;
|
||||
#endif // #ifdef USE_THUMBNAILS
|
||||
}
|
||||
} else {
|
||||
logV("IGFD: sorting by NONE!");
|
||||
}
|
||||
|
||||
if (prFileList.empty()) {
|
||||
logV("IGFD: with an empty file list?");
|
||||
}
|
||||
|
||||
if (vSortingField == SortingFieldEnum::FIELD_FILENAME)
|
||||
{
|
||||
logV("IGFD: sorting by name");
|
||||
if (vCanChangeOrder && puSortingField == vSortingField) {
|
||||
//printf("Change the sorting\n");
|
||||
puSortingDirection[0] = true;//!puSortingDirection[0];
|
||||
|
@ -1279,6 +1299,7 @@ namespace IGFD
|
|||
}
|
||||
else if (vSortingField == SortingFieldEnum::FIELD_TYPE)
|
||||
{
|
||||
logV("IGFD: sorting by type");
|
||||
if (vCanChangeOrder && puSortingField == vSortingField)
|
||||
puSortingDirection[1] = !puSortingDirection[1];
|
||||
|
||||
|
@ -1317,6 +1338,7 @@ namespace IGFD
|
|||
}
|
||||
else if (vSortingField == SortingFieldEnum::FIELD_SIZE)
|
||||
{
|
||||
logV("IGFD: sorting by size");
|
||||
if (vCanChangeOrder && puSortingField == vSortingField)
|
||||
puSortingDirection[2] = !puSortingDirection[2];
|
||||
|
||||
|
@ -1357,6 +1379,7 @@ namespace IGFD
|
|||
}
|
||||
else if (vSortingField == SortingFieldEnum::FIELD_DATE)
|
||||
{
|
||||
logV("IGFD: sorting by date");
|
||||
if (vCanChangeOrder && puSortingField == vSortingField)
|
||||
puSortingDirection[3] = !puSortingDirection[3];
|
||||
|
||||
|
@ -1448,6 +1471,8 @@ namespace IGFD
|
|||
puSortingField = vSortingField;
|
||||
}
|
||||
|
||||
logV("IGFD: applying filtering on file list");
|
||||
|
||||
ApplyFilteringOnFileList(vFileDialogInternal);
|
||||
}
|
||||
|
||||
|
@ -1475,7 +1500,7 @@ namespace IGFD
|
|||
infos->fileNameExt_optimized = prOptimizeFilenameForSearchOperations(infos->fileNameExt);
|
||||
infos->fileType = vFileType;
|
||||
|
||||
if (infos->fileNameExt.empty() || (infos->fileNameExt == "." && !vFileDialogInternal.puFilterManager.puDLGFilters.empty())) return; // filename empty or filename is the current dir '.' //-V807
|
||||
if (infos->fileNameExt.empty() || ((infos->fileNameExt == "." || infos->fileNameExt == "..") && !vFileDialogInternal.puFilterManager.puDLGFilters.empty())) return; // filename empty or filename is the current dir '.' //-V807
|
||||
if (infos->fileNameExt != ".." && (vFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DontShowHiddenFiles) && infos->fileNameExt[0] == '.') // dont show hidden files
|
||||
if (!vFileDialogInternal.puFilterManager.puDLGFilters.empty() || (vFileDialogInternal.puFilterManager.puDLGFilters.empty() && infos->fileNameExt != ".")) // except "." if in directory mode //-V728
|
||||
return;
|
||||
|
@ -1508,14 +1533,17 @@ namespace IGFD
|
|||
void IGFD::FileManager::ScanDir(const FileDialogInternal& vFileDialogInternal, const std::string& vPath)
|
||||
{
|
||||
std::string path = vPath;
|
||||
logV("IGFD: ScanDir(%s)",vPath);
|
||||
|
||||
if (prCurrentPathDecomposition.empty())
|
||||
{
|
||||
logV("IGFD: the current path decomposition is empty. setting.");
|
||||
SetCurrentDir(path);
|
||||
}
|
||||
|
||||
if (!prCurrentPathDecomposition.empty())
|
||||
{
|
||||
logV("IGFD: the current path decomposition is not empty. trying.");
|
||||
#ifdef WIN32
|
||||
if (path == puFsRoot)
|
||||
path += std::string(1u, PATH_SEP);
|
||||
|
@ -1543,6 +1571,7 @@ namespace IGFD
|
|||
#else // dirent
|
||||
struct dirent** files = nullptr;
|
||||
int n = scandir(path.c_str(), &files, nullptr, inAlphaSort);
|
||||
logV("IGFD: %d entries in directory",n);
|
||||
if (n>0)
|
||||
{
|
||||
int i;
|
||||
|
@ -1610,11 +1639,18 @@ namespace IGFD
|
|||
}
|
||||
|
||||
free(files);
|
||||
}
|
||||
} else {
|
||||
logV("IGFD: it's empty");
|
||||
}
|
||||
#endif // USE_STD_FILESYSTEM
|
||||
|
||||
logV("IGFD: sorting fields...");
|
||||
SortFields(vFileDialogInternal, puSortingField, false);
|
||||
}
|
||||
} else {
|
||||
logE("IGFD: current path decomposition is empty!");
|
||||
}
|
||||
|
||||
fileListActuallyEmpty=prFileList.empty();
|
||||
}
|
||||
|
||||
bool IGFD::FileManager::GetDrives()
|
||||
|
@ -2217,6 +2253,16 @@ namespace IGFD
|
|||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip(buttonResetPathString);
|
||||
|
||||
ImGui::SameLine();
|
||||
if (IMGUI_BUTTON(parentDirString))
|
||||
{
|
||||
if (SetPathOnParentDirectoryIfAny()) {
|
||||
OpenCurrentPath(vFileDialogInternal);
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip(buttonParentDirString);
|
||||
|
||||
#ifdef WIN32
|
||||
ImGui::SameLine();
|
||||
|
||||
|
@ -2416,7 +2462,7 @@ namespace IGFD
|
|||
|
||||
void IGFD::FileDialogInternal::ResetForNewDialog()
|
||||
{
|
||||
|
||||
puFileManager.fileListActuallyEmpty=false;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -3401,7 +3447,7 @@ namespace IGFD
|
|||
if (ps.isOk)
|
||||
{
|
||||
prFileDialogInternal.puFileManager.puDLGpath = ps.path;
|
||||
prFileDialogInternal.puFileManager.SetDefaultFileName(vFilePathName);
|
||||
prFileDialogInternal.puFileManager.SetDefaultFileName("");
|
||||
prFileDialogInternal.puFilterManager.puDLGdefaultExt = "." + ps.ext;
|
||||
}
|
||||
else
|
||||
|
@ -3717,16 +3763,17 @@ namespace IGFD
|
|||
fdFilter.SetDefaultFilterIfNotDefined();
|
||||
|
||||
// init list of files
|
||||
if (fdFile.IsFileListEmpty() && !fdFile.puShowDrives)
|
||||
if (fdFile.IsFileListEmpty() && !fdFile.puShowDrives && !fdFile.fileListActuallyEmpty)
|
||||
{
|
||||
IGFD::Utils::ReplaceString(fdFile.puDLGDefaultFileName, fdFile.puDLGpath, ""); // local path
|
||||
if (!fdFile.puDLGDefaultFileName.empty())
|
||||
{
|
||||
fdFile.SetDefaultFileName(fdFile.puDLGDefaultFileName);
|
||||
fdFilter.SetSelectedFilterWithExt(fdFilter.puDLGdefaultExt);
|
||||
}
|
||||
else if (fdFile.puDLGDirectoryMode) // directory mode
|
||||
} else if (fdFile.puDLGDirectoryMode) { // directory mode
|
||||
fdFile.SetDefaultFileName(".");
|
||||
}
|
||||
logV("IGFD: fdFile.IsFileListEmpty() and !fdFile.puShowDrives");
|
||||
fdFile.ScanDir(prFileDialogInternal, fdFile.puDLGpath);
|
||||
}
|
||||
|
||||
|
|
|
@ -875,6 +875,7 @@ namespace IGFD
|
|||
#endif
|
||||
SortingFieldEnum puSortingField = SortingFieldEnum::FIELD_FILENAME; // detail view sorting column
|
||||
bool puShowDrives = false; // drives are shown (only on os windows)
|
||||
bool fileListActuallyEmpty = false;
|
||||
|
||||
std::string puDLGpath; // base path set by user when OpenDialog/OpenModal was called
|
||||
std::string puDLGDefaultFileName; // base default file path name set by user when OpenDialog/OpenModal was called
|
||||
|
|
|
@ -94,13 +94,14 @@ static void ImGui_ImplSDLRenderer_SetupRenderState()
|
|||
SDL_RenderSetClipRect(bd->SDLRenderer, NULL);
|
||||
}
|
||||
|
||||
void ImGui_ImplSDLRenderer_NewFrame()
|
||||
bool ImGui_ImplSDLRenderer_NewFrame()
|
||||
{
|
||||
ImGui_ImplSDLRenderer_Data* bd = ImGui_ImplSDLRenderer_GetBackendData();
|
||||
IM_ASSERT(bd != NULL && "Did you call ImGui_ImplSDLRenderer_Init()?");
|
||||
|
||||
if (!bd->FontTexture)
|
||||
ImGui_ImplSDLRenderer_CreateDeviceObjects();
|
||||
return ImGui_ImplSDLRenderer_CreateDeviceObjects();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplSDLRenderer_RenderDrawData(ImDrawData* draw_data)
|
||||
|
|
|
@ -21,7 +21,7 @@ struct SDL_Renderer;
|
|||
|
||||
IMGUI_IMPL_API bool ImGui_ImplSDLRenderer_Init(SDL_Renderer* renderer);
|
||||
IMGUI_IMPL_API void ImGui_ImplSDLRenderer_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplSDLRenderer_NewFrame();
|
||||
IMGUI_IMPL_API bool ImGui_ImplSDLRenderer_NewFrame();
|
||||
IMGUI_IMPL_API void ImGui_ImplSDLRenderer_RenderDrawData(ImDrawData* draw_data);
|
||||
|
||||
// Called by Init/NewFrame/Shutdown
|
||||
|
|
|
@ -837,6 +837,8 @@ CODE
|
|||
#include <stdint.h> // intptr_t
|
||||
#endif
|
||||
|
||||
#include "../../src/fileutils.h"
|
||||
|
||||
// [Windows] On non-Visual Studio compilers, we default to IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS unless explicitly enabled
|
||||
#if defined(_WIN32) && !defined(_MSC_VER) && !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)
|
||||
#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS
|
||||
|
@ -12457,14 +12459,47 @@ void ImGui::ClearIniSettings()
|
|||
g.SettingsHandlers[handler_n].ClearAllFn(&g, &g.SettingsHandlers[handler_n]);
|
||||
}
|
||||
|
||||
void ImGui::LoadIniSettingsFromDisk(const char* ini_filename)
|
||||
bool ImGui::LoadIniSettingsFromDisk(const char* ini_filename, bool redundancy)
|
||||
{
|
||||
size_t file_data_size = 0;
|
||||
char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size);
|
||||
char* file_data = NULL;
|
||||
|
||||
if (redundancy) {
|
||||
char fileName[4096];
|
||||
|
||||
for (int i=0; i<5; i++) {
|
||||
bool viable=false;
|
||||
if (i>0) {
|
||||
snprintf(fileName,4095,"%s.%d",ini_filename,i);
|
||||
} else {
|
||||
strncpy(fileName,ini_filename,4095);
|
||||
}
|
||||
file_data=(char*)ImFileLoadToMemory(fileName, "rb", &file_data_size);
|
||||
if (!file_data) continue;
|
||||
|
||||
for (size_t j=0; j<file_data_size; j++) {
|
||||
if (file_data[j]!='\r' && file_data[j]!='\n' && file_data[j]!=' ') {
|
||||
viable=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!viable) {
|
||||
IM_FREE(file_data);
|
||||
file_data=NULL;
|
||||
file_data_size=0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
file_data=(char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size);
|
||||
}
|
||||
if (!file_data)
|
||||
return;
|
||||
return false;
|
||||
LoadIniSettingsFromMemory(file_data, (size_t)file_data_size);
|
||||
IM_FREE(file_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Zero-tolerance, no error reporting, cheap .ini parsing
|
||||
|
@ -12538,20 +12573,45 @@ void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size)
|
|||
g.SettingsHandlers[handler_n].ApplyAllFn(&g, &g.SettingsHandlers[handler_n]);
|
||||
}
|
||||
|
||||
void ImGui::SaveIniSettingsToDisk(const char* ini_filename)
|
||||
bool ImGui::SaveIniSettingsToDisk(const char* ini_filename, bool redundancy)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
g.SettingsDirtyTimer = 0.0f;
|
||||
if (!ini_filename)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (redundancy) {
|
||||
char oldPath[4096];
|
||||
char newPath[4096];
|
||||
|
||||
if (fileExists(ini_filename)==1) {
|
||||
for (int i=4; i>=0; i--) {
|
||||
if (i>0) {
|
||||
snprintf(oldPath,4095,"%s.%d",ini_filename,i);
|
||||
} else {
|
||||
strncpy(oldPath,ini_filename,4095);
|
||||
}
|
||||
snprintf(newPath,4095,"%s.%d",ini_filename,i+1);
|
||||
|
||||
if (i>=4) {
|
||||
deleteFile(oldPath);
|
||||
} else {
|
||||
moveFiles(oldPath,newPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t ini_data_size = 0;
|
||||
const char* ini_data = SaveIniSettingsToMemory(&ini_data_size);
|
||||
ImFileHandle f = ImFileOpen(ini_filename, "wt");
|
||||
if (!f)
|
||||
return;
|
||||
ImFileWrite(ini_data, sizeof(char), ini_data_size, f);
|
||||
return false;
|
||||
bool areEqual=ImFileWrite(ini_data, sizeof(char), ini_data_size, f)==ini_data_size;
|
||||
IM_ASSERT_USER_ERROR(areEqual, "ImFileWrite failed to write file!");
|
||||
ImFileClose(f);
|
||||
if (!areEqual && redundancy) deleteFile(ini_filename);
|
||||
return areEqual;
|
||||
}
|
||||
|
||||
// Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer
|
||||
|
|
|
@ -950,9 +950,9 @@ namespace ImGui
|
|||
// - The disk functions are automatically called if io.IniFilename != NULL (default is "imgui.ini").
|
||||
// - Set io.IniFilename to NULL to load/save manually. Read io.WantSaveIniSettings description about handling .ini saving manually.
|
||||
// - Important: default value "imgui.ini" is relative to current working dir! Most apps will want to lock this to an absolute path (e.g. same path as executables).
|
||||
IMGUI_API void LoadIniSettingsFromDisk(const char* ini_filename); // call after CreateContext() and before the first call to NewFrame(). NewFrame() automatically calls LoadIniSettingsFromDisk(io.IniFilename).
|
||||
IMGUI_API bool LoadIniSettingsFromDisk(const char* ini_filename, bool redundancy=false); // call after CreateContext() and before the first call to NewFrame(). NewFrame() automatically calls LoadIniSettingsFromDisk(io.IniFilename).
|
||||
IMGUI_API void LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size=0); // call after CreateContext() and before the first call to NewFrame() to provide .ini data from your own data source.
|
||||
IMGUI_API void SaveIniSettingsToDisk(const char* ini_filename); // this is automatically called (if io.IniFilename is not empty) a few seconds after any modification that should be reflected in the .ini file (and also by DestroyContext).
|
||||
IMGUI_API bool SaveIniSettingsToDisk(const char* ini_filename, bool redundancy=false); // this is automatically called (if io.IniFilename is not empty) a few seconds after any modification that should be reflected in the .ini file (and also by DestroyContext).
|
||||
IMGUI_API const char* SaveIniSettingsToMemory(size_t* out_ini_size = NULL); // return a zero-terminated string with the .ini data which you can save by your own mean. call when io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings.
|
||||
|
||||
// Debug Utilities
|
||||
|
|
|
@ -95,13 +95,15 @@ static void ImGui_ImplSDLRenderer_SetupRenderState()
|
|||
SDL_RenderSetClipRect(bd->SDLRenderer, NULL);
|
||||
}
|
||||
|
||||
void ImGui_ImplSDLRenderer_NewFrame()
|
||||
bool ImGui_ImplSDLRenderer_NewFrame()
|
||||
{
|
||||
ImGui_ImplSDLRenderer_Data* bd = ImGui_ImplSDLRenderer_GetBackendData();
|
||||
IM_ASSERT(bd != NULL && "Did you call ImGui_ImplSDLRenderer_Init()?");
|
||||
|
||||
if (!bd->FontTexture)
|
||||
ImGui_ImplSDLRenderer_CreateDeviceObjects();
|
||||
return ImGui_ImplSDLRenderer_CreateDeviceObjects();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplSDLRenderer_RenderDrawData(ImDrawData* draw_data)
|
||||
|
|
|
@ -21,7 +21,7 @@ struct SDL_Renderer;
|
|||
|
||||
IMGUI_IMPL_API bool ImGui_ImplSDLRenderer_Init(SDL_Renderer* renderer);
|
||||
IMGUI_IMPL_API void ImGui_ImplSDLRenderer_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplSDLRenderer_NewFrame();
|
||||
IMGUI_IMPL_API bool ImGui_ImplSDLRenderer_NewFrame();
|
||||
IMGUI_IMPL_API void ImGui_ImplSDLRenderer_RenderDrawData(ImDrawData* draw_data);
|
||||
|
||||
// Called by Init/NewFrame/Shutdown
|
||||
|
|
|
@ -3112,8 +3112,9 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d
|
|||
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
|
||||
const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
|
||||
|
||||
const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
|
||||
ItemSize(bb, style.FramePadding.y);
|
||||
if (!ItemAdd(frame_bb, id, NULL, ImGuiItemFlags_NoInertialScroll))
|
||||
if (!ItemAdd(frame_bb, id, NULL, (temp_input_allowed ? ImGuiItemFlags_Inputable : 0) | ImGuiItemFlags_NoInertialScroll))
|
||||
return false;
|
||||
|
||||
// Default format string when passing NULL
|
||||
|
@ -3122,13 +3123,29 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d
|
|||
else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
|
||||
format = PatchFormatStringFloatToInt(format);
|
||||
|
||||
// Tabbing or CTRL-clicking on Slider turns it into an input box
|
||||
const bool hovered = ItemHoverable(frame_bb, id);
|
||||
if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavActivateInputId == id)
|
||||
bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
|
||||
if (!temp_input_is_active)
|
||||
{
|
||||
SetActiveID(id, window);
|
||||
SetFocusID(id, window);
|
||||
FocusWindow(window);
|
||||
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
|
||||
const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0;
|
||||
const bool clicked = (hovered && g.IO.MouseClicked[0]);
|
||||
if (input_requested_by_tabbing || clicked || g.NavActivateId == id || g.NavActivateInputId == id)
|
||||
{
|
||||
SetActiveID(id, window);
|
||||
SetFocusID(id, window);
|
||||
FocusWindow(window);
|
||||
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
|
||||
if (temp_input_allowed && (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || g.NavActivateInputId == id))
|
||||
temp_input_is_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (temp_input_is_active)
|
||||
{
|
||||
// Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
|
||||
const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0;
|
||||
return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
|
||||
}
|
||||
|
||||
// Draw frame
|
||||
|
@ -3154,6 +3171,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d
|
|||
if (label_size.x > 0.0f)
|
||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
|
||||
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
|
||||
return value_changed;
|
||||
}
|
||||
|
||||
|
|
|
@ -204,7 +204,9 @@ static void CopyNFDCharToWChar( const nfdchar_t *inStr, wchar_t **outStr )
|
|||
|
||||
#ifdef _DEBUG
|
||||
int inStrCharacterCount = static_cast<int>(NFDi_UTF8_Strlen(inStr));
|
||||
assert( ret == inStrCharacterCount );
|
||||
if (ret!=inStrCharacterCount) {
|
||||
logW("length does not match! %d != %d",ret,inStrCharacterCount);
|
||||
}
|
||||
#else
|
||||
_NFD_UNUSED(ret);
|
||||
#endif
|
||||
|
|
|
@ -78,10 +78,10 @@ C2: 31 9 0 11 15 0 0 1 3 0 0
|
|||
// vgm offset = 00001d22, channels used = 1-------
|
||||
@:8 Synth Bass 2
|
||||
LFO: 0 0 0 0 0
|
||||
CH: 64 5 4 0 0 120 0
|
||||
M1: 27 10 5 11 7 21 1 1 3 0 0
|
||||
C1: 31 0 15 11 7 3 0 1 3 0 0
|
||||
M2: 22 13 13 8 6 21 3 12 3 0 0
|
||||
C2: 31 15 16 11 8 13 0 2 3 0 0
|
||||
CH: 64 0 0 0 0 120 0
|
||||
M1: 22 12 20 8 15 39 3 12 3 0 0
|
||||
C1: 31 13 11 11 4 25 3 4 3 0 0
|
||||
M2: 27 0 5 11 7 23 1 1 3 0 0
|
||||
C2: 31 0 15 11 7 3 0 1 3 0 0
|
||||
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -121,8 +121,8 @@ ID | macro
|
|||
33 | KSR
|
||||
---|-----------------------------
|
||||
40 | operator 2 macros
|
||||
60 | operator 2 macros
|
||||
80 | operator 2 macros
|
||||
60 | operator 3 macros
|
||||
80 | operator 4 macros
|
||||
|
||||
the interpretation of duty, wave and extra macros depends on chip/instrument type:
|
||||
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
# wavetable editor
|
||||
|
||||
Wavetable synthesizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy.
|
||||
Wavetable synthesizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.6pre4, wavetable editor affects PC Engine, WonderSwan, Namco WSGs, Virtual Boy, Game.com, SCC, FDS, Seta X1-010, Konami Bubble System WSG, SNES, Amiga and channel 3 of Game Boy.
|
||||
|
||||
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System and N163, and 32-level height for PCE. If a larger wave is defined for these chips, it will be squashed to fit within the constraints of the chips.
|
||||
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan, Namco WSG, N163, Game.com, Virtual Boy and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System, SNES, Namco WSG and N163, 32-level height for PCE and 64-level height for Virtual Boy. If a larger wave is defined for these chips, it will be squashed to fit within the constraints of the chips.
|
||||
|
||||
Furnace's wavetable editor features multiple ways of creating desired waveform shape:
|
||||
|
||||
- Shape tab allows you to select a few predefined basic shapes and indirectly edit it via "Duty", "Exponent" and "XOR Point" sliders TODO: what the last two are doing? What is amplitude/phase for?)
|
||||
- FM is for creating the waveform with frequency modulation synthesis principles: One can set carrier/modulation levels, frquency multiplier, connection between operators and FM waveforms of these operators.
|
||||
- WaveTools allows user to fine-tune the waveform: scale said waveform in both X and Y axes, smoothen, amplify, normalize, convert to signed/unisgned, invert or even randomize the wavetable.
|
||||
|
||||
## wavetable synthesizer
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
# Ensoniq ES5506 (OTTO)
|
||||
|
||||
Sample-based synthesis chip used in a bunch of Taito arcade machines and PC sound cards like Soundscape Elite. A variant of it was the heart of the well-known Gravis Ultrasound.
|
||||
|
||||
it supports a whooping 32 channels of 16-bit PCM and:
|
||||
|
||||
- Real time digital filters
|
||||
- Frequency interpolation
|
||||
- Loop start and stop positions for each voice (bidirectional and reverse looping)
|
||||
- Internal volume multiplication and stereo panning
|
||||
- Hardware support for envelopes
|
||||
|
||||
# effects
|
||||
|
||||
- `10xx`: set waveform.
|
||||
- `11xx`: set filter mode (0-3)
|
||||
- `120x`: set pause (bit 0). Pauses the sample until the bit is unset, where it will then resume where it left off.
|
||||
- `14xx`: set filter coefficient K1 low byte.
|
||||
- `15xx`: set filter coefficient K1 high byte.
|
||||
- `16xx`: set filter coefficient K2 low byte.
|
||||
- `17xx`: set filter coefficient K2 high byte.
|
||||
- `18xx`: set filter coefficient K1 slide up.
|
||||
- `19xx`: set filter coefficient K1 slide down.
|
||||
- `1Axx`: set filter coefficient K2 slide up.
|
||||
- `1Bxx`: set filter coefficient K2 slide down.
|
||||
- `20xx`: set envelope count.
|
||||
- `22xx`: set envelope left volume ramp.
|
||||
- `23xx`: set envelope right volume ramp.
|
||||
- `24xx`: set envelope filter coefficient K1 ramp.
|
||||
- `25xx`: set envelope filter coefficient K1 ramp (slower).
|
||||
- `26xx`: set envelope filter coefficient K2 ramp.
|
||||
- `27xx`: set envelope filter coefficient K2 ramp (slower).
|
||||
- `3xxx`: set coarse filter coefficient K1.
|
||||
- `4xxx`: set coarse filter coefficient K2.
|
||||
- `81xx`: set panning (left channel).
|
||||
- `82xx`: set panning (right channel).
|
||||
- `88xx`: set panning (rear channels).
|
||||
- `89xx`: set panning (rear left channel).
|
||||
- `8Axx`: set panning (rear right channel).
|
||||
- `9xxx`: set sample offset (x256).
|
||||
- `DFxx`: set sample playback direction.
|
|
@ -42,6 +42,33 @@ also known as Famicom. it is a five-channel sound generator: first two channels
|
|||
- `00`: PCM (software).
|
||||
- `01`: DPCM (hardware).
|
||||
- when in DPCM mode, samples will sound muffled (due to its nature), availables pitches are limited and loop point is ignored.
|
||||
- `19xx`: set triangle linear counter.
|
||||
- `00` to `7F` set the counter.
|
||||
- `80` and higher halt it.
|
||||
- `20xx`: set DPCM frequency.
|
||||
- only works in DPCM mode.
|
||||
- see table below for possible values.
|
||||
|
||||
# DPCM frequency table
|
||||
|
||||
val | NTSC | PAL
|
||||
----|-----------|-----------
|
||||
00 | 4181.7Hz | 4177.4Hz
|
||||
01 | 4709.9Hz | 4696.6Hz
|
||||
02 | 5264.0Hz | 5261.4Hz
|
||||
03 | 5593.0Hz | 5579.2Hz
|
||||
04 | 6257.9Hz | 6023.9Hz
|
||||
05 | 7046.3Hz | 7044.9Hz
|
||||
06 | 7919.3Hz | 7917.2Hz
|
||||
07 | 8363.4Hz | 8397.0Hz
|
||||
08 | 9419.9Hz | 9446.6Hz
|
||||
09 | 11186.1Hz | 11233.8Hz
|
||||
0A | 12604.0Hz | 12595.5Hz
|
||||
0B | 13982.6Hz | 14089.9Hz
|
||||
0C | 16884.6Hz | 16965.4Hz
|
||||
0D | 21306.8Hz | 21315.5Hz
|
||||
0E | 24858.0Hz | 25191.0Hz
|
||||
0F | 33143.9Hz | 33252.1Hz
|
||||
|
||||
# length counter table
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ because the chip lacks sample interpolation, it is recommended that you try to p
|
|||
|
||||
the QSound chip also has a small echo buffer, somewhat similar to the SNES, although with a very basic (and non-adjustable) filter. it is however possible to adjust the feedback and length of the echo buffer (the initial values can be set in the "configure chip" option in the file menu or the chip manager).
|
||||
|
||||
there are also 3 ADPCM channels, however they cannot be used in Furnace yet. they have been reserved in case this feature is added later. ADPCM samples are limited to 8012 Hz.
|
||||
there are also 3 ADPCM channels. ADPCM samples are fixed to 8012 Hz.
|
||||
|
||||
# effects
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# Sharp SM8521
|
||||
|
||||
The SM8521 is the CPU and sound chip of the Game.com, a handheld console released in 1997 as a competitor to the infamous Nintendo Virtual Boy.
|
||||
|
||||
Ultimately, most of the games for the Game.com ended up being failiures in the eyes of reviewers, thus giving the Game.com a pretty bad reputation. This was one of the reasons that the Game.com only ended up selling at least 300,000 units. For these reasons and more, the Game.com ended up being discontinued in 2000.
|
||||
|
||||
However, for its time, it was a pretty competitively priced system. The Gameboy Color was to be released in a year for $79.95, while the Game.com was released for $69.99, and its later model, the Pocket Pro, was released in mid-1999 for $29.99 due to the Game.com's apparent significant decrease in value.
|
||||
|
||||
In fact, most games never used the wavetable/noise mode of the chip. Sonic Jam, for example, uses a sine wave with a software-controlled volume envelope on the DAC channel (see below for more information on the DAC channel).
|
||||
|
||||
The sound-related features and quirks of the SM8521 are as follows:
|
||||
- 2 4-bit wavetable channels
|
||||
- a noise channel (which can go up to a very high pitch, creating an almost periodic noise sound)
|
||||
- 5-bit volume
|
||||
- A low bit-depth output (which means it distorts a lot).
|
||||
- It phase resets when you switch waves
|
||||
- 12-bit pitch with a wide frequency range
|
||||
- A software-controlled D/A register that (potentially) requires all other registers to be stopped to play. Due to this, it is currently, it is not implemented in Furnace as of version 0.6pre4.
|
||||
|
||||
## effect commands
|
||||
|
||||
- `10xx` Set waveform
|
||||
- `xx` is a value between 0 and 255, that sets the waveform of the channel you place it on.
|
|
@ -54,6 +54,10 @@ Furnace also allows the SNES to use wavetables (and the wavetable synthesizer) i
|
|||
- 80 to FF for -128 to -1.
|
||||
- setting this to -128 is not recommended as it may cause echo output to overflow and therefore click.
|
||||
- `1Dxx`: set noise generator frequency (00 to 1F).
|
||||
- `1Exx`: set left dry/global volume.
|
||||
- this does not affect echo.
|
||||
- `1Fxx`: set right dry/global volume.
|
||||
- this does not affect echo.
|
||||
- `20xx`: set attack (0 to F).
|
||||
- only in ADSR envelope mode.
|
||||
- `21xx`: set decay (0 to 7).
|
||||
|
|
|
@ -69,10 +69,13 @@ hex | description
|
|||
.. | ...
|
||||
ef | preset delay 15
|
||||
----|------------------------------------
|
||||
f4 | call symbol (16-bit index follows; only used internally)
|
||||
f5 | jump to sub-block (address follows)
|
||||
f6 | go to sub-block (32-bit offset follows)
|
||||
f7 | full command (command and data follows)
|
||||
f8 | go to sub-block (offset follows)
|
||||
f8 | go to sub-block (16-bit offset follows)
|
||||
f9 | return from sub-block
|
||||
fa | jump (offset follows)
|
||||
fa | jump (address follows)
|
||||
fb | set tick rate (4 bytes)
|
||||
fc | wait (16-bit)
|
||||
fd | wait (8-bit)
|
||||
|
|
|
@ -32,6 +32,19 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
|
|||
|
||||
the format versions are:
|
||||
|
||||
- 155: Furnace dev155
|
||||
- 154: Furnace dev154
|
||||
- 153: Furnace dev153
|
||||
- 152: Furnace dev152
|
||||
- 151: Furnace dev151
|
||||
- 150: Furnace dev150
|
||||
- 149: Furnace dev149
|
||||
- 148: Furnace dev148
|
||||
- 147: Furnace dev147
|
||||
- 146: Furnace Pro (joke version)
|
||||
- 145: Furnace dev145
|
||||
- 144: Furnace dev144
|
||||
- 143: Furnace 0.6pre4
|
||||
- 142: Furnace dev142
|
||||
- 141: Furnace Tournament Edition (for intro tune contest)
|
||||
- 140: Furnace dev140
|
||||
|
@ -245,7 +258,7 @@ size | description
|
|||
| - 0x9c: Virtual Boy - 6 channels
|
||||
| - 0x9d: VRC7 - 6 channels
|
||||
| - 0x9e: YM2610B - 16 channels
|
||||
| - 0x9f: ZX Spectrum (beeper) - 6 channels
|
||||
| - 0x9f: ZX Spectrum (beeper, tildearrow engine) - 6 channels
|
||||
| - 0xa0: YM2612 extended - 9 channels
|
||||
| - 0xa1: Konami SCC - 5 channels
|
||||
| - 0xa2: OPL drums (YM3526) - 11 channels
|
||||
|
@ -287,6 +300,9 @@ size | description
|
|||
| - 0xc6: K007232 - 2 channels
|
||||
| - 0xc7: GA20 - 4 channels
|
||||
| - 0xc8: SM8521 - 3 channels
|
||||
| - 0xc9: M114S - 16 channels
|
||||
| - 0xca: ZX Spectrum (beeper, QuadTone engine) - 5 channels
|
||||
| - 0xcb: Casio PV-1000 - 3 channels
|
||||
| - 0xde: YM2610B extended - 19 channels
|
||||
| - 0xe0: QSound - 19 channels
|
||||
| - 0xfc: Pong - 1 channel
|
||||
|
@ -295,10 +311,12 @@ size | description
|
|||
| - 0xff: reserved for development
|
||||
| - (compound!) means that the system is composed of two or more chips,
|
||||
| and has to be flattened.
|
||||
32 | sound chip volumes
|
||||
32 | sound chip volumes (<135) or reserved
|
||||
| - signed char, 64=1.0, 127=~2.0
|
||||
32 | sound chip panning
|
||||
| - as of version 135 these fields only exist for compatibility reasons.
|
||||
32 | sound chip panning (<135) or reserved
|
||||
| - signed char, -128=left, 127=right
|
||||
| - as of version 135 these fields only exist for compatibility reasons.
|
||||
128 | sound chip flag pointers (>=119) or sound chip flags
|
||||
| - before 118, these were 32-bit flags.
|
||||
| - for conversion details, see the "converting from old flags" section.
|
||||
|
@ -407,7 +425,8 @@ size | description
|
|||
1 | automatic patchbay (>=136)
|
||||
--- | **a couple more compat flags** (>=138)
|
||||
1 | broken portamento during legato
|
||||
7 | reserved
|
||||
1 | broken macro during note off in some FM chips (>=155)
|
||||
6 | reserved
|
||||
--- | **speed pattern of first song** (>=139)
|
||||
1 | length of speed pattern (fail if this is lower than 0 or higher than 16)
|
||||
16 | speed pattern (this overrides speed 1 and speed 2 settings)
|
||||
|
|
|
@ -114,8 +114,9 @@ the following instrument types are available:
|
|||
- 44: T6W28
|
||||
- 45: K007232
|
||||
- 46: GA20
|
||||
- 47: Pokémon Mini
|
||||
- 47: Pokémon Mini/QuadTone
|
||||
- 48: SM8521
|
||||
- 49: PV-1000
|
||||
|
||||
the following feature codes are recognized:
|
||||
|
||||
|
@ -388,7 +389,7 @@ the sample map format:
|
|||
```
|
||||
size | description
|
||||
-----|------------------------------------
|
||||
2 | note to play
|
||||
2 | note to play (>=152) or reserved
|
||||
2 | sample to play
|
||||
```
|
||||
|
||||
|
|
BIN
res/intro.fur
BIN
res/intro.fur
Binary file not shown.
BIN
res/logo3.png
BIN
res/logo3.png
Binary file not shown.
Before Width: | Height: | Size: 565 KiB After Width: | Height: | Size: 554 KiB |
|
@ -15,9 +15,9 @@ fi
|
|||
cd win32build
|
||||
|
||||
# TODO: potential Arch-ism?
|
||||
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON .. || exit 1
|
||||
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON .. || exit 1
|
||||
make -j8 || exit 1
|
||||
i686-w64-mingw32-strip -s furnace.exe || exit 1
|
||||
#i686-w64-mingw32-strip -s furnace.exe || exit 1
|
||||
|
||||
cd ..
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@ fi
|
|||
cd winbuild
|
||||
|
||||
# TODO: potential Arch-ism?
|
||||
x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" .. || exit 1
|
||||
x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Wno-deprecated-declarations -Werror" .. || exit 1
|
||||
make -j8 || exit 1
|
||||
x86_64-w64-mingw32-strip -s furnace.exe || exit 1
|
||||
#x86_64-w64-mingw32-strip -s furnace.exe || exit 1
|
||||
|
||||
cd ..
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
all: player
|
||||
|
||||
player: player.s sample.bin seq.bin wave.bin
|
||||
vasmm68k_mot -Fhunkexe -kick1hunks -nosym -o player player.s
|
|
@ -0,0 +1,56 @@
|
|||
# Amiga verification export format
|
||||
|
||||
"ROM" export format exclusively for verifying whether the Amiga emulation in Furnace is correct.
|
||||
|
||||
do not assume this is the actual ROM export! it is nothing more than a register dump kind of thing...
|
||||
|
||||
# process
|
||||
|
||||
enable the setting in Furnace to unlock the export option by adding this to furnace.cfg:
|
||||
|
||||
```
|
||||
iCannotWait=1
|
||||
```
|
||||
|
||||
go to file > export Amiga validation data...
|
||||
|
||||
put sample.bin, sbook.bin, seq.bin, wave.bin and wbook.bin in this directory.
|
||||
|
||||
type `make`. you need vasm (68000 with Mot syntax) in order for it to work.
|
||||
alternatively, type:
|
||||
|
||||
```
|
||||
vasmm68k_mot -Fhunkexe -kick1hunks -nosym -o player player.s
|
||||
```
|
||||
|
||||
run `player` on Amiga. it should play the exported song.
|
||||
|
||||
# notes
|
||||
|
||||
may not work correctly if you have slow/fast memory!
|
||||
sequence and wave data should reside in fast memory but I haven't figured out how to...
|
||||
|
||||
# sequence format
|
||||
|
||||
## 00-0F: per-channel (00, 10, 20, 30)
|
||||
|
||||
- 00 xxxxxx yyyy: set loc/len
|
||||
- x: loc
|
||||
- y: len
|
||||
- 01 xxxxxx yyyy: initialize wavetable (xxxx: pos; yy: length)
|
||||
- 02 xx: set loc/len from sample book
|
||||
- 03 xx: initialize wavetable from wave book
|
||||
- 04 xxxx: initialize wavetable from wave book (short)
|
||||
- 06 xxxx: set period
|
||||
- 08 xx: set volume
|
||||
- 0a xxxx: set data
|
||||
|
||||
## F0-FF: global
|
||||
|
||||
- f0: do nothing
|
||||
- f1: next tick
|
||||
- f2 xx: wait
|
||||
- f3 xxxx: wait
|
||||
- f6 xxxx: write to DMACON
|
||||
- fe xxxx: write to ADKCON
|
||||
- ff: end of song
|
|
@ -0,0 +1,386 @@
|
|||
; Furnace validation player code
|
||||
; this is NOT the ROM export you're looking for!
|
||||
|
||||
VPOSR = $dff004
|
||||
VHPOSR = $dff006
|
||||
COLOR00 = $dff180
|
||||
|
||||
chipBase=$dff000
|
||||
|
||||
DMACONR = $02
|
||||
POTGOR = $16
|
||||
DMACON = $96
|
||||
ADKCON = $9e
|
||||
AUDBASE = $a0
|
||||
AUD0LCH = $a0
|
||||
AUD0LCL = $a2
|
||||
AUD0LEN = $a4
|
||||
AUD0PER = $a6
|
||||
AUD0VOL = $a8
|
||||
AUD0DAT = $aa
|
||||
AUD1VOL = $b8
|
||||
AUD2VOL = $c8
|
||||
AUD3VOL = $d8
|
||||
|
||||
code_c
|
||||
init:
|
||||
move.b #2,$bfe001
|
||||
lea chipBase,a0
|
||||
move.w #15,DMACON(a0)
|
||||
waitCon:
|
||||
move.w DMACONR(a0),d0
|
||||
andi.w #15,d0
|
||||
bne waitCon
|
||||
|
||||
move.w #$8200,DMACON(a0)
|
||||
move.w #$40,AUD0VOL(a0)
|
||||
move.w #$40,AUD1VOL(a0)
|
||||
move.w #$40,AUD2VOL(a0)
|
||||
move.w #$40,AUD3VOL(a0)
|
||||
lea seqAddr(pc),a0
|
||||
lea sequence(pc),a1
|
||||
move.l a1,(a0)
|
||||
main:
|
||||
bsr waitVBlank
|
||||
|
||||
move.w #$000,d4
|
||||
move.w d4,COLOR00
|
||||
|
||||
lea chipBase,a0
|
||||
btst.b #2,POTGOR(a0)
|
||||
bne next
|
||||
|
||||
lea state(pc),a0
|
||||
move.w #1,2(a0)
|
||||
|
||||
next:
|
||||
bsr nextTick
|
||||
|
||||
lea state(pc),a0
|
||||
tst.w 2(a0)
|
||||
beq main
|
||||
finish:
|
||||
lea chipBase,a0
|
||||
move.w #15,DMACON(a0)
|
||||
clr.l d0
|
||||
rts
|
||||
|
||||
waitVBlank:
|
||||
move.l (VPOSR),d0
|
||||
and.l #$1ff00,d0
|
||||
cmp.l #$bc00,d0
|
||||
bne waitVBlank
|
||||
waitVBlank2:
|
||||
move.l (VPOSR),d0
|
||||
and.l #$1ff00,d0
|
||||
cmp.l #$bd00,d0
|
||||
bne waitVBlank2
|
||||
rts
|
||||
|
||||
nextTick:
|
||||
lea state(pc),a4
|
||||
move.w (a4),d0
|
||||
subi.w #1,d0
|
||||
bmi nextTick0
|
||||
move.w d0,(a4)
|
||||
rts
|
||||
nextTick0:
|
||||
move.l seqAddr(pc),a2
|
||||
nextTick1:
|
||||
; get next command
|
||||
clr.w d0
|
||||
move.b (a2)+,d0
|
||||
move.w #$0ff,d4
|
||||
move.w d4,COLOR00
|
||||
|
||||
testSpecial:
|
||||
cmp.w #$f0,d0
|
||||
bmi testChannel
|
||||
|
||||
testF1:
|
||||
; f1 - next tick
|
||||
cmp.b #$f1,d0
|
||||
bne testF2
|
||||
; end of tick
|
||||
move.w #0,(a4)
|
||||
bra endTick
|
||||
testF2:
|
||||
; f2 - wait (char)
|
||||
cmp.b #$f2,d0
|
||||
bne testF3
|
||||
move.b (a2)+,d0
|
||||
andi.w #$ff,d0
|
||||
move.w d0,(a4)
|
||||
bra endTick
|
||||
testF3:
|
||||
; f3 - wait (short)
|
||||
cmp.b #$f3,d0
|
||||
bne testF6
|
||||
clr.w d2
|
||||
move.b (a2)+,d2
|
||||
lsl.w #8,d2
|
||||
or.b (a2)+,d2
|
||||
move.w d2,(a4)
|
||||
bra endTick
|
||||
testF6:
|
||||
; f6 - write DMACON
|
||||
cmp.b #$f6,d0
|
||||
bne testFE
|
||||
move.w #$f00,d4
|
||||
move.w d4,COLOR00
|
||||
clr.w d2
|
||||
move.b (a2)+,d2
|
||||
lsl.w #8,d2
|
||||
or.b (a2)+,d2
|
||||
move.w d2,chipBase+DMACON
|
||||
; wait for DMACON to be done
|
||||
move.b (VHPOSR),d0
|
||||
dmaConWait:
|
||||
cmp.b (VHPOSR),d0
|
||||
beq dmaConWait
|
||||
; wait for DMACON to be done -2
|
||||
move.b (VHPOSR),d0
|
||||
dmaConWait1:
|
||||
cmp.b (VHPOSR),d0
|
||||
beq dmaConWait1
|
||||
bra nextTick1
|
||||
testFE:
|
||||
; fe - write ADKCON
|
||||
cmp.b #$fe,d0
|
||||
bne testFF
|
||||
clr.w d2
|
||||
move.b (a2)+,d2
|
||||
lsl.w #8,d2
|
||||
or.b (a2)+,d2
|
||||
move.w d2,chipBase+ADKCON
|
||||
bra nextTick1
|
||||
testFF:
|
||||
; ff - end of song
|
||||
cmp.b #$ff,d0
|
||||
bne testOther
|
||||
theEnd:
|
||||
lea sequence(pc),a2
|
||||
testOther:
|
||||
; something else
|
||||
bra nextTick1
|
||||
|
||||
testChannel:
|
||||
cmp.b #$40,d0
|
||||
bge invalidCmd
|
||||
; process channel
|
||||
move.b d0,d1
|
||||
andi.b #15,d0
|
||||
; check for 0
|
||||
bne chanNotZero
|
||||
sampleWrite:
|
||||
; write loc/len
|
||||
move.w #$f0f,d4
|
||||
move.w d4,COLOR00
|
||||
clr.l d2
|
||||
move.b (a2)+,d2
|
||||
lsl.l #8,d2
|
||||
or.b (a2)+,d2
|
||||
lsl.l #8,d2
|
||||
or.b (a2)+,d2
|
||||
lea sampleData,a0
|
||||
add.l a0,d2
|
||||
lea chipBase,a0
|
||||
move.b d1,d0
|
||||
andi.l #$ff,d0
|
||||
addi.l #$a0,d0
|
||||
adda.l d0,a0
|
||||
move.l d2,(a0)+ ; location
|
||||
clr.w d2
|
||||
move.b (a2)+,d2
|
||||
lsl.w #8,d2
|
||||
or.b (a2)+,d2
|
||||
move.w d2,(a0) ; length
|
||||
bra nextTick1
|
||||
chanNotZero:
|
||||
; check for 8 (VOL)
|
||||
cmp.b #8,d0
|
||||
bne chanSampleBook
|
||||
; write volume
|
||||
clr.w d2
|
||||
move.b (a2)+,d2
|
||||
bra chanWrite
|
||||
chanSampleBook:
|
||||
; check for 2 (loc/len from book)
|
||||
cmp.b #2,d0
|
||||
bne chanWaveChange
|
||||
|
||||
move.w #$f0f,d4
|
||||
move.w d4,COLOR00
|
||||
clr.l d3
|
||||
move.b (a2)+,d3
|
||||
bsr getSampleBook
|
||||
|
||||
; write loc/len
|
||||
lea sampleData,a0
|
||||
add.l a0,d2
|
||||
lea chipBase,a0
|
||||
move.b d1,d0
|
||||
andi.l #$f0,d0
|
||||
addi.l #$a0,d0
|
||||
adda.l d0,a0
|
||||
move.l d2,(a0)+ ; location
|
||||
move.w d3,(a0) ; length
|
||||
|
||||
bra nextTick1
|
||||
chanWaveChange:
|
||||
; check for 1 (wave change)
|
||||
cmp.b #1,d0
|
||||
bne chanWaveBookB
|
||||
; copy wave
|
||||
clr.l d2
|
||||
move.b (a2)+,d2
|
||||
lsl.l #8,d2
|
||||
or.b (a2)+,d2
|
||||
lsl.l #8,d2
|
||||
or.b (a2)+,d2
|
||||
add.l #wavetable,d2
|
||||
move.l d2,a0
|
||||
|
||||
lea sampleData,a1
|
||||
andi.l #$30,d1
|
||||
lsl.l #4,d1
|
||||
adda.l d1,a1
|
||||
|
||||
clr.l d2
|
||||
move.b (a2)+,d2
|
||||
lsl.l #8,d2
|
||||
or.b (a2)+,d2
|
||||
|
||||
bsr copyWave
|
||||
bra nextTick1
|
||||
chanWaveBookB:
|
||||
; check for 3 (wave change from book)
|
||||
cmp.b #3,d0
|
||||
bne chanWaveBookW
|
||||
|
||||
clr.l d3
|
||||
move.b (a2)+,d3
|
||||
bsr getWaveBook
|
||||
|
||||
lea sampleData,a1
|
||||
andi.l #$30,d1
|
||||
lsl.l #4,d1
|
||||
adda.l d1,a1
|
||||
|
||||
bsr copyWave
|
||||
bra nextTick1
|
||||
chanWaveBookW:
|
||||
; check for 4 (wave change from book short)
|
||||
cmp.b #4,d0
|
||||
bne chanOther
|
||||
|
||||
clr.l d3
|
||||
move.b (a2)+,d3
|
||||
lsl.l #8,d3
|
||||
or.b (a2)+,d3
|
||||
bsr getWaveBook
|
||||
|
||||
lea sampleData,a1
|
||||
andi.l #$30,d1
|
||||
lsl.l #4,d1
|
||||
adda.l d1,a1
|
||||
|
||||
bsr copyWave
|
||||
bra nextTick1
|
||||
chanOther:
|
||||
; get value and write
|
||||
clr.w d2
|
||||
move.b (a2)+,d2
|
||||
lsl.w #8,d2
|
||||
or.b (a2)+,d2
|
||||
chanWrite:
|
||||
move.w #$ff0,d4
|
||||
move.w d4,COLOR00
|
||||
lea chipBase,a0
|
||||
or.b d1,d0
|
||||
addi.b #AUDBASE,d0
|
||||
andi.l #$ff,d0
|
||||
adda.l d0,a0
|
||||
move.w d2,(a0)
|
||||
invalidCmd:
|
||||
bra nextTick1
|
||||
|
||||
endTick:
|
||||
lea seqAddr(pc),a3
|
||||
move.l a2,(a3)
|
||||
move.w #$000,d4
|
||||
move.w d4,COLOR00
|
||||
rts
|
||||
|
||||
; a0: source. a1: destination. d2: length.
|
||||
copyWave:
|
||||
; don't copy a zero-length wave
|
||||
tst.l d2
|
||||
beq noCopy
|
||||
copyWaveLoop:
|
||||
move.w (a0)+,(a1)+
|
||||
subq.l #2,d2
|
||||
bne copyWaveLoop
|
||||
noCopy:
|
||||
rts
|
||||
|
||||
; put wave book entry in a0/d2. d3: index (modified).
|
||||
getWaveBook:
|
||||
lea waveBook(pc),a3
|
||||
lsl.l #2,d3
|
||||
move.l (a3,d3),d2
|
||||
move.l d2,d3
|
||||
rol.l #8,d2
|
||||
andi.l #$ff,d2
|
||||
bne getWaveBook2
|
||||
move.w #$100,d2
|
||||
getWaveBook2:
|
||||
andi.l #$ffffff,d3
|
||||
add.l #wavetable,d3
|
||||
move.l d3,a0
|
||||
rts
|
||||
|
||||
; get sample book entry in d2/d3. d3: index.
|
||||
getSampleBook:
|
||||
lea sampleBook(pc),a3
|
||||
lsl.l #3,d3
|
||||
adda.l d3,a3
|
||||
move.l (a3)+,d2
|
||||
move.l (a3)+,d3
|
||||
andi.l #$ffffff,d2
|
||||
andi.l #$ffff,d3
|
||||
rts
|
||||
|
||||
data_c
|
||||
cnop 0,4
|
||||
|
||||
curColor:
|
||||
dc.w 0
|
||||
|
||||
state:
|
||||
dc.w 0 ; ticks
|
||||
dc.w 0 ; quit
|
||||
cnop 0,4
|
||||
|
||||
seqAddr:
|
||||
dc.l 0
|
||||
cnop 0,4
|
||||
|
||||
sampleBook:
|
||||
incbin "sbook.bin"
|
||||
cnop 0,4
|
||||
|
||||
waveBook:
|
||||
incbin "wbook.bin"
|
||||
cnop 0,4
|
||||
|
||||
sequence:
|
||||
incbin "seq.bin"
|
||||
cnop 0,4
|
||||
|
||||
sampleData:
|
||||
incbin "sample.bin"
|
||||
cnop 0,4
|
||||
|
||||
wavetable:
|
||||
incbin "wave.bin"
|
|
@ -0,0 +1,428 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include "cmdStream.h"
|
||||
#include "dispatch.h"
|
||||
#include "engine.h"
|
||||
#include "../ta-log.h"
|
||||
|
||||
bool DivCSChannelState::doCall(unsigned int addr) {
|
||||
if (callStackPos>=8) {
|
||||
readPos=0;
|
||||
return false;
|
||||
}
|
||||
|
||||
callStack[callStackPos++]=readPos;
|
||||
readPos=addr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivCSPlayer::cleanup() {
|
||||
delete b;
|
||||
}
|
||||
|
||||
bool DivCSPlayer::tick() {
|
||||
bool ticked=false;
|
||||
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||
bool sendVolume=false;
|
||||
bool sendPitch=false;
|
||||
if (chan[i].readPos==0) continue;
|
||||
|
||||
ticked=true;
|
||||
|
||||
chan[i].waitTicks--;
|
||||
while (chan[i].waitTicks<=0) {
|
||||
if (!stream.seek(chan[i].readPos,SEEK_SET)) {
|
||||
logE("%d: access violation! $%x",i,chan[i].readPos);
|
||||
chan[i].readPos=0;
|
||||
break;
|
||||
}
|
||||
unsigned char next=stream.readC();
|
||||
unsigned char command=0;
|
||||
|
||||
if (next<0xb3) { // note
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,(int)next-60));
|
||||
chan[i].note=(int)next-60;
|
||||
chan[i].vibratoPos=0;
|
||||
} else if (next>=0xd0 && next<=0xdf) {
|
||||
command=fastCmds[next&15];
|
||||
} else if (next>=0xe0 && next<=0xef) { // preset delay
|
||||
chan[i].waitTicks=fastDelays[next&15];
|
||||
} else switch (next) {
|
||||
case 0xb4: // note on null
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL));
|
||||
chan[i].vibratoPos=0;
|
||||
break;
|
||||
case 0xb5: // note off
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i));
|
||||
break;
|
||||
case 0xb6: // note off env
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF_ENV,i));
|
||||
break;
|
||||
case 0xb7: // env release
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_ENV_RELEASE,i));
|
||||
break;
|
||||
case 0xb8: case 0xbe: case 0xc0: case 0xc2:
|
||||
case 0xc3: case 0xc4: case 0xc5: case 0xc6:
|
||||
case 0xc7: case 0xc8: case 0xc9: case 0xca:
|
||||
command=next-0xb4;
|
||||
break;
|
||||
case 0xf7:
|
||||
command=stream.readC();
|
||||
break;
|
||||
case 0xf8: {
|
||||
unsigned int callAddr=chan[i].readPos+2+stream.readS();
|
||||
if (!chan[i].doCall(callAddr)) {
|
||||
logE("%d: (callb16) stack error!",i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xf6: {
|
||||
unsigned int callAddr=chan[i].readPos+4+stream.readI();
|
||||
if (!chan[i].doCall(callAddr)) {
|
||||
logE("%d: (callb32) stack error!",i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xf5: {
|
||||
unsigned int callAddr=stream.readI();
|
||||
if (!chan[i].doCall(callAddr)) {
|
||||
logE("%d: (call) stack error!",i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xf4: {
|
||||
logE("%d: (callsym) not supported here!",i);
|
||||
chan[i].readPos=0;
|
||||
break;
|
||||
}
|
||||
case 0xf9:
|
||||
if (!chan[i].callStackPos) {
|
||||
logE("%d: (ret) stack error!",i);
|
||||
chan[i].readPos=0;
|
||||
break;
|
||||
}
|
||||
chan[i].readPos=chan[i].callStack[--chan[i].callStackPos];
|
||||
break;
|
||||
case 0xfa:
|
||||
chan[i].readPos=stream.readI();
|
||||
break;
|
||||
case 0xfb:
|
||||
logE("TODO: RATE");
|
||||
stream.readI();
|
||||
break;
|
||||
case 0xfc:
|
||||
chan[i].waitTicks=(unsigned short)stream.readS();
|
||||
break;
|
||||
case 0xfd:
|
||||
chan[i].waitTicks=(unsigned char)stream.readC();
|
||||
break;
|
||||
case 0xfe:
|
||||
chan[i].waitTicks=1;
|
||||
break;
|
||||
case 0xff:
|
||||
chan[i].readPos=0;
|
||||
logI("%d: stop",i);
|
||||
break;
|
||||
default:
|
||||
logE("%d: illegal instruction $%.2x! $%.x",i,next,chan[i].readPos);
|
||||
chan[i].readPos=0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (chan[i].readPos==0) break;
|
||||
|
||||
if (command) {
|
||||
int arg0=0;
|
||||
int arg1=0;
|
||||
switch (command) {
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
case DIV_CMD_HINT_VIBRATO_RANGE:
|
||||
case DIV_CMD_HINT_VIBRATO_SHAPE:
|
||||
case DIV_CMD_HINT_VOLUME:
|
||||
case DIV_CMD_HINT_ARP_TIME:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
break;
|
||||
case DIV_CMD_HINT_PITCH:
|
||||
arg0=(signed char)stream.readC();
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
case DIV_CMD_HINT_VIBRATO:
|
||||
case DIV_CMD_HINT_ARPEGGIO:
|
||||
case DIV_CMD_HINT_PORTA:
|
||||
arg0=(signed char)stream.readC();
|
||||
arg1=(unsigned char)stream.readC();
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
arg1=(arg0&0x40)?1:0;
|
||||
arg0=(arg0&0x80)?1:0;
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
arg0=(short)stream.readS();
|
||||
break;
|
||||
case DIV_CMD_HINT_LEGATO:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
if (arg0==0xff) {
|
||||
arg0=DIV_NOTE_NULL;
|
||||
} else {
|
||||
arg0-=60;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
case DIV_CMD_SAMPLE_FREQ:
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
case DIV_CMD_SAMPLE_DIR:
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
case DIV_CMD_FM_LFO:
|
||||
case DIV_CMD_FM_LFO_WAVE:
|
||||
case DIV_CMD_FM_FB:
|
||||
case DIV_CMD_FM_EXTCH:
|
||||
case DIV_CMD_FM_AM_DEPTH:
|
||||
case DIV_CMD_FM_PM_DEPTH:
|
||||
case DIV_CMD_STD_NOISE_FREQ:
|
||||
case DIV_CMD_STD_NOISE_MODE:
|
||||
case DIV_CMD_WAVE:
|
||||
case DIV_CMD_GB_SWEEP_TIME:
|
||||
case DIV_CMD_GB_SWEEP_DIR:
|
||||
case DIV_CMD_PCE_LFO_MODE:
|
||||
case DIV_CMD_PCE_LFO_SPEED:
|
||||
case DIV_CMD_NES_DMC:
|
||||
case DIV_CMD_C64_CUTOFF:
|
||||
case DIV_CMD_C64_RESONANCE:
|
||||
case DIV_CMD_C64_FILTER_MODE:
|
||||
case DIV_CMD_C64_RESET_TIME:
|
||||
case DIV_CMD_C64_RESET_MASK:
|
||||
case DIV_CMD_C64_FILTER_RESET:
|
||||
case DIV_CMD_C64_DUTY_RESET:
|
||||
case DIV_CMD_C64_EXTENDED:
|
||||
case DIV_CMD_AY_ENVELOPE_SET:
|
||||
case DIV_CMD_AY_ENVELOPE_LOW:
|
||||
case DIV_CMD_AY_ENVELOPE_HIGH:
|
||||
case DIV_CMD_AY_ENVELOPE_SLIDE:
|
||||
case DIV_CMD_AY_NOISE_MASK_AND:
|
||||
case DIV_CMD_AY_NOISE_MASK_OR:
|
||||
case DIV_CMD_AY_AUTO_ENVELOPE:
|
||||
case DIV_CMD_FDS_MOD_DEPTH:
|
||||
case DIV_CMD_FDS_MOD_HIGH:
|
||||
case DIV_CMD_FDS_MOD_LOW:
|
||||
case DIV_CMD_FDS_MOD_POS:
|
||||
case DIV_CMD_FDS_MOD_WAVE:
|
||||
case DIV_CMD_SAA_ENVELOPE:
|
||||
case DIV_CMD_AMIGA_FILTER:
|
||||
case DIV_CMD_AMIGA_AM:
|
||||
case DIV_CMD_AMIGA_PM:
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
case DIV_CMD_MACRO_ON:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
break;
|
||||
case DIV_CMD_FM_TL:
|
||||
case DIV_CMD_FM_AM:
|
||||
case DIV_CMD_FM_AR:
|
||||
case DIV_CMD_FM_DR:
|
||||
case DIV_CMD_FM_SL:
|
||||
case DIV_CMD_FM_D2R:
|
||||
case DIV_CMD_FM_RR:
|
||||
case DIV_CMD_FM_DT:
|
||||
case DIV_CMD_FM_DT2:
|
||||
case DIV_CMD_FM_RS:
|
||||
case DIV_CMD_FM_KSR:
|
||||
case DIV_CMD_FM_VIB:
|
||||
case DIV_CMD_FM_SUS:
|
||||
case DIV_CMD_FM_WS:
|
||||
case DIV_CMD_FM_SSG:
|
||||
case DIV_CMD_FM_REV:
|
||||
case DIV_CMD_FM_EG_SHIFT:
|
||||
case DIV_CMD_FM_MULT:
|
||||
case DIV_CMD_FM_FINE:
|
||||
case DIV_CMD_AY_IO_WRITE:
|
||||
case DIV_CMD_AY_AUTO_PWM:
|
||||
case DIV_CMD_SURROUND_PANNING:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
arg1=(unsigned char)stream.readC();
|
||||
break;
|
||||
case DIV_CMD_C64_FINE_DUTY:
|
||||
case DIV_CMD_C64_FINE_CUTOFF:
|
||||
case DIV_CMD_LYNX_LFSR_LOAD:
|
||||
arg0=(unsigned short)stream.readS();
|
||||
break;
|
||||
case DIV_CMD_FM_FIXFREQ:
|
||||
arg0=(unsigned short)stream.readS();
|
||||
arg1=arg0&0x7ff;
|
||||
arg0>>=12;
|
||||
break;
|
||||
case DIV_CMD_NES_SWEEP:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
arg1=arg0&0x77;
|
||||
arg0=(arg0&8)?1:0;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case DIV_CMD_HINT_VOLUME:
|
||||
chan[i].volume=arg0<<8;
|
||||
sendVolume=true;
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
chan[i].volSpeed=arg0;
|
||||
break;
|
||||
case DIV_CMD_HINT_PITCH:
|
||||
chan[i].pitch=arg0;
|
||||
sendPitch=true;
|
||||
break;
|
||||
case DIV_CMD_HINT_VIBRATO:
|
||||
chan[i].vibratoDepth=arg0;
|
||||
chan[i].vibratoRate=arg1;
|
||||
sendPitch=true;
|
||||
break;
|
||||
case DIV_CMD_HINT_PORTA:
|
||||
chan[i].portaTarget=arg0;
|
||||
chan[i].portaSpeed=arg1;
|
||||
break;
|
||||
case DIV_CMD_HINT_LEGATO:
|
||||
chan[i].note=arg0;
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
|
||||
break;
|
||||
case DIV_CMD_HINT_ARPEGGIO:
|
||||
chan[i].arp=(((unsigned char)arg0)<<4)|(arg1&15);
|
||||
break;
|
||||
case DIV_CMD_HINT_ARP_TIME:
|
||||
arpSpeed=arg0;
|
||||
break;
|
||||
default: // dispatch it
|
||||
e->dispatchCmd(DivCommand((DivDispatchCmds)command,i,arg0,arg1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
chan[i].readPos=stream.tell();
|
||||
}
|
||||
|
||||
if (sendVolume || chan[i].volSpeed!=0) {
|
||||
chan[i].volume+=chan[i].volSpeed;
|
||||
if (chan[i].volume<0) {
|
||||
chan[i].volume=0;
|
||||
}
|
||||
if (chan[i].volume>chan[i].volMax) {
|
||||
chan[i].volume=chan[i].volMax;
|
||||
}
|
||||
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
}
|
||||
|
||||
if (sendPitch || chan[i].vibratoDepth!=0) {
|
||||
if (chan[i].vibratoDepth>0) {
|
||||
chan[i].vibratoPos+=chan[i].vibratoRate;
|
||||
if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64;
|
||||
}
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(vibTable[chan[i].vibratoPos&63]*chan[i].vibratoDepth)/15));
|
||||
}
|
||||
|
||||
if (chan[i].portaSpeed) {
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(e->song.linearPitch==2?e->song.pitchSlideSpeed:1),chan[i].portaTarget));
|
||||
}
|
||||
if (chan[i].arp && !chan[i].portaSpeed) {
|
||||
if (chan[i].arpTicks==0) {
|
||||
switch (chan[i].arpStage) {
|
||||
case 0:
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
|
||||
break;
|
||||
case 1:
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp>>4)));
|
||||
break;
|
||||
case 2:
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp&15)));
|
||||
break;
|
||||
}
|
||||
chan[i].arpStage++;
|
||||
if (chan[i].arpStage>=3) chan[i].arpStage=0;
|
||||
chan[i].arpTicks=arpSpeed;
|
||||
}
|
||||
chan[i].arpTicks--;
|
||||
}
|
||||
}
|
||||
|
||||
return ticked;
|
||||
}
|
||||
|
||||
bool DivCSPlayer::init() {
|
||||
unsigned char magic[4];
|
||||
stream.seek(0,SEEK_SET);
|
||||
stream.read(magic,4);
|
||||
|
||||
if (memcmp(magic,"FCS",4)!=0) return false;
|
||||
|
||||
unsigned int chans=stream.readI();
|
||||
|
||||
for (unsigned int i=0; i<chans; i++) {
|
||||
if (i>=DIV_MAX_CHANS) {
|
||||
stream.readI();
|
||||
continue;
|
||||
}
|
||||
if ((int)i>=e->getTotalChannelCount()) {
|
||||
stream.readI();
|
||||
continue;
|
||||
}
|
||||
chan[i].readPos=stream.readI();
|
||||
}
|
||||
|
||||
stream.read(fastDelays,16);
|
||||
stream.read(fastCmds,16);
|
||||
|
||||
// initialize state
|
||||
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||
chan[i].volMax=(e->getDispatch(e->dispatchOfChan[i])->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,e->dispatchChanOfChan[i]))<<8)|0xff;
|
||||
chan[i].volume=chan[i].volMax;
|
||||
}
|
||||
|
||||
for (int i=0; i<64; i++) {
|
||||
vibTable[i]=127*sin(((double)i/64.0)*(2*M_PI));
|
||||
}
|
||||
|
||||
arpSpeed=1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// DivEngine
|
||||
|
||||
bool DivEngine::playStream(unsigned char* f, size_t length) {
|
||||
BUSY_BEGIN;
|
||||
cmdStreamInt=new DivCSPlayer(this,f,length);
|
||||
if (!cmdStreamInt->init()) {
|
||||
logE("not a command stream!");
|
||||
lastError="not a command stream";
|
||||
delete[] f;
|
||||
delete cmdStreamInt;
|
||||
cmdStreamInt=NULL;
|
||||
BUSY_END;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!playing) {
|
||||
reset();
|
||||
freelance=true;
|
||||
playing=true;
|
||||
}
|
||||
BUSY_END;
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef _CMD_STREAM_H
|
||||
#define _CMD_STREAM_H
|
||||
|
||||
#include "defines.h"
|
||||
#include "safeReader.h"
|
||||
|
||||
class DivEngine;
|
||||
|
||||
struct DivCSChannelState {
|
||||
unsigned int readPos;
|
||||
int waitTicks;
|
||||
|
||||
int note, pitch;
|
||||
int volume, volMax, volSpeed;
|
||||
int vibratoDepth, vibratoRate, vibratoPos;
|
||||
int portaTarget, portaSpeed;
|
||||
unsigned char arp, arpStage, arpTicks;
|
||||
|
||||
unsigned int callStack[8];
|
||||
unsigned char callStackPos;
|
||||
|
||||
struct TraceEntry {
|
||||
unsigned int addr;
|
||||
unsigned char length;
|
||||
unsigned char data[11];
|
||||
} trace[32];
|
||||
unsigned char tracePos;
|
||||
|
||||
bool doCall(unsigned int addr);
|
||||
|
||||
DivCSChannelState():
|
||||
readPos(0),
|
||||
waitTicks(0),
|
||||
note(-1),
|
||||
pitch(0),
|
||||
volume(0x7f00),
|
||||
volMax(0),
|
||||
volSpeed(0),
|
||||
vibratoDepth(0),
|
||||
vibratoRate(0),
|
||||
vibratoPos(0),
|
||||
portaTarget(0),
|
||||
portaSpeed(0),
|
||||
arp(0),
|
||||
arpStage(0),
|
||||
arpTicks(0),
|
||||
callStackPos(0) {}
|
||||
};
|
||||
|
||||
class DivCSPlayer {
|
||||
DivEngine* e;
|
||||
unsigned char* b;
|
||||
SafeReader stream;
|
||||
DivCSChannelState chan[DIV_MAX_CHANS];
|
||||
unsigned char fastDelays[16];
|
||||
unsigned char fastCmds[16];
|
||||
unsigned char arpSpeed;
|
||||
|
||||
short vibTable[64];
|
||||
public:
|
||||
void cleanup();
|
||||
bool tick();
|
||||
bool init();
|
||||
DivCSPlayer(DivEngine* en, unsigned char* buf, size_t len):
|
||||
e(en),
|
||||
b(buf),
|
||||
stream(buf,len) {}
|
||||
};
|
||||
|
||||
#endif
|
|
@ -23,21 +23,54 @@
|
|||
#include "../fileutils.h"
|
||||
#include <fmt/printf.h>
|
||||
|
||||
bool DivConfig::save(const char* path) {
|
||||
#define REDUNDANCY_NUM_ATTEMPTS 5
|
||||
#define CHECK_BUF_SIZE 8192
|
||||
|
||||
bool DivConfig::save(const char* path, bool redundancy) {
|
||||
if (redundancy) {
|
||||
char oldPath[4096];
|
||||
char newPath[4096];
|
||||
|
||||
if (fileExists(path)==1) {
|
||||
logD("rotating config files...");
|
||||
for (int i=4; i>=0; i--) {
|
||||
if (i>0) {
|
||||
snprintf(oldPath,4095,"%s.%d",path,i);
|
||||
} else {
|
||||
strncpy(oldPath,path,4095);
|
||||
}
|
||||
snprintf(newPath,4095,"%s.%d",path,i+1);
|
||||
|
||||
if (i>=4) {
|
||||
logV("remove %s",oldPath);
|
||||
deleteFile(oldPath);
|
||||
} else {
|
||||
logV("move %s to %s",oldPath,newPath);
|
||||
moveFiles(oldPath,newPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logD("opening config for write: %s",path);
|
||||
FILE* f=ps_fopen(path,"wb");
|
||||
if (f==NULL) {
|
||||
logW("could not write config file! %s",strerror(errno));
|
||||
reportError(fmt::sprintf("could not write config file! %s",strerror(errno)));
|
||||
return false;
|
||||
}
|
||||
for (auto& i: conf) {
|
||||
String toWrite=fmt::sprintf("%s=%s\n",i.first,i.second);
|
||||
if (fwrite(toWrite.c_str(),1,toWrite.size(),f)!=toWrite.size()) {
|
||||
logW("could not write config file! %s",strerror(errno));
|
||||
reportError(fmt::sprintf("could not write config file! %s",strerror(errno)));
|
||||
logV("removing config file");
|
||||
fclose(f);
|
||||
deleteFile(path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
logD("config file written successfully.");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -63,6 +96,7 @@ void DivConfig::parseLine(const char* line) {
|
|||
String value="";
|
||||
bool keyOrValue=false;
|
||||
for (const char* i=line; *i; i++) {
|
||||
if (*i=='\r') continue;
|
||||
if (*i=='\n') continue;
|
||||
if (keyOrValue) {
|
||||
value+=*i;
|
||||
|
@ -79,17 +113,94 @@ void DivConfig::parseLine(const char* line) {
|
|||
}
|
||||
}
|
||||
|
||||
bool DivConfig::loadFromFile(const char* path, bool createOnFail) {
|
||||
bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundancy) {
|
||||
char line[4096];
|
||||
FILE* f=ps_fopen(path,"rb");
|
||||
if (f==NULL) {
|
||||
if (createOnFail) {
|
||||
logI("creating default config.");
|
||||
return save(path);
|
||||
} else {
|
||||
return false;
|
||||
logD("opening config for read: %s",path);
|
||||
|
||||
FILE* f=NULL;
|
||||
|
||||
if (redundancy) {
|
||||
unsigned char* readBuf=new unsigned char[CHECK_BUF_SIZE];
|
||||
size_t readBufLen=0;
|
||||
for (int i=0; i<REDUNDANCY_NUM_ATTEMPTS; i++) {
|
||||
bool viable=false;
|
||||
if (i>0) {
|
||||
snprintf(line,4095,"%s.%d",path,i);
|
||||
} else {
|
||||
strncpy(line,path,4095);
|
||||
}
|
||||
logV("trying: %s",line);
|
||||
|
||||
// try to open config
|
||||
f=ps_fopen(line,"rb");
|
||||
// check whether we could open it
|
||||
if (f==NULL) {
|
||||
logV("fopen(): %s",strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
// check whether there's something
|
||||
while (!feof(f)) {
|
||||
readBufLen=fread(readBuf,1,CHECK_BUF_SIZE,f);
|
||||
if (ferror(f)) {
|
||||
logV("fread(): %s",strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
for (size_t j=0; j<readBufLen; j++) {
|
||||
if (readBuf[j]!='\r' && readBuf[j]!='\n' && readBuf[j]!=' ') {
|
||||
viable=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (viable) break;
|
||||
}
|
||||
|
||||
// there's something
|
||||
if (viable) {
|
||||
if (fseek(f,0,SEEK_SET)==-1) {
|
||||
logV("fseek(): %s",strerror(errno));
|
||||
viable=false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// close it (because there's nothing)
|
||||
fclose(f);
|
||||
f=NULL;
|
||||
}
|
||||
delete[] readBuf;
|
||||
|
||||
// we couldn't read at all
|
||||
if (f==NULL) {
|
||||
logD("config does not exist");
|
||||
if (createOnFail) {
|
||||
logI("creating default config.");
|
||||
//reportError(fmt::sprintf("Creating default config: %s",strerror(errno)));
|
||||
return save(path,redundancy);
|
||||
} else {
|
||||
reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
f=ps_fopen(path,"rb");
|
||||
if (f==NULL) {
|
||||
logD("config does not exist");
|
||||
if (createOnFail) {
|
||||
logI("creating default config.");
|
||||
//reportError(fmt::sprintf("Creating default config: %s",strerror(errno)));
|
||||
return save(path);
|
||||
} else {
|
||||
reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
logI("loading config.");
|
||||
while (!feof(f)) {
|
||||
if (fgets(line,4095,f)==NULL) {
|
||||
|
@ -97,6 +208,7 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail) {
|
|||
}
|
||||
parseLine(line);
|
||||
}
|
||||
logD("end of file (%s)",strerror(errno));
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
@ -175,6 +287,33 @@ String DivConfig::getString(String key, String fallback) const {
|
|||
return fallback;
|
||||
}
|
||||
|
||||
std::vector<int> DivConfig::getIntList(String key, std::initializer_list<int> fallback) const {
|
||||
String next;
|
||||
std::vector<int> ret;
|
||||
try {
|
||||
String val=conf.at(key);
|
||||
|
||||
for (char i: val) {
|
||||
if (i==',') {
|
||||
int num=std::stoi(next);
|
||||
ret.push_back(num);
|
||||
next="";
|
||||
} else {
|
||||
next+=i;
|
||||
}
|
||||
}
|
||||
if (!next.empty()) {
|
||||
int num=std::stoi(next);
|
||||
ret.push_back(num);
|
||||
}
|
||||
|
||||
return ret;
|
||||
} catch (std::out_of_range& e) {
|
||||
} catch (std::invalid_argument& e) {
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
bool DivConfig::has(String key) const {
|
||||
try {
|
||||
String test=conf.at(key);
|
||||
|
@ -212,6 +351,17 @@ void DivConfig::set(String key, String value) {
|
|||
conf[key]=value;
|
||||
}
|
||||
|
||||
void DivConfig::set(String key, const std::vector<int>& value) {
|
||||
String val;
|
||||
bool comma=false;
|
||||
for (int i: value) {
|
||||
if (comma) val+=',';
|
||||
val+=fmt::sprintf("%d",i);
|
||||
comma=true;
|
||||
}
|
||||
conf[key]=val;
|
||||
}
|
||||
|
||||
bool DivConfig::remove(String key) {
|
||||
return conf.erase(key);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
#include "../ta-utils.h"
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
|
||||
class DivConfig {
|
||||
std::map<String,String> conf;
|
||||
|
@ -30,10 +32,10 @@ class DivConfig {
|
|||
// config loading/saving
|
||||
bool loadFromMemory(const char* buf);
|
||||
bool loadFromBase64(const char* buf);
|
||||
bool loadFromFile(const char* path, bool createOnFail=true);
|
||||
bool loadFromFile(const char* path, bool createOnFail=true, bool redundancy=false);
|
||||
String toString();
|
||||
String toBase64();
|
||||
bool save(const char* path);
|
||||
bool save(const char* path, bool redundancy=false);
|
||||
|
||||
// get the map
|
||||
const std::map<String,String>& configMap();
|
||||
|
@ -44,6 +46,7 @@ class DivConfig {
|
|||
float getFloat(String key, float fallback) const;
|
||||
double getDouble(String key, double fallback) const;
|
||||
String getString(String key, String fallback) const;
|
||||
std::vector<int> getIntList(String key, std::initializer_list<int> fallback) const;
|
||||
|
||||
// check for existence
|
||||
bool has(String key) const;
|
||||
|
@ -55,6 +58,7 @@ class DivConfig {
|
|||
void set(String key, double value);
|
||||
void set(String key, const char* value);
|
||||
void set(String key, String value);
|
||||
void set(String key, const std::vector<int>& value);
|
||||
|
||||
// remove a config value
|
||||
bool remove(String key);
|
||||
|
|
|
@ -110,12 +110,12 @@ void DivEngine::initConfDir() {
|
|||
|
||||
bool DivEngine::saveConf() {
|
||||
configFile=configPath+String(CONFIG_FILE);
|
||||
return conf.save(configFile.c_str());
|
||||
return conf.save(configFile.c_str(),true);
|
||||
}
|
||||
|
||||
bool DivEngine::loadConf() {
|
||||
configFile=configPath+String(CONFIG_FILE);
|
||||
return conf.loadFromFile(configFile.c_str());
|
||||
return conf.loadFromFile(configFile.c_str(),true,true);
|
||||
}
|
||||
|
||||
bool DivEngine::getConfBool(String key, bool fallback) {
|
||||
|
|
|
@ -229,6 +229,13 @@ enum DivDispatchCmds {
|
|||
DIV_CMD_ES5506_ENVELOPE_K2RAMP, // (ramp, slowdown)
|
||||
DIV_CMD_ES5506_PAUSE, // (value)
|
||||
|
||||
DIV_CMD_HINT_ARP_TIME, // (value)
|
||||
|
||||
DIV_CMD_SNES_GLOBAL_VOL_LEFT,
|
||||
DIV_CMD_SNES_GLOBAL_VOL_RIGHT,
|
||||
|
||||
DIV_CMD_NES_LINEAR_LENGTH,
|
||||
|
||||
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol
|
||||
|
||||
DIV_CMD_MAX
|
||||
|
@ -280,19 +287,31 @@ struct DivRegWrite {
|
|||
* - 0xffffffff: reset
|
||||
*/
|
||||
unsigned int addr;
|
||||
unsigned short val;
|
||||
DivRegWrite(unsigned int a, unsigned short v):
|
||||
unsigned int val;
|
||||
DivRegWrite(unsigned int a, unsigned int v):
|
||||
addr(a), val(v) {}
|
||||
};
|
||||
|
||||
struct DivDelayedWrite {
|
||||
int time;
|
||||
DivRegWrite write;
|
||||
DivDelayedWrite(int t, unsigned int a, unsigned short v):
|
||||
DivDelayedWrite(int t, unsigned int a, unsigned int v):
|
||||
time(t),
|
||||
write(a,v) {}
|
||||
};
|
||||
|
||||
struct DivSamplePos {
|
||||
int sample, pos, freq;
|
||||
DivSamplePos(int s, int p, int f):
|
||||
sample(s),
|
||||
pos(p),
|
||||
freq(f) {}
|
||||
DivSamplePos():
|
||||
sample(-1),
|
||||
pos(0),
|
||||
freq(0) {}
|
||||
};
|
||||
|
||||
struct DivDispatchOscBuffer {
|
||||
bool follow;
|
||||
unsigned int rate;
|
||||
|
@ -371,18 +390,29 @@ class DivDispatch {
|
|||
|
||||
/**
|
||||
* get the state of a channel.
|
||||
* @param chan the channel.
|
||||
* @return a pointer, or NULL.
|
||||
*/
|
||||
virtual void* getChanState(int chan);
|
||||
|
||||
/**
|
||||
* get the DivMacroInt of a chanmel.
|
||||
* get the DivMacroInt of a channel.
|
||||
* @param chan the channel.
|
||||
* @return a pointer, or NULL.
|
||||
*/
|
||||
virtual DivMacroInt* getChanMacroInt(int chan);
|
||||
|
||||
/**
|
||||
* get currently playing sample (and its position).
|
||||
* @param chan the channel.
|
||||
* @return a DivSamplePos. if sample is -1 then nothing is playing or the
|
||||
* channel doesn't play samples.
|
||||
*/
|
||||
virtual DivSamplePos getSamplePos(int chan);
|
||||
|
||||
/**
|
||||
* get an oscilloscope buffer for a channel.
|
||||
* @param chan the channel.
|
||||
* @return a pointer to a DivDispatchOscBuffer, or NULL if not supported.
|
||||
*/
|
||||
virtual DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
#include "platform/lynx.h"
|
||||
#include "platform/pokey.h"
|
||||
#include "platform/zxbeeper.h"
|
||||
#include "platform/zxbeeperquadtone.h"
|
||||
#include "platform/bubsyswsg.h"
|
||||
#include "platform/n163.h"
|
||||
#include "platform/pet.h"
|
||||
|
@ -76,6 +77,7 @@
|
|||
#include "platform/k007232.h"
|
||||
#include "platform/ga20.h"
|
||||
#include "platform/sm8521.h"
|
||||
#include "platform/pv1000.h"
|
||||
#include "platform/pcmdac.h"
|
||||
#include "platform/dummy.h"
|
||||
#include "../ta-log.h"
|
||||
|
@ -388,6 +390,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_SFX_BEEPER:
|
||||
dispatch=new DivPlatformZXBeeper;
|
||||
break;
|
||||
case DIV_SYSTEM_SFX_BEEPER_QUADTONE:
|
||||
dispatch=new DivPlatformZXBeeperQuadTone;
|
||||
break;
|
||||
case DIV_SYSTEM_LYNX:
|
||||
dispatch=new DivPlatformLynx;
|
||||
break;
|
||||
|
@ -493,6 +498,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_SM8521:
|
||||
dispatch=new DivPlatformSM8521;
|
||||
break;
|
||||
case DIV_SYSTEM_PV1000:
|
||||
dispatch=new DivPlatformPV1000;
|
||||
break;
|
||||
case DIV_SYSTEM_PCM_DAC:
|
||||
dispatch=new DivPlatformPCMDAC;
|
||||
break;
|
||||
|
|
|
@ -55,6 +55,10 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
|
|||
return "03xx: Portamento";
|
||||
case 0x04:
|
||||
return "04xy: Vibrato (x: speed; y: depth)";
|
||||
case 0x05:
|
||||
return "05xy: Volume slide + vibrato (compatibility only!)";
|
||||
case 0x06:
|
||||
return "06xy: Volume slide + portamento (compatibility only!)";
|
||||
case 0x07:
|
||||
return "07xy: Tremolo (x: speed; y: depth)";
|
||||
case 0x08:
|
||||
|
@ -418,6 +422,7 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
|
|||
case DIV_CMD_AMIGA_PM:
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
case DIV_CMD_MACRO_ON:
|
||||
case DIV_CMD_HINT_ARP_TIME:
|
||||
w->writeC(1); // length
|
||||
w->writeC(c.value);
|
||||
break;
|
||||
|
@ -1481,6 +1486,7 @@ void DivEngine::createNewFromDefaults() {
|
|||
bool oldVol=getConfInt("configVersion",DIV_ENGINE_VERSION)<135;
|
||||
if (preset.empty()) {
|
||||
// try loading old preset
|
||||
logD("trying to load old preset");
|
||||
preset=decodeSysDesc(getConfString("initialSys",""));
|
||||
oldVol=false;
|
||||
}
|
||||
|
@ -1566,6 +1572,49 @@ void DivEngine::changeSong(size_t songIndex) {
|
|||
prevRow=0;
|
||||
}
|
||||
|
||||
void DivEngine::checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries) {
|
||||
bool* inAssetDir=new bool[entries];
|
||||
memset(inAssetDir,0,entries*sizeof(bool));
|
||||
|
||||
for (DivAssetDir& i: dir) {
|
||||
for (size_t j=0; j<i.entries.size(); j++) {
|
||||
// erase invalid entry
|
||||
if (i.entries[j]<0 || i.entries[j]>=(int)entries) {
|
||||
i.entries.erase(i.entries.begin()+j);
|
||||
j--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// mark entry as present
|
||||
inAssetDir[j]=true;
|
||||
}
|
||||
}
|
||||
|
||||
// get unsorted directory
|
||||
DivAssetDir* unsortedDir=NULL;
|
||||
for (DivAssetDir& i: dir) {
|
||||
if (i.name.empty()) {
|
||||
unsortedDir=&i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// create unsorted directory if it doesn't exist
|
||||
if (unsortedDir==NULL) {
|
||||
dir.push_back(DivAssetDir(""));
|
||||
unsortedDir=&(*dir.rbegin());
|
||||
}
|
||||
|
||||
// add missing items to unsorted directory
|
||||
for (size_t i=0; i<entries; i++) {
|
||||
if (!inAssetDir[i]) {
|
||||
unsortedDir->entries.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
delete[] inAssetDir;
|
||||
}
|
||||
|
||||
void DivEngine::swapChannelsP(int src, int dest) {
|
||||
if (src<0 || src>=chans) return;
|
||||
if (dest<0 || dest>=chans) return;
|
||||
|
@ -1985,11 +2034,17 @@ String DivEngine::getPlaybackDebugInfo() {
|
|||
"divider: %f\n"
|
||||
"cycles: %d\n"
|
||||
"clockDrift: %f\n"
|
||||
"midiClockCycles: %d\n"
|
||||
"midiClockDrift: %f\n"
|
||||
"midiTimeCycles: %d\n"
|
||||
"midiTimeDrift: %f\n"
|
||||
"changeOrd: %d\n"
|
||||
"changePos: %d\n"
|
||||
"totalSeconds: %d\n"
|
||||
"totalTicks: %d\n"
|
||||
"totalTicksR: %d\n"
|
||||
"curMidiClock: %d\n"
|
||||
"curMidiTime: %d\n"
|
||||
"totalCmds: %d\n"
|
||||
"lastCmds: %d\n"
|
||||
"cmdsPerSecond: %d\n"
|
||||
|
@ -1999,7 +2054,8 @@ String DivEngine::getPlaybackDebugInfo() {
|
|||
"totalProcessed: %d\n"
|
||||
"bufferPos: %d\n",
|
||||
curOrder,prevOrder,curRow,prevRow,ticks,subticks,totalLoops,lastLoopPos,nextSpeed,divider,cycles,clockDrift,
|
||||
changeOrd,changePos,totalSeconds,totalTicks,totalTicksR,totalCmds,lastCmds,cmdsPerSecond,globalPitch,
|
||||
midiClockCycles,midiClockDrift,midiTimeCycles,midiTimeDrift,changeOrd,changePos,totalSeconds,totalTicks,
|
||||
totalTicksR,curMidiClock,curMidiTime,totalCmds,lastCmds,cmdsPerSecond,globalPitch,
|
||||
(int)extValue,(int)tempoAccum,(int)totalProcessed,(int)bufferPos
|
||||
);
|
||||
}
|
||||
|
@ -2075,6 +2131,11 @@ DivMacroInt* DivEngine::getMacroInt(int chan) {
|
|||
return disCont[dispatchOfChan[chan]].dispatch->getChanMacroInt(dispatchChanOfChan[chan]);
|
||||
}
|
||||
|
||||
DivSamplePos DivEngine::getSamplePos(int chan) {
|
||||
if (chan<0 || chan>=chans) return DivSamplePos();
|
||||
return disCont[dispatchOfChan[chan]].dispatch->getSamplePos(dispatchChanOfChan[chan]);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivEngine::getOscBuffer(int chan) {
|
||||
if (chan<0 || chan>=chans) return NULL;
|
||||
return disCont[dispatchOfChan[chan]].dispatch->getOscBuffer(dispatchChanOfChan[chan]);
|
||||
|
@ -2111,16 +2172,26 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
|
|||
prevOrder=0;
|
||||
prevRow=0;
|
||||
stepPlay=0;
|
||||
int prevDrift;
|
||||
int prevDrift, prevMidiClockDrift, prevMidiTimeDrift;
|
||||
prevDrift=clockDrift;
|
||||
prevMidiClockDrift=midiClockDrift;
|
||||
prevMidiTimeDrift=midiTimeDrift;
|
||||
clockDrift=0;
|
||||
cycles=0;
|
||||
midiClockCycles=0;
|
||||
midiClockDrift=0;
|
||||
midiTimeCycles=0;
|
||||
midiTimeDrift=0;
|
||||
if (!preserveDrift) {
|
||||
ticks=1;
|
||||
tempoAccum=0;
|
||||
totalTicks=0;
|
||||
totalSeconds=0;
|
||||
totalTicksR=0;
|
||||
curMidiClock=0;
|
||||
curMidiTime=0;
|
||||
curMidiTimeCode=0;
|
||||
curMidiTimePiece=0;
|
||||
totalLoops=0;
|
||||
lastLoopPos=-1;
|
||||
}
|
||||
|
@ -2137,6 +2208,10 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
|
|||
skipping=false;
|
||||
return;
|
||||
}
|
||||
if (!preserveDrift) {
|
||||
runMidiClock(cycles);
|
||||
runMidiTime(cycles);
|
||||
}
|
||||
}
|
||||
int oldOrder=curOrder;
|
||||
while (playing && (curRow<goalRow || ticks>1)) {
|
||||
|
@ -2144,6 +2219,10 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
|
|||
skipping=false;
|
||||
return;
|
||||
}
|
||||
if (!preserveDrift) {
|
||||
runMidiClock(cycles);
|
||||
runMidiTime(cycles);
|
||||
}
|
||||
if (oldOrder!=curOrder) break;
|
||||
if (ticks-((tempoAccum+curSubSong->virtualTempoN)/MAX(1,curSubSong->virtualTempoD))<1 && curRow>=goalRow) break;
|
||||
}
|
||||
|
@ -2157,9 +2236,22 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
|
|||
repeatPattern=oldRepeatPattern;
|
||||
if (preserveDrift) {
|
||||
clockDrift=prevDrift;
|
||||
midiClockDrift=prevMidiClockDrift;
|
||||
midiTimeDrift=prevMidiTimeDrift;
|
||||
} else {
|
||||
clockDrift=0;
|
||||
cycles=0;
|
||||
midiClockCycles=0;
|
||||
midiClockDrift=0;
|
||||
midiTimeCycles=0;
|
||||
midiTimeDrift=0;
|
||||
if (curMidiTime>0) {
|
||||
curMidiTime--;
|
||||
}
|
||||
if (curMidiClock>0) {
|
||||
curMidiClock--;
|
||||
}
|
||||
curMidiTimePiece=0;
|
||||
}
|
||||
if (!preserveDrift) {
|
||||
ticks=1;
|
||||
|
@ -2328,9 +2420,74 @@ void DivEngine::play() {
|
|||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
keyHit[i]=false;
|
||||
}
|
||||
curMidiTimePiece=0;
|
||||
if (output) if (!skipping && output->midiOut!=NULL) {
|
||||
int pos=totalTicksR/6;
|
||||
output->midiOut->send(TAMidiMessage(TA_MIDI_POSITION,(pos>>7)&0x7f,pos&0x7f));
|
||||
if (midiOutClock) {
|
||||
output->midiOut->send(TAMidiMessage(TA_MIDI_POSITION,(curMidiClock>>7)&0x7f,curMidiClock&0x7f));
|
||||
}
|
||||
if (midiOutTime) {
|
||||
TAMidiMessage msg;
|
||||
msg.type=TA_MIDI_SYSEX;
|
||||
msg.sysExData.reset(new unsigned char[10],std::default_delete<unsigned char[]>());
|
||||
msg.sysExLen=10;
|
||||
unsigned char* msgData=msg.sysExData.get();
|
||||
int actualTime=curMidiTime;
|
||||
int timeRate=midiOutTimeRate;
|
||||
int drop=0;
|
||||
if (timeRate<1 || timeRate>4) {
|
||||
if (curSubSong->hz>=47.98 && curSubSong->hz<=48.02) {
|
||||
timeRate=1;
|
||||
} else if (curSubSong->hz>=49.98 && curSubSong->hz<=50.02) {
|
||||
timeRate=2;
|
||||
} else if (curSubSong->hz>=59.9 && curSubSong->hz<=60.11) {
|
||||
timeRate=4;
|
||||
} else {
|
||||
timeRate=4;
|
||||
}
|
||||
}
|
||||
|
||||
switch (timeRate) {
|
||||
case 1: // 24
|
||||
msgData[5]=(actualTime/(60*60*24))%24;
|
||||
msgData[6]=(actualTime/(60*24))%60;
|
||||
msgData[7]=(actualTime/24)%60;
|
||||
msgData[8]=actualTime%24;
|
||||
break;
|
||||
case 2: // 25
|
||||
msgData[5]=(actualTime/(60*60*25))%24;
|
||||
msgData[6]=(actualTime/(60*25))%60;
|
||||
msgData[7]=(actualTime/25)%60;
|
||||
msgData[8]=actualTime%25;
|
||||
break;
|
||||
case 3: // 29.97 (NTSC drop)
|
||||
// drop
|
||||
drop=((actualTime/(30*60))-(actualTime/(30*600)))*2;
|
||||
actualTime+=drop;
|
||||
|
||||
msgData[5]=(actualTime/(60*60*30))%24;
|
||||
msgData[6]=(actualTime/(60*30))%60;
|
||||
msgData[7]=(actualTime/30)%60;
|
||||
msgData[8]=actualTime%30;
|
||||
break;
|
||||
case 4: // 30 (NTSC non-drop)
|
||||
default:
|
||||
msgData[5]=(actualTime/(60*60*30))%24;
|
||||
msgData[6]=(actualTime/(60*30))%60;
|
||||
msgData[7]=(actualTime/30)%60;
|
||||
msgData[8]=actualTime%30;
|
||||
break;
|
||||
}
|
||||
|
||||
msgData[5]|=(timeRate-1)<<5;
|
||||
|
||||
msgData[0]=0xf0;
|
||||
msgData[1]=0x7f;
|
||||
msgData[2]=0x7f;
|
||||
msgData[3]=0x01;
|
||||
msgData[4]=0x01;
|
||||
msgData[9]=0xf7;
|
||||
output->midiOut->send(msg);
|
||||
}
|
||||
output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_PLAY,0,0));
|
||||
}
|
||||
BUSY_END;
|
||||
|
@ -2369,6 +2526,13 @@ void DivEngine::stepOne(int row) {
|
|||
void DivEngine::stop() {
|
||||
BUSY_BEGIN;
|
||||
freelance=false;
|
||||
if (!playing) {
|
||||
//Send midi panic
|
||||
if (output) if (output->midiOut!=NULL) {
|
||||
output->midiOut->send(TAMidiMessage(TA_MIDI_CONTROL,0x7B,0));
|
||||
logV("Midi panic sent");
|
||||
}
|
||||
}
|
||||
playing=false;
|
||||
extValuePresent=false;
|
||||
endOfSong=false; // what?
|
||||
|
@ -2391,6 +2555,16 @@ void DivEngine::stop() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reset all chan oscs
|
||||
for (int i=0; i<chans; i++) {
|
||||
DivDispatchOscBuffer* buf=disCont[dispatchOfChan[i]].dispatch->getOscBuffer(dispatchChanOfChan[i]);
|
||||
if (buf!=NULL) {
|
||||
memset(buf->data,0,65536*sizeof(short));
|
||||
buf->needle=0;
|
||||
buf->readNeedle=0;
|
||||
}
|
||||
}
|
||||
BUSY_END;
|
||||
}
|
||||
|
||||
|
@ -2454,6 +2628,10 @@ void DivEngine::recalcChans() {
|
|||
if (isInsTypePossible[i]) possibleInsTypes.push_back((DivInstrumentType)i);
|
||||
}
|
||||
|
||||
checkAssetDir(song.insDir,song.ins.size());
|
||||
checkAssetDir(song.waveDir,song.wave.size());
|
||||
checkAssetDir(song.sampleDir,song.sample.size());
|
||||
|
||||
hasLoadedSomething=true;
|
||||
}
|
||||
|
||||
|
@ -2532,6 +2710,10 @@ int DivEngine::divToFileRate(int drate) {
|
|||
return 4;
|
||||
}
|
||||
|
||||
void DivEngine::testFunction() {
|
||||
logI("it works!");
|
||||
}
|
||||
|
||||
int DivEngine::getEffectiveSampleRate(int rate) {
|
||||
if (rate<1) return 0;
|
||||
switch (song.system[0]) {
|
||||
|
@ -2600,10 +2782,17 @@ void DivEngine::previewSampleNoLock(int sample, int note, int pStart, int pEnd)
|
|||
if (rate<=0) rate=song.sample[sample]->centerRate;
|
||||
}
|
||||
if (rate<100) rate=100;
|
||||
double rateOrig=rate;
|
||||
sPreview.rateMul=1;
|
||||
while (sPreview.rateMul<0x40000000 && rate<got.rate) {
|
||||
sPreview.rateMul<<=1;
|
||||
rate*=2.0;
|
||||
}
|
||||
blip_set_rates(samp_bb,rate,got.rate);
|
||||
samp_prevSample=0;
|
||||
sPreview.rate=rate;
|
||||
sPreview.rate=rateOrig;
|
||||
sPreview.pos=(sPreview.pBegin>=0)?sPreview.pBegin:0;
|
||||
sPreview.posSub=0;
|
||||
sPreview.sample=sample;
|
||||
sPreview.wave=-1;
|
||||
sPreview.dir=false;
|
||||
|
@ -2628,10 +2817,17 @@ void DivEngine::previewWaveNoLock(int wave, int note) {
|
|||
blip_clear(samp_bb);
|
||||
double rate=song.wave[wave]->len*((song.tuning*0.0625)*pow(2.0,(double)(note+3)/12.0));
|
||||
if (rate<100) rate=100;
|
||||
double rateOrig=rate;
|
||||
sPreview.rateMul=1;
|
||||
while (sPreview.rateMul<0x40000000 && rate<got.rate) {
|
||||
sPreview.rateMul<<=1;
|
||||
rate*=2.0;
|
||||
}
|
||||
blip_set_rates(samp_bb,rate,got.rate);
|
||||
samp_prevSample=0;
|
||||
sPreview.rate=rate;
|
||||
sPreview.rate=rateOrig;
|
||||
sPreview.pos=0;
|
||||
sPreview.posSub=0;
|
||||
sPreview.sample=-1;
|
||||
sPreview.wave=wave;
|
||||
sPreview.dir=false;
|
||||
|
@ -2997,15 +3193,17 @@ DivWavetable* DivEngine::waveFromFile(const char* path, bool addRaw) {
|
|||
// read as .dmw
|
||||
reader.seek(0,SEEK_SET);
|
||||
int len=reader.readI();
|
||||
logD("wave length %d",len);
|
||||
if (len<=0 || len>256) {
|
||||
throw EndOfFileException(&reader,reader.size());
|
||||
}
|
||||
wave->len=len;
|
||||
wave->max=(unsigned char)reader.readC();
|
||||
if (wave->max==255) { // new wavetable format
|
||||
unsigned char waveVersion=reader.readC();
|
||||
logI("reading modern .dmw...");
|
||||
logD("wave version %d",waveVersion);
|
||||
wave->max=reader.readC();
|
||||
wave->max=(unsigned char)reader.readC();
|
||||
for (int i=0; i<len; i++) {
|
||||
wave->data[i]=reader.readI();
|
||||
}
|
||||
|
@ -3624,7 +3822,8 @@ void DivEngine::addOrder(int pos, bool duplicate, bool where) {
|
|||
}
|
||||
curSubSong->ordersLen++;
|
||||
saveLock.unlock();
|
||||
if (pos<=curOrder) curOrder++;
|
||||
curOrder=pos+1;
|
||||
prevOrder=curOrder;
|
||||
if (playing && !freelance) {
|
||||
playSub(false);
|
||||
}
|
||||
|
@ -4271,6 +4470,10 @@ void DivEngine::quitDispatch() {
|
|||
}
|
||||
cycles=0;
|
||||
clockDrift=0;
|
||||
midiClockCycles=0;
|
||||
midiClockDrift=0;
|
||||
midiTimeCycles=0;
|
||||
midiTimeDrift=0;
|
||||
chans=0;
|
||||
playing=false;
|
||||
curSpeed=0;
|
||||
|
@ -4287,6 +4490,10 @@ void DivEngine::quitDispatch() {
|
|||
totalTicks=0;
|
||||
totalSeconds=0;
|
||||
totalTicksR=0;
|
||||
curMidiClock=0;
|
||||
curMidiTime=0;
|
||||
curMidiTimeCode=0;
|
||||
curMidiTimePiece=0;
|
||||
totalCmds=0;
|
||||
lastCmds=0;
|
||||
cmdsPerSecond=0;
|
||||
|
@ -4313,6 +4520,9 @@ bool DivEngine::initAudioBackend() {
|
|||
lowLatency=getConfInt("lowLatency",0);
|
||||
metroVol=(float)(getConfInt("metroVol",100))/100.0f;
|
||||
midiOutClock=getConfInt("midiOutClock",0);
|
||||
midiOutTime=getConfInt("midiOutTime",0);
|
||||
midiOutTimeRate=getConfInt("midiOutTimeRate",0);
|
||||
midiOutProgramChange = getConfInt("midiOutProgramChange",0);
|
||||
midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE);
|
||||
if (metroVol<0.0f) metroVol=0.0f;
|
||||
if (metroVol>2.0f) metroVol=2.0f;
|
||||
|
@ -4468,6 +4678,7 @@ bool DivEngine::init() {
|
|||
bool oldVol=getConfInt("configVersion",DIV_ENGINE_VERSION)<135;
|
||||
if (preset.empty()) {
|
||||
// try loading old preset
|
||||
logD("trying to load old preset");
|
||||
preset=decodeSysDesc(getConfString("initialSys",""));
|
||||
oldVol=false;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue