diff --git a/CMakeLists.txt b/CMakeLists.txt index c57da901f..661cb566f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,10 @@ set(SYSTEM_SDL2_DEFAULT OFF) if (ANDROID) set(USE_RTMIDI_DEFAULT OFF) set(USE_BACKWARD_DEFAULT OFF) + find_library(TERMUX rt) + if (TERMUX) + message(STATUS "Termux detected") + endif() else() set(USE_RTMIDI_DEFAULT ON) set(USE_BACKWARD_DEFAULT ON) @@ -53,7 +57,7 @@ option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be tre set(DEPENDENCIES_INCLUDE_DIRS "") -if (ANDROID) +if (ANDROID AND NOT TERMUX) set(DEPENDENCIES_DEFINES "IS_MOBILE") else() set(DEPENDENCIES_DEFINES "") @@ -188,7 +192,7 @@ if (USE_SDL2) endif() message(STATUS "Using system-installed SDL2") else() - if (ANDROID) + if (ANDROID AND NOT TERMUX) set(SDL_SHARED ON CACHE BOOL "Force no dynamically-linked SDL" FORCE) set(SDL_STATIC OFF CACHE BOOL "Force statically-linked SDL" FORCE) else() @@ -203,7 +207,7 @@ if (USE_SDL2) add_subdirectory(extern/SDL EXCLUDE_FROM_ALL) list(APPEND DEPENDENCIES_DEFINES HAVE_SDL2) list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/SDL/include) - if (ANDROID) + if (ANDROID AND NOT TERMUX) list(APPEND DEPENDENCIES_LIBRARIES SDL2) else() list(APPEND DEPENDENCIES_LIBRARIES SDL2-static) @@ -579,7 +583,7 @@ endif() if (MSVC) add_executable(furnace WIN32 ${USED_SOURCES}) -elseif(ANDROID) +elseif(ANDROID AND NOT TERMUX) add_library(furnace SHARED ${USED_SOURCES}) else() add_executable(furnace ${USED_SOURCES}) @@ -602,7 +606,7 @@ if (PKG_CONFIG_FOUND AND (SYSTEM_FMT OR SYSTEM_LIBSNDFILE OR SYSTEM_ZLIB OR SYST endif() endif() -if (NOT ANDROID) +if (NOT ANDROID OR TERMUX) install(TARGETS furnace RUNTIME DESTINATION bin) if (NOT WIN32 AND NOT APPLE) diff --git a/TODO.md b/TODO.md index b5d890ff9..44ea3cd0f 100644 --- a/TODO.md +++ b/TODO.md @@ -8,12 +8,10 @@ - additional YM2612 features - CSM - - DualPCM - - reverse sample playback -- ADPCM chips +- MSM6258 pitch and clock select +- the last three compat flags +- add OPL drum instrument type - Game Boy envelope macro/sequence -- drag-and-drop ins/wave/sample loading -- "set loop" in right click menu of sample editor - sample editor preview in selection - rewrite the system name detection function anyway - unified data view diff --git a/demos/AY-3-8910_Jam.fur b/demos/AY-3-8910_Jam.fur new file mode 100644 index 000000000..b249c5bfd Binary files /dev/null and b/demos/AY-3-8910_Jam.fur differ diff --git a/demos/AgentX.fur b/demos/AgentX.fur new file mode 100644 index 000000000..07fe08320 Binary files /dev/null and b/demos/AgentX.fur differ diff --git a/demos/C64 junk.fur b/demos/C64 junk.fur new file mode 100644 index 000000000..fb8e53b04 Binary files /dev/null and b/demos/C64 junk.fur differ diff --git a/demos/Eternal_Forest_Taito_Arcade.fur b/demos/Eternal_Forest_Taito_Arcade.fur new file mode 100644 index 000000000..28c0cd002 Binary files /dev/null and b/demos/Eternal_Forest_Taito_Arcade.fur differ diff --git a/demos/HoldOn.fur b/demos/HoldOn.fur new file mode 100644 index 000000000..6eb2e5df0 Binary files /dev/null and b/demos/HoldOn.fur differ diff --git a/demos/Lagrange_Point.fur b/demos/Lagrange_Point.fur new file mode 100644 index 000000000..bff5248c3 Binary files /dev/null and b/demos/Lagrange_Point.fur differ diff --git a/demos/LedStorm.fur b/demos/LedStorm.fur new file mode 100644 index 000000000..b16674ef6 Binary files /dev/null and b/demos/LedStorm.fur differ diff --git a/demos/Neo_Seaside_Volley_Court.fur b/demos/Neo_Seaside_Volley_Court.fur new file mode 100644 index 000000000..2ab629c23 Binary files /dev/null and b/demos/Neo_Seaside_Volley_Court.fur differ diff --git a/demos/OPL3_SegaPCM_Xeno_Crisis_-_Facility_Area_2.fur b/demos/OPL3_SegaPCM_Xeno_Crisis_-_Facility_Area_2.fur new file mode 100644 index 000000000..b6538bc58 Binary files /dev/null and b/demos/OPL3_SegaPCM_Xeno_Crisis_-_Facility_Area_2.fur differ diff --git a/demos/README.md b/demos/README.md index f7afbdb43..ea3ef883e 100644 --- a/demos/README.md +++ b/demos/README.md @@ -2,8 +2,13 @@ demo songs for Furnace. -these demo songs are not under the GPL. in the case of covers, all rights are reserved to the original author. +these demo songs are not under the GPL. all rights are reserved to the original author(s). # submit demo songs! -just send a pull request if you want your song to be added to this collection. thank you! +contact me or send a pull request if you want your song to be added to this collection. be noted we have two rules: + +- Nintendo covers are frowned upon +- big label music covers also are discouraged + +thank you for contributing! diff --git a/demos/Red_Planet.fur b/demos/Red_Planet.fur new file mode 100644 index 000000000..9f3b4d1e7 Binary files /dev/null and b/demos/Red_Planet.fur differ diff --git a/demos/Road_Rash_Grass_Valley_Lynx_01d.fur b/demos/Road_Rash_Grass_Valley_Lynx_01d.fur new file mode 100644 index 000000000..a8f1cd666 Binary files /dev/null and b/demos/Road_Rash_Grass_Valley_Lynx_01d.fur differ diff --git a/demos/Rusty_-_Queen_in_the_Dark_Night.fur b/demos/Rusty_-_Queen_in_the_Dark_Night.fur new file mode 100644 index 000000000..45e0729dd Binary files /dev/null and b/demos/Rusty_-_Queen_in_the_Dark_Night.fur differ diff --git a/demos/Samsung SGH-x830 - Ringtone 8.fur b/demos/Samsung SGH-x830 - Ringtone 8.fur new file mode 100644 index 000000000..df4a51a18 Binary files /dev/null and b/demos/Samsung SGH-x830 - Ringtone 8.fur differ diff --git a/demos/Stereotactics_Rewritten.fur b/demos/Stereotactics_Rewritten.fur new file mode 100644 index 000000000..6e0787241 Binary files /dev/null and b/demos/Stereotactics_Rewritten.fur differ diff --git a/demos/Tyrian-Camanis.fur b/demos/Tyrian-Camanis.fur new file mode 100644 index 000000000..f57059cc5 Binary files /dev/null and b/demos/Tyrian-Camanis.fur differ diff --git a/demos/UNATCOPCM.fur b/demos/UNATCOPCM.fur new file mode 100644 index 000000000..ca8a0a9f0 Binary files /dev/null and b/demos/UNATCOPCM.fur differ diff --git a/demos/UT99_Run_Taito_Arcade.fur b/demos/UT99_Run_Taito_Arcade.fur new file mode 100644 index 000000000..232ece3cd Binary files /dev/null and b/demos/UT99_Run_Taito_Arcade.fur differ diff --git a/demos/carve_your_own_path.fur b/demos/carve_your_own_path.fur new file mode 100644 index 000000000..2d6736b25 Binary files /dev/null and b/demos/carve_your_own_path.fur differ diff --git a/demos/chippylotus.fur b/demos/chippylotus.fur new file mode 100644 index 000000000..2ff6d3573 Binary files /dev/null and b/demos/chippylotus.fur differ diff --git a/demos/doorintosummer.fur b/demos/doorintosummer.fur new file mode 100644 index 000000000..52a89fb0a Binary files /dev/null and b/demos/doorintosummer.fur differ diff --git a/demos/ecolove.fur b/demos/ecolove.fur new file mode 100644 index 000000000..9f287ec61 Binary files /dev/null and b/demos/ecolove.fur differ diff --git a/demos/fight and flight.fur b/demos/fight and flight.fur new file mode 100644 index 000000000..8f6e416f8 Binary files /dev/null and b/demos/fight and flight.fur differ diff --git a/demos/green_biker_dude_opl.fur b/demos/green_biker_dude_opl.fur new file mode 100644 index 000000000..341abed4e Binary files /dev/null and b/demos/green_biker_dude_opl.fur differ diff --git a/demos/insert_title_lynx.fur b/demos/insert_title_lynx.fur new file mode 100644 index 000000000..8f5781360 Binary files /dev/null and b/demos/insert_title_lynx.fur differ diff --git a/demos/meteor_shower.fur b/demos/meteor_shower.fur new file mode 100644 index 000000000..09df95342 Binary files /dev/null and b/demos/meteor_shower.fur differ diff --git a/demos/oby1_ingame.fur b/demos/oby1_ingame.fur new file mode 100644 index 000000000..956ac22d4 Binary files /dev/null and b/demos/oby1_ingame.fur differ diff --git a/demos/opll-fashioned_drums.fur b/demos/opll-fashioned_drums.fur new file mode 100644 index 000000000..3217182b3 Binary files /dev/null and b/demos/opll-fashioned_drums.fur differ diff --git a/demos/puggs_in_space.fur b/demos/puggs_in_space.fur new file mode 100644 index 000000000..ca3177f78 Binary files /dev/null and b/demos/puggs_in_space.fur differ diff --git a/demos/sijofsjfsoeife.fur b/demos/sijofsjfsoeife.fur new file mode 100644 index 000000000..8c50bc921 Binary files /dev/null and b/demos/sijofsjfsoeife.fur differ diff --git a/demos/skate_or_die.fur b/demos/skate_or_die.fur new file mode 100644 index 000000000..112ba6967 Binary files /dev/null and b/demos/skate_or_die.fur differ diff --git a/demos/su_memory.fur b/demos/su_memory.fur new file mode 100644 index 000000000..cb80b8b17 Binary files /dev/null and b/demos/su_memory.fur differ diff --git a/demos/yky.fur b/demos/yky.fur index 106ed1004..fc8188c19 100644 Binary files a/demos/yky.fur and b/demos/yky.fur differ diff --git a/extern/Nuked-PSG/ympsg.c b/extern/Nuked-PSG/ympsg.c index f6b8247b3..3df4f8e39 100644 --- a/extern/Nuked-PSG/ympsg.c +++ b/extern/Nuked-PSG/ympsg.c @@ -8,6 +8,10 @@ const float ympsg_vol[17] = { 1.0, 0.772, 0.622, 0.485, 0.382, 0.29, 0.229, 0.174, 0.132, 0.096, 0.072, 0.051, 0.034, 0.019, 0.009, 0.0, -1.059 }; +const float tipsg_vol[17] = { + 1.0, 0.794, 0.631, 0.501, 0.398, 0.316, 0.251, 0.2, 0.158, 0.126, 0.1, 0.079, 0.063, 0.05, 0.04, 0.0, -1.059 +}; + static void YMPSG_WriteLatch(ympsg_t *chip) { uint8_t data = chip->data; @@ -260,6 +264,10 @@ void YMPSG_Init(ympsg_t *chip, uint8_t real_sn) YMPSG_SetIC(chip, 1); chip->noise_tap2 = real_sn ? 13 : 15; chip->noise_size = real_sn ? 16383 : 32767; + for (i = 0; i < 17; i++) + { + chip->vol_table[i]=(real_sn?tipsg_vol[i]:ympsg_vol[i]) * 8192.0f; + } for (i = 0; i < 16; i++) { YMPSG_Clock(chip); @@ -307,29 +315,29 @@ void YMPSG_Clock(ympsg_t *chip) } } -float YMPSG_GetOutput(ympsg_t *chip) +int YMPSG_GetOutput(ympsg_t *chip) { - float sample = 0.f; + int sample = 0; uint32_t i; YMPSG_UpdateSample(chip); if (chip->test & 1) { - sample += ympsg_vol[chip->volume_out[chip->test >> 1]]; - sample += ympsg_vol[16] * 3.f; + sample += chip->vol_table[chip->volume_out[chip->test >> 1]]; + sample += chip->vol_table[16] * 3; } else if (!chip->mute) { - sample += ympsg_vol[chip->volume_out[0]]; - sample += ympsg_vol[chip->volume_out[1]]; - sample += ympsg_vol[chip->volume_out[2]]; - sample += ympsg_vol[chip->volume_out[3]]; + sample += chip->vol_table[chip->volume_out[0]]; + sample += chip->vol_table[chip->volume_out[1]]; + sample += chip->vol_table[chip->volume_out[2]]; + sample += chip->vol_table[chip->volume_out[3]]; } else { for (i = 0; i < 4; i++) { if (!((chip->mute>>i) & 1)) - sample += ympsg_vol[chip->volume_out[i]]; + sample += chip->vol_table[chip->volume_out[i]]; } } return sample; diff --git a/extern/Nuked-PSG/ympsg.h b/extern/Nuked-PSG/ympsg.h index 063105809..c00b3d720 100644 --- a/extern/Nuked-PSG/ympsg.h +++ b/extern/Nuked-PSG/ympsg.h @@ -58,6 +58,10 @@ typedef struct { uint64_t writebuf_lasttime; ympsg_writebuf writebuf[YMPSG_WRITEBUF_SIZE]; + // + short vol_table[17]; + + uint8_t mute; } ympsg_t; @@ -67,7 +71,7 @@ uint16_t YMPSG_Read(ympsg_t *chip); void YMPSG_Init(ympsg_t *chip, uint8_t real_sn); void YMPSG_SetIC(ympsg_t *chip, uint32_t ic); void YMPSG_Clock(ympsg_t *chip); -float YMPSG_GetOutput(ympsg_t *chip); +int YMPSG_GetOutput(ympsg_t *chip); void YMPSG_Test(ympsg_t *chip, uint16_t test); diff --git a/extern/backward/backward.hpp b/extern/backward/backward.hpp index 37620265c..e778d58f3 100644 --- a/extern/backward/backward.hpp +++ b/extern/backward/backward.hpp @@ -4252,7 +4252,20 @@ public: st.load_here(32, reinterpret_cast(uctx), info->si_addr); } - FILE* crashDump=fopen("furnace_crash.txt","w"); +#ifdef _WIN32 + MessageBox(NULL,"Error","Furnace has crashed! please report this to the issue tracker immediately:\r\nhttps://github.com/tildearrow/furnace/issues/new\r\n\r\na file called furnace_crash.txt will be created in your user directory.\r\nthis will be important for locating the origin of the crash.",MB_OK|MB_ICONERROR); + std::string crashLocation; + char* userProfile=getenv("USERPROFILE"); + if (userProfile==NULL) { + crashLocation="C:\\furnace_crash.txt"; + } else { + crashLocation=userProfile; + crashLocation+="\\furnace_crash.txt"; + } + FILE* crashDump=fopen(crashLocation.c_str(),"w"); +#else + FILE* crashDump=fopen("/tmp/furnace_crash.txt","w"); +#endif Printer printer; printer.address = true; @@ -4263,6 +4276,16 @@ public: printer.address = true; printer.print(st, crashDump); fclose(crashDump); + } else { +#ifdef _WIN32 + std::string str; + Printer failedPrinter; + failedPrinter.address = true; + failedPrinter.print(st, str); + str+="\r\ncould not open furnace_crash.txt!\r\nplease take a screenshot of this error message box!"; + fprintf(stderr,"NOTICE: could not open furnace_crash.txt!\n"); + MessageBox(NULL,"Error",str.c_str(),MB_OK|MB_ICONERROR); +#endif } #if (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700) || \ diff --git a/extern/imgui_patched/imgui_internal.h b/extern/imgui_patched/imgui_internal.h index 070d59f0c..3ec72742e 100644 --- a/extern/imgui_patched/imgui_internal.h +++ b/extern/imgui_patched/imgui_internal.h @@ -559,14 +559,44 @@ template struct ImBitArray { ImU32 Storage[(BITCOUNT + 31) >> 5]; - ImBitArray() { ClearAllBits(); } - void ClearAllBits() { memset(Storage, 0, sizeof(Storage)); } - void SetAllBits() { memset(Storage, 255, sizeof(Storage)); } - bool TestBit(int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return ImBitArrayTestBit(Storage, n); } - void SetBit(int n) { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArraySetBit(Storage, n); } - void ClearBit(int n) { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArrayClearBit(Storage, n); } - void SetBitRange(int n, int n2) { n += OFFSET; n2 += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT && n2 > n && n2 <= BITCOUNT); ImBitArraySetBitRange(Storage, n, n2); } // Works on range [n..n2) - bool operator[](int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return ImBitArrayTestBit(Storage, n); } + ImBitArray() { ClearAllBits(); } + void ClearAllBits() { memset(Storage, 0, sizeof(Storage)); } + void SetAllBits() { memset(Storage, 255, sizeof(Storage)); } + bool TestBit(int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return ImBitArrayTestBit(Storage, n); } + void SetBit(int n) { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArraySetBit(Storage, n); } + void ClearBit(int n) { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArrayClearBit(Storage, n); } + void SetBitRange(int n, int n2) { n += OFFSET; n2 += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT && n2 > n && n2 <= BITCOUNT); ImBitArraySetBitRange(Storage, n, n2); } // Works on range [n..n2) + bool operator[](int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return ImBitArrayTestBit(Storage, n); } + ImBitArray& operator|=(int n) { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArraySetBit(Storage, n); return *this; } + bool operator&(int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return ImBitArrayTestBit(Storage, n); } + bool operator==(ImBitArray const &a) const + { + for (int i = 0; i < ((BITCOUNT + 31) >> 5); ++i) + if (Storage[i] != a.Storage[i]) + return false; + return true; + } + bool operator!=(ImBitArray const &a) const + { + for (int i = 0; i < ((BITCOUNT + 31) >> 5); ++i) + if (Storage[i] == a.Storage[i]) + return false; + return true; + } + template bool operator==(ImBitArray const &a) const + { + for (int i = 0; i < ImMin((DSTBITCOUNT + 31) >> 5, (BITCOUNT + 31) >> 5); ++i) + if (Storage[i] != a.Storage[i]) + return false; + return true; + } + template bool operator!=(ImBitArray const &a) const + { + for (int i = 0; i < ImMin((DSTBITCOUNT + 31) >> 5, (BITCOUNT + 31) >> 5); ++i) + if (Storage[i] == a.Storage[i]) + return false; + return true; + } }; // Helper: ImBitVector @@ -574,11 +604,33 @@ struct ImBitArray struct IMGUI_API ImBitVector { ImVector Storage; - void Create(int sz) { Storage.resize((sz + 31) >> 5); memset(Storage.Data, 0, (size_t)Storage.Size * sizeof(Storage.Data[0])); } - void Clear() { Storage.clear(); } - bool TestBit(int n) const { IM_ASSERT(n < (Storage.Size << 5)); return ImBitArrayTestBit(Storage.Data, n); } - void SetBit(int n) { IM_ASSERT(n < (Storage.Size << 5)); ImBitArraySetBit(Storage.Data, n); } - void ClearBit(int n) { IM_ASSERT(n < (Storage.Size << 5)); ImBitArrayClearBit(Storage.Data, n); } + int BitCount = 0; + ImBitVector(int sz = 0) { if (sz > 0) { Create(sz); } } + void Create(int sz) { BitCount = sz; Storage.resize((sz + 31) >> 5); memset(Storage.Data, 0, (size_t)Storage.Size * sizeof(Storage.Data[0])); } + void Clear() { Storage.clear(); } + void ClearAllBits() { IM_ASSERT(Storage.Size > 0); memset(Storage.Data, 0, (size_t)Storage.Size * sizeof(Storage.Data[0])); } + void SetAllBits() { IM_ASSERT(Storage.Size > 0); memset(Storage.Data, 255, (size_t)Storage.Size * sizeof(Storage.Data[0])); } + bool TestBit(int n) const { IM_ASSERT(n >= 0 && n < BitCount); return ImBitArrayTestBit(Storage.Data, n); } + void SetBit(int n) { IM_ASSERT(n >= 0 && n < BitCount); ImBitArraySetBit(Storage.Data, n); } + void SetBitRange(int n, int n2) { IM_ASSERT(n >= 0 && n < BitCount && n2 > n && n2 <= BitCount); ImBitArraySetBitRange(Storage.Data, n, n2); } // Works on range [n..n2) + void ClearBit(int n) { IM_ASSERT(n >= 0 && n < BitCount); ImBitArrayClearBit(Storage.Data, n); } + bool operator[](int n) const { IM_ASSERT(n >= 0 && n < BitCount); return ImBitArrayTestBit(Storage.Data, n); } + ImBitVector& operator|=(int n) { IM_ASSERT(n >= 0 && n < BitCount); ImBitArraySetBit(Storage.Data, n); return *this; } + bool operator&(int n) const { IM_ASSERT(n >= 0 && n < BitCount); return ImBitArrayTestBit(Storage.Data, n); } + bool operator==(ImBitVector const &a) const + { + for (int i = 0; i < ImMin((a.BitCount + 31) >> 5, (BitCount + 31) >> 5); ++i) + if (Storage[i] != a.Storage[i]) + return false; + return true; + } + bool operator!=(ImBitVector const &a) const + { + for (int i = 0; i < ImMin((a.BitCount + 31) >> 5, (BitCount + 31) >> 5); ++i) + if (Storage[i] == a.Storage[i]) + return false; + return true; + } }; // Helper: ImSpan<> @@ -2447,13 +2499,12 @@ struct IMGUI_API ImGuiTabBar // [SECTION] Table support //----------------------------------------------------------------------------- -#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. -#define IMGUI_TABLE_MAX_COLUMNS 64 // sizeof(ImU64) * 8. This is solely because we frequently encode columns set in a ImU64. -#define IMGUI_TABLE_MAX_DRAW_CHANNELS (4 + 64 * 2) // See TableSetupDrawChannels() +#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. +#define IMGUI_TABLE_DRAW_CHANNELS(c) (4 + (c) * 2) // See TableSetupDrawChannels() -// Our current column maximum is 64 but we may raise that in the future. -typedef ImS8 ImGuiTableColumnIdx; -typedef ImU8 ImGuiTableDrawChannelIdx; +// Our current column maximum is IMGUI_TABLE_MAX_COLUMNS but we may raise that in the future. +typedef ImS32 ImGuiTableColumnIdx; +typedef ImU32 ImGuiTableDrawChannelIdx; // [Internal] sizeof() ~ 104 // We use the terminology "Enabled" to refer to a column that is not Hidden by user/api. @@ -2543,10 +2594,10 @@ struct IMGUI_API ImGuiTable ImSpan Columns; // Point within RawData[] ImSpan DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1) ImSpan RowCellData; // Point within RawData[]. Store cells background requests for current row. - ImU64 EnabledMaskByDisplayOrder; // Column DisplayOrder -> IsEnabled map - ImU64 EnabledMaskByIndex; // Column Index -> IsEnabled map (== not hidden by user/api) in a format adequate for iterating column without touching cold data - ImU64 VisibleMaskByIndex; // Column Index -> IsVisibleX|IsVisibleY map (== not hidden by user/api && not hidden by scrolling/cliprect) - ImU64 RequestOutputMaskByIndex; // Column Index -> IsVisible || AutoFit (== expect user to submit items) + ImBitVector EnabledMaskByDisplayOrder; // Column DisplayOrder -> IsEnabled map + ImBitVector EnabledMaskByIndex; // Column Index -> IsEnabled map (== not hidden by user/api) in a format adequate for iterating column without touching cold data + ImBitVector VisibleMaskByIndex; // Column Index -> IsVisibleX|IsVisibleY map (== not hidden by user/api && not hidden by scrolling/cliprect) + ImBitVector RequestOutputMaskByIndex; // Column Index -> IsVisible || AutoFit (== expect user to submit items) ImGuiTableFlags SettingsLoadedFlags; // Which data were loaded from the .ini file (e.g. when order is not altered we won't save order) int SettingsOffset; // Offset in g.SettingsTables int LastFrameActive; diff --git a/extern/imgui_patched/imgui_tables.cpp b/extern/imgui_patched/imgui_tables.cpp index bf04f44b5..9b67811c7 100644 --- a/extern/imgui_patched/imgui_tables.cpp +++ b/extern/imgui_patched/imgui_tables.cpp @@ -315,7 +315,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG return false; // Sanity checks - IM_ASSERT(columns_count > 0 && columns_count <= IMGUI_TABLE_MAX_COLUMNS && "Only 1..64 columns allowed!"); + IM_ASSERT(columns_count > 0 && "Only 1..64 columns allowed!"); if (flags & ImGuiTableFlags_ScrollX) IM_ASSERT(inner_width >= 0.0f); @@ -358,6 +358,16 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->LastFrameActive = g.FrameCount; table->OuterWindow = table->InnerWindow = outer_window; table->ColumnsCount = columns_count; + if (table->EnabledMaskByDisplayOrder.BitCount < columns_count || + table->EnabledMaskByIndex.BitCount < columns_count || + table->VisibleMaskByIndex.BitCount < columns_count || + table->RequestOutputMaskByIndex.BitCount < columns_count) + { + table->EnabledMaskByDisplayOrder.Create(columns_count); + table->EnabledMaskByIndex.Create(columns_count); + table->VisibleMaskByIndex.Create(columns_count); + table->RequestOutputMaskByIndex.Create(columns_count); + } table->IsLayoutLocked = false; table->InnerWidth = inner_width; temp_data->UserOuterSize = outer_size; @@ -721,8 +731,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_); table->IsDefaultDisplayOrder = true; table->ColumnsEnabledCount = 0; - table->EnabledMaskByIndex = 0x00; - table->EnabledMaskByDisplayOrder = 0x00; + table->EnabledMaskByIndex.ClearAllBits(); + table->EnabledMaskByDisplayOrder.ClearAllBits(); table->LeftMostEnabledColumn = -1; table->MinColumnWidth = ImMax(1.0f, g.Style.FramePadding.x * 1.0f); // g.Style.ColumnsMinSpacing; // FIXME-TABLE @@ -787,8 +797,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) else table->LeftMostEnabledColumn = (ImGuiTableColumnIdx)column_n; column->IndexWithinEnabledSet = table->ColumnsEnabledCount++; - table->EnabledMaskByIndex |= (ImU64)1 << column_n; - table->EnabledMaskByDisplayOrder |= (ImU64)1 << column->DisplayOrder; + table->EnabledMaskByIndex |= column_n; + table->EnabledMaskByDisplayOrder |= column->DisplayOrder; prev_visible_column_idx = column_n; IM_ASSERT(column->IndexWithinEnabledSet <= column->DisplayOrder); @@ -836,7 +846,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->LeftMostStretchedColumn = table->RightMostStretchedColumn = -1; for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { - if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n))) + if (!(table->EnabledMaskByIndex & column_n)) continue; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -852,7 +862,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!) if (column->AutoFitQueue != 0x00) column->WidthRequest = width_auto; - else if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !column_is_resizable && (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n))) + else if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !column_is_resizable && (table->RequestOutputMaskByIndex & column_n)) column->WidthRequest = width_auto; // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets @@ -899,7 +909,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->ColumnsGivenWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount; for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { - if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n))) + if (!(table->EnabledMaskByIndex & column_n)) continue; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -926,7 +936,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (width_remaining_for_stretched_columns >= 1.0f && !(table->Flags & ImGuiTableFlags_PreciseWidths)) for (int order_n = table->ColumnsCount - 1; stretch_sum_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--) { - if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!(table->EnabledMaskByDisplayOrder & order_n)) continue; ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]]; if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch)) @@ -949,8 +959,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) float offset_x = ((table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x) + table->OuterPaddingX - table->CellSpacingX1; ImRect host_clip_rect = table->InnerClipRect; //host_clip_rect.Max.x += table->CellPaddingX + table->CellSpacingX2; - table->VisibleMaskByIndex = 0x00; - table->RequestOutputMaskByIndex = 0x00; + table->VisibleMaskByIndex.ClearAllBits(); + table->RequestOutputMaskByIndex.ClearAllBits(); for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { const int column_n = table->DisplayOrderToIndex[order_n]; @@ -967,7 +977,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Clear status flags column->Flags &= ~ImGuiTableColumnFlags_StatusMask_; - if ((table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)) == 0) + if ((table->EnabledMaskByDisplayOrder & order_n) == 0) { // Hidden column: clear a few fields and we are done with it for the remainder of the function. // We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper. @@ -1020,12 +1030,12 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->IsVisibleY = true; // (column->ClipRect.Max.y > column->ClipRect.Min.y); const bool is_visible = column->IsVisibleX; //&& column->IsVisibleY; if (is_visible) - table->VisibleMaskByIndex |= ((ImU64)1 << column_n); + table->VisibleMaskByIndex |= column_n; // Mark column as requesting output from user. Note that fixed + non-resizable sets are auto-fitting at all times and therefore always request output. column->IsRequestOutput = is_visible || column->AutoFitQueue != 0 || column->CannotSkipItemsQueue != 0; if (column->IsRequestOutput) - table->RequestOutputMaskByIndex |= ((ImU64)1 << column_n); + table->RequestOutputMaskByIndex |= column_n; // Mark column as SkipItems (ignoring all items/layout) column->IsSkipItems = !column->IsEnabled || table->HostSkipItems; @@ -1153,7 +1163,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { - if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!(table->EnabledMaskByDisplayOrder & order_n)) continue; const int column_n = table->DisplayOrderToIndex[order_n]; @@ -1289,7 +1299,7 @@ void ImGui::EndTable() float auto_fit_width_for_stretched = 0.0f; float auto_fit_width_for_stretched_min = 0.0f; for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - if (table->EnabledMaskByIndex & ((ImU64)1 << column_n)) + if (table->EnabledMaskByIndex & column_n) { ImGuiTableColumn* column = &table->Columns[column_n]; float column_width_request = ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !(column->Flags & ImGuiTableColumnFlags_NoResize)) ? column->WidthRequest : TableGetColumnWidthAuto(table, column); @@ -1480,7 +1490,7 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows) ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); - IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS); + IM_ASSERT(columns >= 0); IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit table->FreezeColumnsRequest = (table->Flags & ImGuiTableFlags_ScrollX) ? (ImGuiTableColumnIdx)ImMin(columns, table->ColumnsCount) : 0; @@ -1635,7 +1645,7 @@ void ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n return; if (column_n == -1) column_n = table->CurrentColumn; - if ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0) + if ((table->VisibleMaskByIndex & column_n) == 0) return; if (table->RowCellDataCurrent < 0 || table->RowCellData[table->RowCellDataCurrent].Column != column_n) table->RowCellDataCurrent++; @@ -1910,7 +1920,7 @@ bool ImGui::TableSetColumnIndex(int column_n) // Return whether the column is visible. User may choose to skip submitting items based on this return value, // however they shouldn't skip submitting for columns that may have the tallest contribution to row height. - return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0; + return (table->RequestOutputMaskByIndex & column_n) != 0; } // [Public] Append into the next column, wrap and create a new row when already on last column @@ -1936,7 +1946,7 @@ bool ImGui::TableNextColumn() // Return whether the column is visible. User may choose to skip submitting items based on this return value, // however they shouldn't skip submitting for columns that may have the tallest contribution to row height. int column_n = table->CurrentColumn; - return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0; + return (table->RequestOutputMaskByIndex & column_n) != 0; } @@ -2349,17 +2359,23 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) { ImRect ClipRect; int ChannelsCount; - ImBitArray ChannelsMask; + ImBitVector ChannelsMask; - MergeGroup() { ChannelsCount = 0; } + MergeGroup(int sz) : ChannelsMask(sz) { ChannelsCount = 0; } }; int merge_group_mask = 0x00; - MergeGroup merge_groups[4]; + int merge_group_bitlen = IMGUI_TABLE_DRAW_CHANNELS(table->ColumnsCount); + MergeGroup merge_groups[4]{ + merge_group_bitlen, + merge_group_bitlen, + merge_group_bitlen, + merge_group_bitlen + }; // 1. Scan channels and take note of those which can be merged for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { - if ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0) + if ((table->VisibleMaskByIndex & column_n) == 0) continue; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -2391,7 +2407,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) } const int merge_group_n = (has_freeze_h && column_n < table->FreezeColumnsCount ? 0 : 1) + (has_freeze_v && merge_group_sub_n == 0 ? 0 : 2); - IM_ASSERT(channel_no < IMGUI_TABLE_MAX_DRAW_CHANNELS); + IM_ASSERT(channel_no < merge_group_bitlen); MergeGroup* merge_group = &merge_groups[merge_group_n]; if (merge_group->ChannelsCount == 0) merge_group->ClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); @@ -2431,7 +2447,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) const int LEADING_DRAW_CHANNELS = 2; g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - LEADING_DRAW_CHANNELS); // Use shared temporary storage so the allocation gets amortized ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data; - ImBitArray remaining_mask; // We need 132-bit of storage + ImBitVector remaining_mask(merge_group_bitlen); // We need 132-bit of storage remaining_mask.SetBitRange(LEADING_DRAW_CHANNELS, splitter->_Count); remaining_mask.ClearBit(table->Bg2DrawChannelUnfrozen); IM_ASSERT(has_freeze_v == false || table->Bg2DrawChannelUnfrozen != TABLE_DRAW_CHANNEL_BG2_FROZEN); @@ -2466,7 +2482,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) GetOverlayDrawList()->AddLine(merge_group->ClipRect.Max, merge_clip_rect.Max, IM_COL32(255, 100, 0, 200)); #endif remaining_count -= merge_group->ChannelsCount; - for (int n = 0; n < IM_ARRAYSIZE(remaining_mask.Storage); n++) + for (int n = 0; n < remaining_mask.Storage.size(); n++) remaining_mask.Storage[n] &= ~merge_group->ChannelsMask.Storage[n]; for (int n = 0; n < splitter->_Count && merge_channels_count != 0; n++) { @@ -2523,7 +2539,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) { for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { - if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!(table->EnabledMaskByDisplayOrder & order_n)) continue; const int column_n = table->DisplayOrderToIndex[order_n]; diff --git a/papers/format.md b/papers/format.md index d519e8636..013f2c5d2 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,8 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 99: Furnace dev99 +- 98: Furnace dev98 - 97: Furnace dev97 - 96: Furnace dev96 - 95: Furnace dev95 @@ -226,6 +228,9 @@ size | description | - 0xb9: Namco WSG - 3 channels | - 0xba: Namco 15xx - 8 channels | - 0xbb: Namco CUS30 - 8 channels + | - 0xbc: reserved - 8 channels + | - 0xbd: YM2612 extra features extended - 11 channels + | - 0xbe: YM2612 extra features - 7 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfd: Dummy System - 8 channels @@ -302,7 +307,11 @@ size | description 1 | pitch macro is linear (>=90) or reserved 1 | pitch slide speed in full linear pitch mode (>=94) or reserved 1 | old octave boundary behavior (>=97) or reserved - 13 | reserved + 1 | disable OPN2 DAC volume control (>=98) or reserved + 1 | new volume scaling strategy (>=99) or reserved + 1 | volume macro still applies after end (>=99) or reserved + 1 | broken outVol (>=99) or reserved + 9 | reserved --- | **virtual tempo data** 2 | virtual tempo numerator of first song (>=96) or reserved 2 | virtual tempo denominator of first song (>=96) or reserved diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 8e9530b2f..adad1c0c9 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -59,6 +59,7 @@ enum DivDispatchCmds { DIV_CMD_SAMPLE_FREQ, // (frequency) DIV_CMD_SAMPLE_BANK, // (bank) DIV_CMD_SAMPLE_POS, // (pos) + DIV_CMD_SAMPLE_DIR, // (direction) DIV_CMD_SAMPLE_TRANSWAVE_SLICE_MODE, // (enabled) DIV_CMD_SAMPLE_TRANSWAVE_SLICE_POS, // (slice) @@ -236,6 +237,8 @@ struct DivRegWrite { * - data is the sample rate * - 0xffffxx02: stop sample playback * - xx is the instance ID + * - 0xffffxx03: set sample playback direction + * - x is the instance ID * - 0xffffffff: reset */ unsigned int addr; diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 3f7d0ba48..5f7c082ea 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -185,10 +185,22 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_YM2612: dispatch=new DivPlatformGenesis; ((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); + ((DivPlatformGenesis*)dispatch)->setSoftPCM(false); break; case DIV_SYSTEM_YM2612_EXT: dispatch=new DivPlatformGenesisExt; ((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); + ((DivPlatformGenesisExt*)dispatch)->setSoftPCM(false); + break; + case DIV_SYSTEM_YM2612_FRAC: + dispatch=new DivPlatformGenesis; + ((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); + ((DivPlatformGenesis*)dispatch)->setSoftPCM(true); + break; + case DIV_SYSTEM_YM2612_FRAC_EXT: + dispatch=new DivPlatformGenesisExt; + ((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); + ((DivPlatformGenesisExt*)dispatch)->setSoftPCM(true); break; case DIV_SYSTEM_SMS: dispatch=new DivPlatformSMS; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a4227feec..f481d67c9 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -697,7 +697,7 @@ void DivEngine::initSongWithDesc(const int* description) { song.systemFlags[index]=description[i+3]; index++; chanCount+=getChannelCount(song.system[index]); - if (chanCount>=63) break; + if (chanCount>=DIV_MAX_CHANS) break; if (index>=32) break; } song.systemLen=index; @@ -887,9 +887,8 @@ bool DivEngine::addSystem(DivSystem which) { lastError="max number of systems is 32"; return false; } - // this was DIV_MAX_CHANS but I am setting it to 63 for now due to an ImGui limitation - if (chans+getChannelCount(which)>63) { - lastError="max number of total channels is 63"; + if (chans+getChannelCount(which)>DIV_MAX_CHANS) { + lastError=fmt::sprintf("max number of total channels is %d",DIV_MAX_CHANS); return false; } quitDispatch(); @@ -1773,7 +1772,7 @@ int DivEngine::addWave() { return waveCount; } -bool DivEngine::addWaveFromFile(const char* path) { +bool DivEngine::addWaveFromFile(const char* path, bool addRaw) { if (song.wave.size()>=256) { lastError="too many wavetables!"; return false; @@ -1869,8 +1868,27 @@ bool DivEngine::addWaveFromFile(const char* path) { } } else { // read as binary - logI("reading binary..."); + if (addRaw) { + logI("reading binary..."); + len=reader.size(); + if (len>256) len=256; + reader.seek(0,SEEK_SET); + for (int i=0; idata[i]=(unsigned char)reader.readC(); + if (wave->maxdata[i]) wave->max=wave->data[i]; + } + wave->len=len; + } else { + delete wave; + delete[] buf; + return false; + } + } + } catch (EndOfFileException& e) { + // read as binary + if (addRaw) { len=reader.size(); + logI("reading binary for being too small..."); if (len>256) len=256; reader.seek(0,SEEK_SET); for (int i=0; imaxdata[i]) wave->max=wave->data[i]; } wave->len=len; + } else { + delete wave; + delete[] buf; + return false; } - } catch (EndOfFileException& e) { - // read as binary - len=reader.size(); - logI("reading binary for being too small..."); - if (len>256) len=256; - reader.seek(0,SEEK_SET); - for (int i=0; idata[i]=(unsigned char)reader.readC(); - if (wave->maxdata[i]) wave->max=wave->data[i]; - } - wave->len=len; } } } catch (EndOfFileException& e) { diff --git a/src/engine/engine.h b/src/engine/engine.h index 6d0743e68..22466d00b 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -45,8 +45,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev97" -#define DIV_ENGINE_VERSION 97 +#define DIV_VERSION "dev99" +#define DIV_ENGINE_VERSION 99 // for imports #define DIV_VERSION_MOD 0xff01 @@ -377,7 +377,7 @@ class DivEngine { void processRow(int i, bool afterDelay); void nextOrder(); void nextRow(); - void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool isSecond); + void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond); // returns true if end of song. bool nextTick(bool noAccum=false, bool inhibitLowLat=false); bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal); @@ -676,7 +676,7 @@ class DivEngine { int addWave(); // add wavetable from file - bool addWaveFromFile(const char* path); + bool addWaveFromFile(const char* path, bool loadRaw=true); // delete wavetable void delWave(int index); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 7ce1b7b2c..3bc0b4912 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -167,6 +167,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.fbPortaPause=true; ds.snDutyReset=true; ds.oldOctaveBoundary=false; + ds.noOPN2Vol=true; + ds.newVolumeScaling=false; + ds.volMacroLinger=false; + ds.brokenOutVol=true; // ??? // 1.1 compat flags if (ds.version>24) { @@ -1031,6 +1035,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<97) { ds.oldOctaveBoundary=true; } + if (ds.version<97) { // actually should be 98 but yky uses this feature ahead of time + ds.noOPN2Vol=true; + } + if (ds.version<99) { + ds.newVolumeScaling=false; + ds.volMacroLinger=false; + ds.brokenOutVol=true; + } ds.isDMF=false; reader.readS(); // reserved @@ -1413,7 +1425,21 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<13; i++) { + if (ds.version>=98) { + ds.noOPN2Vol=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=99) { + ds.newVolumeScaling=reader.readC(); + ds.volMacroLinger=reader.readC(); + ds.brokenOutVol=reader.readC(); + } else { + reader.readC(); + reader.readC(); + reader.readC(); + } + for (int i=0; i<9; i++) { reader.readC(); } } @@ -2894,7 +2920,11 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeC(song.pitchMacroIsLinear); w->writeC(song.pitchSlideSpeed); w->writeC(song.oldOctaveBoundary); - for (int i=0; i<13; i++) { + w->writeC(song.noOPN2Vol); + w->writeC(song.newVolumeScaling); + w->writeC(song.volMacroLinger); + w->writeC(song.brokenOutVol); + for (int i=0; i<9; i++) { w->writeC(0); } diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index d1bf35a6e..de5283e07 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -125,51 +125,109 @@ const char* DivPlatformGenesis::getEffectName(unsigned char effect) { case 0x5f: return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; break; + case 0xdf: + return "DFxx: Set sample playback direction (0: normal; 1: reverse)"; + break; } return NULL; } +void DivPlatformGenesis::processDAC() { + if (softPCM) { + softPCMTimer+=chipClock/576; + if (softPCMTimer>rate) { + softPCMTimer-=rate; + + int sample=0; + for (int i=5; i<7; i++) { + if (chan[i].dacSample!=-1) { + DivSample* s=parent->getSample(chan[i].dacSample); + if (!isMuted[i] && s->samples>0) { + if (parent->song.noOPN2Vol) { + sample+=s->data8[chan[i].getDacDirection()?(s->samples-chan[i].dacPos-1):chan[i].dacPos]; + } else { + sample+=(s->data8[chan[i].getDacDirection()?(s->samples-chan[i].dacPos-1):chan[i].dacPos]*dacVolTable[chan[i].outVol])>>7; + } + } + chan[i].dacPeriod+=chan[i].dacRate; + if (chan[i].dacPeriod>=(chipClock/576)) { + if (s->samples>0) { + while (chan[i].dacPeriod>=(chipClock/576)) { + chan[i].dacPos++; + if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].dacPos>=s->loopEnd) || (chan[i].dacPos>=s->samples)) { + if (s->isLoopable() && !chan[i].getDacDirection()) { + chan[i].dacPos=s->loopStart; + } else { + chan[i].dacSample=-1; + chan[i].dacPeriod=0; + break; + } + } + chan[i].dacPeriod-=(chipClock/576); + } + } else { + chan[i].dacSample=-1; + } + } + } + } + //sample>>=1; + if (sample<-128) sample=-128; + if (sample>127) sample=127; + urgentWrite(0x2a,(unsigned char)sample+0x80); + } + } else { + if (!chan[5].dacReady) { + chan[5].dacDelay+=32000; + if (chan[5].dacDelay>=rate) { + chan[5].dacDelay-=rate; + chan[5].dacReady=true; + } + } + if (chan[5].dacMode && chan[5].dacSample!=-1) { + chan[5].dacPeriod+=chan[5].dacRate; + if (chan[5].dacPeriod>=rate) { + DivSample* s=parent->getSample(chan[5].dacSample); + if (s->samples>0) { + if (!isMuted[5]) { + if (chan[5].dacReady && writes.size()<16) { + int sample; + if (parent->song.noOPN2Vol) { + sample=s->data8[chan[5].getDacDirection()?(s->samples-chan[5].dacPos-1):chan[5].dacPos]; + } else { + sample=(s->data8[chan[5].getDacDirection()?(s->samples-chan[5].dacPos-1):chan[5].dacPos]*dacVolTable[chan[5].outVol])>>7; + } + urgentWrite(0x2a,(unsigned char)sample+0x80); + chan[5].dacReady=false; + } + } + chan[5].dacPos++; + if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[5].dacPos>=s->loopEnd) || (chan[5].dacPos>=s->samples)) { + if (s->isLoopable() && !chan[5].getDacDirection()) { + chan[5].dacPos=s->loopStart; + } else { + chan[5].dacSample=-1; + if (parent->song.brokenDACMode) { + rWrite(0x2b,0); + } + } + } + while (chan[5].dacPeriod>=rate) chan[5].dacPeriod-=rate; + } else { + chan[5].dacSample=-1; + } + } + } + } +} + void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { static short o[2]; static int os[2]; for (size_t h=start; h=rate) { - dacDelay-=rate; - dacReady=true; - } - } - if (dacMode && dacSample!=-1) { - dacPeriod+=dacRate; - if (dacPeriod>=rate) { - DivSample* s=parent->getSample(dacSample); - if (s->samples>0) { - if (!isMuted[5]) { - if (dacReady && writes.size()<16) { - urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); - dacReady=false; - } - } - dacPos++; - if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) { - if (s->isLoopable()) { - dacPos=s->loopStart; - } else { - dacSample=-1; - if (parent->song.brokenDACMode) { - rWrite(0x2b,0); - } - } - } - while (dacPeriod>=rate) dacPeriod-=rate; - } else { - dacSample=-1; - } - } - } - + processDAC(); + os[0]=0; os[1]=0; for (int i=0; i<6; i++) { if (!writes.empty() && --delay<0) { @@ -215,41 +273,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si ymfm::ym2612::fm_engine* fme=fm_ymfm->debug_engine(); for (size_t h=start; h=rate) { - dacDelay-=rate; - dacReady=true; - } - } - if (dacMode && dacSample!=-1) { - dacPeriod+=dacRate; - if (dacPeriod>=rate) { - DivSample* s=parent->getSample(dacSample); - if (s->samples>0) { - if (!isMuted[5]) { - if (dacReady && writes.size()<16) { - urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); - dacReady=false; - } - } - dacPos++; - if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) { - if (s->isLoopable()) { - dacPos=s->loopStart; - } else { - dacSample=-1; - if (parent->song.brokenDACMode) { - rWrite(0x2b,0); - } - } - } - while (dacPeriod>=rate) dacPeriod-=rate; - } else { - dacSample=-1; - } - } - } + processDAC(); os[0]=0; os[1]=0; if (!writes.empty()) { @@ -474,7 +498,7 @@ void DivPlatformGenesis::tick(bool sysTick) { } - for (int i=0; i<6; i++) { + for (int i=0; i<8; i++) { if (i==2 && extMode) continue; if (chan[i].freqChanged) { if (parent->song.linearPitch==2) { @@ -493,12 +517,14 @@ void DivPlatformGenesis::tick(bool sysTick) { chan[i].freq=(block<<11)|fNum; } if (chan[i].freq>0x3fff) chan[i].freq=0x3fff; - immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8); - immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); - if (chan[i].furnaceDac && dacMode) { + if (i<6) { + immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8); + immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); + } + if (chan[i].furnaceDac && chan[i].dacMode) { double off=1.0; - if (dacSample>=0 && dacSamplesong.sampleLen) { - DivSample* s=parent->getSample(dacSample); + if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].dacSample); if (s->centerRate<1) { off=1.0; } else { @@ -506,14 +532,14 @@ void DivPlatformGenesis::tick(bool sysTick) { } } chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,1,1); - dacRate=chan[i].freq*off; - if (dacRate<1) dacRate=1; - if (dumpWrites) addWrite(0xffff0001,dacRate); + chan[i].dacRate=chan[i].freq*off; + if (chan[i].dacRate<1) chan[i].dacRate=1; + if (dumpWrites) addWrite(0xffff0001,chan[i].dacRate); } chan[i].freqChanged=false; } if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + if (i<6) immWrite(0x28,0xf0|konOffs[i]); chan[i].keyOn=false; } } @@ -521,6 +547,7 @@ void DivPlatformGenesis::tick(bool sysTick) { void DivPlatformGenesis::muteChannel(int ch, bool mute) { isMuted[ch]=mute; + if (ch>5) return; for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[ch]|opOffs[j]; DivInstrumentFM::Operator& op=chan[ch].state.op[j]; @@ -541,29 +568,33 @@ int DivPlatformGenesis::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); - if (c.chan==5) { + if (c.chan>=5) { if (ins->type==DIV_INS_AMIGA) { - dacMode=1; + chan[c.chan].dacMode=1; rWrite(0x2b,1<<7); } else if (chan[c.chan].furnaceDac) { - dacMode=0; + chan[c.chan].dacMode=0; rWrite(0x2b,0<<7); } } - if (c.chan==5 && dacMode) { + if (c.chan>=5 && chan[c.chan].dacMode) { if (skipRegisterWrites) break; if (ins->type==DIV_INS_AMIGA) { // Furnace mode - dacSample=ins->amiga.getSample(c.value); - if (dacSample<0 || dacSample>=parent->song.sampleLen) { - dacSample=-1; + chan[c.chan].dacSample=ins->amiga.getSample(c.value); + if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { + chan[c.chan].dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); break; } else { + chan[c.chan].dacReversed=ins->amiga.getReversed(c.value); rWrite(0x2b,1<<7); - if (dumpWrites) addWrite(0xffff0000,dacSample); + if (dumpWrites) { + addWrite(0xffff0000,chan[c.chan].dacSample); + addWrite(0xffff0003,chan[c.chan].getDacDirection()); + } } - dacPos=0; - dacPeriod=0; + chan[c.chan].dacPos=0; + chan[c.chan].dacPeriod=0; if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false); chan[c.chan].freqChanged=true; @@ -573,23 +604,25 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; } - dacSample=12*sampleBank+chan[c.chan].note%12; - if (dacSample>=parent->song.sampleLen) { - dacSample=-1; + chan[c.chan].dacSample=12*chan[c.chan].sampleBank+chan[c.chan].note%12; + if (chan[c.chan].dacSample>=parent->song.sampleLen) { + chan[c.chan].dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); break; } else { rWrite(0x2b,1<<7); - if (dumpWrites) addWrite(0xffff0000,dacSample); + if (dumpWrites) addWrite(0xffff0000,chan[c.chan].dacSample); } - dacPos=0; - dacPeriod=0; - dacRate=MAX(1,parent->getSample(dacSample)->rate); - if (dumpWrites) addWrite(0xffff0001,parent->getSample(dacSample)->rate); + chan[c.chan].dacPos=0; + chan[c.chan].dacPeriod=0; + chan[c.chan].dacRate=MAX(1,parent->getSample(chan[c.chan].dacSample)->rate); + if (dumpWrites) addWrite(0xffff0001,parent->getSample(chan[c.chan].dacSample)->rate); chan[c.chan].furnaceDac=false; + chan[c.chan].dacReversed=false; } break; } + if (c.chan>=6) break; if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; @@ -642,12 +675,12 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_OFF: - if (c.chan==5) { - dacSample=-1; + if (c.chan>=5) { + chan[c.chan].dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); if (parent->song.brokenDACMode) { rWrite(0x2b,0); - if (dacMode) break; + if (chan[c.chan].dacMode) break; } } chan[c.chan].keyOff=true; @@ -655,8 +688,8 @@ int DivPlatformGenesis::dispatch(DivCommand c) { chan[c.chan].active=false; break; case DIV_CMD_NOTE_OFF_ENV: - if (c.chan==5) { - dacSample=-1; + if (c.chan>=5) { + chan[c.chan].dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); } chan[c.chan].keyOff=true; @@ -672,6 +705,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } + if (c.chan>=6) break; for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -698,6 +732,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { + if (c.chan>5) c.chan=5; if (c.value==0 && c.value2==0) { chan[c.chan].pan=3; } else { @@ -735,7 +770,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { } break; } - if (c.chan==5 && chan[c.chan].furnaceDac && dacMode) { + if (c.chan>=5 && chan[c.chan].furnaceDac && chan[c.chan].dacMode) { int destFreq=parent->calcBaseFreq(1,1,c.value2,false); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { @@ -762,18 +797,26 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_SAMPLE_MODE: { - dacMode=c.value; + if (c.chan<5) c.chan=5; + chan[c.chan].dacMode=c.value; rWrite(0x2b,c.value<<7); break; } case DIV_CMD_SAMPLE_BANK: - sampleBank=c.value; - if (sampleBank>(parent->song.sample.size()/12)) { - sampleBank=parent->song.sample.size()/12; + if (c.chan<5) c.chan=5; + chan[c.chan].sampleBank=c.value; + if (chan[c.chan].sampleBank>(parent->song.sample.size()/12)) { + chan[c.chan].sampleBank=parent->song.sample.size()/12; } break; + case DIV_CMD_SAMPLE_DIR: { + if (c.chan<5) c.chan=5; + chan[c.chan].dacDirection=c.value; + if (dumpWrites) addWrite(0xffff0003,chan[c.chan].dacDirection); + break; + } case DIV_CMD_LEGATO: { - if (c.chan==5 && chan[c.chan].furnaceDac && dacMode) { + if (c.chan>=5 && chan[c.chan].furnaceDac && chan[c.chan].dacMode) { chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false); } else { chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11); @@ -783,16 +826,19 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { + if (c.chan>=6) break; lfoValue=(c.value&7)|((c.value>>4)<<3); rWrite(0x22,lfoValue); break; } case DIV_CMD_FM_FB: { + if (c.chan>=6) break; chan[c.chan].state.fb=c.value&7; rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); break; } case DIV_CMD_FM_MULT: { + if (c.chan>=6) break; unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.mult=c.value2&15; @@ -800,6 +846,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_FM_TL: { + if (c.chan>=6) break; unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.tl=c.value2; @@ -815,6 +862,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AR: { + if (c.chan>=6) break; if (c.value<0) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -831,6 +879,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_FM_RS: { + if (c.chan>=6) break; if (c.value<0) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -847,6 +896,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AM: { + if (c.chan>=6) break; if (c.value<0) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -863,6 +913,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_FM_DR: { + if (c.chan>=6) break; if (c.value<0) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -879,6 +930,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_FM_SL: { + if (c.chan>=6) break; if (c.value<0) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -895,6 +947,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_FM_RR: { + if (c.chan>=6) break; if (c.value<0) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -911,6 +964,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_FM_D2R: { + if (c.chan>=6) break; if (c.value<0) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -927,6 +981,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_FM_DT: { + if (c.chan>=6) break; if (c.value<0) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -943,6 +998,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_FM_SSG: { + if (c.chan>=6) break; if (c.value<0) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -959,6 +1015,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_FM_HARD_RESET: + if (c.chan>=6) break; chan[c.chan].hardReset=c.value; break; case DIV_ALWAYS_SET_VOLUME: @@ -1007,7 +1064,7 @@ void DivPlatformGenesis::forceIns() { chan[i].freqChanged=true; } } - if (dacMode) { + if (chan[5].dacMode) { rWrite(0x2b,0x80); } immWrite(0x22,lfoValue); @@ -1057,18 +1114,18 @@ void DivPlatformGenesis::reset() { } lastBusy=60; - dacMode=0; - dacPeriod=0; - dacPos=0; - dacRate=0; - dacDelay=0; - dacReady=true; - dacSample=-1; - sampleBank=0; lfoValue=8; - + softPCMTimer=0; extMode=false; + if (softPCM) { + chan[5].dacMode=true; + chan[6].dacMode=true; + } + + // normal sample direction + if (dumpWrites) addWrite(0xffff0003,0); + // LFO immWrite(0x22,lfoValue); @@ -1088,7 +1145,7 @@ bool DivPlatformGenesis::keyOffAffectsPorta(int ch) { } void DivPlatformGenesis::notifyInsChange(int ins) { - for (int i=0; i<6; i++) { + for (int i=0; i<10; i++) { if (chan[i].ins==ins) { chan[i].insChanged=true; } @@ -1114,6 +1171,10 @@ void DivPlatformGenesis::setYMFM(bool use) { useYMFM=use; } +void DivPlatformGenesis::setSoftPCM(bool value) { + softPCM=value; +} + void DivPlatformGenesis::setFlags(unsigned int flags) { switch (flags) { case 1: chipClock=COLOR_PAL*12.0/7.0; break; @@ -1152,6 +1213,11 @@ int DivPlatformGenesis::init(DivEngine* p, int channels, int sugRate, unsigned i fm_ymfm=NULL; setFlags(flags); + for (int i=0; i<128; i++) { + dacVolTable[127-i]=128*pow(10.0f,(float)(-i)*0.75f/20.0f); + } + dacVolTable[0]=0; + reset(); return 10; } diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 236e5c43f..c2a8f740d 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -41,10 +41,24 @@ class DivPlatformGenesis: public DivDispatch { bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset; int vol, outVol; unsigned char pan; + + bool dacMode; + int dacPeriod; + int dacRate; + unsigned int dacPos; + int dacSample; + int dacDelay; + bool dacReady; + bool dacDirection; + bool dacReversed; + unsigned char sampleBank; void macroInit(DivInstrument* which) { std.init(which); pitch2=0; } + bool getDacDirection() { + return dacReversed^dacDirection; + } Channel(): freqH(0), freqL(0), @@ -65,7 +79,18 @@ class DivPlatformGenesis: public DivDispatch { inPorta(false), hardReset(false), vol(0), - pan(3) {} + outVol(0), + pan(3), + dacMode(false), + dacPeriod(0), + dacRate(0), + dacPos(0), + dacSample(-1), + dacDelay(0), + dacReady(true), + dacDirection(false), + dacReversed(false), + sampleBank(0) {} }; Channel chan[10]; DivDispatchOscBuffer* oscBuf[10]; @@ -86,24 +111,21 @@ class DivPlatformGenesis: public DivDispatch { DivYM2612Interface iface; unsigned char regPool[512]; - bool dacMode; - int dacPeriod; - int dacRate; - unsigned int dacPos; - int dacSample; - int dacDelay; - bool dacReady; - unsigned char sampleBank; unsigned char lfoValue; - bool extMode, useYMFM; + int softPCMTimer; + + bool extMode, softPCM, useYMFM; bool ladder; short oldWrites[512]; short pendingWrites[512]; + unsigned char dacVolTable[128]; + friend void putDispatchChan(void*,int,int); + inline void processDAC(); void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len); void acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len); @@ -126,6 +148,7 @@ class DivPlatformGenesis: public DivDispatch { void setFlags(unsigned int flags); void notifyInsChange(int ins); void notifyInsDeletion(void* ins); + void setSoftPCM(bool value); int getPortaFloor(int ch); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 9f768ad26..4d4935b5f 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -156,16 +156,16 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { case DIV_CMD_SAMPLE_MODE: { // not ignored actually! if (!parent->song.ignoreDACModeOutsideIntendedChannel) { - dacMode=c.value; + chan[5].dacMode=c.value; rWrite(0x2b,c.value<<7); } break; } case DIV_CMD_SAMPLE_BANK: if (!parent->song.ignoreDACModeOutsideIntendedChannel) { - sampleBank=c.value; - if (sampleBank>(parent->song.sample.size()/12)) { - sampleBank=parent->song.sample.size()/12; + chan[5].sampleBank=c.value; + if (chan[5].sampleBank>(parent->song.sample.size()/12)) { + chan[5].sampleBank=parent->song.sample.size()/12; } } break; @@ -484,7 +484,7 @@ void DivPlatformGenesisExt::forceIns() { chan[i].freqChanged=true; } } - if (dacMode) { + if (chan[5].dacMode) { rWrite(0x2b,0x80); } immWrite(0x22,lfoValue); diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index a87ae763e..af17053b1 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -25,6 +25,7 @@ #define WRITE_VOLUME(ch,v) rWrite(0x20+(ch<<3),(v)) #define WRITE_FEEDBACK(ch,v) rWrite(0x21+(ch<<3),(v)) +#define WRITE_OUTPUT(ch,v) rWrite(0x22+(ch<<3),(v)) #define WRITE_LFSR(ch,v) rWrite(0x23+(ch<<3),(v)) #define WRITE_BACKUP(ch,v) rWrite(0x24+(ch<<3),(v)) #define WRITE_CONTROL(ch,v) rWrite(0x25+(ch<<3),(v)) @@ -151,13 +152,18 @@ void DivPlatformLynx::acquire(short* bufL, short* bufR, size_t start, size_t len DivSample* s=parent->getSample(chan[i].sample); if (s!=NULL) { if (isMuted[i]) { - WRITE_VOLUME(i,0); + WRITE_OUTPUT(i,0); chan[i].samplePos++; } else { - WRITE_VOLUME(i,(s->data8[chan[i].samplePos++]*chan[i].outVol)>>7); + WRITE_OUTPUT(i,(s->data8[chan[i].samplePos++]*chan[i].outVol)>>7); } + if (chan[i].samplePos>=(int)s->samples) { - chan[i].sample=-1; + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { + chan[i].samplePos=s->loopStart; + } else { + chan[i].sample=-1; + } } } } @@ -176,8 +182,8 @@ void DivPlatformLynx::tick(bool sysTick) { chan[i].outVol=((chan[i].vol&127)*MIN(64,chan[i].std.vol.val))>>6; } else { chan[i].outVol=((chan[i].vol&127)*MIN(127,chan[i].std.vol.val))>>7; - WRITE_VOLUME(i,(isMuted[i]?0:(chan[i].outVol&127))); } + WRITE_VOLUME(i,(isMuted[i]?0:(chan[i].outVol&127))); } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { @@ -244,11 +250,6 @@ void DivPlatformLynx::tick(bool sysTick) { } } chan[i].sampleFreq=off*parent->calcFreq(chan[i].sampleBaseFreq,chan[i].pitch,false,2,chan[i].pitch2,1,1); - WRITE_FEEDBACK(i,0); - WRITE_LFSR(i,0); - WRITE_OTHER(i,0); - WRITE_CONTROL(i,0x18); - WRITE_BACKUP(i,2); } else { if (chan[i].lfsr >= 0) { WRITE_LFSR(i, (chan[i].lfsr&0xff)); @@ -300,7 +301,8 @@ int DivPlatformLynx::dispatch(DivCommand c) { } case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; - WRITE_VOLUME(c.chan, 0); + WRITE_VOLUME(c.chan,0); + WRITE_CONTROL(c.chan,0); chan[c.chan].macroInit(NULL); if (chan[c.chan].pcm) { chan[c.chan].pcm=false; diff --git a/src/engine/platform/msm6295.cpp b/src/engine/platform/msm6295.cpp index c426af014..f1d4e997f 100644 --- a/src/engine/platform/msm6295.cpp +++ b/src/engine/platform/msm6295.cpp @@ -23,13 +23,18 @@ #include #include -#define rWrite(v) if (!skipRegisterWrites) {writes.emplace(0,v); if (dumpWrites) {addWrite(0,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } const char** DivPlatformMSM6295::getRegisterSheet() { return NULL; } const char* DivPlatformMSM6295::getEffectName(unsigned char effect) { + switch (effect) { + case 0x20: + return "20xx: Set chip output rate (0: clock/132; 1: clock/165)"; + break; + } return NULL; } @@ -42,7 +47,28 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t if (delay<=0) { if (!writes.empty()) { QueuedWrite& w=writes.front(); - msm->command_w(w.val); + switch (w.addr) { + case 0: // command + msm->command_w(w.val); + break; + case 8: // chip clock select (VGM) + case 9: + case 10: + case 11: + break; + case 12: // rate select + msm->ss_w(!w.val); + break; + case 14: // enable bankswitch + break; + case 15: // set bank base + break; + case 16: // switch bank + case 17: + case 18: + case 19: + break; + } writes.pop(); delay=32; } @@ -92,9 +118,9 @@ int DivPlatformMSM6295::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - rWrite((8<getSample(12*sampleBank+c.value%12); chan[c.chan].sample=12*sampleBank+c.value%12; - rWrite((8<(parent->song.sample.size()/12)) { @@ -190,6 +220,7 @@ void DivPlatformMSM6295::forceIns() { for (int i=0; i<4; i++) { chan[i].insChanged=true; } + rWrite(12,!rateSel); } void* DivPlatformMSM6295::getChanState(int ch) { @@ -219,6 +250,7 @@ void DivPlatformMSM6295::poke(std::vector& wlist) { void DivPlatformMSM6295::reset() { while (!writes.empty()) writes.pop(); msm->reset(); + msm->ss_w(false); if (dumpWrites) { addWrite(0xffffffff,0); } @@ -232,6 +264,7 @@ void DivPlatformMSM6295::reset() { } sampleBank=0; + rateSel=false; delay=0; } @@ -240,6 +273,10 @@ bool DivPlatformMSM6295::keyOffAffectsArp(int ch) { return false; } +float DivPlatformMSM6295::getPostAmp() { + return 3.0f; +} + void DivPlatformMSM6295::notifyInsChange(int ins) { for (int i=0; i<4; i++) { if (chan[i].ins==ins) { @@ -302,12 +339,21 @@ void DivPlatformMSM6295::renderSamples() { } void DivPlatformMSM6295::setFlags(unsigned int flags) { - if (flags&1) { - chipClock=8448000; - } else { - chipClock=8000000; + switch (flags) { + case 0: + chipClock=4000000/4; + break; + case 1: + chipClock=4224000/4; + break; + case 2: + chipClock=4000000; + break; + case 3: + chipClock=4224000; + break; } - rate=chipClock/((flags&2)?6:24); + rate=chipClock/3; for (int i=0; i<4; i++) { isMuted[i]=false; oscBuf[i]->rate=rate/22; diff --git a/src/engine/platform/msm6295.h b/src/engine/platform/msm6295.h index bedaab41c..2930a169b 100644 --- a/src/engine/platform/msm6295.h +++ b/src/engine/platform/msm6295.h @@ -101,6 +101,7 @@ class DivPlatformMSM6295: public DivDispatch { int delay, updateOsc; bool extMode; + bool rateSel; short oldWrites[512]; short pendingWrites[512]; @@ -119,6 +120,7 @@ class DivPlatformMSM6295: public DivDispatch { void tick(bool sysTick=true); void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); + float getPostAmp(); void notifyInsChange(int ins); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index aed4c315c..c9996e296 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -735,6 +735,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(8,0); + immWrite(7,0x01); // reset immWrite(9,(s->offB>>2)&0xff); immWrite(10,(s->offB>>10)&0xff); int end=s->offB+s->lengthB-1; @@ -770,6 +771,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { } DivSample* s=parent->getSample(12*sampleBank+c.value%12); immWrite(8,0); + immWrite(7,0x01); // reset immWrite(9,(s->offB>>2)&0xff); immWrite(10,(s->offB>>10)&0xff); int end=s->offB+s->lengthB-1; diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index c0dd9ed5b..7dd5c65d7 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -42,6 +42,7 @@ const char* DivPlatformSMS::getEffectName(unsigned char effect) { } void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { + int o=0; for (size_t h=start; h32767) o=32767; + bufL[h]=o; /* for (int i=0; i<4; i++) { if (isMuted[i]) { diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index ea493fd66..d9911bb65 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -63,6 +63,7 @@ const char* cmdName[]={ "SAMPLE_FREQ", "SAMPLE_BANK", "SAMPLE_POS", + "SAMPLE_DIR", "SAMPLE_TRANSWAVE_SLICE_MODE", // (enabled) "SAMPLE_TRANSWAVE_SLICE_POS", // (slice) @@ -570,6 +571,9 @@ void DivEngine::processRow(int i, bool afterDelay) { clockDrift=0; subticks=0; break; + case 0xdf: // set sample direction + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_DIR,i,effectVal)); + break; case 0xe0: // arp speed if (effectVal>0) { curSubSong->arpLen=effectVal; diff --git a/src/engine/song.h b/src/engine/song.h index dd98ee81e..4d75d8e15 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -111,6 +111,9 @@ enum DivSystem { DIV_SYSTEM_NAMCO, DIV_SYSTEM_NAMCO_15XX, DIV_SYSTEM_NAMCO_CUS30, + DIV_SYSTEM_YM2612_FRAC, + DIV_SYSTEM_YM2612_FRAC_EXT, + DIV_SYSTEM_RESERVED_8, DIV_SYSTEM_DUMMY, DIV_SYSTEM_MAX // boundary for max system number }; @@ -395,6 +398,10 @@ struct DivSong { bool snDutyReset; bool pitchMacroIsLinear; bool oldOctaveBoundary; + bool noOPN2Vol; + bool newVolumeScaling; + bool volMacroLinger; + bool brokenOutVol; std::vector ins; std::vector wave; @@ -488,7 +495,11 @@ struct DivSong { fbPortaPause(false), snDutyReset(false), pitchMacroIsLinear(true), - oldOctaveBoundary(false) { + oldOctaveBoundary(false), + noOPN2Vol(false), + newVolumeScaling(true), + volMacroLinger(true), + brokenOutVol(false) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index a983bd483..5ca49f79b 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -2027,12 +2027,23 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_MSM6295]=new DivSysDef( - "OKI MSM6295", NULL, 0xaa, 0, 4, false, true, 0, false, + "OKI MSM6295", NULL, 0xaa, 0, 4, false, true, 0x161, false, "an ADPCM sound chip manufactured by OKI and used in many arcade boards.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, {"CH1", "CH2", "CH3", "CH4"}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, - {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA} + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + {}, + [this](int ch, unsigned char effect, unsigned char effectVal) -> bool { + switch (effect) { + case 0x20: // select rate + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal)); + break; + default: + return false; + } + return true; + } ); sysDefs[DIV_SYSTEM_MSM6258]=new DivSysDef( @@ -2100,6 +2111,40 @@ void DivEngine::registerSystems() { namcoEffectHandler ); + // replace with an 8-channel chip in a future + sysDefs[DIV_SYSTEM_RESERVED_8]=new DivSysDef( + "Reserved", NULL, 0xbc, 0, 8, false, true, 0, false, + "this was YM2612_FRAC, but due to changes this ID is reserved.", + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, + {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, + {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, + {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD} + ); + + sysDefs[DIV_SYSTEM_YM2612_FRAC]=new DivSysDef( + "Yamaha YM2612 (OPN2) with DualPCM", NULL, 0xbe, 0, 7, true, false, 0, false, + "this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.", + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6/PCM 1", "PCM 2"}, + {"F1", "F2", "F3", "F4", "F5", "P1", "P2"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM, DIV_CH_PCM}, + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AMIGA}, + {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_NULL}, + opn2EffectHandler, + fmPostEffectHandler + ); + + sysDefs[DIV_SYSTEM_YM2612_FRAC_EXT]=new DivSysDef( + "Yamaha YM2612 (OPN2) Extended Channel 3 with DualPCM and CSM", NULL, 0xbd, 0, 11, true, false, 0, false, + "this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.", + {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6/PCM 1", "PCM 2", "CSM Timer"}, + {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "P1", "P2", "CSM"}, + {DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_NOISE}, + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AMIGA, DIV_INS_FM}, + {DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_NULL, DIV_INS_NULL}, + opn2EffectHandler, + fmPostEffectHandler + ); + sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef( "Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false, "this is a system designed for testing purposes.", diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index efcdfef6d..bbea1f7f7 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -24,7 +24,7 @@ constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; -void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool isSecond) { +void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond) { unsigned char baseAddr1=isSecond?0xa0:0x50; unsigned char baseAddr2=isSecond?0x80:0; unsigned short baseAddr2S=isSecond?0x8000:0; @@ -420,22 +420,22 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_Y8950_DRUMS: // disable envelope for (int i=0; i<6; i++) { - w->writeC(0x0b|baseAddr1); + w->writeC(0x0c|baseAddr1); w->writeC(0x80+i); w->writeC(0x0f); - w->writeC(0x0b|baseAddr1); + w->writeC(0x0c|baseAddr1); w->writeC(0x88+i); w->writeC(0x0f); - w->writeC(0x0b|baseAddr1); + w->writeC(0x0c|baseAddr1); w->writeC(0x90+i); w->writeC(0x0f); } // key off + freq reset for (int i=0; i<9; i++) { - w->writeC(0x0b|baseAddr1); + w->writeC(0x0c|baseAddr1); w->writeC(0xa0+i); w->writeC(0); - w->writeC(0x0b|baseAddr1); + w->writeC(0x0c|baseAddr1); w->writeC(0xb0+i); w->writeC(0); } @@ -522,6 +522,15 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(rf5c68Addr); w->writeC(8); w->writeC(0xff); + break; + case DIV_SYSTEM_MSM6295: + w->writeC(0xb8); // disable all channels + w->writeC(baseAddr2|0); + w->writeC(0x78); + w->writeC(0xb8); // select rate + w->writeC(baseAddr2|12); + w->writeC(1); + break; default: break; } @@ -536,8 +545,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(0x95); w->writeC(streamID); w->writeS(write.val); // sample number - w->writeC((sample->loopStart==0)); // flags - if (sample->loopStart>0) { + w->writeC((sample->loopStart==0)|(sampleDir[streamID]?0x10:0)); // flags + if (sample->loopStart>0 && !sampleDir[streamID]) { loopTimer[streamID]=(double)sample->loopEnd; loopSample[streamID]=write.val; } @@ -554,6 +563,9 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(streamID); loopSample[streamID]=-1; break; + case 3: // set sample direction + sampleDir[streamID]=write.val; + break; } return; } @@ -818,6 +830,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(write.addr&0xff); w->writeC(write.val); break; + case DIV_SYSTEM_MSM6295: + w->writeC(0xb8); + w->writeC(baseAddr2|(write.addr&0x7f)); + w->writeC(write.val); + break; default: logW("write not handled!"); break; @@ -923,11 +940,13 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { double loopTimer[DIV_MAX_CHANS]; double loopFreq[DIV_MAX_CHANS]; int loopSample[DIV_MAX_CHANS]; + bool sampleDir[DIV_MAX_CHANS]; for (int i=0; ichipClock; + willExport[i]=true; + writeMSM6295[0]=disCont[i].dispatch; + } else if (!(hasOKIM6295&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + writeMSM6295[1]=disCont[i].dispatch; + hasOKIM6295|=0x40000000; + howManyChips++; + } + break; default: break; } @@ -1694,6 +1727,15 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { w->writeI(0); w->write(writeRF5C68[i]->getSampleMem(),writeRF5C68[i]->getSampleMemUsage()); } + if (writeMSM6295[i]!=NULL && writeMSM6295[i]->getSampleMemUsage()>0) { + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0x8b); + w->writeI((writeMSM6295[i]->getSampleMemUsage()+8)|(i*0x80000000)); + w->writeI(writeMSM6295[i]->getSampleMemCapacity()); + w->writeI(0); + w->write(writeMSM6295[i]->getSampleMem(),writeMSM6295[i]->getSampleMemUsage()); + } } // TODO @@ -1829,7 +1871,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { for (int i=0; i& writes=disCont[i].dispatch->getRegisterWrites(); for (DivRegWrite& j: writes) { - performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,isSecond[i]); + performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i]); writeCount++; } writes.clear(); diff --git a/src/engine/waveSynth.cpp b/src/engine/waveSynth.cpp index c61fc4242..e69031686 100644 --- a/src/engine/waveSynth.cpp +++ b/src/engine/waveSynth.cpp @@ -186,7 +186,7 @@ bool DivWaveSynth::tick(bool skipSubDiv) { break; case DIV_WS_PHASE_MOD: for (int i=0; i<=state.speed; i++) { - int mod=(wave2[pos]*(state.param2-stage)*width)/512; + int mod=(wave2[pos]*(state.param2-stage)*width)/(64*(height+1)); output[pos]=wave1[(pos+mod)%width]; if (++pos>=width) { pos=0; diff --git a/src/gui/channels.cpp b/src/gui/channels.cpp index 846fca971..353e9a557 100644 --- a/src/gui/channels.cpp +++ b/src/gui/channels.cpp @@ -31,26 +31,42 @@ void FurnaceGUI::drawChannels() { if (!channelsOpen) return; if (ImGui::Begin("Channels",&channelsOpen,globalWinFlags)) { if (ImGui::BeginTable("ChannelList",3)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,48.0f*dpiScale); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,0.0); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed,48.0f*dpiScale); + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Visible"); + ImGui::TableNextColumn(); + ImGui::Text("Name"); for (int i=0; igetTotalChannelCount(); i++) { ImGui::PushID(i); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Checkbox("##Visible",&e->curSubSong->chanShow[i]); ImGui::SameLine(); - ImGui::BeginDisabled(i==0); - if (ImGui::Button(ICON_FA_CHEVRON_UP)) { - e->swapChannelsP(i,i-1); + if (ImGui::Button(ICON_FA_ARROWS)) { } - ImGui::EndDisabled(); - ImGui::SameLine(); - ImGui::BeginDisabled(i==(e->getTotalChannelCount()-1)); - if (ImGui::Button(ICON_FA_CHEVRON_DOWN)) { - e->swapChannelsP(i,i+1); + if (ImGui::BeginDragDropSource()) { + chanToMove=i; + ImGui::SetDragDropPayload("FUR_CHAN",NULL,0,ImGuiCond_Once); + ImGui::Button(ICON_FA_ARROWS "##ChanDrag"); + ImGui::EndDragDropSource(); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s #%d\n(drag to swap channels)",e->getSystemName(e->sysOfChan[i]),e->dispatchChanOfChan[i]); + } + if (ImGui::BeginDragDropTarget()) { + const ImGuiPayload* dragItem=ImGui::AcceptDragDropPayload("FUR_CHAN"); + if (dragItem!=NULL) { + if (dragItem->IsDataType("FUR_CHAN")) { + if (chanToMove!=i && chanToMove>=0) { + e->swapChannelsP(chanToMove,i); + } + chanToMove=-1; + } + } + ImGui::EndDragDropTarget(); } - ImGui::EndDisabled(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->curSubSong->chanName[i]); diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index b6dabafcb..03e13594c 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -213,6 +213,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6"); } + ImGui::Checkbox("No OPN2 DAC volume control",&e->song.noOPN2Vol); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6"); + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS; ImGui::End(); diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 4dfc9f28f..0c0457ea3 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -394,7 +394,7 @@ void FurnaceGUI::drawSampleList() { } void FurnaceGUI::actualWaveList() { - float wavePreview[256]; + float wavePreview[257]; for (int i=0; i<(int)e->song.wave.size(); i++) { DivWavetable* wave=e->song.wave[i]; for (int i=0; ilen; i++) { diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 90959bb94..2be83902e 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -20,6 +20,7 @@ #include "gui.h" #include "debug.h" #include "IconsFontAwesome4.h" +#include #include #include @@ -373,6 +374,13 @@ void FurnaceGUI::drawDebug() { } ImGui::TreePop(); } + if (ImGui::TreeNode("Performance")) { + double perfFreq=SDL_GetPerformanceFrequency()/1000000.0; + ImGui::Text("render: %.0fµs",(double)renderTimeDelta/perfFreq); + ImGui::Text("layout: %.0fµs",(double)layoutTimeDelta/perfFreq); + ImGui::Text("event: %.0fµs",(double)eventTimeDelta/perfFreq); + ImGui::TreePop(); + } if (ImGui::TreeNode("Settings")) { if (ImGui::Button("Sync")) syncSettings(); ImGui::SameLine(); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index ef42c759e..50956bc24 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -1233,6 +1233,22 @@ void FurnaceGUI::doAction(int what) { } break; } + case GUI_ACTION_SAMPLE_SET_LOOP: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + sample->prepareUndo(true); + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + sample->trim(0,end); + sample->loopStart=start; + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } case GUI_ACTION_ORDERS_UP: if (curOrder>0) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2aaee3300..2bf6888fa 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2368,6 +2368,10 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { point->x=ev.tfinger.x*scrW*dpiScale; point->y=ev.tfinger.y*scrH*dpiScale; point->z=ev.tfinger.pressure; + + if (point->id==0) { + ImGui::GetIO().AddMousePosEvent(point->x,point->y); + } } break; } @@ -2383,6 +2387,11 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { TouchPoint newPoint(ev.tfinger.fingerId,ev.tfinger.x*scrW*dpiScale,ev.tfinger.y*scrH*dpiScale,ev.tfinger.pressure); activePoints.push_back(newPoint); pressedPoints.push_back(newPoint); + + if (newPoint.id==0) { + ImGui::GetIO().AddMousePosEvent(newPoint.x,newPoint.y); + ImGui::GetIO().AddMouseButtonEvent(ImGuiMouseButton_Left,true); + } break; } case SDL_FINGERUP: { @@ -2391,6 +2400,11 @@ void FurnaceGUI::processPoint(SDL_Event& ev) { if (point.id==ev.tfinger.fingerId) { releasedPoints.push_back(point); activePoints.erase(activePoints.begin()+i); + + if (point.id==0) { + ImGui::GetIO().AddMouseButtonEvent(ImGuiMouseButton_Left,false); + ImGui::GetIO().AddMousePosEvent(-FLT_MAX,-FLT_MAX); + } break; } } @@ -2411,6 +2425,7 @@ bool FurnaceGUI::loop() { drawHalt=0; if (settings.powerSave) SDL_WaitEventTimeout(NULL,500); } + eventTimeBegin=SDL_GetPerformanceCounter(); while (SDL_PollEvent(&ev)) { WAKE_UP; ImGui_ImplSDL2_ProcessEvent(&ev); @@ -2529,7 +2544,23 @@ bool FurnaceGUI::loop() { break; case SDL_DROPFILE: if (ev.drop.file!=NULL) { - if (modified) { + std::vector instruments=e->instrumentFromFile(ev.drop.file); + if (!instruments.empty()) { + if (!e->getWarnings().empty()) { + showWarning(e->getWarnings(),GUI_WARN_GENERIC); + } + for (DivInstrument* i: instruments) { + e->addInstrumentPtr(i); + } + nextWindow=GUI_WINDOW_INS_LIST; + MARK_MODIFIED; + } else if (e->addWaveFromFile(ev.drop.file,false)) { + nextWindow=GUI_WINDOW_WAVE_LIST; + MARK_MODIFIED; + } else if (e->addSampleFromFile(ev.drop.file)!=-1) { + nextWindow=GUI_WINDOW_SAMPLE_LIST; + MARK_MODIFIED; + } else if (modified) { nextFile=ev.drop.file; showWarning("Unsaved changes! Save changes before opening file?",GUI_WARN_OPEN_DROP); } else { @@ -2720,6 +2751,10 @@ bool FurnaceGUI::loop() { midiQueue.pop(); midiLock.unlock(); } + + eventTimeEnd=SDL_GetPerformanceCounter(); + + layoutTimeBegin=SDL_GetPerformanceCounter(); ImGui_ImplSDLRenderer_NewFrame(); ImGui_ImplSDL2_NewFrame(sdlWin); @@ -3740,6 +3775,8 @@ bool FurnaceGUI::loop() { ImGui::EndPopup(); } + layoutTimeEnd=SDL_GetPerformanceCounter(); + // backup trigger if (modified) { if (backupTimer>0) { @@ -3778,10 +3815,16 @@ bool FurnaceGUI::loop() { uiColors[GUI_COLOR_BACKGROUND].z*255, uiColors[GUI_COLOR_BACKGROUND].w*255); SDL_RenderClear(sdlRend); + renderTimeBegin=SDL_GetPerformanceCounter(); ImGui::Render(); + renderTimeEnd=SDL_GetPerformanceCounter(); ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData()); SDL_RenderPresent(sdlRend); + layoutTimeDelta=layoutTimeEnd-layoutTimeBegin; + renderTimeDelta=renderTimeEnd-renderTimeBegin; + eventTimeDelta=eventTimeEnd-eventTimeBegin; + if (--soloTimeout<0) soloTimeout=0; wheelX=0; @@ -3867,6 +3910,16 @@ bool FurnaceGUI::init() { if (orderEditMode<0) orderEditMode=0; if (orderEditMode>3) orderEditMode=3; + pianoOctaves=e->getConfInt("pianoOctaves",pianoOctaves); + pianoOctavesEdit=e->getConfInt("pianoOctavesEdit",pianoOctavesEdit); + pianoOptions=e->getConfBool("pianoOptions",pianoOptions); + pianoSharePosition=e->getConfBool("pianoSharePosition",pianoSharePosition); + pianoOptionsSet=e->getConfBool("pianoOptionsSet",pianoOptionsSet); + pianoOffset=e->getConfInt("pianoOffset",pianoOffset); + pianoOffsetEdit=e->getConfInt("pianoOffsetEdit",pianoOffsetEdit); + pianoView=e->getConfInt("pianoView",pianoView); + pianoInputPadMode=e->getConfInt("pianoInputPadMode",pianoInputPadMode); + syncSettings(); if (settings.dpiScale>=0.5f) { @@ -3887,8 +3940,11 @@ bool FurnaceGUI::init() { SDL_Rect displaySize; #endif - SDL_SetHint("SDL_HINT_VIDEO_ALLOW_SCREENSAVER","1"); - SDL_SetHint("SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH","1"); + SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER,"1"); + SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS,"0"); + SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS,"0"); + // don't disable compositing on KWin + SDL_SetHint(SDL_HINT_X11_WINDOW_TYPE,"_NET_WM_WINDOW_TYPE_NORMAL"); SDL_Init(SDL_INIT_VIDEO); @@ -4064,6 +4120,17 @@ bool FurnaceGUI::finish() { e->setConf("followPattern",followPattern); e->setConf("orderEditMode",orderEditMode); + // commit piano state + e->setConf("pianoOctaves",pianoOctaves); + e->setConf("pianoOctavesEdit",pianoOctavesEdit); + e->setConf("pianoOptions",pianoOptions); + e->setConf("pianoSharePosition",pianoSharePosition); + e->setConf("pianoOptionsSet",pianoOptionsSet); + e->setConf("pianoOffset",pianoOffset); + e->setConf("pianoOffsetEdit",pianoOffsetEdit); + e->setConf("pianoView",pianoView); + e->setConf("pianoInputPadMode",pianoInputPadMode); + for (int i=0; i32) settings.noteCellSpacing=32; + } + + if (CWSliderInt("Instrument",&settings.insCellSpacing,0,32)) { + if (settings.insCellSpacing<0) settings.insCellSpacing=0; + if (settings.insCellSpacing>32) settings.insCellSpacing=32; + } + + if (CWSliderInt("Volume",&settings.volCellSpacing,0,32)) { + if (settings.volCellSpacing<0) settings.volCellSpacing=0; + if (settings.volCellSpacing>32) settings.volCellSpacing=32; + } + + if (CWSliderInt("Effect",&settings.effectCellSpacing,0,32)) { + if (settings.effectCellSpacing<0) settings.effectCellSpacing=0; + if (settings.effectCellSpacing>32) settings.effectCellSpacing=32; + } + + if (CWSliderInt("Effect value",&settings.effectValCellSpacing,0,32)) { + if (settings.effectValCellSpacing<0) settings.effectValCellSpacing=0; + if (settings.effectValCellSpacing>32) settings.effectValCellSpacing=32; + } + + ImGui::Separator(); + if (ImGui::TreeNode("Color scheme")) { if (ImGui::Button("Import")) { openFileDialog(GUI_FILE_IMPORT_COLORS); @@ -1806,6 +1835,7 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_OUT); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_AUTO); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_MAKE_INS); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SET_LOOP); KEYBIND_CONFIG_END; ImGui::TreePop(); @@ -1975,6 +2005,11 @@ void FurnaceGUI::syncSettings() { settings.noMultiSystem=e->getConfInt("noMultiSystem",0); settings.oldMacroVSlider=e->getConfInt("oldMacroVSlider",0); settings.displayAllInsTypes=e->getConfInt("displayAllInsTypes",0); + settings.noteCellSpacing=e->getConfInt("noteCellSpacing",0); + settings.insCellSpacing=e->getConfInt("insCellSpacing",0); + settings.volCellSpacing=e->getConfInt("volCellSpacing",0); + settings.effectCellSpacing=e->getConfInt("effectCellSpacing",0); + settings.effectValCellSpacing=e->getConfInt("effectValCellSpacing",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2052,6 +2087,11 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.noMultiSystem,0,1); clampSetting(settings.oldMacroVSlider,0,1); clampSetting(settings.displayAllInsTypes,0,1); + clampSetting(settings.noteCellSpacing,0,32); + clampSetting(settings.insCellSpacing,0,32); + clampSetting(settings.volCellSpacing,0,32); + clampSetting(settings.effectCellSpacing,0,32); + clampSetting(settings.effectValCellSpacing,0,32); settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys","")); if (settings.initialSys.size()<4) { @@ -2178,6 +2218,11 @@ void FurnaceGUI::commitSettings() { e->setConf("noMultiSystem",settings.noMultiSystem); e->setConf("oldMacroVSlider",settings.oldMacroVSlider); e->setConf("displayAllInsTypes",settings.displayAllInsTypes); + e->setConf("noteCellSpacing",settings.noteCellSpacing); + e->setConf("insCellSpacing",settings.insCellSpacing); + e->setConf("volCellSpacing",settings.volCellSpacing); + e->setConf("effectCellSpacing",settings.effectCellSpacing); + e->setConf("effectValCellSpacing",settings.effectValCellSpacing); // colors for (int i=0; i