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

This commit is contained in:
cam900 2023-02-23 21:15:49 +09:00
commit e694687fff
415 changed files with 152066 additions and 7336 deletions

View File

@ -11,7 +11,7 @@ defaults:
shell: bash
env:
BUILD_TYPE: Release
BUILD_TYPE: RelWithDebInfo
jobs:
build:
@ -20,8 +20,8 @@ jobs:
config:
- { name: 'Windows MSVC x86', os: windows-latest, compiler: msvc, arch: x86 }
- { name: 'Windows MSVC x86_64', os: windows-latest, compiler: msvc, arch: x86_64 }
- { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 }
- { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 }
#- { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 }
#- { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 }
- { name: 'macOS x86_64', os: macos-latest, arch: x86_64 }
- { name: 'macOS ARM', os: macos-latest, arch: arm64 }
- { name: 'Linux x86_64', os: ubuntu-18.04, arch: x86_64 }
@ -277,7 +277,10 @@ jobs:
cp -v ../LICENSE LICENSE.txt
cp -v ../README.md README.txt
cp -vr ../{papers,demos,instruments} ../${binPath}/furnace.exe ./
cp -vr ../papers ../${binPath}/furnace.exe ./
if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then
cp -v ../${binPath}/furnace.pdb ./
fi
sha256sum ../${binPath}/furnace.exe > checksum.txt
popd

View File

@ -70,6 +70,7 @@ option(SUPPORT_XP "Build a Windows XP-compatible binary" OFF)
option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF)
option(WITH_DEMOS "Install demo songs" ON)
option(WITH_INSTRUMENTS "Install instruments" ON)
option(WITH_WAVETABLES "Install wavetables" ON)
set(DEPENDENCIES_INCLUDE_DIRS "")
@ -365,6 +366,7 @@ src/engine/platform/sound/sn76496.cpp
src/engine/platform/sound/ay8910.cpp
src/engine/platform/sound/saa1099.cpp
src/engine/platform/sound/namco.cpp
src/engine/platform/sound/segapcm.cpp
src/engine/platform/sound/gb/apu.c
src/engine/platform/sound/gb/timing.c
src/engine/platform/sound/pce_psg.cpp
@ -399,6 +401,7 @@ src/engine/platform/sound/c64/wave8580_PST.cc
src/engine/platform/sound/c64/wave8580_P_T.cc
src/engine/platform/sound/c64/wave8580__ST.cc
src/engine/platform/sound/c64_fp/array.cpp
src/engine/platform/sound/c64_fp/Dac.cpp
src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp
src/engine/platform/sound/c64_fp/ExternalFilter.cpp
@ -454,6 +457,8 @@ src/engine/platform/sound/snes/SPC_DSP.cpp
src/engine/platform/sound/ga20/iremga20.cpp
src/engine/platform/sound/sm8521.c
src/engine/platform/oplAInterface.cpp
src/engine/platform/ym2608Interface.cpp
src/engine/platform/ym2610Interface.cpp
@ -530,6 +535,7 @@ src/engine/platform/pokemini.cpp
src/engine/platform/pong.cpp
src/engine/platform/vic20.cpp
src/engine/platform/vrc6.cpp
src/engine/platform/es5506.cpp
src/engine/platform/scc.cpp
src/engine/platform/ymz280b.cpp
src/engine/platform/namcowsg.cpp
@ -537,6 +543,7 @@ src/engine/platform/rf5c68.cpp
src/engine/platform/snes.cpp
src/engine/platform/k007232.cpp
src/engine/platform/ga20.cpp
src/engine/platform/sm8521.cpp
src/engine/platform/pcmdac.cpp
src/engine/platform/dummy.cpp
)
@ -566,6 +573,7 @@ extern/imgui_patched/misc/cpp/imgui_stdlib.cpp
extern/igfd/ImGuiFileDialog.cpp
src/gui/plot_nolerp.cpp
src/gui/font_exo.cpp
src/gui/font_liberationSans.cpp
src/gui/font_mononoki.cpp
@ -576,12 +584,24 @@ src/gui/font_ptMono.cpp
src/gui/font_unifont.cpp
src/gui/font_icon.cpp
src/gui/fonts.cpp
src/gui/image_icon.cpp
src/gui/image_talogo.cpp
src/gui/image_tachip.cpp
src/gui/image_logo.cpp
src/gui/image_wordmark.cpp
src/gui/image_introbg.cpp
src/gui/image_pat.cpp
src/gui/image.cpp
src/gui/debug.cpp
src/gui/fileDialog.cpp
src/gui/intConst.cpp
src/gui/guiConst.cpp
src/gui/introTune.cpp
src/gui/about.cpp
src/gui/channels.cpp
src/gui/chanOsc.cpp
@ -596,7 +616,9 @@ src/gui/editControls.cpp
src/gui/effectList.cpp
src/gui/findReplace.cpp
src/gui/gradient.cpp
src/gui/grooves.cpp
src/gui/insEdit.cpp
src/gui/intro.cpp
src/gui/log.cpp
src/gui/mixer.cpp
src/gui/midiMap.cpp
@ -613,6 +635,7 @@ src/gui/scaling.cpp
src/gui/settings.cpp
src/gui/songInfo.cpp
src/gui/songNotes.cpp
src/gui/speed.cpp
src/gui/spoiler.cpp
src/gui/stats.cpp
src/gui/subSongs.cpp
@ -620,6 +643,7 @@ src/gui/sysConf.cpp
src/gui/sysEx.cpp
src/gui/sysManager.cpp
src/gui/sysPicker.cpp
src/gui/tutorial.cpp
src/gui/util.cpp
src/gui/waveEdit.cpp
src/gui/volMeter.cpp
@ -638,8 +662,6 @@ if (APPLE)
endif()
if (NOT WIN32 AND NOT APPLE)
list(APPEND GUI_SOURCES src/gui/icon.c)
CHECK_INCLUDE_FILE(sys/io.h SYS_IO_FOUND)
CHECK_INCLUDE_FILE(linux/input.h LINUX_INPUT_FOUND)
CHECK_INCLUDE_FILE(linux/kd.h LINUX_KD_FOUND)
@ -756,9 +778,7 @@ if (WARNINGS_ARE_ERRORS)
)
endif()
if (WIN32)
add_executable(furnace WIN32 ${USED_SOURCES})
elseif(ANDROID AND NOT TERMUX)
if(ANDROID AND NOT TERMUX)
add_library(furnace SHARED ${USED_SOURCES})
else()
add_executable(furnace ${USED_SOURCES})
@ -795,6 +815,9 @@ if (NOT ANDROID OR TERMUX)
if (WITH_INSTRUMENTS)
install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
endif()
if (WITH_WAVETABLES)
install(DIRECTORY wavetables DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
endif()
foreach(num 16 32 64 128 256 512)
set(res ${num}x${num})
install(FILES res/icon.iconset/icon_${res}.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}/apps)

View File

@ -214,6 +214,7 @@ Available options:
| `WARNINGS_ARE_ERRORS` | `OFF` (but consider enabling this & reporting any errors that arise from it!) | Whether warnings in furnace's C++ code should be treated as errors |
| `WITH_DEMOS` | `ON` | Install demo songs on `make install` |
| `WITH_INSTRUMENTS` | `ON` | Install demo instruments on `make install` |
| `WITH_WAVETABLES` | `ON` | Install wavetables on `make install` |
## console usage
@ -282,17 +283,13 @@ two possibilities:
- the recommended way is by creating the "Sample" type instrument and assigning a sample to it.
- otherwise you may employ the DefleMask-compatible method, using `17xx` effect.
> my .dmf song sounds very odd at a certain point
> my .dmf song sounds odd at a certain point
file a bug report. use the Issues page. it's probably another playback inaccuracy.
> my .dmf song sounds correct, but it doesn't in DefleMask
file a bug report **here**. it still is a playback inaccuracy.
Furnace's .dmf compatibility isn't perfect and it's mostly because DefleMask does things different.
> my song sounds terrible after saving as .dmf!
the DefleMask format has several limitations. save in Furnace song format instead (.fur).
you should only save as .dmf if you're really sure, because the DefleMask format has several limitations. save in Furnace song format instead (.fur).
> how do I solo channels?
@ -301,7 +298,7 @@ right click on the channel name.
---
# footnotes
copyright (C) 2021-2022 tildearrow and contributors.
copyright (C) 2021-2023 tildearrow and contributors.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
@ -310,4 +307,4 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
despite the fact this program works with the .dmf file format, it is NOT affiliated with Delek or DefleMask in any way, nor it is a replacement for the original program.
despite the fact this program works with the .dmf, .dmp and .dmw file formats (besides its native .fur format), it is NOT affiliated with Delek or DefleMask in any way, nor it is a replacement for the original program.

BIN
demos/amiga/serendipid.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demos/opl/e3m2_opl3.fur Normal file

Binary file not shown.

BIN
demos/snes/amalgam.fur Normal file

Binary file not shown.

BIN
demos/specs2/Tim_Follin.fur Normal file

Binary file not shown.

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 Nuke.YKT
* Copyright (C) 2019-2023 Nuke.YKT
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -17,7 +17,7 @@
* siliconpr0n.org(digshadow, John McMaster):
* VRC VII decap and die shot.
*
* version: 1.0.1
* version: 1.0.2
*/
#include <string.h>
@ -132,7 +132,7 @@ static const opll_patch_t patch_ds1001[opll_patch_max] = {
{ 0x00, 0x00, 0x00, 0x00,{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x01, 0x00 },{ 0x00, 0x00 },{ 0x0c, 0x00 },{ 0x08, 0x00 },{ 0x0a, 0x00 },{ 0x07, 0x00 } },
{ 0x00, 0x00, 0x00, 0x00,{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x05, 0x00 },{ 0x00, 0x00 },{ 0x0f, 0x00 },{ 0x08, 0x00 },{ 0x05, 0x00 },{ 0x09, 0x00 } },
{ 0x00, 0x00, 0x00, 0x00,{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x01 },{ 0x00, 0x00 },{ 0x00, 0x0f },{ 0x00, 0x08 },{ 0x00, 0x06 },{ 0x00, 0x0d } },
{ 0x00, 0x00, 0x00, 0x00,{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x01 },{ 0x00, 0x00 },{ 0x00, 0x0d },{ 0x00, 0x08 },{ 0x00, 0x06 },{ 0x00, 0x08 } },
{ 0x00, 0x00, 0x00, 0x00,{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x01 },{ 0x00, 0x00 },{ 0x00, 0x0d },{ 0x00, 0x08 },{ 0x00, 0x04 },{ 0x00, 0x08 } },
{ 0x00, 0x00, 0x00, 0x00,{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x00 },{ 0x00, 0x01 },{ 0x00, 0x00 },{ 0x00, 0x0a },{ 0x00, 0x0a },{ 0x00, 0x05 },{ 0x00, 0x05 } }
};
@ -1018,6 +1018,9 @@ static void OPLL_Operator(opll_t *chip) {
}
}
if (!(chip->rm_enable & 0x80))
routput = 0;
chip->ch_out = ismod1 ? routput : (output>>3);
if (!ismod1) {

View File

@ -30,6 +30,9 @@
// hack I know
#include "../../../src/utfutils.h"
// hack 2...
#include "../../../src/ta-log.h"
class NFDWinEvents: public IFileDialogEvents {
nfdselcallback_t selCallback;
size_t refCount;
@ -38,21 +41,21 @@ class NFDWinEvents: public IFileDialogEvents {
}
public:
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) {
printf("QueryInterface called DAMN IT\n");
logV("%p: QueryInterface called DAMN IT",(const void*)this);
*ppv=NULL;
return E_NOTIMPL;
}
IFACEMETHODIMP_(ULONG) AddRef() {
printf("AddRef() called\n");
logV("%p: AddRef() called",(const void*)this);
return InterlockedIncrement(&refCount);
}
IFACEMETHODIMP_(ULONG) Release() {
printf("Release() called\n");
logV("%p: Release() called",(const void*)this);
LONG ret=InterlockedDecrement(&refCount);
if (ret==0) {
printf("Destroying the final object.\n");
logV("%p: Destroying the final object.",(const void*)this);
delete this;
}
return ret;
@ -67,30 +70,40 @@ class NFDWinEvents: public IFileDialogEvents {
IFACEMETHODIMP OnSelectionChange(IFileDialog* dialog) {
// Get the file name
logV("%p: OnSelectionChange() called",(const void*)this);
::IShellItem *shellItem(NULL);
logV("%p: GetCurrentSelection",(const void*)this);
HRESULT result = dialog->GetCurrentSelection(&shellItem);
if ( !SUCCEEDED(result) )
{
printf("failure!\n");
logV("%p: failure!",(const void*)this);
return S_OK;
}
wchar_t *filePath(NULL);
result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
if ( !SUCCEEDED(result) )
{
printf("GDN failure!\n");
logV("%p: GDN failure!",(const void*)this);
shellItem->Release();
return S_OK;
}
std::string utf8FilePath=utf16To8(filePath);
if (selCallback!=NULL) selCallback(utf8FilePath.c_str());
printf("I got you for a value of %s\n",utf8FilePath.c_str());
if (selCallback!=NULL) {
logV("%p: calling back.",(const void*)this);
selCallback(utf8FilePath.c_str());
logV("%p: end of callback",(const void*)this);
} else {
logV("%p: no callback.",(const void*)this);
}
logV("%p: I got you for a value of %s",(const void*)this,utf8FilePath.c_str());
shellItem->Release();
logV("%p: shellItem->Release()",(const void*)this);
return S_OK;
}
NFDWinEvents(nfdselcallback_t callback):
selCallback(callback),
refCount(1) {
logV("%p: CONSTRUCT!",(const void*)this);
}
};

3
extern/opn/ym3438.c vendored
View File

@ -981,6 +981,9 @@ static void OPN2_ChOutput(ym3438_t *chip)
{
out = (Bit16s)chip->dacdata;
out = SIGN_EXTEND(8, out);
if (chip->chip_type & ym3438_mode_opn) {
out <<=5;
}
}
else
{

View File

@ -8,4 +8,5 @@ you can get original software from [here](https://gitlab.com/cam900/vgsound_emu/
## Modifier
- [cam900](https://gitlab.com/cam900)
- [cam900](https://gitlab.com/cam900)
- [tildearrow](https://github.com/tildearrow)

View File

@ -19,6 +19,10 @@
#include <string>
#include <vector>
#define VGS_MIN(a,b) (((a)<(b))?(a):(b))
#define VGS_MAX(a,b) (((a)>(b))?(a):(b))
#define VGS_CLAMP(x,xMin,xMax) (VGS_MIN(VGS_MAX((x),(xMin)),(xMax)))
namespace vgsound_emu
{
typedef unsigned char u8;
@ -43,7 +47,7 @@ namespace vgsound_emu
return std::clamp(in, min, max);
#else
// otherwise, use my own implementation of std::clamp
return std::min(std::max(in, min), max);
return VGS_CLAMP(in,min,max);
#endif
}
@ -58,7 +62,7 @@ namespace vgsound_emu
template<typename T>
static inline T sign_ext(T in, u8 len)
{
len = std::max<u8>(0, (8 * sizeof(T)) - len);
len = VGS_MAX(0, (8 * sizeof(T)) - len);
return T(T(in) << len) >> len;
}

View File

@ -23,7 +23,7 @@ void es5504_core::tick()
// /CAS low, E low: fetch sample
if (!m_e.current_edge())
{
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
m_voice[m_voice_cycle].fetch(m_voice_fetch);
}
}
}
@ -41,19 +41,19 @@ void es5504_core::tick()
if (m_e.falling_edge()) // Voice memory
{
m_host_intf.clear_host_access();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
m_voice[m_voice_cycle].fetch(m_voice_fetch);
}
}
if (m_e.current_edge()) // Host interface
{
if (m_host_intf.host_access())
if (m_host_intf.m_host_access)
{
if (m_host_intf.rw() && (m_e.cycle() == 2)) // Read
if (m_host_intf.m_rw && (m_e.cycle() == 2)) // Read
{
m_hd = read(m_ha);
m_host_intf.clear_host_access();
}
else if ((!m_host_intf.rw()) && (m_e.cycle() == 2))
else if ((!m_host_intf.m_rw) && (m_e.cycle() == 2))
{ // Write
write(m_ha, m_hd);
}
@ -81,7 +81,7 @@ void es5504_core::tick_perf()
m_intf.e_pin(false);
m_host_intf.clear_host_access();
m_host_intf.clear_strobe();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
m_voice[m_voice_cycle].fetch(m_voice_fetch);
voice_tick();
// rising edge
m_e.edge().set(true);
@ -92,7 +92,7 @@ void es5504_core::tick_perf()
m_intf.e_pin(false);
m_host_intf.clear_host_access();
m_host_intf.clear_strobe();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
m_voice[m_voice_cycle].fetch(m_voice_fetch);
voice_tick();
// rising edge
m_e.edge().set(true);
@ -126,11 +126,11 @@ void es5504_core::voice_tick()
}
}
void es5504_core::voice_t::fetch(u8 voice, u8 cycle)
void es5504_core::voice_t::fetch(u8 cycle)
{
m_alu.set_sample(
cycle,
m_host.m_intf.read_sample(voice,
m_host.m_intf.read_sample(
bitfield(m_cr.ca(), 0, 3),
bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer)));
}
@ -193,7 +193,7 @@ void es5504_core::voice_t::reset()
// Accessors
u16 es5504_core::host_r(u8 address)
{
if (!m_host_intf.host_access())
if (!m_host_intf.m_host_access)
{
m_ha = address;
if (m_e.rising_edge())
@ -210,7 +210,7 @@ u16 es5504_core::host_r(u8 address)
void es5504_core::host_w(u8 address, u16 data)
{
if (!m_host_intf.host_access())
if (!m_host_intf.m_host_access)
{
m_ha = address;
m_hd = data;

View File

@ -32,7 +32,7 @@ class es5504_core : public es550x_shared_core
// internal state
virtual void reset() override;
virtual void fetch(u8 voice, u8 cycle) override;
virtual void fetch(u8 cycle) override;
virtual void tick(u8 voice) override;
// setters

View File

@ -97,7 +97,7 @@ void es5505_core::tick()
// /CAS low, E low: fetch sample
if (!m_e.current_edge())
{
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
m_voice[m_voice_cycle].fetch(m_voice_fetch);
}
}
}
@ -113,18 +113,18 @@ void es5505_core::tick()
else if (m_e.falling_edge()) // Voice memory
{
m_host_intf.clear_host_access();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
m_voice[m_voice_cycle].fetch(m_voice_fetch);
}
if (m_e.current_edge()) // Host interface
{
if (m_host_intf.host_access())
if (m_host_intf.m_host_access)
{
if (m_host_intf.rw() && (m_e.cycle() == 2)) // Read
if (m_host_intf.m_rw && (m_e.cycle() == 2)) // Read
{
m_hd = read(m_ha);
m_host_intf.clear_host_access();
}
else if ((!m_host_intf.rw()) && (m_e.cycle() == 2))
else if ((!m_host_intf.m_rw) && (m_e.cycle() == 2))
{ // Write
write(m_ha, m_hd);
}
@ -159,7 +159,7 @@ void es5505_core::tick_perf()
m_intf.e_pin(false);
m_host_intf.clear_host_access();
m_host_intf.clear_strobe();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
m_voice[m_voice_cycle].fetch(m_voice_fetch);
voice_tick();
// rising edge
m_e.edge().set(true);
@ -170,7 +170,7 @@ void es5505_core::tick_perf()
m_intf.e_pin(false);
m_host_intf.clear_host_access();
m_host_intf.clear_strobe();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
m_voice[m_voice_cycle].fetch(m_voice_fetch);
voice_tick();
// rising edge
m_e.edge().set(true);
@ -211,12 +211,12 @@ void es5505_core::voice_tick()
}
}
void es5505_core::voice_t::fetch(u8 voice, u8 cycle)
void es5505_core::voice_t::fetch(u8 cycle)
{
m_alu.set_sample(
cycle,
m_host.m_intf.read_sample(voice,
bitfield(m_cr.bs(), 0),
m_host.m_intf.read_sample(
bitfield(m_cr.m_bs, 0),
bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer)));
}
@ -301,7 +301,7 @@ void es5505_core::voice_t::reset()
// Accessors
u16 es5505_core::host_r(u8 address)
{
if (!m_host_intf.host_access())
if (!m_host_intf.m_host_access)
{
m_ha = address;
if (m_e.rising_edge())
@ -318,7 +318,7 @@ u16 es5505_core::host_r(u8 address)
void es5505_core::host_w(u8 address, u16 data)
{
if (!m_host_intf.host_access())
if (!m_host_intf.m_host_access)
{
m_ha = address;
m_hd = data;

View File

@ -112,7 +112,7 @@ class es5505_core : public es550x_shared_core
// internal state
virtual void reset() override;
virtual void fetch(u8 voice, u8 cycle) override;
virtual void fetch(u8 cycle) override;
virtual void tick(u8 voice) override;
// setters

View File

@ -9,182 +9,10 @@
#include "es5506.hpp"
// Internal functions
// DO NOT USE THIS ONE!
void es5506_core::tick()
{
// CLKIN
if (m_clkin.tick())
{
// BCLK
if (m_clkin.edge().changed() && (!m_mode.bclk_en())) // BCLK is freely running clock
{
if (m_bclk.tick())
{
m_intf.bclk(m_bclk.current_edge());
// Serial output
if (!m_mode.lrclk_en())
{
if (m_bclk.falling_edge())
{
// LRCLK
if (m_lrclk.tick())
{
m_intf.lrclk(m_lrclk.current_edge());
if (m_lrclk.rising_edge())
{
m_w_st_curr = m_w_st;
m_w_end_curr = m_w_end;
}
if (m_lrclk.falling_edge())
{ // update width
m_lrclk.set_width_latch(m_lr_end);
}
}
}
}
// WCLK
if (!m_mode.wclk_en())
{
if (!m_mode.lrclk_en())
{
if (m_lrclk.edge().changed())
{
m_wclk = 0;
}
}
if (m_bclk.falling_edge())
{
if (m_wclk == m_w_st_curr)
{
m_intf.wclk(true);
if (m_lrclk.current_edge())
{
for (int i = 0; i < 6; i++)
{
// copy output
m_output[i].copy_output(m_output_temp[i]);
// clamp to 20 bit (upper 3 bits are
// overflow guard bits)
m_output_latch[i].clamp20(m_ch[i]);
m_output_temp[i].reset();
m_output_latch[i].clamp20();
// set signed
if (m_output_latch[i].left() < 0)
{
m_output_temp[i].set_left(-1);
}
if (m_output_latch[i].right() < 0)
{
m_output_temp[i].set_right(-1);
}
}
}
m_wclk_lr = m_lrclk.current_edge();
m_output_bit = 20;
}
if (m_wclk < m_w_end_curr)
{
s8 output_bit = --m_output_bit;
if (m_output_bit >= 0)
{
for (int i = 0; i < 6; i++)
{
if (m_wclk_lr)
{
// Right output
m_output_temp[i].serial_in(
m_wclk_lr,
bitfield(m_output_latch[i].right(), output_bit));
}
else
{
// Left output
m_output_temp[i].serial_in(
m_wclk_lr,
bitfield(m_output_latch[i].left(), output_bit));
}
}
}
}
if (m_wclk == m_w_end_curr)
{
m_intf.wclk(false);
}
m_wclk++;
}
}
}
}
// /CAS, E
if (m_clkin.falling_edge()) // falling edge triggers /CAS, E clock
{
// /CAS
if (m_cas.tick())
{
// single OTTO master mode, /CAS high, E low: get sample address
// single OTTO early mode, /CAS falling, E high: get sample
// address
if (m_cas.falling_edge())
{
if (!m_e.current_edge())
{
// single OTTO master mode, /CAS low, E low: fetch
// sample
if (m_mode.master())
{
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
}
else if (m_e.current_edge())
{
// dual OTTO slave mode, /CAS low, E high: fetch sample
if (m_mode.dual() && (!m_mode.master()))
{ // Dual OTTO, slave mode
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
}
}
}
// E
if (m_e.tick())
{
m_intf.e_pin(m_e.current_edge());
if (m_e.rising_edge())
{
m_host_intf.update_strobe();
}
else if (m_e.falling_edge())
{
m_host_intf.clear_host_access();
voice_tick();
}
if (m_e.current_edge()) // Host interface
{
if (m_host_intf.host_access())
{
if (m_host_intf.rw() && (m_e.cycle() == 0)) // Read
{
m_hd = read(m_ha);
m_host_intf.clear_host_access();
}
else if ((!m_host_intf.rw()) && (m_e.cycle() == 2))
{ // Write
write(m_ha, m_hd);
}
}
}
else if (!m_e.current_edge())
{
if (m_e.cycle() == 2)
{
// reset host access state
m_hd = 0;
m_host_intf.clear_strobe();
}
}
}
}
}
}
// less cycle accurate, but less CPU heavy routine
@ -193,12 +21,18 @@ void es5506_core::tick_perf()
// output
if (((!m_mode.lrclk_en()) && (!m_mode.bclk_en()) && (!m_mode.wclk_en())) && (m_w_st < m_w_end))
{
const int output_bits = 20 - (m_w_end - m_w_st);
const int output_bits = (20 - (m_w_end - m_w_st));
if (output_bits < 20)
{
for (int c = 0; c < 6; c++)
{
m_output[c].clamp20(m_ch[c] >> output_bits);
m_output[c].m_left=m_ch[c].m_left>>output_bits;
if (m_output[c].m_left<-0x80000) m_output[c].m_left=-0x80000;
if (m_output[c].m_left>0x7ffff) m_output[c].m_left=0x7ffff;
m_output[c].m_right=m_ch[c].m_right>>output_bits;
if (m_output[c].m_right<-0x80000) m_output[c].m_right=-0x80000;
if (m_output[c].m_right>0x7ffff) m_output[c].m_right=0x7ffff;
}
}
}
@ -211,93 +45,63 @@ void es5506_core::tick_perf()
}
// update
// falling edge
m_e.edge().set(false);
m_intf.e_pin(false);
m_host_intf.clear_host_access();
m_host_intf.clear_strobe();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.edge().set(true);
m_intf.e_pin(true);
m_host_intf.update_strobe();
// falling edge
m_e.edge().set(false);
m_intf.e_pin(false);
m_host_intf.clear_host_access();
m_host_intf.clear_strobe();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.edge().set(true);
m_intf.e_pin(true);
m_host_intf.update_strobe();
voice_tick();
}
void es5506_core::voice_tick()
{
// Voice updates every 2 E clock cycle (or 4 BCLK clock cycle)
m_voice_update = bitfield(m_voice_fetch++, 0);
if (m_voice_update)
{
// Update voice
m_voice[m_voice_cycle].tick(m_voice_cycle);
// Refresh output
m_voice_end = true;
m_voice_cycle = 0;
for (int i=0; i<6; i++)
{
m_ch[i].reset();
}
// Update voice
const int total=VGS_CLAMP(m_active,4,31);
for (int i=0; i<=total; i++) {
m_voice[i].tick(i);
// Refresh output
if ((++m_voice_cycle) > clamp<u8>(m_active, 4, 31)) // 5 ~ 32 voices
{
m_voice_end = true;
m_voice_cycle = 0;
for (output_t &elem : m_ch)
{
elem.reset();
}
for (voice_t &elem : m_voice)
{
const u8 ca = bitfield<u8>(elem.cr().ca(), 0, 3);
if (ca < 6)
{
m_ch[ca] += elem.ch();
}
elem.ch().reset();
}
}
else
{
m_voice_end = false;
}
m_voice_fetch = 0;
}
const u8 ca = m_voice[i].cr().ca()&7;
if (ca < 6)
{
m_ch[ca] += m_voice[i].ch();
}
}
}
void es5506_core::voice_t::fetch(u8 voice, u8 cycle)
void es5506_core::voice_t::fetch(u8 cycle)
{
m_alu.set_sample(
cycle,
m_host.m_intf.read_sample(voice,
m_cr.bs(),
bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer)));
0,
m_host.m_intf.read_sample(m_cr.m_bs,
(m_alu.get_accum_integer())&((1<<m_alu.m_integer)-1)));
if (m_cr.cmpd())
{ // Decompress (Upper 8 bit is used for compressed format)
m_alu.set_sample(cycle, decompress(bitfield(m_alu.sample(cycle), 8, 8)));
m_alu.set_sample(0, decompress((m_alu.sample(0)>>8)&255));
}
m_alu.set_sample(
1,
m_host.m_intf.read_sample(m_cr.m_bs,
(m_alu.get_accum_integer() + 1)&((1<<m_alu.m_integer)-1)));
if (m_cr.cmpd())
{ // Decompress (Upper 8 bit is used for compressed format)
m_alu.set_sample(1, decompress((m_alu.sample(1)>>8)&255));
}
}
void es5506_core::voice_t::tick(u8 voice)
{
m_output[0] = m_output[1] = 0;
m_ch.reset();
// Filter execute
m_filter.tick(m_alu.interpolation());
if (m_alu.busy())
{
if ((m_alu.m_last_accum&(~m_alu.m_fraction))!=(m_alu.m_accum&(~m_alu.m_fraction))) fetch(0);
m_filter.tick(m_alu.interpolation());
// Send to output
m_output[0] = volume_calc(m_lvol, sign_ext<s32>(m_filter.o4_1(), 16));
m_output[1] = volume_calc(m_rvol, sign_ext<s32>(m_filter.o4_1(), 16));
m_output[0] = m_mute ? 0 : volume_calc(m_lvol, (short)m_filter.o4_1());
m_output[1] = m_mute ? 0 : volume_calc(m_rvol, (short)m_filter.o4_1());
m_ch.set_left(m_output[0]);
m_ch.set_right(m_output[1]);
@ -307,18 +111,23 @@ void es5506_core::voice_t::tick(u8 voice)
{
m_alu.loop_exec();
}
}
} else {
m_filter.tick(m_alu.interpolation());
m_output[0] = m_output[1] = 0;
m_ch.reset();
}
// Envelope
if (m_ecount != 0)
{
// Left and Right volume
if (bitfield(m_lvramp, 0, 8) != 0)
{
m_lvol = clamp<s32>(m_lvol + sign_ext<s32>(bitfield(m_lvramp, 0, 8), 8), 0, 0xffff);
m_lvol = VGS_CLAMP(m_lvol + sign_ext<s32>(bitfield(m_lvramp, 0, 8), 8), 0, 0xffff);
}
if (bitfield(m_rvramp, 0, 8) != 0)
{
m_rvol = clamp<s32>(m_rvol + sign_ext<s32>(bitfield(m_rvramp, 0, 8), 8), 0, 0xffff);
m_rvol = VGS_CLAMP(m_rvol + sign_ext<s32>(bitfield(m_rvramp, 0, 8), 8), 0, 0xffff);
}
// Filter coeffcient
@ -326,13 +135,13 @@ void es5506_core::voice_t::tick(u8 voice)
((m_k1ramp.slow() == 0) || (bitfield(m_filtcount, 0, 3) == 0)))
{
m_filter.set_k1(
clamp<s32>(m_filter.k1() + sign_ext<s32>(m_k1ramp.ramp(), 8), 0, 0xffff));
VGS_CLAMP(m_filter.k1() + sign_ext<s32>(m_k1ramp.ramp(), 8), 0, 0xffff));
}
if ((m_k2ramp.ramp() != 0) &&
((m_k2ramp.slow() == 0) || (bitfield(m_filtcount, 0, 3) == 0)))
{
m_filter.set_k2(
clamp<s32>(m_filter.k2() + sign_ext<s32>(m_k2ramp.ramp(), 8), 0, 0xffff));
VGS_CLAMP(m_filter.k2() + sign_ext<s32>(m_k2ramp.ramp(), 8), 0, 0xffff));
}
m_ecount--;
@ -340,7 +149,7 @@ void es5506_core::voice_t::tick(u8 voice)
m_filtcount = bitfield(m_filtcount + 1, 0, 3);
// Update IRQ
m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice);
//m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice);
}
// Compressed format
@ -357,9 +166,7 @@ s16 es5506_core::voice_t::decompress(u8 sample)
// volume calculation
s32 es5506_core::voice_t::volume_calc(u16 volume, s32 in)
{
u8 exponent = bitfield(volume, 12, 4);
u8 mantissa = bitfield(volume, 4, 8);
return (in * s32(0x100 | mantissa)) >> (20 - exponent);
return (in * s32(0x100 | ((volume>>4)&255))) >> (20 - ((volume>>12)&15));
}
void es5506_core::reset()
@ -420,36 +227,16 @@ void es5506_core::voice_t::reset()
// Accessors
u8 es5506_core::host_r(u8 address)
{
if (!m_host_intf.host_access())
{
m_ha = address;
if (m_e.rising_edge())
{ // update directly
m_hd = read(m_ha, true);
}
else
{
m_host_intf.set_strobe(true);
}
}
return m_hd;
}
void es5506_core::host_w(u8 address, u8 data)
{
if (!m_host_intf.host_access())
{
m_ha = address;
m_hd = data;
if (m_e.rising_edge())
{ // update directly
write(m_ha, m_hd);
}
else
{
m_host_intf.set_strobe(false);
}
}
}
u8 es5506_core::read(u8 address, bool cpu_access)

View File

@ -27,7 +27,7 @@ class es5506_core : public es550x_shared_core
{
}
void reset()
inline void reset()
{
m_left = 0;
m_right = 0;
@ -39,7 +39,7 @@ class es5506_core : public es550x_shared_core
m_right = src.right();
}
inline s32 clamp20(s32 in) { return clamp(in, -0x80000, 0x7ffff); }
inline s32 clamp20(s32 in) { return VGS_CLAMP(in, -0x80000, 0x7ffff); }
inline void clamp20(output_t &src)
{
@ -89,7 +89,7 @@ class es5506_core : public es550x_shared_core
return *this;
}
private:
public: // oh my...
s32 m_left = 0;
s32 m_right = 0;
};
@ -145,15 +145,14 @@ class es5506_core : public es550x_shared_core
, m_k2ramp(filter_ramp_t())
, m_k1ramp(filter_ramp_t())
, m_filtcount(0)
, m_ch(output_t())
, m_mute(false)
, m_output{0,0}
{
m_output.fill(0);
}
// internal state
virtual void reset() override;
virtual void fetch(u8 voice, u8 cycle) override;
virtual void fetch(u8 cycle) override;
virtual void tick(u8 voice) override;
// Setters
@ -182,6 +181,8 @@ class es5506_core : public es550x_shared_core
inline filter_ramp_t &k1ramp() { return m_k1ramp; }
inline bool muted() { return m_mute; }
output_t &ch() { return m_ch; }
// for debug/preview only
@ -211,7 +212,7 @@ class es5506_core : public es550x_shared_core
u8 m_filtcount = 0; // Internal counter for slow mode
output_t m_ch; // channel output
bool m_mute = false; // mute flag (for debug purpose)
std::array<s32, 2> m_output; // output preview (for debug purpose)
s32 m_output[2]; // output preview (for debug purpose)
};
// 5 bit mode
@ -313,9 +314,9 @@ class es5506_core : public es550x_shared_core
inline bool bclk_falling_edge() { return m_bclk.falling_edge(); }
// 6 stereo output channels
inline s32 lout(u8 ch) { return m_output[std::min<u8>(5, ch & 0x7)].left(); }
inline s32 lout(u8 ch) { return m_output[ch&7].left(); }
inline s32 rout(u8 ch) { return m_output[std::min<u8>(5, ch & 0x7)].right(); }
inline s32 rout(u8 ch) { return m_output[ch&7].right(); }
//-----------------------------------------------------------------
//
@ -352,7 +353,7 @@ class es5506_core : public es550x_shared_core
virtual void voice_tick() override;
private:
std::array<voice_t, 32> m_voice; // 32 voices
voice_t m_voice[32]; // 32 voices
// Host interfaces
u32 m_read_latch = 0; // 32 bit register latch for host read
@ -372,10 +373,10 @@ class es5506_core : public es550x_shared_core
s16 m_wclk = 0; // WCLK
bool m_wclk_lr = false; // WCLK, L/R output select
s8 m_output_bit = 0; // Bit position in output
std::array<output_t, 6> m_ch; // 6 stereo output channels
std::array<output_t, 6> m_output; // Serial outputs
std::array<output_t, 6> m_output_temp; // temporary signal for serial output
std::array<output_t, 6> m_output_latch; // output latch
output_t m_ch[6]; // 6 stereo output channels
output_t m_output[8]; // Serial outputs
output_t m_output_temp[6]; // temporary signal for serial output
output_t m_output_latch[6]; // output latch
};
#endif

View File

@ -14,6 +14,8 @@
#include "../core/core.hpp"
#include "../core/util/clock_pulse.hpp"
#include <string.h>
using namespace vgsound_emu;
// ES5504/ES5505/ES5506 interface
@ -39,7 +41,7 @@ class es550x_intf : public vgsound_emu_core
virtual void adc_w(u16 data) {} // ADC output
virtual s16 read_sample(u8 voice, u8 bank, u32 address) { return 0; }
virtual s16 read_sample(u8 bank, u32 address) { return 0; }
};
// Shared functions for ES5504/ES5505/ES5506
@ -135,7 +137,7 @@ class es550x_shared_core : public vgsound_emu_core
inline bool cmpd() { return m_cmpd; }
protected:
public:
// Channel assign -
// 4 bit (16 channel or Bank) for ES5504
// 2 bit (4 stereo channels) for ES5505
@ -158,6 +160,7 @@ class es550x_shared_core : public vgsound_emu_core
: vgsound_emu_core("es550x_voice_alu")
, m_integer(integer)
, m_fraction(fraction)
, m_fraction_m9(std::max<s8>(0, m_fraction - 9))
, m_total_bits(integer + fraction)
, m_accum_mask(
u32(std::min<u64>(~0, u64(u64(1) << u64(integer + fraction)) - 1)))
@ -166,13 +169,15 @@ class es550x_shared_core : public vgsound_emu_core
, m_start(0)
, m_end(0)
, m_accum(0)
, m_last_accum(0)
, m_sample{0,0}
{
m_sample.fill(0);
}
// configurations
const u8 m_integer = 21;
const u8 m_fraction = 11;
const u8 m_fraction_m9 = 2;
const u8 m_total_bits = 32;
const u32 m_accum_mask = 0xffffffff;
const bool m_transwave = true;
@ -264,7 +269,7 @@ class es550x_shared_core : public vgsound_emu_core
inline s32 sample(u8 slot) { return m_sample[slot & 1]; }
private:
public:
class es550x_alu_cr_t : public vgsound_emu_core
{
public:
@ -343,7 +348,7 @@ class es550x_shared_core : public vgsound_emu_core
inline u8 loop() { return (m_lpe << 0) | (m_ble << 1); }
private:
public:
u8 m_stop0 : 1; // Stop with ALU
u8 m_stop1 : 1; // Stop with processor
u8 m_lpe : 1; // Loop enable
@ -366,8 +371,9 @@ class es550x_shared_core : public vgsound_emu_core
// 20 integer, 9 fraction for ES5504/ES5505
// 21 integer, 11 fraction for ES5506
u32 m_accum = 0;
u32 m_last_accum = 0;
// Samples
std::array<s32, 2> m_sample;
s32 m_sample[2];
};
// Filter
@ -380,10 +386,7 @@ class es550x_shared_core : public vgsound_emu_core
, m_k2(0)
, m_k1(0)
{
for (std::array<s32, 2> &elem : m_o)
{
std::fill(elem.begin(), elem.end(), 0);
}
memset(m_o,0,2*5*sizeof(s32));
}
void reset();
@ -428,8 +431,6 @@ class es550x_shared_core : public vgsound_emu_core
inline s32 o4_1() { return m_o[4][0]; }
private:
void lp_exec(s32 coeff, s32 in, s32 out);
void hp_exec(s32 coeff, s32 in, s32 out);
// Registers
u8 m_lp = 0; // Filter mode
@ -441,7 +442,7 @@ class es550x_shared_core : public vgsound_emu_core
s32 m_k1 = 0; // Filter coefficient 1
// Filter storage registers
std::array<std::array<s32, 2>, 5> m_o;
s32 m_o[5][2];
};
public:
@ -455,7 +456,7 @@ class es550x_shared_core : public vgsound_emu_core
// internal state
virtual void reset();
virtual void fetch(u8 voice, u8 cycle) = 0;
virtual void fetch(u8 cycle) = 0;
virtual void tick(u8 voice) = 0;
void irq_update(es550x_intf &intf, es550x_irq_t &irqv)
@ -515,16 +516,12 @@ class es550x_shared_core : public vgsound_emu_core
m_host_access = m_host_access_strobe;
}
// Getters
bool host_access() { return m_host_access; }
bool rw() { return m_rw; }
private:
u8 m_host_access : 1; // Host access trigger
u8 m_host_access_strobe : 1; // Host access strobe
u8 m_rw : 1; // R/W state
u8 m_rw_strobe : 1; // R/W strobe
public:
u8 m_host_access ; // Host access trigger
u8 m_host_access_strobe ; // Host access strobe
u8 m_rw ; // R/W state
u8 m_rw_strobe ; // R/W strobe
};
public:

View File

@ -16,13 +16,15 @@ void es550x_shared_core::es550x_voice_t::es550x_alu_t::reset()
m_start = 0;
m_end = 0;
m_accum = 0;
m_last_accum=0;
m_sample[0] = m_sample[1] = 0;
}
bool es550x_shared_core::es550x_voice_t::es550x_alu_t::busy() { return m_cr.stop() == 0; }
bool es550x_shared_core::es550x_voice_t::es550x_alu_t::busy() { return !(m_cr.m_stop0 || m_cr.m_stop1); }
bool es550x_shared_core::es550x_voice_t::es550x_alu_t::tick()
{
m_last_accum = m_accum;
if (m_cr.dir())
{
m_accum -= m_fc;
@ -33,24 +35,19 @@ bool es550x_shared_core::es550x_voice_t::es550x_alu_t::tick()
}
m_accum &= m_accum_mask;
return ((!m_cr.lei()) &&
(((m_cr.dir()) && (m_accum < m_start)) || ((!m_cr.dir()) && (m_accum > m_end))))
return ((!m_cr.m_lei) &&
(((m_cr.m_dir) && (m_accum < m_start)) || ((!m_cr.m_dir) && (m_accum > m_end))))
? true
: false;
}
void es550x_shared_core::es550x_voice_t::es550x_alu_t::loop_exec()
{
if (m_cr.irqe())
{ // Set IRQ
m_cr.set_irq(true);
}
if (m_cr.dir()) // Reverse playback
if (m_cr.m_dir) // Reverse playback
{
if (m_cr.lpe()) // Loop enable
if (m_cr.m_lpe) // Loop enable
{
if (m_cr.ble()) // Bidirectional
if (m_cr.m_ble) // Bidirectional
{
m_cr.set_dir(false);
m_accum = m_start + (m_start - m_accum);
@ -60,7 +57,7 @@ void es550x_shared_core::es550x_voice_t::es550x_alu_t::loop_exec()
m_accum = m_end - (m_start - m_accum);
}
}
else if (m_cr.ble() && m_transwave) // m_transwave
else if (m_cr.m_ble && m_transwave) // m_transwave
{
m_cr.set_loop(0);
m_cr.set_lei(true); // Loop end ignore
@ -73,9 +70,9 @@ void es550x_shared_core::es550x_voice_t::es550x_alu_t::loop_exec()
}
else
{
if (m_cr.lpe()) // Loop enable
if (m_cr.m_lpe) // Loop enable
{
if (m_cr.ble()) // Bidirectional
if (m_cr.m_ble) // Bidirectional
{
m_cr.set_dir(true);
m_accum = m_end - (m_end - m_accum);
@ -85,7 +82,7 @@ void es550x_shared_core::es550x_voice_t::es550x_alu_t::loop_exec()
m_accum = (m_accum - m_end) + m_start;
}
}
else if (m_cr.ble() && m_transwave) // m_transwave
else if (m_cr.m_ble && m_transwave) // m_transwave
{
m_cr.set_loop(0);
m_cr.set_lei(true); // Loop end ignore
@ -101,14 +98,14 @@ void es550x_shared_core::es550x_voice_t::es550x_alu_t::loop_exec()
s32 es550x_shared_core::es550x_voice_t::es550x_alu_t::interpolation()
{
// SF = S1 + ACCfr * (S2 - S1)
return m_sample[0] + ((bitfield<s32>(m_accum, std::max<s8>(0, m_fraction - 9), 9) *
return m_sample[0] + (((((int)m_accum>>(int)2)&(int)511) *
(m_sample[1] - m_sample[0])) >>
9);
}
u32 es550x_shared_core::es550x_voice_t::es550x_alu_t::get_accum_integer()
{
return bitfield(m_accum, m_fraction, m_integer);
return (m_accum>>m_fraction)&((1<<m_integer)-1);
}
void es550x_shared_core::es550x_voice_t::es550x_alu_t::irq_exec(es550x_intf &intf,

View File

@ -8,16 +8,24 @@
#include "es550x.hpp"
// Yn = K*(Xn - Yn-1) + Yn-1
#define lp_exec(coeff,in,out) \
m_o[out][1] = m_o[out][0]; \
m_o[out][0] = ((coeff * (m_o[in][0] - m_o[out][0])) / 4096) + m_o[out][0];
// Yn = Xn - Xn-1 + K*Yn-1
#define hp_exec(coeff,in,out) \
m_o[out][1] = m_o[out][0]; \
m_o[out][0] = m_o[in][0] - m_o[in][1] + ((coeff * m_o[out][0]) / 8192) + (m_o[out][0] / 2);
// Filter functions
void es550x_shared_core::es550x_voice_t::es550x_filter_t::reset()
{
m_lp = 0;
m_k2 = 0;
m_k1 = 0;
for (std::array<s32, 2> &elem : m_o)
{
std::fill(elem.begin(), elem.end(), 0);
}
memset(m_o,0,2*5*sizeof(s32));
}
void es550x_shared_core::es550x_voice_t::es550x_filter_t::tick(s32 in)
@ -52,21 +60,3 @@ void es550x_shared_core::es550x_voice_t::es550x_filter_t::tick(s32 in)
break;
}
}
void es550x_shared_core::es550x_voice_t::es550x_filter_t::lp_exec(s32 coeff, s32 in, s32 out)
{
// Store previous filter data
m_o[out][1] = m_o[out][0];
// Yn = K*(Xn - Yn-1) + Yn-1
m_o[out][0] = ((coeff * (m_o[in][0] - m_o[out][0])) / 4096) + m_o[out][0];
}
void es550x_shared_core::es550x_voice_t::es550x_filter_t::hp_exec(s32 coeff, s32 in, s32 out)
{
// Store previous filter data
m_o[out][1] = m_o[out][0];
// Yn = Xn - Xn-1 + K*Yn-1
m_o[out][0] = m_o[in][0] - m_o[in][1] + ((coeff * m_o[out][0]) / 8192) + (m_o[out][0] / 2);
}

View File

@ -8,11 +8,11 @@
#include "k005289.hpp"
void k005289_core::tick()
void k005289_core::tick(const unsigned int cycles)
{
for (timer_t &elem : m_timer)
{
elem.tick();
elem.tick(cycles);
}
}
@ -24,12 +24,12 @@ void k005289_core::reset()
}
}
void k005289_core::timer_t::tick()
{
if (bitfield(++m_counter, 0, 12) == 0)
void k005289_core::timer_t::tick(const unsigned int cycles) {
m_counter-=cycles;
while (m_counter < 0)
{
m_addr = bitfield(m_addr + 1, 0, 5);
m_counter = m_freq;
m_counter += 0x1000-(m_freq&0xfff);
}
}

View File

@ -32,7 +32,7 @@ class k005289_core : public vgsound_emu_core
// internal state
void reset();
void tick();
void tick(const unsigned int cycles=1);
// accessors
// Replace current frequency to lastest loaded pitch
@ -63,7 +63,7 @@ class k005289_core : public vgsound_emu_core
// internal state
void reset();
void tick();
void tick(const unsigned int cycles);
// accessors
// TG1/2 pin

View File

@ -1,5 +1,9 @@
# Konami SCC
## modified
the emulation core has been modified for optimization.
## Summary
- 5 voice wavetable

View File

@ -10,17 +10,17 @@
#include "scc.hpp"
// shared SCC features
void scc_core::tick()
void scc_core::tick(const int cycles)
{
m_out = 0;
for (auto &elem : m_voice)
for (int elem=0; elem<5; elem++)
{
elem.tick();
m_out += elem.out();
m_voice[elem].tick(cycles);
m_out += m_voice[elem].out();
}
}
void scc_core::voice_t::tick()
void scc_core::voice_t::tick(const int cycles)
{
if (m_pitch >= 9) // or voice is halted
{
@ -28,23 +28,27 @@ void scc_core::voice_t::tick()
const u16 temp = m_counter;
if (m_host.m_test.freq_4bit()) // 4 bit frequency mode
{
m_counter = (m_counter & ~0x0ff) | (bitfield(bitfield(m_counter, 0, 8) - 1, 0, 8) << 0);
m_counter = (m_counter & ~0xf00) | (bitfield(bitfield(m_counter, 8, 4) - 1, 0, 4) << 8);
m_counter = (m_counter & ~0x0ff) | (bitfield(bitfield(m_counter, 0, 8) - cycles, 0, 8) << 0);
m_counter = (m_counter & ~0xf00) | (bitfield(bitfield(m_counter, 8, 4) - cycles, 0, 4) << 8);
}
else
{
m_counter = bitfield(m_counter - 1, 0, 12);
m_counter = bitfield(m_counter - cycles, 0, 12);
}
// handle counter carry
const bool carry = m_host.m_test.freq_8bit()
const bool carry = (temp<cycles) || (m_host.m_test.freq_8bit()
? (bitfield(temp, 0, 8) == 0)
: (m_host.m_test.freq_4bit() ? (bitfield(temp, 8, 4) == 0)
: (bitfield(temp, 0, 12) == 0));
: (bitfield(temp, 0, 12) == 0)));
if (carry)
{
m_addr = bitfield(m_addr + 1, 0, 5);
m_counter = m_pitch;
m_counter = m_pitch - ((temp<cycles)?(cycles-temp-1):0);
while (m_counter>m_pitch) {
m_addr = bitfield(m_addr + 1, 0, 5);
m_counter+=m_pitch-1;
}
}
}
// get output
@ -60,19 +64,19 @@ void scc_core::voice_t::tick()
void scc_core::reset()
{
for (auto &elem : m_voice)
for (int elem=0; elem<5; elem++)
{
elem.reset();
m_voice[elem].reset();
}
m_test.reset();
m_out = 0;
std::fill(m_reg.begin(), m_reg.end(), 0);
memset(m_reg,0,256);
}
void scc_core::voice_t::reset()
{
std::fill(m_wave.begin(), m_wave.end(), 0);
memset(m_wave,0,32);
m_enable = false;
m_pitch = 0;
m_volume = 0;

View File

@ -14,6 +14,7 @@
#include "../core/core.hpp"
#include "../core/util/mem_intf.hpp"
#include <string.h>
using namespace vgsound_emu;
@ -36,12 +37,12 @@ class scc_core : public vgsound_emu_core
, m_counter(0)
, m_out(0)
{
m_wave.fill(0);
memset(m_wave,0,32);
}
// internal state
void reset();
void tick();
void tick(const int cycles=1);
// accessors
inline void reset_addr() { m_addr = 0; }
@ -69,7 +70,7 @@ class scc_core : public vgsound_emu_core
private:
// registers
scc_core &m_host;
std::array<s8, 32> m_wave; // internal waveform
s8 m_wave[32]; // internal waveform
bool m_enable = false; // output enable flag
u16 m_pitch : 12; // pitch
u16 m_volume : 4; // volume
@ -139,7 +140,7 @@ class scc_core : public vgsound_emu_core
, m_test(test_t())
, m_out(0)
{
m_reg.fill(0);
memset(m_reg,0,256);
}
// destructor
@ -151,7 +152,7 @@ class scc_core : public vgsound_emu_core
// internal state
virtual void reset();
void tick();
void tick(const int cycles=1);
// getters
inline s32 out() { return m_out; } // output to DA0...DA10 pin
@ -168,12 +169,12 @@ class scc_core : public vgsound_emu_core
void freq_vol_enable_w(u8 address, u8 data);
// internal values
std::array<voice_t, 5> m_voice; // 5 voices
voice_t m_voice[5]; // 5 voices
test_t m_test; // test register
s32 m_out = 0; // output to DA0...10
std::array<u8, 256> m_reg; // register pool
u8 m_reg[256]; // register pool
};
// SCC core

View File

@ -11,13 +11,11 @@
void vrcvi_core::tick()
{
m_out = 0;
if (!m_control.halt()) // Halt flag
if (!m_control.m_halt) // Halt flag
{
// tick per each clock
for (auto &elem : m_pulse)
{
m_out += elem.get_output(); // add 4 bit pulse output
}
m_out += m_pulse[0].get_output(); // add 4 bit pulse output
m_out += m_pulse[1].get_output();
m_out += m_sawtooth.get_output(); // add 5 bit sawtooth output
}
if (m_timer.tick())
@ -28,11 +26,8 @@ void vrcvi_core::tick()
void vrcvi_core::reset()
{
for (auto &elem : m_pulse)
{
elem.reset();
}
m_pulse[0].reset();
m_pulse[1].reset();
m_sawtooth.reset();
m_timer.reset();
m_control.reset();
@ -41,65 +36,71 @@ void vrcvi_core::reset()
bool vrcvi_core::alu_t::tick()
{
if (m_divider.enable())
if (m_divider.m_enable)
{
const u16 temp = m_counter;
// post decrement
if (bitfield(m_host.m_control.shift(), 1))
if (m_host.m_control.m_shift&2)
{
m_counter = (m_counter & 0x0ff) | (bitfield(bitfield(m_counter, 8, 4) - 1, 0, 4) << 8);
m_counter = (m_counter & 0xf00) | (bitfield(bitfield(m_counter, 0, 8) - 1, 0, 8) << 0);
if (bitfield(temp, 8, 4) == 0)
{
m_counter = m_divider.m_divider;
return true;
}
}
else if (bitfield(m_host.m_control.shift(), 0))
else if (m_host.m_control.m_shift&1)
{
m_counter = (m_counter & 0x00f) | (bitfield(bitfield(m_counter, 4, 8) - 1, 0, 8) << 4);
m_counter = (m_counter & 0xff0) | (bitfield(bitfield(m_counter, 0, 4) - 1, 0, 4) << 0);
if (bitfield(temp, 4, 8) == 0)
{
m_counter = m_divider.m_divider;
return true;
}
}
else
{
m_counter = bitfield(bitfield(m_counter, 0, 12) - 1, 0, 12);
m_counter = (m_counter-1)&0xfff; //bitfield(bitfield(m_counter, 0, 12) - 1, 0, 12);
if (!(temp&0xfff)) {
m_counter = m_divider.m_divider;
return true;
}
}
// carry handling
const bool carry = bitfield(m_host.m_control.shift(), 1)
? (bitfield(temp, 8, 4) == 0)
: (bitfield(m_host.m_control.shift(), 0) ? (bitfield(temp, 4, 8) == 0)
: (bitfield(temp, 0, 12) == 0));
if (carry)
{
m_counter = m_divider.divider();
}
return carry;
return false;
}
return false;
}
bool vrcvi_core::pulse_t::tick()
{
if (!m_divider.enable())
if (!m_divider.m_enable)
{
return false;
}
if (vrcvi_core::alu_t::tick())
{
m_cycle = bitfield(m_cycle + 1, 0, 4);
m_cycle = (m_cycle+1)&15;
}
return m_control.mode() ? true : ((m_cycle > m_control.duty()) ? true : false);
return m_control.m_mode ? true : ((m_cycle > m_control.m_duty) ? true : false);
}
bool vrcvi_core::sawtooth_t::tick()
{
if (!m_divider.enable())
if (!m_divider.m_enable)
{
return false;
}
if (vrcvi_core::alu_t::tick())
{
if (bitfield(m_cycle++, 0))
if ((m_cycle++)&1)
{ // Even step only
m_accum += m_rate;
}
@ -109,20 +110,20 @@ bool vrcvi_core::sawtooth_t::tick()
m_cycle = 0;
}
}
return (m_accum == 0) ? false : true;
return (m_accum != 0);
}
s8 vrcvi_core::pulse_t::get_output()
{
// add 4 bit pulse output
m_out = tick() ? m_control.volume() : 0;
m_out = tick() ? m_control.m_volume : 0;
return m_out;
}
s8 vrcvi_core::sawtooth_t::get_output()
{
// add 5 bit sawtooth output
m_out = tick() ? bitfield(m_accum, 3, 5) : 0;
m_out = tick() ? ((m_accum>>3)&31) : 0;
return m_out;
}
@ -209,7 +210,7 @@ void vrcvi_core::pulse_w(u8 voice, u8 address, u8 data)
break;
case 0x02: // Pitch MSB, Enable/Disable - 0x9002/0x9001 (Pulse 1), 0xa002/0xa001 (Pulse 2)
v.divider().write(true, data);
if (!v.divider().enable())
if (!v.divider().m_enable)
{ // Reset duty cycle
v.clear_cycle();
}
@ -229,7 +230,7 @@ void vrcvi_core::saw_w(u8 address, u8 data)
break;
case 0x02: // Pitch MSB, Enable/Disable - 0xb002/0xb001 (Sawtooth)
m_sawtooth.divider().write(true, data);
if (!m_sawtooth.divider().enable())
if (!m_sawtooth.divider().m_enable)
{ // Reset accumulator
m_sawtooth.clear_accum();
}

View File

@ -58,7 +58,7 @@ class vrcvi_core : public vgsound_emu_core
inline bool enable() { return m_enable; }
private:
public:
u16 m_divider : 12; // divider (pitch)
u16 m_enable : 1; // channel disable flag
};
@ -141,7 +141,7 @@ class vrcvi_core : public vgsound_emu_core
inline u8 volume() { return m_volume; }
private:
public:
u8 m_mode : 1; // duty toggle flag
u8 m_duty : 3; // 3 bit duty cycle
u8 m_volume : 4; // 4 bit volume
@ -359,9 +359,9 @@ class vrcvi_core : public vgsound_emu_core
inline u8 shift() { return m_shift; }
private:
u8 m_halt : 1; // halt sound
u8 m_shift : 2; // 4/8 bit right shift
public:
u8 m_halt; // halt sound
u8 m_shift; // 4/8 bit right shift
};
public:
@ -398,7 +398,7 @@ class vrcvi_core : public vgsound_emu_core
private:
vrcvi_intf &m_intf;
std::array<pulse_t, 2> m_pulse; // 2 pulse channels
pulse_t m_pulse[2]; // 2 pulse channels
sawtooth_t m_sawtooth; // sawtooth channel
timer_t m_timer; // internal timer
global_control_t m_control; // control

Binary file not shown.

View File

@ -4,3 +4,4 @@ the Furnace user interface is where the job gets done.
- [UI components](components.md)
- [global keyboard shortcuts](keyboard.md)
- [basic mode](basic-mode.md)

View File

@ -0,0 +1,34 @@
# basic mode
Furnace comes with a "basic mode" that can be toggled through the "settings" menu. it disables certain features in Furnace that may look intimidating or confusing for newcomers. if you find that a certain feature of furnace is missing, see if this setting is enabled or not.
among the features that cannot be accessed in this mode are:
* file menu:
* chip manipulation options (add, configure, change, remove chips)
* edit menu:
* paste special&hellip;
* operation masking
* input latch
* find and replace
* speed window:
* virtual tempo
* divider
* song length
* song info window:
* manual system naming
* tuning options
* right-clicking on the pattern window:
* gradient/fade&hellip;
* scale
* randomize
* invert values
* flip selection
* collapse
* expand
* these windows:
* mixer
* grooves
* channels
* pattern manager
* chip manager
* compatibility flags

View File

@ -57,9 +57,6 @@ however, effects are continuous, which means you only need to type it once and t
- `EDxx`: delay note by `xx` ticks.
- `EExx`: send external command.
- this effect is currently incomplete.
- `EFxx`: add or subtract global pitch.
- this effect is rather weird. use with caution.
- `80` is center.
- `F0xx`: change song Hz by BPM value.
- `F1xx`: single tick slide up.
- `F2xx`: single tick slide down.
@ -135,7 +132,7 @@ ex | FM | OPM | OPZ | OPLL | AY-3-8910 | AY8930 | Lynx
W | | LFO Shape | LFO Shape | Patch | Waveform | Waveform | | Waveform | Waveform | Waveform | Waveform | Waveform | Waveform | | | | Waveform | |
1 | | AMD | AMD | | | Duty | | FilterMode | Envelope | EnvMode | WaveLen | Mod Depth | Cutoff | Filter K1 | ClockDiv | EchoFeedback | Special | GroupAtk |
2 | | PMD | PMD | | Envelope | Envelope | | Resonance | | Envelope | WaveUpdate | Mod Speed | Resonance | Filter K2 | | Echo Length | Gain | GroupDec |
3 | | LFO Speed | LFO Speed | | AutoEnvNum | AutoEnvNum | | Special | | AutoEnvNum | WaveLoad W | | Control | Env Count | | | | Noise |
3 | LFOSpd | LFO Speed | LFO Speed | | AutoEnvNum | AutoEnvNum | | Special | | AutoEnvNum | WaveLoad W | | Control | Env Count | | | | Noise |
A | ALG | ALG | ALG | | AutoEnvDen | AutoEnvDen | | | | AutoEnvDen | WaveLoad P | | | Control | | | | |
B | FB | FB | FB | | | Noise AND | | | | | WaveLoad L | | | | | | | |
C | FMS | FMS | FMS | | | Noise OR | | | | | WaveLoad T | | | | | | | |
@ -144,4 +141,4 @@ ex | FM | OPM | OPZ | OPLL | AY-3-8910 | AY8930 | Lynx
5 | | | AMD2 | | | | | | | | | | | EnvRampR | | | | |
6 | | | PMD2 | | | | | | | | | | | EnvRampK1 | | | | |
7 | | | LFO2Speed | | | | | | | | | | | EnvRampK2 | | | | |
8 | | | LFO2Shape | | | | | | | | | | | Env Mode | | | | |
8 | | | LFO2Shape | | | | | | | | | | | Env Mode | | | | |

View File

@ -45,6 +45,16 @@ no plans have been made for TX81Z MIDI passthrough, because:
- `1Bxx`: set attack of operator 2.
- `1Cxx`: set attack of operator 3.
- `1Dxx`: set attack of operator 4.
- `1Exx`: set LFO AM depth.
- `1Fxx`: set LFO PM depth.
- `24xx`: set LFO 2 speed.
- `25xx`: set LFO 2 waveform. `xx` may be one of the following:
- `00`: saw
- `01`: square
- `02`: triangle
- `03`: noise
- `26xx`: set LFO 2 AM depth.
- `27xx`: set LFO 2 PM depth.
- `28xy`: set reverb of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.

View File

@ -1,6 +1,6 @@
# Yamaha YM2151
the sound chip powering several arcade boards and the Sharp X1/X68000. Eight 4-op FM channels, with overpowered LFO and almost unused noise generator.
the sound chip powering several arcade boards, the Sharp X1/X68000 and the Commander X16. Eight 4-op FM channels, with overpowered LFO and almost unused noise generator.
it also was present on several pinball machines and synthesizers of the era, and later surpassed by the YM2414 (OPZ) present in the world-famous TX81Z.

View File

@ -20,6 +20,7 @@ writers:
- DeMOSic
- cam900
- host12prog
- WindowxDeveloper
other:

View File

@ -32,6 +32,12 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
the format versions are:
- 142: Furnace dev142
- 141: Furnace Tournament Edition (for intro tune contest)
- 140: Furnace dev140
- 139: Furnace dev139
- 138: Furnace dev138
- 137: Furnace dev137
- 136: Furnace dev136
- 135: Furnace dev135
- 134: Furnace dev134
@ -280,6 +286,7 @@ size | description
| - 0xc5: YM2610B CSM - 20 channels
| - 0xc6: K007232 - 2 channels
| - 0xc7: GA20 - 4 channels
| - 0xc8: SM8521 - 3 channels
| - 0xde: YM2610B extended - 19 channels
| - 0xe0: QSound - 19 channels
| - 0xfc: Pong - 1 channel
@ -398,6 +405,17 @@ size | description
4?? | patchbay
| - see next section for more details.
1 | automatic patchbay (>=136)
--- | **a couple more compat flags** (>=138)
1 | broken portamento during legato
7 | reserved
--- | **speed pattern of first song** (>=139)
1 | length of speed pattern (fail if this is lower than 0 or higher than 16)
16 | speed pattern (this overrides speed 1 and speed 2 settings)
--- | **groove list** (>=139)
1 | number of entries
??? | groove entries. the format is:
| - 1 byte: length of groove
| - 16 bytes: groove pattern
```
# patchbay
@ -467,6 +485,9 @@ size | description
| - a list of channelCount C strings
S?? | channel short names
| - same as above
--- | **speed pattern** (>=139)
1 | length of speed pattern (fail if this is lower than 0 or higher than 16)
16 | speed pattern (this overrides speed 1 and speed 2 settings)
```
# chip flags
@ -1640,6 +1661,10 @@ chips which aren't on this list don't have any flags.
- 1: 16.67MHz
- bit 4: stereo (bool)
## 0xb1: Ensoniq ES5506
- bit 0-4: channels (int)
## 0xb5: tildearrow Sound Unit
- bit 0: clockSel (int)

View File

@ -115,6 +115,7 @@ the following instrument types are available:
- 45: K007232
- 46: GA20
- 47: Pokémon Mini
- 48: SM8521
the following feature codes are recognized:

View File

@ -15,17 +15,17 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>0.6pre3</string>
<string>0.6pre4</string>
<key>CFBundleName</key>
<string>Furnace</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.6pre3</string>
<string>0.6pre4</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.6pre3</string>
<string>0.6pre4</string>
<key>NSHumanReadableCopyright</key>
<string></string>
<key>NSHighResolutionCapable</key>

BIN
res/furnaceword.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

102
res/furnaceword.svg Normal file
View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="205.65253mm"
height="85.955002mm"
viewBox="0 0 205.65253 85.955"
version="1.1"
id="svg5"
inkscape:export-filename="/tmp/furnaceword.png"
inkscape:export-xdpi="166.66396"
inkscape:export-ydpi="166.66396"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
sodipodi:docname="furnaceword.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#424242"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="true"
inkscape:document-units="mm"
showgrid="false"
fit-margin-top="32"
fit-margin-left="32"
fit-margin-right="32"
fit-margin-bottom="32"
inkscape:zoom="1.0935553"
inkscape:cx="414.70239"
inkscape:cy="225.86877"
inkscape:window-width="1920"
inkscape:window-height="1056"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<rect
x="6.4661274"
y="115.09706"
width="584.08398"
height="238.58037"
id="rect1970" />
<rect
x="6.4661274"
y="115.09706"
width="488.38528"
height="165.18983"
id="rect25170" />
<rect
x="6.4661274"
y="115.09706"
width="584.08398"
height="238.58037"
id="rect93388" />
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter93390"
x="-0.053540562"
y="-0.35937083"
width="1.1070811"
height="1.7187417">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="9.1033312"
id="feGaussianBlur93392" />
</filter>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(30.4643,23.150964)">
<text
xml:space="preserve"
transform="matrix(0.26458333,0,0,0.26458333,-2.0565565,-29.517635)"
id="text93386"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:120px;line-height:1.25;font-family:'Averia Libre';-inkscape-font-specification:'Averia Libre Bold';letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect93388);fill:#75aae9;fill-opacity:1;stroke:#006bff;stroke-width:13.22834663;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.46923077;filter:url(#filter93390);opacity:1"><tspan
x="6.4667969"
y="226.09766"
id="tspan109265"><tspan
style="font-family:Xolonium;-inkscape-font-specification:'Xolonium Bold'"
id="tspan109263">Furnace</tspan></tspan></text>
<text
xml:space="preserve"
transform="matrix(0.26458333,0,0,0.26458333,-2.0565565,-29.517635)"
id="text1968"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:120px;line-height:1.25;font-family:'Averia Libre';-inkscape-font-specification:'Averia Libre Bold';letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect1970);fill:#8cf5ff;fill-opacity:1;stroke:#4dc4ff;stroke-width:3.77953;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.61758202"><tspan
x="6.4667969"
y="226.09766"
id="tspan109269"><tspan
style="font-family:Xolonium;-inkscape-font-specification:'Xolonium Bold'"
id="tspan109267">Furnace</tspan></tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
res/intro.fur Normal file

Binary file not shown.

BIN
res/introbg_1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

BIN
res/introbg_512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

BIN
res/patexcerpt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
res/tachip.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
res/talogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -30,8 +30,9 @@ cp ../../README.md README.txt || exit 1
cp -r ../../papers papers || exit 1
cp -r ../../demos demos || exit 1
cp -r ../../instruments instruments || exit 1
cp -r ../../wavetables wavetables || exit 1
zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos instruments
zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos instruments wavetables
furName=$(git describe --tags | sed "s/v0/0/")

View File

@ -30,8 +30,9 @@ cp ../../README.md README.txt || exit 1
cp -r ../../papers papers || exit 1
cp -r ../../demos demos || exit 1
cp -r ../../instruments instruments || exit 1
cp -r ../../wavetables wavetables || exit 1
zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos instruments
zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos instruments wavetables
furName=$(git describe --tags | sed "s/v0/0/")

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -58,7 +58,7 @@ bool TAMidiInRtMidi::gather() {
if (m.type!=TA_MIDI_SYSEX && msg.size()>1) {
memcpy(m.data,msg.data()+1,MIN(msg.size()-1,7));
} else if (m.type==TA_MIDI_SYSEX) {
m.sysExData.reset(new unsigned char[msg.size()]);
m.sysExData=std::shared_ptr<unsigned char>(new unsigned char[msg.size()],std::default_delete<unsigned char[]>());
m.sysExLen=msg.size();
logD("got a SysEx of length %ld!",msg.size());
memcpy(m.sysExData.get(),msg.data(),msg.size());

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -175,7 +175,7 @@ String DivConfig::getString(String key, String fallback) const {
return fallback;
}
bool DivConfig::has(String key) {
bool DivConfig::has(String key) const {
try {
String test=conf.at(key);
} catch (std::out_of_range& e) {

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -46,7 +46,7 @@ class DivConfig {
String getString(String key, String fallback) const;
// check for existence
bool has(String key);
bool has(String key) const;
// set a config value
void set(String key, bool value);

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -161,3 +161,7 @@ void DivEngine::setConf(String key, const char* value) {
void DivEngine::setConf(String key, String value) {
conf.set(key,value);
}
bool DivEngine::hasConf(String key) {
return conf.has(key);
}

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -24,6 +24,7 @@
#define DIV_MAX_CHIPS 32
#define DIV_MAX_CHANS 128
#define DIV_MAX_PATTERNS 256
#define DIV_MAX_CHIP_DEFS 256
// in-pattern
#define DIV_MAX_ROWS 256

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -103,9 +103,8 @@ enum DivDispatchCmds {
DIV_CMD_FM_AM_DEPTH, // (depth)
DIV_CMD_FM_PM_DEPTH, // (depth)
DIV_CMD_GENESIS_LFO, // unused?
DIV_CMD_ARCADE_LFO, // unused?
DIV_CMD_FM_LFO2, // (speed)
DIV_CMD_FM_LFO2_WAVE, // (waveform)
DIV_CMD_STD_NOISE_FREQ, // (freq)
DIV_CMD_STD_NOISE_MODE, // (mode)
@ -215,6 +214,21 @@ enum DivDispatchCmds {
DIV_CMD_SURROUND_PANNING, // (out, val)
DIV_CMD_FM_AM2_DEPTH, // (depth)
DIV_CMD_FM_PM2_DEPTH, // (depth)
DIV_CMD_ES5506_FILTER_MODE, // (value)
DIV_CMD_ES5506_FILTER_K1, // (value, mask)
DIV_CMD_ES5506_FILTER_K2, // (value, mask)
DIV_CMD_ES5506_FILTER_K1_SLIDE, // (value, negative)
DIV_CMD_ES5506_FILTER_K2_SLIDE, // (value, negative)
DIV_CMD_ES5506_ENVELOPE_COUNT, // (count)
DIV_CMD_ES5506_ENVELOPE_LVRAMP, // (ramp)
DIV_CMD_ES5506_ENVELOPE_RVRAMP, // (ramp)
DIV_CMD_ES5506_ENVELOPE_K1RAMP, // (ramp, slowdown)
DIV_CMD_ES5506_ENVELOPE_K2RAMP, // (ramp, slowdown)
DIV_CMD_ES5506_PAUSE, // (value)
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol
DIV_CMD_MAX
@ -632,8 +646,8 @@ class DivDispatch {
#define COLOR_PAL (283.75*15625.0+25.0)
#define CLAMP_VAR(x,xMin,xMax) \
if (x<xMin) x=xMin; \
if (x>xMax) x=xMax;
if ((x)<(xMin)) (x)=(xMin); \
if ((x)>(xMax)) (x)=(xMax);
#define NEW_ARP_STRAT (parent->song.linearPitch==2 && !parent->song.oldArpStrategy)
#define HACKY_LEGATO_MESS chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode && !NEW_ARP_STRAT

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -67,6 +67,7 @@
#include "platform/vrc6.h"
#include "platform/fds.h"
#include "platform/mmc5.h"
#include "platform/es5506.h"
#include "platform/scc.h"
#include "platform/ymz280b.h"
#include "platform/rf5c68.h"
@ -74,6 +75,7 @@
#include "platform/vb.h"
#include "platform/k007232.h"
#include "platform/ga20.h"
#include "platform/sm8521.h"
#include "platform/pcmdac.h"
#include "platform/dummy.h"
#include "../ta-log.h"
@ -436,6 +438,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_MMC5:
dispatch=new DivPlatformMMC5;
break;
case DIV_SYSTEM_ES5506:
dispatch=new DivPlatformES5506;
break;
case DIV_SYSTEM_SCC:
dispatch=new DivPlatformSCC;
((DivPlatformSCC*)dispatch)->setChipModel(false);
@ -485,6 +490,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_GA20:
dispatch=new DivPlatformGA20;
break;
case DIV_SYSTEM_SM8521:
dispatch=new DivPlatformSM8521;
break;
case DIV_SYSTEM_PCM_DAC:
dispatch=new DivPlatformPCMDAC;
break;

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -60,7 +60,7 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
case 0x08:
return "08xy: Set panning (x: left; y: right)";
case 0x09:
return "09xx: Set speed 1";
return "09xx: Set groove pattern (speed 1 if no grooves exist)";
case 0x0a:
return "0Axy: Volume slide (0y: down; x0: up)";
case 0x0b:
@ -70,7 +70,7 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
case 0x0d:
return "0Dxx: Jump to next pattern";
case 0x0f:
return "0Fxx: Set speed 2";
return "0Fxx: Set speed (speed 2 if no grooves exist)";
case 0x80:
return "80xx: Set panning (00: left; 80: center; FF: right)";
case 0x81:
@ -110,8 +110,6 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
return "EDxx: Note delay";
case 0xee:
return "EExx: Send external command";
case 0xef:
return "EFxx: Set global tuning (quirky!)";
case 0xf0:
return "F0xx: Set tick rate (bpm)";
case 0xf1:
@ -1471,6 +1469,42 @@ void DivEngine::createNew(const char* description, String sysName, bool inBase64
BUSY_END;
}
void DivEngine::createNewFromDefaults() {
quitDispatch();
BUSY_BEGIN;
saveLock.lock();
song.unload();
song=DivSong();
changeSong(0);
String preset=getConfString("initialSys2","");
bool oldVol=getConfInt("configVersion",DIV_ENGINE_VERSION)<135;
if (preset.empty()) {
// try loading old preset
preset=decodeSysDesc(getConfString("initialSys",""));
oldVol=false;
}
logD("preset size %ld",preset.size());
if (preset.size()>0 && (preset.size()&3)==0) {
initSongWithDesc(preset.c_str(),true,oldVol);
}
String sysName=getConfString("initialSysName","");
if (sysName=="") {
song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0));
} else {
song.systemName=sysName;
}
recalcChans();
saveLock.unlock();
BUSY_END;
initDispatch();
BUSY_BEGIN;
renderSamples();
reset();
BUSY_END;
}
void DivEngine::swapChannels(int src, int dest) {
logV("swapping channel %d with %d",src,dest);
if (src==dest) {
@ -1961,14 +1995,12 @@ String DivEngine::getPlaybackDebugInfo() {
"cmdsPerSecond: %d\n"
"globalPitch: %d\n"
"extValue: %d\n"
"speed1: %d\n"
"speed2: %d\n"
"tempoAccum: %d\n"
"totalProcessed: %d\n"
"bufferPos: %d\n",
curOrder,prevOrder,curRow,prevRow,ticks,subticks,totalLoops,lastLoopPos,nextSpeed,divider,cycles,clockDrift,
changeOrd,changePos,totalSeconds,totalTicks,totalTicksR,totalCmds,lastCmds,cmdsPerSecond,globalPitch,
(int)extValue,(int)speed1,(int)speed2,(int)tempoAccum,(int)totalProcessed,(int)bufferPos
(int)extValue,(int)tempoAccum,(int)totalProcessed,(int)bufferPos
);
}
@ -2093,7 +2125,8 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
lastLoopPos=-1;
}
endOfSong=false;
speedAB=false;
// whaaaaa?
curSpeed=0;
playing=true;
skipping=true;
memset(walked,0,8192);
@ -2441,15 +2474,14 @@ void DivEngine::reset() {
}
extValue=0;
extValuePresent=0;
speed1=curSubSong->speed1;
speed2=curSubSong->speed2;
speeds=curSubSong->speeds;
firstTick=false;
shallStop=false;
shallStopSched=false;
pendingMetroTick=0;
elapsedBars=0;
elapsedBeats=0;
nextSpeed=speed1;
nextSpeed=speeds.val[0];
divider=60;
if (curSubSong->customTempo) {
divider=curSubSong->hz;
@ -2519,6 +2551,8 @@ int DivEngine::getEffectiveSampleRate(int rate) {
return (48828*MIN(128,(rate*128/48828)))/128;
case DIV_SYSTEM_X1_010:
return (31250*MIN(255,(rate*16/31250)))/16; // TODO: support variable clock case
case DIV_SYSTEM_ES5506:
return (31250*MIN(131071,(rate*2048/31250)))/2048; // TODO: support variable clock, channel limit case
default:
break;
}
@ -2649,12 +2683,8 @@ size_t DivEngine::getCurrentSubSong() {
return curSubSongIndex;
}
unsigned char DivEngine::getSpeed1() {
return speed1;
}
unsigned char DivEngine::getSpeed2() {
return speed2;
const DivGroovePattern& DivEngine::getSpeeds() {
return speeds;
}
float DivEngine::getHz() {
@ -3552,14 +3582,14 @@ void DivEngine::delSample(int index) {
BUSY_END;
}
void DivEngine::addOrder(bool duplicate, bool where) {
void DivEngine::addOrder(int pos, bool duplicate, bool where) {
unsigned char order[DIV_MAX_CHANS];
if (curSubSong->ordersLen>=(DIV_MAX_PATTERNS-1)) return;
memset(order,0,DIV_MAX_CHANS);
BUSY_BEGIN_SOFT;
if (duplicate) {
for (int i=0; i<DIV_MAX_CHANS; i++) {
order[i]=curOrders->ord[i][curOrder];
order[i]=curOrders->ord[i][pos];
}
} else {
bool used[DIV_MAX_PATTERNS];
@ -3587,14 +3617,14 @@ void DivEngine::addOrder(bool duplicate, bool where) {
} else { // after current order
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=curSubSong->ordersLen; j>curOrder; j--) {
for (int j=curSubSong->ordersLen; j>pos; j--) {
curOrders->ord[i][j]=curOrders->ord[i][j-1];
}
curOrders->ord[i][curOrder+1]=order[i];
curOrders->ord[i][pos+1]=order[i];
}
curSubSong->ordersLen++;
saveLock.unlock();
curOrder++;
if (pos<=curOrder) curOrder++;
if (playing && !freelance) {
playSub(false);
}
@ -3602,7 +3632,7 @@ void DivEngine::addOrder(bool duplicate, bool where) {
BUSY_END;
}
void DivEngine::deepCloneOrder(bool where) {
void DivEngine::deepCloneOrder(int pos, bool where) {
unsigned char order[DIV_MAX_CHANS];
if (curSubSong->ordersLen>=(DIV_MAX_PATTERNS-1)) return;
warnings="";
@ -3610,7 +3640,7 @@ void DivEngine::deepCloneOrder(bool where) {
for (int i=0; i<chans; i++) {
bool didNotFind=true;
logD("channel %d",i);
order[i]=curOrders->ord[i][curOrder];
order[i]=curOrders->ord[i][pos];
// find free slot
for (int j=0; j<DIV_MAX_PATTERNS; j++) {
logD("finding free slot in %d...",j);
@ -3639,14 +3669,14 @@ void DivEngine::deepCloneOrder(bool where) {
} else { // after current order
saveLock.lock();
for (int i=0; i<chans; i++) {
for (int j=curSubSong->ordersLen; j>curOrder; j--) {
for (int j=curSubSong->ordersLen; j>pos; j--) {
curOrders->ord[i][j]=curOrders->ord[i][j-1];
}
curOrders->ord[i][curOrder+1]=order[i];
curOrders->ord[i][pos+1]=order[i];
}
curSubSong->ordersLen++;
saveLock.unlock();
curOrder++;
if (pos<=curOrder) curOrder++;
if (playing && !freelance) {
playSub(false);
}
@ -3654,17 +3684,18 @@ void DivEngine::deepCloneOrder(bool where) {
BUSY_END;
}
void DivEngine::deleteOrder() {
void DivEngine::deleteOrder(int pos) {
if (curSubSong->ordersLen<=1) return;
BUSY_BEGIN_SOFT;
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=curOrder; j<curSubSong->ordersLen; j++) {
for (int j=pos; j<curSubSong->ordersLen; j++) {
curOrders->ord[i][j]=curOrders->ord[i][j+1];
}
}
curSubSong->ordersLen--;
saveLock.unlock();
if (curOrder>pos) curOrder--;
if (curOrder>=curSubSong->ordersLen) curOrder=curSubSong->ordersLen-1;
if (playing && !freelance) {
playSub(false);
@ -3672,40 +3703,46 @@ void DivEngine::deleteOrder() {
BUSY_END;
}
void DivEngine::moveOrderUp() {
void DivEngine::moveOrderUp(int& pos) {
BUSY_BEGIN_SOFT;
if (curOrder<1) {
if (pos<1) {
BUSY_END;
return;
}
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
curOrders->ord[i][curOrder-1]^=curOrders->ord[i][curOrder];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
curOrders->ord[i][pos]^=curOrders->ord[i][pos-1];
curOrders->ord[i][pos-1]^=curOrders->ord[i][pos];
curOrders->ord[i][pos]^=curOrders->ord[i][pos-1];
}
saveLock.unlock();
curOrder--;
if (curOrder==pos) {
curOrder--;
}
pos--;
if (playing && !freelance) {
playSub(false);
}
BUSY_END;
}
void DivEngine::moveOrderDown() {
void DivEngine::moveOrderDown(int& pos) {
BUSY_BEGIN_SOFT;
if (curOrder>=curSubSong->ordersLen-1) {
if (pos>=curSubSong->ordersLen-1) {
BUSY_END;
return;
}
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
curOrders->ord[i][curOrder+1]^=curOrders->ord[i][curOrder];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
curOrders->ord[i][pos]^=curOrders->ord[i][pos+1];
curOrders->ord[i][pos+1]^=curOrders->ord[i][pos];
curOrders->ord[i][pos]^=curOrders->ord[i][pos+1];
}
saveLock.unlock();
curOrder++;
if (curOrder==pos) {
curOrder++;
}
pos++;
if (playing && !freelance) {
playSub(false);
}
@ -4236,7 +4273,7 @@ void DivEngine::quitDispatch() {
clockDrift=0;
chans=0;
playing=false;
speedAB=false;
curSpeed=0;
endOfSong=false;
ticks=0;
tempoAccum=0;
@ -4415,6 +4452,8 @@ void DivEngine::preInit() {
String logPath=configPath+DIR_SEPARATOR_STR+"furnace.log";
startLogFile(logPath.c_str());
logI("Furnace version " DIV_VERSION ".");
loadConf();
}
@ -4472,6 +4511,9 @@ bool DivEngine::init() {
for (int i=0; i<64; i++) {
vibTable[i]=127*sin(((double)i/64.0)*(2*M_PI));
}
for (int i=0; i<128; i++) {
tremTable[i]=255*0.5*(1.0-cos(((double)i/128.0)*(2*M_PI)));
}
for (int i=0; i<4096; i++) {
reversePitchTable[i]=round(1024.0*pow(2.0,(2048.0-(double)i)/(12.0*128.0)));
pitchTable[i]=round(1024.0*pow(2.0,((double)i-2048.0)/(12.0*128.0)));

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -47,8 +47,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev136"
#define DIV_ENGINE_VERSION 136
#define DIV_VERSION "dev142"
#define DIV_ENGINE_VERSION 142
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -100,6 +100,7 @@ struct DivChannelState {
unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR;
bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff;
bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp;
bool wentThroughNote, goneThroughNote;
int midiNote, curMidiNote, midiPitch;
size_t midiAge;
@ -152,6 +153,8 @@ struct DivChannelState {
wasShorthandPorta(false),
noteOnInhibit(false),
resetArp(false),
wentThroughNote(false),
goneThroughNote(false),
midiNote(-1),
curMidiNote(-1),
midiPitch(-1),
@ -337,7 +340,6 @@ class DivEngine {
bool playing;
bool freelance;
bool shallStop, shallStopSched;
bool speedAB;
bool endOfSong;
bool consoleMode;
bool extValuePresent;
@ -359,7 +361,7 @@ class DivEngine {
bool midiOutClock;
int midiOutMode;
int softLockCount;
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats;
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats, curSpeed;
size_t curSubSongIndex;
size_t bufferPos;
double divider;
@ -368,7 +370,7 @@ class DivEngine {
int stepPlay;
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
unsigned char extValue, pendingMetroTick;
unsigned char speed1, speed2;
DivGroovePattern speeds;
short tempoAccum;
DivStatusView view;
DivHaltPositions haltOn;
@ -391,9 +393,9 @@ class DivEngine {
std::vector<String> midiOuts;
std::vector<DivCommand> cmdStream;
std::vector<DivInstrumentType> possibleInsTypes;
static DivSysDef* sysDefs[256];
static DivSystem sysFileMapFur[256];
static DivSystem sysFileMapDMF[256];
static DivSysDef* sysDefs[DIV_MAX_CHIP_DEFS];
static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS];
static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS];
struct SamplePreview {
double rate;
@ -413,6 +415,7 @@ class DivEngine {
} sPreview;
short vibTable[64];
short tremTable[128];
int reversePitchTable[4096];
int pitchTable[4096];
char c163NameCS[1024];
@ -513,6 +516,7 @@ class DivEngine {
String decodeSysDesc(String desc);
// start fresh
void createNew(const char* description, String sysName, bool inBase64=true);
void createNewFromDefaults();
// load a file.
bool load(unsigned char* f, size_t length);
// save as .dmf.
@ -524,7 +528,12 @@ class DivEngine {
// specify system to build ROM for.
SafeWriter* buildROM(int sys);
// dump to VGM.
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false, bool directStream=false);
// set trailingTicks to:
// - 0 to add one tick of trailing
// - x to add x+1 ticks of trailing
// - -1 to auto-determine trailing
// - -2 to add a whole loop of trailing
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false, bool directStream=false, int trailingTicks=-1);
// dump to ZSM.
SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true);
// dump command stream.
@ -581,6 +590,9 @@ class DivEngine {
void setConf(String key, const char* value);
void setConf(String key, String value);
// get whether config value exists
bool hasConf(String key);
// calculate base frequency/period
double calcBaseFreq(double clock, double divider, int note, bool period);
@ -729,11 +741,8 @@ class DivEngine {
// get current subsong
size_t getCurrentSubSong();
// get speed 1
unsigned char getSpeed1();
// get speed 2
unsigned char getSpeed2();
// get speeds
const DivGroovePattern& getSpeeds();
// get Hz
float getHz();
@ -813,19 +822,19 @@ class DivEngine {
void delSample(int index);
// add order
void addOrder(bool duplicate, bool where);
void addOrder(int pos, bool duplicate, bool where);
// deep clone orders
void deepCloneOrder(bool where);
void deepCloneOrder(int pos, bool where);
// delete order
void deleteOrder();
void deleteOrder(int pos);
// move order up
void moveOrderUp();
void moveOrderUp(int& pos);
// move order down
void moveOrderDown();
void moveOrderDown(int& pos);
// move thing up
bool moveInsUp(int which);
@ -1064,7 +1073,6 @@ class DivEngine {
freelance(false),
shallStop(false),
shallStopSched(false),
speedAB(false),
endOfSong(false),
consoleMode(false),
extValuePresent(false),
@ -1098,6 +1106,7 @@ class DivEngine {
nextSpeed(3),
elapsedBars(0),
elapsedBeats(0),
curSpeed(0),
curSubSongIndex(0),
bufferPos(0),
divider(60),
@ -1115,8 +1124,6 @@ class DivEngine {
globalPitch(0),
extValue(0),
pendingMetroTick(0),
speed1(3),
speed2(3),
tempoAccum(0),
view(DIV_STATUS_NOTHING),
haltOn(DIV_HALT_NONE),
@ -1158,13 +1165,14 @@ class DivEngine {
memset(dispatchOfChan,0,DIV_MAX_CHANS*sizeof(int));
memset(sysOfChan,0,DIV_MAX_CHANS*sizeof(int));
memset(vibTable,0,64*sizeof(short));
memset(tremTable,0,128*sizeof(short));
memset(reversePitchTable,0,4096*sizeof(int));
memset(pitchTable,0,4096*sizeof(int));
memset(sysDefs,0,256*sizeof(void*));
memset(sysDefs,0,DIV_MAX_CHIP_DEFS*sizeof(void*));
memset(walked,0,8192);
memset(oscBuf,0,DIV_MAX_OUTPUTS*(sizeof(float*)));
for (int i=0; i<256; i++) {
for (int i=0; i<DIV_MAX_CHIP_DEFS; i++) {
sysFileMapFur[i]=DIV_SYSTEM_NULL;
sysFileMapDMF[i]=DIV_SYSTEM_NULL;
}

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -83,7 +83,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
}
ds.version=(unsigned char)reader.readC();
logI("module version %d (0x%.2x)",ds.version,ds.version);
if (ds.version>0x1a) {
if (ds.version>0x1b) {
logE("this version is not supported by Furnace yet!");
lastError="this version is not supported by Furnace yet";
delete[] file;
@ -219,14 +219,15 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
}
ds.subsong[0]->timeBase=reader.readC();
ds.subsong[0]->speed1=reader.readC();
ds.subsong[0]->speeds.len=2;
ds.subsong[0]->speeds.val[0]=reader.readC();
if (ds.version>0x07) {
ds.subsong[0]->speed2=reader.readC();
ds.subsong[0]->speeds.val[1]=reader.readC();
ds.subsong[0]->pal=reader.readC();
ds.subsong[0]->hz=(ds.subsong[0]->pal)?60:50;
ds.subsong[0]->customTempo=reader.readC();
} else {
ds.subsong[0]->speed2=ds.subsong[0]->speed1;
ds.subsong[0]->speeds.len=1;
}
if (ds.version>0x0a) {
String hz=reader.readString(3);
@ -827,6 +828,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
for (int i=0; i<ds.sampleLen; i++) {
DivSample* sample=new DivSample;
int length=reader.readI();
int cutStart=0;
int cutEnd=length;
int pitch=5;
int vol=50;
short* data;
@ -866,6 +869,29 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
sample->depth=DIV_SAMPLE_DEPTH_YMZ_ADPCM;
}
}
if (ds.version>=0x1b) {
// what the hell man...
cutStart=reader.readI();
cutEnd=reader.readI();
if (cutStart<0 || cutStart>length) {
logE("cutStart is out of range! (%d)",cutStart);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (cutEnd<0 || cutEnd>length) {
logE("cutEnd is out of range! (%d)",cutEnd);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (cutEnd<cutStart) {
logE("cutEnd %d is before cutStart %d. what's going on?",cutEnd,cutStart);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
}
if (length>0) {
if (ds.version>0x08) {
if (ds.version<0x0b) {
@ -876,6 +902,19 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
data=new short[length];
reader.read(data,length*2);
}
if (ds.version>0x1b) {
if (cutStart!=0 || cutEnd!=length) {
// cut data
short* newData=new short[cutEnd-cutStart];
memcpy(newData,&data[cutStart],(cutEnd-cutStart)*sizeof(short));
delete[] data;
data=newData;
length=cutEnd-cutStart;
cutStart=0;
cutEnd=length;
}
}
#ifdef TA_BIG_ENDIAN
// convert to big-endian
@ -1716,6 +1755,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<130) {
ds.oldArpStrategy=true;
}
if (ds.version<138) {
ds.brokenPortaLegato=true;
}
ds.isDMF=false;
reader.readS(); // reserved
@ -1739,8 +1781,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
reader.readI();
subSong->timeBase=reader.readC();
subSong->speed1=reader.readC();
subSong->speed2=reader.readC();
subSong->speeds.len=2;
subSong->speeds.val[0]=reader.readC();
subSong->speeds.val[1]=reader.readC();
subSong->arpLen=reader.readC();
subSong->hz=reader.readF();
subSong->pal=(subSong->hz>=53);
@ -2221,6 +2264,32 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version>=136) song.patchbayAuto=reader.readC();
if (ds.version>=138) {
ds.brokenPortaLegato=reader.readC();
for (int i=0; i<7; i++) {
reader.readC();
}
}
if (ds.version>=139) {
subSong->speeds.len=reader.readC();
for (int i=0; i<16; i++) {
subSong->speeds.val[i]=reader.readC();
}
// grooves
unsigned char grooveCount=reader.readC();
for (int i=0; i<grooveCount; i++) {
DivGroovePattern gp;
gp.len=reader.readC();
for (int j=0; j<16; j++) {
gp.val[j]=reader.readC();
}
ds.grooves.push_back(gp);
}
}
// read system flags
if (ds.version>=119) {
logD("reading chip flags...");
@ -2279,8 +2348,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
subSong=ds.subsong[i+1];
subSong->timeBase=reader.readC();
subSong->speed1=reader.readC();
subSong->speed2=reader.readC();
subSong->speeds.len=2;
subSong->speeds.val[0]=reader.readC();
subSong->speeds.val[1]=reader.readC();
subSong->arpLen=reader.readC();
subSong->hz=reader.readF();
subSong->pal=(subSong->hz>=53);
@ -2328,6 +2398,13 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
for (int i=0; i<tchans; i++) {
subSong->chanShortName[i]=reader.readString();
}
if (ds.version>=139) {
subSong->speeds.len=reader.readC();
for (int i=0; i<16; i++) {
subSong->speeds.val[i]=reader.readC();
}
}
}
}
@ -2574,6 +2651,32 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
}
}
// new YM2612/SN/X1-010 volumes
if (ds.version<137) {
for (int i=0; i<ds.systemLen; i++) {
switch (ds.system[i]) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
case DIV_SYSTEM_YM2612_CSM:
ds.systemVol[i]/=2.0;
break;
case DIV_SYSTEM_SMS:
case DIV_SYSTEM_T6W28:
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
ds.systemVol[i]/=1.5;
break;
case DIV_SYSTEM_X1_010:
ds.systemVol[i]/=4.0;
break;
default:
break;
}
}
}
if (active) quitDispatch();
BUSY_BEGIN_SOFT;
saveLock.lock();
@ -2920,7 +3023,6 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
if (fxVal>0x20 && ds.name!="klisje paa klisje") {
writeFxCol(0xf0,fxVal);
} else {
writeFxCol(0x09,fxVal);
writeFxCol(0x0f,fxVal);
}
break;
@ -3399,8 +3501,8 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
ds.subsong[0]->pal=true;
ds.subsong[0]->customTempo=true;
ds.subsong[0]->pat[3].effectCols=3;
ds.subsong[0]->speed1=3;
ds.subsong[0]->speed2=3;
ds.subsong[0]->speeds.val[0]=3;
ds.subsong[0]->speeds.len=1;
int lastIns[4];
int lastNote[4];
@ -3417,10 +3519,8 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
ds.subsong[0]->orders.ord[j][i]=i;
DivPattern* p=ds.subsong[0]->pat[j].getPattern(i,true);
if (j==3 && seq[i].speed) {
p->data[0][6]=0x09;
p->data[0][6]=0x0f;
p->data[0][7]=seq[i].speed;
p->data[0][8]=0x0f;
p->data[0][9]=seq[i].speed;
}
bool ignoreNext=false;
@ -4307,8 +4407,9 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeI(0);
w->writeC(subSong->timeBase);
w->writeC(subSong->speed1);
w->writeC(subSong->speed2);
// these are for compatibility
w->writeC(subSong->speeds.val[0]);
w->writeC((subSong->speeds.len>=2)?subSong->speeds.val[1]:subSong->speeds.val[0]);
w->writeC(subSong->arpLen);
w->writeF(subSong->hz);
w->writeS(subSong->patLen);
@ -4489,6 +4590,27 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
}
w->writeC(song.patchbayAuto);
// even more compat flags
w->writeC(song.brokenPortaLegato);
for (int i=0; i<7; i++) {
w->writeC(0);
}
// speeds of first song
w->writeC(subSong->speeds.len);
for (int i=0; i<16; i++) {
w->writeC(subSong->speeds.val[i]);
}
// groove list
w->writeC((unsigned char)song.grooves.size());
for (const DivGroovePattern& i: song.grooves) {
w->writeC(i.len);
for (int j=0; j<16; j++) {
w->writeC(i.val[j]);
}
}
blockEndSeek=w->tell();
w->seek(blockStartSeek,SEEK_SET);
w->writeI(blockEndSeek-blockStartSeek-4);
@ -4503,8 +4625,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeI(0);
w->writeC(subSong->timeBase);
w->writeC(subSong->speed1);
w->writeC(subSong->speed2);
w->writeC(subSong->speeds.val[0]);
w->writeC((subSong->speeds.len>=2)?subSong->speeds.val[1]:subSong->speeds.val[0]);
w->writeC(subSong->arpLen);
w->writeF(subSong->hz);
w->writeS(subSong->patLen);
@ -4543,6 +4665,12 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeString(subSong->chanShortName[i],false);
}
// speeds
w->writeC(subSong->speeds.len);
for (int i=0; i<16; i++) {
w->writeC(subSong->speeds.val[i]);
}
blockEndSeek=w->tell();
w->seek(blockStartSeek,SEEK_SET);
w->writeI(blockEndSeek-blockStartSeek-4);
@ -4798,8 +4926,8 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
w->writeC(curSubSong->hilightB);
w->writeC(curSubSong->timeBase);
w->writeC(curSubSong->speed1);
w->writeC(curSubSong->speed2);
w->writeC(curSubSong->speeds.val[0]);
w->writeC((curSubSong->speeds.len>=2)?curSubSong->speeds.val[1]:curSubSong->speeds.val[0]);
w->writeC(curSubSong->pal);
w->writeC(curSubSong->customTempo);
char customHz[4];
@ -4823,6 +4951,14 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
addWarning("only the currently selected subsong will be saved");
}
if (!song.grooves.empty()) {
addWarning("grooves will not be saved");
}
if (curSubSong->speeds.len>2) {
addWarning("only the first two speeds will be effective");
}
if (curSubSong->virtualTempoD!=curSubSong->virtualTempoN) {
addWarning(".dmf format does not support virtual tempo");
}

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -924,6 +924,10 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) {
break;
case DIV_INS_POKEMINI:
break;
case DIV_INS_SM8521:
checkForWL=true;
if (ws.enabled) featureWS=true;
break;
case DIV_INS_MAX:
break;

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -78,6 +78,7 @@ enum DivInstrumentType: unsigned short {
DIV_INS_K007232=45,
DIV_INS_GA20=46,
DIV_INS_POKEMINI=47,
DIV_INS_SM8521=48,
DIV_INS_MAX,
DIV_INS_NULL
};

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -56,7 +56,7 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic
}
if (delay>0) {
delay--;
had=false;
if (!linger) had=false;
return;
}
if (began && source.delay>0) {
@ -523,4 +523,4 @@ DivMacroStruct* DivMacroInt::structByName(const String& name) {
return NULL;
}
#undef CONSIDER
#undef CONSIDER

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,6 +18,7 @@
*/
#include "../dispatch.h"
#include "../../ta-log.h"
void DivDispatch::acquire(short** buf, size_t len) {
}
@ -121,7 +122,8 @@ void DivDispatch::notifyWaveChange(int ins) {
}
void DivDispatch::notifyInsDeletion(void* ins) {
logE("notifyInsDeletion NOT implemented!");
abort();
}
void DivDispatch::notifyPlaybackStop() {

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -89,7 +89,7 @@ void DivPlatformAmiga::acquire(short** buf, size_t len) {
chan[i].audSub-=AMIGA_DIVIDER;
if (chan[i].audSub<0) {
if (chan[i].useWave) {
writeAudDat(chan[i].ws.output[chan[i].audPos++]^0x80);
writeAudDat(chan[i].ws.output[(chan[i].audPos++)&255]^0x80);
if (chan[i].audPos>=(unsigned int)(chan[i].audLen<<1)) {
chan[i].audPos=0;
}

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -152,7 +152,9 @@ void DivPlatformArcade::tick(bool sysTick) {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (KVS(i,j)) {
if (!op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[i].outVol&0x7f,127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
@ -235,7 +237,7 @@ void DivPlatformArcade::tick(bool sysTick) {
if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (isMuted[i]) {
if (isMuted[i] || !op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (KVS(i,j)) {
@ -296,7 +298,9 @@ void DivPlatformArcade::tick(bool sysTick) {
}
if (m.tl.had) {
op.tl=127-m.tl.val;
if (KVS(i,j)) {
if (!op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[i].outVol&0x7f,127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
@ -319,27 +323,6 @@ void DivPlatformArcade::tick(bool sysTick) {
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
}
}
if (chan[i].keyOn || chan[i].keyOff) {
if (chan[i].hardReset && chan[i].keyOn) {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
immWrite(baseAddr+ADDR_SL_RR,0x0f);
immWrite(baseAddr+ADDR_TL,0x7f);
oldWrites[baseAddr+ADDR_SL_RR]=-1;
oldWrites[baseAddr+ADDR_TL]=-1;
}
}
immWrite(0x08,i);
if (chan[i].hardReset && chan[i].keyOn) {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
for (int k=0; k<9; k++) {
immWrite(baseAddr+ADDR_SL_RR,0x0f);
}
}
}
chan[i].keyOff=false;
}
}
for (int i=0; i<256; i++) {
@ -349,6 +332,24 @@ void DivPlatformArcade::tick(bool sysTick) {
}
}
int hardResetElapsed=0;
bool mustHardReset=false;
for (int i=0; i<8; i++) {
if (chan[i].keyOn || chan[i].keyOff) {
immWrite(0x08,i);
if (chan[i].hardReset && chan[i].keyOn) {
mustHardReset=true;
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
immWrite(baseAddr+ADDR_SL_RR,0x0f);
hardResetElapsed++;
}
}
chan[i].keyOff=false;
}
}
for (int i=0; i<8; i++) {
if (chan[i].freqChanged) {
chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64+chan[i].pitch2;
@ -363,14 +364,37 @@ void DivPlatformArcade::tick(bool sysTick) {
if (chan[i].freq>=(95<<6)) chan[i].freq=(95<<6)-1;
immWrite(i+0x28,hScale(chan[i].freq>>6));
immWrite(i+0x30,chan[i].freq<<2);
hardResetElapsed+=2;
chan[i].freqChanged=false;
}
if (chan[i].keyOn || chan[i].opMaskChanged) {
if ((chan[i].keyOn || chan[i].opMaskChanged) && !chan[i].hardReset) {
immWrite(0x08,(chan[i].opMask<<3)|i);
hardResetElapsed++;
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
// hard reset handling
if (mustHardReset) {
for (unsigned int i=hardResetElapsed; i<hardResetCycles; i++) {
immWrite(0x1f,i&0xff);
}
for (int i=0; i<8; i++) {
if ((chan[i].keyOn || chan[i].opMaskChanged) && chan[i].hardReset) {
// restore SL/RR
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
immWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
immWrite(0x08,(chan[i].opMask<<3)|i);
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
}
}
void DivPlatformArcade::muteChannel(int ch, bool mute) {
@ -382,53 +406,59 @@ void DivPlatformArcade::muteChannel(int ch, bool mute) {
}
}
void DivPlatformArcade::commitState(int ch, DivInstrument* ins) {
if (chan[ch].insChanged) {
chan[ch].state=ins->fm;
chan[ch].opMask=
(chan[ch].state.op[0].enable?1:0)|
(chan[ch].state.op[2].enable?2:0)|
(chan[ch].state.op[1].enable?4:0)|
(chan[ch].state.op[3].enable?8:0);
}
for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[ch]|opOffs[i];
DivInstrumentFM::Operator op=chan[ch].state.op[i];
if (!op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else if (KVS(ch,i)) {
if (!chan[ch].active || chan[ch].insChanged) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[ch].outVol&0x7f,127));
}
} else {
if (chan[ch].insChanged) {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
if (chan[ch].insChanged) {
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
}
if (chan[ch].insChanged) {
if (isMuted[ch]) {
rWrite(chanOffs[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3));
} else {
rWrite(chanOffs[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3)|((chan[ch].chVolL&1)<<6)|((chan[ch].chVolR&1)<<7));
}
rWrite(chanOffs[ch]+ADDR_FMS_AMS,((chan[ch].state.fms&7)<<4)|(chan[ch].state.ams&3));
}
}
int DivPlatformArcade::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
if (chan[c.chan].insChanged) {
chan[c.chan].state=ins->fm;
chan[c.chan].opMask=
(chan[c.chan].state.op[0].enable?1:0)|
(chan[c.chan].state.op[2].enable?2:0)|
(chan[c.chan].state.op[1].enable?4:0)|
(chan[c.chan].state.op[3].enable?8:0);
}
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
DivInstrumentFM::Operator op=chan[c.chan].state.op[i];
if (KVS(c.chan,i)) {
if (!chan[c.chan].active || chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[c.chan].outVol&0x7f,127));
}
} else {
if (chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
if (chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
}
if (chan[c.chan].insChanged) {
if (isMuted[c.chan]) {
rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3));
} else {
rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)|((chan[c.chan].chVolL&1)<<6)|((chan[c.chan].chVolR&1)<<7));
}
rWrite(chanOffs[c.chan]+ADDR_FMS_AMS,((chan[c.chan].state.fms&7)<<4)|(chan[c.chan].state.ams&3));
}
commitState(c.chan,ins);
chan[c.chan].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
@ -462,7 +492,9 @@ int DivPlatformArcade::dispatch(DivCommand c) {
for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
if (KVS(c.chan,i)) {
if (!op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else if (KVS(c.chan,i)) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[c.chan].outVol&0x7f,127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
@ -521,6 +553,11 @@ int DivPlatformArcade::dispatch(DivCommand c) {
break;
}
case DIV_CMD_LEGATO: {
if (chan[c.chan].insChanged) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_OPM);
commitState(c.chan,ins);
chan[c.chan].insChanged=false;
}
chan[c.chan].baseFreq=NOTE_LINEAR(c.value);
chan[c.chan].freqChanged=true;
break;
@ -559,7 +596,9 @@ int DivPlatformArcade::dispatch(DivCommand c) {
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;
if (KVS(c.chan,c.value)) {
if (!op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else if (KVS(c.chan,c.value)) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[c.chan].outVol&0x7f,127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
@ -766,7 +805,9 @@ void DivPlatformArcade::forceIns() {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator op=chan[i].state.op[j];
if (KVS(i,j)) {
if (!op.enable) {
rWrite(baseAddr+ADDR_TL,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[i].outVol&0x7f,127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
@ -800,6 +841,12 @@ void DivPlatformArcade::notifyInsChange(int ins) {
}
}
void DivPlatformArcade::notifyInsDeletion(void* ins) {
for (int i=0; i<8; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void* DivPlatformArcade::getChanState(int ch) {
return &chan[ch];
}

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -57,6 +57,7 @@ class DivPlatformArcade: public DivPlatformOPM {
int octave(int freq);
int toFreq(int freq);
void commitState(int ch, DivInstrument* ins);
void acquire_nuked(short** buf, size_t len);
void acquire_ymfm(short** buf, size_t len);
@ -76,6 +77,7 @@ class DivPlatformArcade: public DivPlatformOPM {
void muteChannel(int ch, bool mute);
DivMacroInt* getChanMacroInt(int ch);
void notifyInsChange(int ins);
void notifyInsDeletion(void* ins);
void setFlags(const DivConfig& flags);
int getOutputCount();
void setYMFM(bool use);

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

Some files were not shown because too many files have changed in this diff Show More