Merge branch 'master' of https://github.com/tildearrow/furnace into es5506_alt

* 'master' of https://github.com/tildearrow/furnace: (53 commits)
  prepare for better backward writing
  VGM export: fix oops
  GUI: drag-and-drop ins/wave/sample loading
  GUI: add "set loop" to sample editor
  MSM6295: VGM export!
  oops
  MSM6295: add rate select effect (20xx)
  update meteor shower
  MSVC is better than GCC right?
  update to-do list
  door into summer
  GUI: implement input for touch events
  GUI: update SDL hints
  fix Termux build
  add another demo song
  add demos/ecolove.fur
  update to-do list
  update demos/README.md
  add new demo songs
  dev99 - major Fractal system change
  ...

# Conflicts:
#	src/engine/dispatch.h
#	src/engine/platform/genesis.cpp
#	src/engine/playback.cpp
#	src/engine/song.h
#	src/engine/vgmOps.cpp
This commit is contained in:
cam900 2022-05-29 13:47:39 +09:00
commit 32152fd89b
72 changed files with 967 additions and 304 deletions

View file

@ -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)

View file

@ -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

BIN
demos/AY-3-8910_Jam.fur Normal file

Binary file not shown.

BIN
demos/AgentX.fur Normal file

Binary file not shown.

BIN
demos/C64 junk.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/HoldOn.fur Normal file

Binary file not shown.

BIN
demos/Lagrange_Point.fur Normal file

Binary file not shown.

BIN
demos/LedStorm.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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!

BIN
demos/Red_Planet.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demos/Tyrian-Camanis.fur Normal file

Binary file not shown.

BIN
demos/UNATCOPCM.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demos/chippylotus.fur Normal file

Binary file not shown.

BIN
demos/doorintosummer.fur Normal file

Binary file not shown.

BIN
demos/ecolove.fur Normal file

Binary file not shown.

BIN
demos/fight and flight.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/insert_title_lynx.fur Normal file

Binary file not shown.

BIN
demos/meteor_shower.fur Normal file

Binary file not shown.

BIN
demos/oby1_ingame.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/puggs_in_space.fur Normal file

Binary file not shown.

BIN
demos/sijofsjfsoeife.fur Normal file

Binary file not shown.

BIN
demos/skate_or_die.fur Normal file

Binary file not shown.

BIN
demos/su_memory.fur Normal file

Binary file not shown.

Binary file not shown.

View file

@ -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;

View file

@ -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);

View file

@ -4252,7 +4252,20 @@ public:
st.load_here(32, reinterpret_cast<void *>(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) || \

View file

@ -559,14 +559,44 @@ template<int BITCOUNT, int OFFSET = 0>
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<int DSTBITCOUNT> bool operator==(ImBitArray<DSTBITCOUNT> 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<int DSTBITCOUNT> bool operator!=(ImBitArray<DSTBITCOUNT> 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<ImU32> 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<ImGuiTableColumn> Columns; // Point within RawData[]
ImSpan<ImGuiTableColumnIdx> DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1)
ImSpan<ImGuiTableCellData> 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;

View file

@ -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<IMGUI_TABLE_MAX_DRAW_CHANNELS> 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<IMGUI_TABLE_MAX_DRAW_CHANNELS> 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];

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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; i<len; i++) {
wave->data[i]=(unsigned char)reader.readC();
if (wave->max<wave->data[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; i<len; i++) {
@ -1878,18 +1896,11 @@ bool DivEngine::addWaveFromFile(const char* path) {
if (wave->max<wave->data[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; i<len; i++) {
wave->data[i]=(unsigned char)reader.readC();
if (wave->max<wave->data[i]) wave->max=wave->data[i];
}
wave->len=len;
}
}
} catch (EndOfFileException& e) {

View file

@ -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);

View file

@ -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);
}

View file

@ -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<start+len; h++) {
if (!dacReady) {
dacDelay+=32000;
if (dacDelay>=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<start+len; h++) {
if (!dacReady) {
dacDelay+=32000;
if (dacDelay>=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 && dacSample<parent->song.sampleLen) {
DivSample* s=parent->getSample(dacSample);
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.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;
}

View file

@ -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<DivRegWrite>& wlist);

View file

@ -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);

View file

@ -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;

View file

@ -23,13 +23,18 @@
#include <string.h>
#include <math.h>
#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<<c.chan)); // turn off
rWrite(0x80|chan[c.chan].sample); // set phrase
rWrite((16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
rWrite(0,(8<<c.chan)); // turn off
rWrite(0,0x80|chan[c.chan].sample); // set phrase
rWrite(0,(16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
} else {
break;
}
@ -107,9 +133,9 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
}
//DivSample* s=parent->getSample(12*sampleBank+c.value%12);
chan[c.chan].sample=12*sampleBank+c.value%12;
rWrite((8<<c.chan)); // turn off
rWrite(0x80|chan[c.chan].sample); // set phrase
rWrite((16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
rWrite(0,(8<<c.chan)); // turn off
rWrite(0,0x80|chan[c.chan].sample); // set phrase
rWrite(0,(16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
}
break;
}
@ -117,14 +143,14 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
rWrite((8<<c.chan)); // turn off
rWrite(0,(8<<c.chan)); // turn off
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
rWrite((8<<c.chan)); // turn off
rWrite(0,(8<<c.chan)); // turn off
chan[c.chan].std.release();
break;
case DIV_CMD_ENV_RELEASE:
@ -153,6 +179,10 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
case DIV_CMD_NOTE_PORTA: {
return 2;
}
case DIV_CMD_SAMPLE_FREQ:
rateSel=c.value;
rWrite(12,!rateSel);
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(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<DivRegWrite>& 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;

View file

@ -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);

View file

@ -735,6 +735,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.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;

View file

@ -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; h<start+len; h++) {
if (!writes.empty()) {
unsigned char w=writes.front();
@ -64,7 +65,10 @@ void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_
YMPSG_Clock(&sn_nuked);
YMPSG_Clock(&sn_nuked);
YMPSG_Clock(&sn_nuked);
bufL[h]=YMPSG_GetOutput(&sn_nuked)*8192.0;
o=YMPSG_GetOutput(&sn_nuked);
if (o<-32768) o=-32768;
if (o>32767) o=32767;
bufL[h]=o;
/*
for (int i=0; i<4; i++) {
if (isMuted[i]) {

View file

@ -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;

View file

@ -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<DivInstrument*> ins;
std::vector<DivWavetable*> 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;

View file

@ -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.",

View file

@ -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; i<DIV_MAX_CHANS; i++) {
loopTimer[i]=0;
loopFreq[i]=0;
loopSample[i]=-1;
sampleDir[i]=false;
}
bool writeDACSamples=false;
@ -942,6 +961,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
DivDispatch* writeES5506[2]={NULL,NULL};
DivDispatch* writeZ280[2]={NULL,NULL};
DivDispatch* writeRF5C68[2]={NULL,NULL};
DivDispatch* writeMSM6295[2]={NULL,NULL};
for (int i=0; i<song.systemLen; i++) {
willExport[i]=false;
@ -1348,6 +1368,19 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
writeRF5C68[0]=disCont[i].dispatch;
}
break;
case DIV_SYSTEM_MSM6295:
if (!hasOKIM6295) {
hasOKIM6295=disCont[i].dispatch->chipClock;
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<song.systemLen; i++) {
std::vector<DivRegWrite>& 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();

View file

@ -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;

View file

@ -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; i<e->getTotalChannelCount(); 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]);

View file

@ -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();

View file

@ -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; i<wave->len; i++) {

View file

@ -20,6 +20,7 @@
#include "gui.h"
#include "debug.h"
#include "IconsFontAwesome4.h"
#include <SDL_timer.h>
#include <fmt/printf.h>
#include <imgui.h>
@ -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();

View file

@ -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) {

View file

@ -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<DivInstrument*> 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; i<DIV_MAX_CHANS; i++) {
delete oldPat[i];
}
@ -4283,6 +4350,16 @@ FurnaceGUI::FurnaceGUI():
bindSetPending(false),
nextScroll(-1.0f),
nextAddScroll(0.0f),
layoutTimeBegin(0),
layoutTimeEnd(0),
layoutTimeDelta(0),
renderTimeBegin(0),
renderTimeEnd(0),
renderTimeDelta(0),
eventTimeBegin(0),
eventTimeEnd(0),
eventTimeDelta(0),
chanToMove(-1),
transposeAmount(0),
randomizeMin(0),
randomizeMax(255),

View file

@ -491,6 +491,7 @@ enum FurnaceGUIActions {
GUI_ACTION_SAMPLE_ZOOM_OUT,
GUI_ACTION_SAMPLE_ZOOM_AUTO,
GUI_ACTION_SAMPLE_MAKE_INS,
GUI_ACTION_SAMPLE_SET_LOOP,
GUI_ACTION_SAMPLE_MAX,
GUI_ACTION_ORDERS_MIN,
@ -944,6 +945,11 @@ class FurnaceGUI {
int noMultiSystem;
int oldMacroVSlider;
int displayAllInsTypes;
int noteCellSpacing;
int insCellSpacing;
int volCellSpacing;
int effectCellSpacing;
int effectValCellSpacing;
unsigned int maxUndoSteps;
String mainFontPath;
String patFontPath;
@ -1037,6 +1043,11 @@ class FurnaceGUI {
noMultiSystem(0),
oldMacroVSlider(0),
displayAllInsTypes(0),
noteCellSpacing(0),
insCellSpacing(0),
volCellSpacing(0),
effectCellSpacing(0),
effectValCellSpacing(0),
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),
@ -1180,10 +1191,17 @@ class FurnaceGUI {
float nextScroll, nextAddScroll;
int layoutTimeBegin, layoutTimeEnd, layoutTimeDelta;
int renderTimeBegin, renderTimeEnd, renderTimeDelta;
int eventTimeBegin, eventTimeEnd, eventTimeDelta;
int chanToMove;
ImVec2 patWindowPos, patWindowSize;
// pattern view specific
ImVec2 fourChars, threeChars, twoChars;
ImVec2 noteCellSize, insCellSize, volCellSize, effectCellSize, effectValCellSize;
SelectionPoint sel1, sel2;
int dummyRows, demandX;
int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax;

View file

@ -401,7 +401,7 @@ const FurnaceGUIColors fxColors[256]={
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_MISC, // DF
// E0-FF extended effects
GUI_COLOR_PATTERN_EFFECT_MISC, // E0
@ -638,6 +638,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("SAMPLE_ZOOM_OUT", "Zoom out", FURKMOD_CMD|SDLK_MINUS),
D("SAMPLE_ZOOM_AUTO", "Toggle auto-zoom", FURKMOD_CMD|SDLK_0),
D("SAMPLE_MAKE_INS", "Create instrument from sample", 0),
D("SAMPLE_SET_LOOP", "Set loop to selection", FURKMOD_CMD|SDLK_l),
D("SAMPLE_MAX", "", NOT_AN_ACTION),
D("ORDERS_MIN", "---Orders", NOT_AN_ACTION),
@ -832,6 +833,8 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={
const int availableSystems[]={
DIV_SYSTEM_YM2612,
DIV_SYSTEM_YM2612_EXT,
DIV_SYSTEM_YM2612_FRAC,
DIV_SYSTEM_YM2612_FRAC_EXT,
DIV_SYSTEM_SMS,
DIV_SYSTEM_GB,
DIV_SYSTEM_PCE,

View file

@ -1868,12 +1868,12 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn();
op.ar&=maxArDr;
CENTER_VSLIDER;
P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&_ZERO,&maxArDr));
P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO));
ImGui::TableNextColumn();
op.dr&=maxArDr;
CENTER_VSLIDER;
P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&_ZERO,&maxArDr));
P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO));
if (settings.susPosition==0) {
ImGui::TableNextColumn();
@ -1886,13 +1886,13 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn();
op.d2r&=31;
CENTER_VSLIDER;
P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_ZERO,&_THIRTY_ONE));
P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO));
}
ImGui::TableNextColumn();
op.rr&=15;
CENTER_VSLIDER;
P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_ZERO,&_FIFTEEN));
P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO));
if (settings.susPosition==1) {
ImGui::TableNextColumn();

View file

@ -138,12 +138,12 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]);
ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]);
ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars);
ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,noteCellSize);
demandX=ImGui::GetCursorPosX();
ImGui::PopStyleColor(3);
} else {
if (selectedNote) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
ImGui::Selectable(id,isPushing || selectedNote,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars);
ImGui::Selectable(id,isPushing || selectedNote,ImGuiSelectableFlags_NoPadWithHalfSpacing,noteCellSize);
if (selectedNote) ImGui::PopStyleColor();
}
if (ImGui::IsItemClicked()) {
@ -178,12 +178,12 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]);
ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]);
ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,insCellSize);
demandX=ImGui::GetCursorPosX();
ImGui::PopStyleColor(3);
} else {
if (selectedIns) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
ImGui::Selectable(id,isPushing || selectedIns,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
ImGui::Selectable(id,isPushing || selectedIns,ImGuiSelectableFlags_NoPadWithHalfSpacing,insCellSize);
if (selectedIns) ImGui::PopStyleColor();
}
if (ImGui::IsItemClicked()) {
@ -212,12 +212,12 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]);
ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]);
ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,volCellSize);
demandX=ImGui::GetCursorPosX();
ImGui::PopStyleColor(3);
} else {
if (selectedVol) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
ImGui::Selectable(id,isPushing || selectedVol,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
ImGui::Selectable(id,isPushing || selectedVol,ImGuiSelectableFlags_NoPadWithHalfSpacing,volCellSize);
if (selectedVol) ImGui::PopStyleColor();
}
if (ImGui::IsItemClicked()) {
@ -257,12 +257,12 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]);
ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]);
ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,effectCellSize);
demandX=ImGui::GetCursorPosX();
ImGui::PopStyleColor(3);
} else {
if (selectedEffect) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
ImGui::Selectable(id,isPushing || selectedEffect,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
ImGui::Selectable(id,isPushing || selectedEffect,ImGuiSelectableFlags_NoPadWithHalfSpacing,effectCellSize);
if (selectedEffect) ImGui::PopStyleColor();
}
if (ImGui::IsItemClicked()) {
@ -283,12 +283,12 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]);
ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]);
ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,effectValCellSize);
demandX=ImGui::GetCursorPosX();
ImGui::PopStyleColor(3);
} else {
if (selectedEffectVal) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
ImGui::Selectable(id,isPushing || selectedEffectVal,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
ImGui::Selectable(id,isPushing || selectedEffectVal,ImGuiSelectableFlags_NoPadWithHalfSpacing,effectValCellSize);
if (selectedEffectVal) ImGui::PopStyleColor();
}
if (ImGui::IsItemClicked()) {
@ -549,7 +549,20 @@ void FurnaceGUI::drawPattern() {
threeChars=ImVec2(oneCharSize*3.0f,lineHeight);
twoChars=ImVec2(oneCharSize*2.0f,lineHeight);
//ImVec2 oneChar=ImVec2(oneCharSize,lineHeight);
noteCellSize=threeChars;
noteCellSize.x+=(float)settings.noteCellSpacing*dpiScale;
insCellSize=twoChars;
insCellSize.x+=(float)settings.insCellSpacing*dpiScale;
volCellSize=twoChars;
volCellSize.x+=(float)settings.volCellSpacing*dpiScale;
effectCellSize=twoChars;
effectCellSize.x+=(float)settings.effectCellSpacing*dpiScale;
effectValCellSize=twoChars;
effectValCellSize.x+=(float)settings.effectValCellSpacing*dpiScale;
dummyRows=(ImGui::GetWindowSize().y/lineHeight)/2;
// オップナー2608 i owe you one more for this horrible code
// previous pattern
ImGui::BeginDisabled();

View file

@ -102,6 +102,18 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2612 (OPN2) with DualPCM", {
DIV_SYSTEM_YM2612, 64, 0, (int)0x80000000,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2612 (extended channel 3) with DualPCM", {
DIV_SYSTEM_YM2612_EXT, 64, 0, (int)0x80000000,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2413 (OPLL)", {
DIV_SYSTEM_OPLL, 64, 0, 0,
@ -132,6 +144,18 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM3438 (OPN2C) with DualPCM", {
DIV_SYSTEM_YM2612_FRAC, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM3438 (extended channel 3) with DualPCM", {
DIV_SYSTEM_YM2612_FRAC_EXT, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM3526 (OPL)", {
DIV_SYSTEM_OPL, 64, 0, 0,
@ -435,6 +459,20 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis (Fractal Sound template)", {
DIV_SYSTEM_YM2612_FRAC, 64, 0, 0,
DIV_SYSTEM_SMS, 24, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis (Fractal Sound template, extended channel 3)", {
DIV_SYSTEM_YM2612_FRAC_EXT, 64, 0, 0,
DIV_SYSTEM_SMS, 24, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis (with Sega CD)", {
DIV_SYSTEM_YM2612, 64, 0, 0,

View file

@ -1312,6 +1312,10 @@ void FurnaceGUI::drawSampleEdit() {
if (ImGui::MenuItem("select all",BIND_FOR(GUI_ACTION_SAMPLE_SELECT_ALL))) {
doAction(GUI_ACTION_SAMPLE_SELECT_ALL);
}
ImGui::Separator();
if (ImGui::MenuItem("set loop to selection",BIND_FOR(GUI_ACTION_SAMPLE_SET_LOOP))) {
doAction(GUI_ACTION_SAMPLE_SET_LOOP);
}
ImGui::EndPopup();
}

View file

@ -1240,6 +1240,35 @@ void FurnaceGUI::drawSettings() {
ImGui::Separator();
ImGui::Text("Pattern view spacing after:");
if (CWSliderInt("Note",&settings.noteCellSpacing,0,32)) {
if (settings.noteCellSpacing<0) settings.noteCellSpacing=0;
if (settings.noteCellSpacing>32) 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<GUI_COLOR_MAX; i++) {