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

This commit is contained in:
cam900 2022-10-22 10:17:00 +09:00
commit 487607b6ae
110 changed files with 1707 additions and 854 deletions

View File

@ -594,6 +594,7 @@ src/gui/piano.cpp
src/gui/presets.cpp
src/gui/regView.cpp
src/gui/sampleEdit.cpp
src/gui/scaling.cpp
src/gui/settings.cpp
src/gui/songInfo.cpp
src/gui/songNotes.cpp

View File

@ -79,7 +79,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
- bug/quirk implementation for increased playback accuracy through compatibility flags
- VGM export
- modular layout that you may adapt to your needs
- audio file export - entire song, per system or per channel
- audio file export - entire song, per chip or per channel
- quality emulation cores (Nuked, MAME, SameBoy, Mednafen PCE, NSFplay, puNES, reSID, Stella, SAASound, vgsound_emu and ymfm)
- wavetable synthesizer
- available on wavetable chips
@ -237,12 +237,23 @@ yup, it's real.
> where's the manual?
see [papers/](papers/doc/README.md). it's kind of incomplete, but at least the systems (sound chips) section is there.
see [papers/](papers/doc/README.md). it's kind of incomplete, but at least the sound chips section is there.
> it doesn't open under macOS!
this is due to Apple's application signing policy. a workaround is to right click on the Furnace app icon and select Open.
**as of Monterey, this workaround no longer works (especially on ARM).** yeah, Apple has decided to be strict on the matter.
if you happen to be on that version, use this workaround instead (on a Terminal):
```
xattr -d com.apple.quarantine /path/to/Furnace.app
```
(replace /path/to/ with the path where Furnace.app is located)
you may need to log out and/or reboot after doing this.
> how do I use C64 absolute filter/duty?
on Instrument Editor in the C64 tab there are two options to toggle these.
@ -252,7 +263,7 @@ also provided are two effects:
- `4xxx`: set fine cutoff. `xxx` range is 000-7ff.
additionally, you can change the cutoff and/or duty as a macro inside an instrument by clicking the `absolute cutoff macro` and/or `absolute duty macro` checkbox at the bottom of the instrument. (for the filter, you also need to click the checkbox that says `volume macro is cutoff macro`.)
> how do I use PCM on a PCM-capable system?
> how do I use PCM on a PCM-capable chip?
two possibilities:
- the recommended way is by creating the "Sample" type instrument and assigning a sample to it.

View File

@ -2,8 +2,6 @@
- POKEY
- Pokémon Mini
- Virtual Boy
- T6W28
- (maybe) YM2612 CSM (no DualPCM)
- port presets to new format
- bug fixes

Binary file not shown.

View File

@ -1159,6 +1159,10 @@ public:
s.AddrPC.Offset = ctx_->Rip;
s.AddrStack.Offset = ctx_->Rsp;
s.AddrFrame.Offset = ctx_->Rbp;
#elif _M_ARM64
s.AddrPC.Offset = ctx_->Pc;
s.AddrStack.Offset = ctx_->Sp;
s.AddrFrame.Offset = ctx_->Fp;
#else
s.AddrPC.Offset = ctx_->Eip;
s.AddrStack.Offset = ctx_->Esp;
@ -1168,6 +1172,8 @@ public:
if (!machine_type_) {
#ifdef _M_X64
machine_type_ = IMAGE_FILE_MACHINE_AMD64;
#elif _M_ARM64
machine_type_ = IMAGE_FILE_MACHINE_ARM64;
#else
machine_type_ = IMAGE_FILE_MACHINE_I386;
#endif

View File

@ -3993,7 +3993,13 @@ namespace IGFD
} else {
fdi.SelectFileName(prFileDialogInternal, vInfos);
if (prFileDialogInternal.puDLGselFun!=NULL) {
prFileDialogInternal.puDLGselFun(GetFilePathName().c_str());
std::string argPath;
for (auto& i: GetSelection()) {
argPath=i.second;
}
if (!argPath.empty()) {
prFileDialogInternal.puDLGselFun(argPath.c_str());
}
}
}
}

View File

@ -276,9 +276,6 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
case SDL_MOUSEMOTION:
{
ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y);
#ifdef __APPLE__
#endif
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
int window_x, window_y;
@ -286,16 +283,14 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
mouse_pos.x += window_x;
mouse_pos.y += window_y;
}
#ifdef __APPLE__
// Fix for high DPI mac
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
if (!platform_io.Monitors.empty() && platform_io.Monitors[0].DpiScale > 1.0f)
{
// The Framebuffer is scaled by an integer ceiling of the actual ratio, so 2.0 not 1.685 on Mac!
mouse_pos.x *= std::ceil(platform_io.Monitors[0].DpiScale);
mouse_pos.y *= std::ceil(platform_io.Monitors[0].DpiScale);
}
#endif
// Fix for high DPI mac/idevice/wayland
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
if (!platform_io.Monitors.empty() && platform_io.Monitors[0].DpiScale > 1.0f)
{
// The Framebuffer is scaled by an integer ceiling of the actual ratio, so 2.0 not 1.685 on Mac!
mouse_pos.x *= std::ceil(platform_io.Monitors[0].DpiScale);
mouse_pos.y *= std::ceil(platform_io.Monitors[0].DpiScale);
}
io.AddMousePosEvent(mouse_pos.x, mouse_pos.y);
return true;
}
@ -551,16 +546,14 @@ static void ImGui_ImplSDL2_UpdateMouseData()
mouse_x -= window_x;
mouse_y -= window_y;
}
#ifdef __APPLE__
// Fix for high DPI mac
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
if (!platform_io.Monitors.empty() && platform_io.Monitors[0].DpiScale > 1.0f)
{
// The Framebuffer is scaled by an integer ceiling of the actual ratio, so 2.0 not 1.685 on Mac!
mouse_x *= std::ceil(platform_io.Monitors[0].DpiScale);
mouse_y *= std::ceil(platform_io.Monitors[0].DpiScale);
}
#endif
// Fix for high DPI mac/idevice/wayland
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
if (!platform_io.Monitors.empty() && platform_io.Monitors[0].DpiScale > 1.0f)
{
// The Framebuffer is scaled by an integer ceiling of the actual ratio, so 2.0 not 1.685 on Mac!
mouse_x *= std::ceil(platform_io.Monitors[0].DpiScale);
mouse_y *= std::ceil(platform_io.Monitors[0].DpiScale);
}
io.AddMousePosEvent((float)mouse_x, (float)mouse_y);
}
}
@ -669,13 +662,7 @@ static void ImGui_ImplSDL2_UpdateMonitors()
monitor.WorkSize = ImVec2((float)r.w, (float)r.h);
#endif
#if SDL_HAS_PER_MONITOR_DPI
#ifdef __APPLE__
monitor.DpiScale=getMacDPIScale();
#else
float dpi = 0.0f;
if (!SDL_GetDisplayDPI(n, &dpi, NULL, NULL))
monitor.DpiScale = dpi / 96.0f;
#endif
monitor.DpiScale = 1.0f;
#endif
platform_io.Monitors.push_back(monitor);
}
@ -701,15 +688,14 @@ void ImGui_ImplSDL2_NewFrame()
if (w > 0 && h > 0)
io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h);
#if defined(__APPLE__)
// On Apple, The window size is reported in Low DPI, even when running in high DPI mode
// On Apple and Wayland, The window size is reported in Low DPI, even when running in high DPI mode
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
if (!platform_io.Monitors.empty() && platform_io.Monitors[0].DpiScale > 1.0f && display_h != h)
if (!platform_io.Monitors.empty() /*&& platform_io.Monitors[0].DpiScale > 1.0f*/ && display_h != h)
{
io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
io.DisplaySize = ImVec2((float)display_w, (float)display_h);
platform_io.Monitors[0].DpiScale=(float)display_w/(float)w;
}
#endif
// Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
static Uint64 frequency = SDL_GetPerformanceFrequency();

View File

@ -2,6 +2,17 @@
## Important changes
### V 2.1.2 (2022-10-21)
Split utilities and core framework header
Use static constexprs for global constants
Fix initialization
Fix CMake
### V 2.1.1 (2022-10-20)
Add C++11 detection for CMake
### V 2.1.0 (2022-09-08)
Move source folder into vgsound_emu folder

View File

@ -8,9 +8,11 @@
cmake_minimum_required(VERSION 3.0)
project(vgsound_emu
VERSION 2.1.0
VERSION 2.1.1
LANGUAGES CXX)
enable_language(CXX)
option(VGSOUND_EMU_ES5504 "Use ES5504 core" ON)
option(VGSOUND_EMU_ES5505 "Use ES5505 core" ON)
option(VGSOUND_EMU_ES5506 "Use ES5506 core" ON)
@ -31,19 +33,80 @@ message(STATUS "Generator: ${CMAKE_GENERATOR}")
message(STATUS "Extra generator: ${CMAKE_EXTRA_GENERATOR}")
message(STATUS "Make program: ${CMAKE_MAKE_PROGRAM}")
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.1.0")
message(STATUS "Generating C++11 compile features:")
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.8.0")
set(cxx_features cxx_std_11)
else()
set(cxx_features
cxx_template_template_parameters
cxx_alias_templates
cxx_alignas
cxx_alignof
cxx_attributes
cxx_auto_type
cxx_constexpr
cxx_decltype
cxx_decltype_incomplete_return_types
cxx_default_function_template_args
cxx_defaulted_functions
cxx_defaulted_move_initializers
cxx_delegating_constructors
cxx_deleted_functions
cxx_enum_forward_declarations
cxx_explicit_conversions
cxx_extended_friend_declarations
cxx_extern_templates
cxx_final
cxx_func_identifier
cxx_generalized_initializers
cxx_inheriting_constructors
cxx_inline_namespaces
cxx_lambdas
cxx_local_type_template_args
cxx_long_long_type
cxx_noexcept
cxx_nonstatic_member_init
cxx_nullptr
cxx_override
cxx_range_for
cxx_raw_string_literals
cxx_reference_qualified_functions
cxx_right_angle_brackets
cxx_rvalue_references
cxx_sizeof_member
cxx_static_assert
cxx_strong_enums
cxx_thread_local
cxx_trailing_return_types
cxx_unicode_literals
cxx_uniform_initialization
cxx_unrestricted_unions
cxx_user_literals
cxx_variadic_macros
cxx_variadic_templates)
endif()
message(STATUS "${cxx_features}")
endif()
set(CORE_SOURCE "")
set(EMU_SOURCE "")
# Core functions
list(APPEND CORE_SOURCE
vgsound_emu/src/core/core.hpp
vgsound_emu/src/core/util.hpp
vgsound_emu/src/core/util/clock_pulse.hpp
vgsound_emu/src/core/util/mem_intf.hpp
)
# Dialogic ADPCM
if(VGSOUND_EMU_MSM6295)
list(APPEND CORE_SOURCE
vgsound_emu/src/core/vox/vox.cpp
vgsound_emu/src/core/vox/vox.hpp
vgsound_emu/src/core/vox/vox.cpp
)
message(STATUS "Using Dialogic ADPCM core")
endif()
@ -156,6 +219,18 @@ if(VGSOUND_EMU_X1_010)
endif()
add_library(vgsound_emu STATIC ${CORE_SOURCE} ${EMU_SOURCE})
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.1.0")
set_target_properties(vgsound_emu
PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF)
target_compile_features(vgsound_emu PRIVATE ${cxx_features})
message(STATUS "C++11 Enabled")
endif()
target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
# target_compile_options(vgsound_emu PRIVATE -Wall -Werror)
# for Test purpose (GCC)
# target_compile_options(vgsound_emu PRIVATE -Wall -Wextra -Werror)

View File

@ -1,6 +1,6 @@
# vgsound_emu V2 (modified)
This is a library of video game sound chip emulation cores. useful for emulators, chiptune trackers, or players.
This is a C++ library of video game sound chip emulation cores. useful for emulators, chiptune trackers, or players.
This is a modified version of vgsound_emu, tailored for Furnace.
@ -46,7 +46,8 @@ See [here](https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE) for detail
### dependencies
- CMake
- C++11 (or later)
- CMake (3.1 or later is recommended)
- git (for source repository management)
- MSVC or GCC or Clang (for compile)
@ -59,7 +60,7 @@ git clone https://gitlab.com/cam900/vgsound_emu.git
cd vgsound_emu
```
### Compile
### Compile with CMake
#### MSVC

View File

@ -0,0 +1,34 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Core framework for vgsound_emu
*/
#ifndef _VGSOUND_EMU_SRC_CORE_CORE_HPP
#define _VGSOUND_EMU_SRC_CORE_CORE_HPP
#pragma once
#include "util.hpp"
namespace vgsound_emu
{
class vgsound_emu_core
{
public:
// constructors
vgsound_emu_core(std::string tag)
: m_tag(tag)
{
}
// getters
std::string tag() { return m_tag; }
private:
std::string m_tag = ""; // core tags
};
}; // namespace vgsound_emu
#endif

View File

@ -32,232 +32,39 @@ namespace vgsound_emu
typedef float f32;
typedef double f64;
class vgsound_emu_core
{
public:
// constructors
vgsound_emu_core(std::string tag)
: m_tag(tag)
{
}
// getters
std::string tag() { return m_tag; }
protected:
const f64 PI = 3.1415926535897932384626433832795;
// std::clamp is only for C++17 or later; I use my own code
template<typename T>
T clamp(T in, T min, T max)
{
#if defined(_HAS_CXX17) && _HAS_CXX17
// just use std::clamp if C++17 or above
return std::clamp(in, min, max);
#else
// otherwise, use my own implementation of std::clamp
return std::min(std::max(in, min), max);
#endif
}
// get bitfield, bitfield(input, position, len)
template<typename T>
T bitfield(T in, u8 pos, u8 len = 1)
{
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
}
// get sign extended value, sign_ext<type>(input, len)
template<typename T>
T sign_ext(T in, u8 len)
{
len = std::max<u8>(0, (8 * sizeof(T)) - len);
return T(T(in) << len) >> len;
}
// convert attenuation decibel value to gain
inline f32 dB_to_gain(f32 attenuation) { return std::pow(10.0f, attenuation / 20.0f); }
private:
std::string m_tag = ""; // core tags
};
class vgsound_emu_mem_intf : public vgsound_emu_core
{
public:
// constructor
vgsound_emu_mem_intf()
: vgsound_emu_core("mem_intf")
{
}
virtual u8 read_byte(u32 address) { return 0; }
virtual u16 read_word(u32 address) { return 0; }
virtual u32 read_dword(u32 address) { return 0; }
virtual u64 read_qword(u32 address) { return 0; }
virtual void write_byte(u32 address, u8 data) {}
virtual void write_word(u32 address, u16 data) {}
virtual void write_dword(u32 address, u32 data) {}
virtual void write_qword(u32 address, u64 data) {}
};
static constexpr f64 PI = 3.1415926535897932384626433832795;
// std::clamp is only for C++17 or later; I use my own code
template<typename T>
class clock_pulse_t : public vgsound_emu_core
static inline T clamp(T in, T min, T max)
{
private:
const T m_init_width = 1;
#if defined(_HAS_CXX17) && _HAS_CXX17
// just use std::clamp if C++17 or above
return std::clamp(in, min, max);
#else
// otherwise, use my own implementation of std::clamp
return std::min(std::max(in, min), max);
#endif
}
class edge_t : public vgsound_emu_core
{
private:
const u8 m_init_edge = 1;
// get bitfield, bitfield(input, position, len)
template<typename T>
static inline T bitfield(T in, u8 pos, u8 len = 1)
{
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
}
public:
edge_t(u8 init_edge = 0)
: vgsound_emu_core("clock_pulse_edge")
, m_init_edge(init_edge)
, m_current(init_edge ^ 1)
, m_previous(init_edge)
, m_rising(0)
, m_falling(0)
, m_changed(0)
{
set(init_edge);
}
// get sign extended value, sign_ext<type>(input, len)
template<typename T>
static inline T sign_ext(T in, u8 len)
{
len = std::max<u8>(0, (8 * sizeof(T)) - len);
return T(T(in) << len) >> len;
}
// internal states
void reset()
{
m_previous = m_init_edge;
m_current = m_init_edge ^ 1;
set(m_init_edge);
}
// convert attenuation decibel value to gain
static inline f32 dB_to_gain(f32 attenuation) { return std::pow(10.0f, attenuation / 20.0f); }
void tick(bool toggle)
{
u8 current = m_current;
if (toggle)
{
current ^= 1;
}
set(current);
}
void set(u8 edge)
{
edge &= 1;
m_rising = m_falling = m_changed = 0;
if (m_current != edge)
{
m_changed = 1;
if (m_current && (!edge))
{
m_falling = 1;
}
else if ((!m_current) && edge)
{
m_rising = 1;
}
m_current = edge;
}
m_previous = m_current;
}
// getters
inline bool current() { return m_current; }
inline bool rising() { return m_rising; }
inline bool falling() { return m_falling; }
inline bool changed() { return m_changed; }
private:
u8 m_current : 1; // current edge
u8 m_previous : 1; // previous edge
u8 m_rising : 1; // rising edge
u8 m_falling : 1; // falling edge
u8 m_changed : 1; // changed flag
};
public:
clock_pulse_t(T init_width, u8 init_edge = 0)
: vgsound_emu_core("clock_pulse")
, m_init_width(init_width)
, m_edge(edge_t(init_edge & 1))
, m_width(init_width)
, m_width_latch(init_width)
, m_counter(init_width)
, m_cycle(0)
{
}
void reset(T init)
{
m_edge.reset();
m_width = m_width_latch = m_counter = init;
m_cycle = 0;
}
inline void reset() { reset(m_init_width); }
bool tick(T width = 0)
{
bool carry = ((--m_counter) <= 0);
if (carry)
{
if (!width)
{
m_width = m_width_latch;
}
else
{
m_width = width; // reset width
}
m_counter = m_width;
m_cycle = 0;
}
else
{
m_cycle++;
}
m_edge.tick(carry);
return carry;
}
inline void set_width(T width) { m_width = width; }
inline void set_width_latch(T width) { m_width_latch = width; }
// Accessors
inline bool current_edge() { return m_edge.current(); }
inline bool rising_edge() { return m_edge.rising(); }
inline bool falling_edge() { return m_edge.falling(); }
// getters
edge_t &edge() { return m_edge; }
inline T cycle() { return m_cycle; }
private:
edge_t m_edge;
T m_width = 1; // clock pulse width
T m_width_latch = 1; // clock pulse width latch
T m_counter = 1; // clock counter
T m_cycle = 0; // clock cycle
};
}; // namespace vgsound_emu
using namespace vgsound_emu;
#endif

View File

@ -0,0 +1,167 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Common clock pulse emulation for vgsound_emu
*/
#ifndef _VGSOUND_EMU_SRC_CORE_UTIL_CLOCK_PULSE_HPP
#define _VGSOUND_EMU_SRC_CORE_UTIL_CLOCK_PULSE_HPP
#pragma once
#include "../core.hpp"
namespace vgsound_emu
{
template<typename T>
class clock_pulse_t : public vgsound_emu_core
{
private:
const T m_init_width = 1;
class edge_t : public vgsound_emu_core
{
private:
const u8 m_init_edge = 1;
public:
edge_t(u8 init_edge = 0)
: vgsound_emu_core("clock_pulse_edge")
, m_init_edge(init_edge)
, m_current(init_edge ^ 1)
, m_previous(init_edge)
, m_rising(0)
, m_falling(0)
, m_changed(0)
{
set(init_edge);
}
// internal states
void reset()
{
m_previous = m_init_edge;
m_current = m_init_edge ^ 1;
set(m_init_edge);
}
void tick(bool toggle)
{
u8 current = m_current;
if (toggle)
{
current ^= 1;
}
set(current);
}
void set(u8 edge)
{
edge &= 1;
m_rising = m_falling = m_changed = 0;
if (m_current != edge)
{
m_changed = 1;
if (m_current && (!edge))
{
m_falling = 1;
}
else if ((!m_current) && edge)
{
m_rising = 1;
}
m_current = edge;
}
m_previous = m_current;
}
// getters
inline bool current() { return m_current; }
inline bool rising() { return m_rising; }
inline bool falling() { return m_falling; }
inline bool changed() { return m_changed; }
private:
u8 m_current : 1; // current edge
u8 m_previous : 1; // previous edge
u8 m_rising : 1; // rising edge
u8 m_falling : 1; // falling edge
u8 m_changed : 1; // changed flag
};
public:
clock_pulse_t(T init_width, u8 init_edge = 0)
: vgsound_emu_core("clock_pulse")
, m_init_width(init_width)
, m_edge(edge_t(init_edge & 1))
, m_width(init_width)
, m_width_latch(init_width)
, m_counter(init_width)
, m_cycle(0)
{
}
void reset(T init)
{
m_edge.reset();
m_width = m_width_latch = m_counter = init;
m_cycle = 0;
}
inline void reset() { reset(m_init_width); }
bool tick(T width = 0)
{
bool carry = ((--m_counter) <= 0);
if (carry)
{
if (!width)
{
m_width = m_width_latch;
}
else
{
m_width = width; // reset width
}
m_counter = m_width;
m_cycle = 0;
}
else
{
m_cycle++;
}
m_edge.tick(carry);
return carry;
}
inline void set_width(T width) { m_width = width; }
inline void set_width_latch(T width) { m_width_latch = width; }
// Accessors
inline bool current_edge() { return m_edge.current(); }
inline bool rising_edge() { return m_edge.rising(); }
inline bool falling_edge() { return m_edge.falling(); }
// getters
edge_t &edge() { return m_edge; }
inline T cycle() { return m_cycle; }
private:
edge_t m_edge;
T m_width = 1; // clock pulse width
T m_width_latch = 1; // clock pulse width latch
T m_counter = 1; // clock counter
T m_cycle = 0; // clock cycle
};
}; // namespace vgsound_emu
#endif

View File

@ -0,0 +1,44 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Common memory interface for vgsound_emu
*/
#ifndef _VGSOUND_EMU_SRC_CORE_UTIL_MEM_INTF_HPP
#define _VGSOUND_EMU_SRC_CORE_UTIL_MEM_INTF_HPP
#pragma once
#include "../core.hpp"
namespace vgsound_emu
{
class vgsound_emu_mem_intf : public vgsound_emu_core
{
public:
// constructor
vgsound_emu_mem_intf()
: vgsound_emu_core("mem_intf")
{
}
virtual u8 read_byte(u32 address) { return 0; }
virtual u16 read_word(u32 address) { return 0; }
virtual u32 read_dword(u32 address) { return 0; }
virtual u64 read_qword(u32 address) { return 0; }
virtual void write_byte(u32 address, u8 data) {}
virtual void write_word(u32 address, u16 data) {}
virtual void write_dword(u32 address, u32 data) {}
virtual void write_qword(u32 address, u64 data) {}
};
}; // namespace vgsound_emu
#endif

View File

@ -3,7 +3,7 @@
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Dialogic ADPCM core
OKI/Dialogic ADPCM core
*/
#ifndef _VGSOUND_EMU_SRC_CORE_VOX_VOX_HPP
@ -11,7 +11,9 @@
#pragma once
#include "../util.hpp"
#include "../core.hpp"
using namespace vgsound_emu;
class vox_core : public vgsound_emu_core
{

View File

@ -11,7 +11,10 @@
#pragma once
#include "../core/util.hpp"
#include "../core/core.hpp"
#include "../core/util/clock_pulse.hpp"
using namespace vgsound_emu;
// ES5504/ES5505/ES5506 interface
class es550x_intf : public vgsound_emu_core
@ -168,11 +171,11 @@ class es550x_shared_core : public vgsound_emu_core
}
// configurations
const u8 m_integer;
const u8 m_fraction;
const u8 m_total_bits;
const u32 m_accum_mask;
const bool m_transwave;
const u8 m_integer = 21;
const u8 m_fraction = 11;
const u8 m_total_bits = 32;
const u32 m_accum_mask = 0xffffffff;
const bool m_transwave = true;
// internal states
void reset();

View File

@ -11,7 +11,8 @@
#pragma once
#include "../core/util.hpp"
#include "../core/core.hpp"
using namespace vgsound_emu;
class k005289_core : public vgsound_emu_core
{

View File

@ -11,7 +11,9 @@
#pragma once
#include "../core/util.hpp"
#include "../core/core.hpp"
using namespace vgsound_emu;
class k007232_intf : public vgsound_emu_core
{

View File

@ -11,7 +11,9 @@
#pragma once
#include "../core/util.hpp"
#include "../core/core.hpp"
using namespace vgsound_emu;
class k053260_intf : public vgsound_emu_core
{

View File

@ -11,9 +11,12 @@
#pragma once
#include "../core/util.hpp"
#include "../core/core.hpp"
#include "../core/util/mem_intf.hpp"
#include "../core/vox/vox.hpp"
using namespace vgsound_emu;
class msm6295_core : public vox_core
{
friend class vgsound_emu_mem_intf; // common memory interface

View File

@ -11,7 +11,9 @@
#pragma once
#include "../core/util.hpp"
#include "../core/core.hpp"
using namespace vgsound_emu;
class n163_core : public vgsound_emu_core
{

View File

@ -12,7 +12,10 @@
#pragma once
#include "../core/util.hpp"
#include "../core/core.hpp"
#include "../core/util/mem_intf.hpp"
using namespace vgsound_emu;
// shared for SCCs
class scc_core : public vgsound_emu_core

View File

@ -11,7 +11,10 @@
#pragma once
#include "../core/util.hpp"
#include "../core/core.hpp"
#include "../core/util/mem_intf.hpp"
using namespace vgsound_emu;
class template_core : public vgsound_emu_core
{

View File

@ -61,10 +61,10 @@ bool vrcvi_core::alu_t::tick()
}
// carry handling
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));
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();

View File

@ -11,7 +11,9 @@
#pragma once
#include "../core/util.hpp"
#include "../core/core.hpp"
using namespace vgsound_emu;
class vrcvi_intf : public vgsound_emu_core
{

View File

@ -11,7 +11,10 @@
#pragma once
#include "../core/util.hpp"
#include "../core/core.hpp"
#include "../core/util/mem_intf.hpp"
using namespace vgsound_emu;
class x1_010_core : public vgsound_emu_core
{

View File

@ -1,7 +1,7 @@
# introduction
Furnace is a tool which allows you to create music using emulated sound chips from the 8/16-bit era.
For a full list of soundchips that Furnace supports, please see [the list of systems](https://github.com/tildearrow/furnace/tree/master/papers/doc/7-systems).
For a full list of soundchips that Furnace supports, please see [this list](https://github.com/tildearrow/furnace/tree/master/papers/doc/7-systems).
It has a music tracker interface. think of a piano roll, or a table that scrolls up and plays the notes.

View File

@ -13,16 +13,16 @@ however, effects are continuous, which means you only need to type it once and t
- `07xy`: tremolo. `x` is the speed, while `y` is the depth.
- maximum tremolo depth is -60 volume steps.
- `08xy`: set panning. `x` is the left channel and `y` is the right one.
- not all systems support this effect.
- not all chips support this effect.
- `80xx`: set panning (linear). this effect behaves more like other trackers:
- `00` is left.
- `80` is center.
- `FF` is right.
- not all systems support this effect.
- not all chips support this effect.
- `81xx`: set volume of left channel (from `00` to `FF`).
- not all systems support this effect.
- not all chips support this effect.
- `82xx`: set volume of right channel (from `00` to `FF`).
- not all systems support this effect.
- not all chips support this effect.
- `09xx`: set speed 1.
- `0Axy`: volume slide.
- if `x` is 0 then this is a slide down.
@ -34,7 +34,7 @@ however, effects are continuous, which means you only need to type it once and t
- `0Fxx`: set speed 2.
- `9xxx`: set sample position to `xxx`\*0x100.
- not all systems support this effect.
- not all chips support this effect.
- `Cxxx`: change song Hz.
- `xxx` may be from `000` to `3ff`.
@ -72,4 +72,4 @@ however, effects are continuous, which means you only need to type it once and t
- if `y` is 0 then this is a slide up.
- `FFxx`: end of song/stop playback.
additionally each system has its own effects. [click here for more details](../7-systems/README.md).
additionally each chip has its own effects. [click here for more details](../7-systems/README.md).

View File

@ -20,7 +20,7 @@ depending on the instrument type, there are currently 13 different types of an i
- [AY8930](8930.md) - for use with Microchip AY8930 E-PSG sound source.
- [Commodore 64](c64.md) - for use with Commodore 64 SID.
- [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source.
- [TIA](tia.md) - for use with Atari 2600 system.
- [TIA](tia.md) - for use with Atari 2600 chip.
- [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610.
- [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM, X1-010 and PC Engine's sample playback mode.
- [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console.

View File

@ -2,7 +2,7 @@
Wavetable synthesizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy.
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System and N163, and 32-level height for PCE. If a larger wave is defined for these systems, it will be squashed to fit within the constraints of the system.
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System and N163, and 32-level height for PCE. If a larger wave is defined for these chips, it will be squashed to fit within the constraints of the chips.
## wavetable synthesizer

View File

@ -1,41 +1,52 @@
# systems
# sound chips
this is a list of systems that Furnace supports, including each system's effects.
this is a list of sound chips that Furnace supports, including effects.
- [Sega Genesis/Mega Drive](genesis.md)
- [Sega Master System](sms.md)
- [Yamaha OPLL](opll.md)
- [Game Boy](game-boy.md)
- [PC Engine/TurboGrafx-16](pce.md)
- [NES](nes.md)
- [Commodore 64](c64.md)
- [Arcade (YM2151/PCM)](arcade.md)
- [Neo Geo/YM2610](ym2610.md)
- [Taito Arcade/YM2610B](ym2610b.md)
- [AY-3-8910](ay8910.md)
- [Amiga](amiga.md)
- [Yamaha YM2612 standalone](ym2612.md)
- [Yamaha YM2151 standalone](ym2151.md)
- [SegaPCM](segapcm.md)
- [Capcom QSound](qsound.md)
- [Atari 2600](tia.md)
- [Philips SAA1099](saa1099.md)
- [AY-3-8910](ay8910.md)
- [Microchip AY8930](ay8930.md)
- [VERA](vera.md)
- [tildearrow Sound Unit](soundunit.md)
- [Seta/Allumer X1-010](x1-010.md)
- [WonderSwan](wonderswan.md)
- [Bubble System WSG](bubblesystem.md)
- [Commodore 64](c64.md)
- [Commodore PET](pet.md)
- [Commodore VIC-20](vic20.md)
- [Generic PCM DAC](dac.md)
- [Famicom Disk System](fds.md)
- [Game Boy](game-boy.md)
- [Konami SCC](scc.md)
- [Konami VRC6](vrc6.md)
- [Atari Lynx](lynx.md)
- [Namco 163](n163.md)
- [Namco WSG](namco.md)
- [Yamaha OPL](opl.md)
- [PC Speaker](pcspkr.md)
- [Commodore PET](pet.md)
- [Konami SCC](scc.md)
- [Commodore VIC-20](vic20.md)
- [Konami VRC6](vrc6.md)
- [Famicom Disk System](fds.md)
- [NES](nes.md)
- [Nintendo MMC5](mmc5.md)
- [OKI MSM5232](msm5232.md)
- [OKI MSM6258](msm6258.md)
- [OKI MSM6295](msm6295.md)
- [PC Engine/TurboGrafx-16](pce.md)
- [PC Speaker](pcspkr.md)
- [Philips SAA1099](saa1099.md)
- [Capcom QSound](qsound.md)
- [Ricoh RF5C68](ricoh.md)
- [SegaPCM](segapcm.md)
- [Seta/Allumer X1-010](x1-010.md)
- [SNES](snes.md)
- [Atari 2600 (TIA)](tia.md)
- [tildearrow Sound Unit](soundunit.md)
- [TI SN76489](sms.md)
- [Toshiba T6W28](t6w28.md)
- [VERA](vera.md)
- [WonderSwan](wonderswan.md)
- [Virtual Boy](virtual-boy.md)
- [Yamaha OPLL](opll.md)
- [Yamaha OPL](opl.md)
- [Yamaha YM2151](ym2151.md)
- [Yamaha YM2203](ym2203.md)
- [Yamaha YM2413](opz.md)
- [Yamaha YM2608](ym2608.md)
- [Neo Geo/YM2610](ym2610.md)
- [Taito Arcade/YM2610B](ym2610b.md)
- [Yamaha YM2612](ym2612.md)
- [Yamaha YMZ280B](ymz280b.md)
- [ZX Spectrum Beeper](zxbeep.md)
Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but...

View File

@ -6,10 +6,10 @@ in this very computer music trackers were born...
# effects
- `10xx`: change wave.
- only works when "Mode" is set to "Wavetable" in the instrument.
- `10xx`: toggle low-pass filter. `0` turns it off and `1` turns it on.
- `11xx`: toggle amplitude modulation with the next channel.
- does not work on the last channel.
- `12xx`: toggle period (frequency) modulation with the next channel.
- does not work on the last channel.
- `13xx`: toggle low-pass filter. `0` turns it off and `1` turns it on.
- `13xx`: change wave.
- only works when "Mode" is set to "Wavetable" in the instrument.

View File

@ -1,32 +0,0 @@
# Arcade (Yamaha YM2151/PCM)
this chip combination was used in the Sega OutRun, X and Y arcade boards, and perhaps a few others.
the actual PCM chip had 16 channels, but the number has been cut to 5 in DefleMask for seemingly arbitrary reasons.
# effects
- `10xx`: set noise frequency of channel 8 operator 4. 00 disables noise while 01 to 20 enables it.
- `11xx`: set feedback of channel.
- `12xx`: set operator 1 level.
- `13xx`: set operator 2 level.
- `14xx`: set operator 3 level.
- `15xx`: set operator 4 level.
- `16xy`: set multiplier of operator.
- `x` is the operator (1-4).
- `y` is the mutliplier.
- `17xx`: set LFO speed.
- `18xx`: set LFO waveform. `xx` may be one of the following:
- `00`: saw
- `01`: square
- `02`: triangle
- `03`: noise
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.
- `1Cxx`: set attack of operator 3.
- `1Dxx`: set attack of operator 4.
- `1Exx`: set AM depth.
- `1Fxx`: set PM depth.
- `20xx`: set PCM frequency.
- only works on the PCM channels.
- `xx` is a 256th fraction of 31250Hz.

View File

@ -2,9 +2,9 @@
this chip was used in many home computers (ZX Spectrum, MSX, Amstrad CPC, Atari ST, etc.), video game consoles (Intellivision and Vectrex), arcade boards and even slot machines!
It is a 3-channel PSG sound source. The chip's powerful sound comes from the envelope...
it is a 3-channel square/noise/envelope sound generator. the chip's powerful sound comes from the envelope...
AY-3-8914 variant was used in Intellivision, it's basically original AY with 4 level envelope volume per channel and different register format.
the AY-3-8914 variant was used in Intellivision, which is pretty much an AY with 4 level envelope volume per channel and different register format.
# effects
@ -43,4 +43,4 @@ AY-3-8914 variant was used in Intellivision, it's basically original AY with 4 l
- `2Exx`: write to I/O port A.
- this changes the port's mode to "write". make sure you have connected something to it.
- `2Fxx`: write to I/O port B.
- this changes the port's mode to "write". make sure you have connected something to it.
- this changes the port's mode to "write". make sure you have connected something to it.

View File

@ -5,7 +5,7 @@ a backwards-compatible successor to the AY-3-8910, with increased volume resolut
sadly, this soundchip has only ever observed minimal success, and has remained rather obscure since.
it is known for being used in the Covox Sound Master, which didn't sell well either.
while emulation of this chip is mostly complete, hardware comparison hasn't been performed yet due to its scarcity. it also was emulated in a now-abandoned tracker with similar goal as Furnace, which sparked interest on the chip.
emulation of this chip in Furnace is now complete thanks to community efforts and hardware testing, which an MSX board called Darky has permitted.
# effects

View File

@ -6,7 +6,7 @@ however, the K005289 is just part of the logic used for pitch and wavetable ROM
waveform select and volume control are tied with single AY-3-8910 IO for both channels.
another AY-3-8910 IO is used for reading sound hardware status.
Furnace emulates this configuration as single system with 32x16 wavetables.
Furnace emulates this configuration as a "chip" with 32x16 wavetables.
# effects

View File

@ -1,10 +1,10 @@
# Commodore 64
a home computer with a synthesizer-grade sound chip of which people took decades to master. Three oscillators with four selectable waveforms, ring modulation, oscillator sync, multimode filter, ADSR envelope...
a home computer with a synthesizer-grade sound chip of which people took decades to master. three oscillators with four selectable waveforms, ring modulation, oscillator sync, multi-mode filter and ADSR envelope.
very popular in Europe and mostly due to the demoscene, which stretched the machine's limbs to no end.
Two revisions do exist - 6581 (original chip) and 8580 (improved version with working waveform mixing and somewhat more consistent filter curves)
two versions of aforementioned chip exist - 6581 (original chip) and 8580 (improved version with working waveform mixing and somewhat more consistent filter curves).
# effects

View File

@ -1,6 +1,8 @@
# Generic PCM DAC
Realtek HD Audio's predecessor. It's just a 1/8/16-bit sample channel, with freely selectable rate and mono/stereo settings. With it, you can emulate PCM DACs found in Williams arcade boards, Sound Blasters, MSX TurboR, Atari STE, NEC PC-9801-86 etc.
a sample channel, with freely selectable rate, mono/stereo and bit depth settings.
with it, you can emulate PCM DACs found in Williams arcade boards, Sound Blasters, MSX TurboR, Atari STe, NEC PC-9801-86, among others.
# effects

View File

@ -24,3 +24,4 @@ it also offers an additional 6-bit, 64-byte wavetable sound channel with (somewh
- 5: -3
- 6: -2
- 7: -1
- why is this mapping so unnatural? because that's how DefleMask does it (yeah, as you may have guessed this effect is mostly for compatibility reasons)...

View File

@ -2,7 +2,7 @@
a video game console that showed itself as the first true rival to Nintendo's video game market near-monopoly in the US during the '80's.
this console is powered by two sound chips: the Yamaha YM2612 and a derivative of the SN76489.
this console is powered by two sound chips: the [Yamaha YM2612](ym2612.md) and [a derivative of the SN76489](sms.md).
# effects
@ -19,9 +19,10 @@ this console is powered by two sound chips: the Yamaha YM2612 and a derivative o
- `y` is the mutliplier.
- `17xx`: enable PCM channel.
- this only works on channel 6.
- **this effect is there for compatibility reasons** - it is otherwise recommended to use Sample type instruments (which automatically enable PCM mode when used).
- `18xx`: toggle extended channel 3 mode.
- 0 disables it and 1 enables it.
- only in extended channel 3 system.
- only in extended channel 3 chip.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.

View File

@ -1,22 +1,20 @@
# Atari Lynx/MIKEY
The Atari Lynx is a 16 bit handheld console developed by (obviously) Atari Corporation, and initially released in September of 1989, with the worldwide release being in 1990.
the Atari Lynx is a 16 bit handheld console developed by (obviously) Atari Corporation, and initially released in September of 1989, with the worldwide release being in 1990.
The Lynx, while being an incredible handheld for the time (and a lot more powerful than a Game Boy), unfortunately meant nothing in the end due to the Lynx being a market failure, and ending up as one of the things that contributed to the downfall of Atari.
while it was an incredible handheld for the time (and a lot more powerful than a Game Boy), it unfortunately meant nothing in the end due to the Lynx being a market failure, and ending up as one of the things that contributed to the downfall of Atari.
Although the Lynx is still getting (rather impressive) homebrew developed for it, it does not mean that the Lynx is a popular system at all.
although the Lynx is still getting (rather impressive) homebrew developed for it, that does not mean the Lynx is a popular system at all.
The Atari Lynx's custom sound chip and CPU (MIKEY) is a 6502-based 8 bit CPU running at 16MHz, however this information is generally not useful in the context of Furnace.
the Atari Lynx has a 6502-based CPU with a sound part (this chip is known as MIKEY). it has the following sound capabilities:
- 4 channels of LFSR-based sound, which can be modulated with different frequencies (×0, ×1, ×2, ×3, ×4, ×5, ×7, ×10, and ×11) to create square waves and wavetable-like results.
- likewise, when a lot of the modulators are activated, this can provide a "pseudo-white noise"-like effect, which can be useful for drums and sound effects.
- hard stereo panning capabilities via the `08xx` effect command.
- four 8-bit DACs (Digital to Analog Converter), one for each voice. this allows for sample playback (at the cost of CPU time and memory).
- a variety of pitches to choose from, and they go from 32Hz to "above the range of human hearing", according to Atari.
## Sound capabilities
# effects
- The MIKEY has 4 channels of square wave-based sound, which can be modulated with different frequencies (×0, ×1, ×2, ×3, ×4, ×5, ×7, ×10, and ×11) to create wavetable-like results.
- Likewise, when a lot of the modulators are activated, this can provide a "pseudo-white noise"-like effect, whoch can be useful for drums and sound effects.
- The MIKEY also has hard stereo panning capabilities via the `08xx` effect command.
- The MIKEY has four 8-bit DACs (Digital to Analog Converter) — one for each voice — that essentially mean you can play samples on the MIKEY (at the cost of CPU time and memory).
- The MIKEY also has a variety of pitches to choose from, and they go from 32Hz to "above the range of human hearing", according to Atari.
## Effect commands
- `3xxx`: Load LFSR (0 to FFF).
- `3xxx`: Load LFSR (0 to FFF).
- this is a bitmask.
- for it to work, duty macro in instrument editor must be set to some value, without it LFSR will not be fed with any bits.
- for it to work, duty macro in instrument editor must be set to some value. without it LFSR will not be fed with any bits.

View File

@ -0,0 +1,26 @@
# OKI MSM5232
a rather primitive (but powerful) sound generator chip used in some arcade boards and even synthesizers (like Korg Poly-800).
it has 8 channels in 2 groups (of 4 channels each). each group can be configured with an envelope and the ability to produce 2', 4', 8' and/or 16' square/noise overtones on 8 outputs (four (2', 4', 8' and 16') per group).
however, this chip cannot entirely produce sound alone. it has to be paired with either an external envelope generator or capacitors to assist the internal envelope generator.
it also has no fine tune whatsoever. the frequency resolution is exactly a semitone.
Furnace implements this chip in a way that allows the following features:
- internal (capacitors) or external (volume macro) envelope modes per group
- the ability to change the volume of each output (this can be used to generate saw waves if you set each part/overtone's volume to exactly half of the previous one)
- global fine tune
- global vibrato (some arcade boards played with the clock input to simulate vibrato)
# effects
- `10xy`: set group control.
- `x` sets sustain mode.
- `y` is a 4-bit mask which toggles overtones.
- `11xx`: set noise mode.
- `12xx`: set group attack (0 to 5).
- only in internal (capacitor-based) envelope mode.
- `13xx`: set group decay (0 to 11).
- only in internal (capacitor-based) envelope mode.

View File

@ -0,0 +1,7 @@
# OKI MSM6258
a single-channel ADPCM sound source developed by OKI. it allows max sample rate of 15.6 KHz... with no variable pitch. most prominent use of this chip was Sharp X68000 computer, where it was paired with Yamaha YM2151.
# effects
...

View File

@ -0,0 +1,7 @@
# OKI MSM6295
an upgrade from 6258 - it provides 4 ADPCM channels, at max 32 KHz (still no variable pitch though). between late 80s and late 90s, it was one of the most common, if not THE most common soundchip used in arcade machines (Capcom, Toaplan, Kaneko, Atari, Tecmo, the list can go on and on...)
# effects
- `20xx`: set chip output rate.

View File

@ -1,25 +1,32 @@
# Namco 163 (also called N163, Namco C163, Namco 106 (sic), Namco 160 or Namco 129)
This is one of Namco's NES mappers, with up to 8 wavetable channels. It has also 256 nibbles (128 bytes) of internal RAM, and both channel registers and wavetables are stored here. Wavetables are variable in size and freely allocable anywhere in RAM, it means it can use part of or continuously pre-loaded waveform and its sequences in RAM. At least 128 nibbles (64 bytes) can be dedicated to waves, with more available if not all channels are used - waveform RAM area becomes smaller as more channels are activated, since channel registers consume 8 bytes for each channel. You must avoid conflict with channel register area and waveform to avoid broken channel playback.
this is one of Namco's NES mappers, with up to 8 wavetable channels.
it has 256 nibbles (128 bytes) of internal RAM which is shared between channel state and waves.
wavetables are variable in size and may be allocated anywhere in RAM. at least 128 nibbles (64 bytes) can be dedicated to waves, with more available if not all channels are used - waveform RAM area becomes smaller as more channels are activated, since channel registers consume 8 bytes for each channel.
Namco 163 uses time-division multiplexing for its output. this means that only one channel is output per sample (like OPLL and OPN2). therefore, its sound quality gets worse as more channels are activated.
Furnace supports loading waveforms into RAM and waveform playback simultaneously, and channel limit is dynamically changeable with effect commands.
You must load waveform to RAM first for playback, as its load behavior auto-updates when every waveform changes.
Both waveform playback and load command works independently per each channel columns, (Global) commands don't care about the channel columns for work commands and its load behavior is independent with per-channel column load commands.
you must load waveform to RAM first for playback, as its load behavior auto-updates when every waveform changes.
both waveform playback and load command work independently per each channel columns.
(Global) commands don't care about the channel columns for work commands and its load behavior is independent with per-channel column load commands.
# effects
- `10xx`: set waveform for playback.
- `11xx`: set waveform position in RAM for playback. (single nibble unit)
- `12xx`: set waveform length in RAM for playback. (04 to FC, 4 nibble unit)
- `130x`: set playback waveform update behavior. (0: off, bit 0: update now, bit 1: update when every waveform is changed)
- `11xx`: set waveform position in RAM for playback (single nibble unit).
- `12xx`: set waveform length in RAM for playback (04 to FC, 4 nibble unit).
- `130x`: set playback waveform update behavior (0: off, bit 0: update now, bit 1: update when every waveform is changed).
- `14xx`: set waveform for load to RAM.
- `15xx`: set waveform position for load to RAM. (single nibble unit)
- `16xx`: set waveform length for load to RAM. (04 to FC, 4 nibble unit)
- `170x`: set waveform load behavior. (0: off, bit 0: load now, bit 1: load when every waveform is changed)
- `180x`: set channel limit (0 to 7, x + 1)
- `15xx`: set waveform position for load to RAM (single nibble unit).
- `16xx`: set waveform length for load to RAM (04 to FC, 4 nibble unit).
- `170x`: set waveform load behavior (0: off, bit 0: load now, bit 1: load when every waveform is changed).
- `180x`: set channel limit (0 to 7, x + 1).
- `20xx`: (Global) set waveform for load to RAM.
- `21xx`: (Global) set waveform position for load to RAM. (single nibble unit)
- `22xx`: (Global) set waveform length for load to RAM. (04 to FC, 4 nibble unit)
- `230x`: (Global) set waveform load behavior. (0: off, bit 0: load now, bit 1: load when every waveform is changed)
- `21xx`: (Global) set waveform position for load to RAM (single nibble unit).
- `22xx`: (Global) set waveform length for load to RAM (04 to FC, 4 nibble unit).
- `230x`: (Global) set waveform load behavior (0: off, bit 0: load now, bit 1: load when every waveform is changed).

View File

@ -1,13 +1,10 @@
# Namco WSG | Namco C15 | Namco C30
a family of wavetable synth sound chips used by Namco in their arcade machines (Pacman and later). Waveforms are 4-bit, with 32-byte sample length.
a family of wavetable synth sound chips used by Namco in their arcade machines (Pacman and later). waveforms are 4-bit, with 32-byte sample length.
Everything starts with Namco WSG, simple 3ch wavetable with no extra frills. C15 is much more advanced sound source with 8 channels. C30 adds stereo output and noise mode.
everything starts with Namco WSG, which is a simple 3-channel wavetable with no extra frills. C15 is a much more advanced sound source with 8 channels, and C30 adds stereo output and noise mode.
# effects
-`10xx`: change waveform
-`11xx`: toggle noise mode WARNING: only on C30
- `10xx`: change waveform.
- `11xx`: toggle noise mode (WARNING: only on C30).

View File

@ -2,7 +2,7 @@
the console from Nintendo that plays Super Mario Bros. and helped revive the agonizing video game market in the US during mid-80s.
also known as Famicom. It is a five-channel PSG: first two channels play pulse wave with three different duty cycles, third is a fixed-volume triangle channel, fourth is a noise channel (can work in both pseudo-random and periodic modes) and fifth is a (D)PCM sample channel.
also known as Famicom. it is a five-channel sound generator: first two channels play pulse wave with three different duty cycles, third is a fixed-volume triangle channel, fourth is a noise channel (can work in both pseudo-random and periodic modes) and fifth is a (D)PCM sample channel.
# effects
@ -22,4 +22,4 @@ also known as Famicom. It is a five-channel PSG: first two channels play pulse w
- `18xx`: set PCM channel mode.
- `00`: PCM (software).
- `01`: DPCM (hardware).
- when in DPCM mode, samples will sound muffled (due to its nature), availables pitches are limited and loop point is ignored.
- when in DPCM mode, samples will sound muffled (due to its nature), availables pitches are limited and loop point is ignored.

View File

@ -1,9 +0,0 @@
# OKI MSM6258 | OKI MSM6295
MSM6258 is a single-channel ADPCM sound source developed by OKI. It allows max sample rate of 15.6 kHz... with no variable pitch. Most prominent use of this chip was Sharp X68000 computer, where it was paired with Yamaha YM2151.
MSM6295 is an upgrade from 6258 - it provides 4 ADPCM channels, at max 32 kHz (still no variable pitch though). Between late 80s and late 90s, it was one of the most common, if not THE most common soundchip used in arcade machines (Capcom, Toaplan, Kaneko, Atari, Tecmo, the list can go on and on...)
# effects
- `20xx`: set chip output rate

View File

@ -36,7 +36,7 @@ afterwards everyone moved to Windows and software mixed PCM streaming...
- this effect applies to all channels.
- `18xx`: toggle drums mode.
- 0 disables it and 1 enables it.
- only in drums system.
- only in drums chip.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.

View File

@ -33,7 +33,7 @@ the YM2413 is equipped with the following features:
- `y` is the mutliplier.
- `18xx`: toggle drums mode.
- 0 disables it and 1 enables it.
- only in drums system.
- only in drums chip.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.

View File

@ -19,3 +19,4 @@ furthermore, it has some PCM and LFO!
- when LFO is enabled, channel 2 is muted and its output is passed to channel 1's frequency.
- `13xx`: set LFO speed.
- `17xx`: toggle PCM mode.
- **this effect is there for compatibility reasons** - it is otherwise recommended to use Sample type instruments (which automatically enable PCM mode when used).

View File

@ -4,8 +4,8 @@
# real output
so far this is the only system in Furnace which has a real hardware output option.
to enable it, select file > configure system... > PC Speaker > Use system beeper.
so far this is the only chip in Furnace which has a real hardware output option.
to enable it, select file > configure chip... > PC Speaker > Use system beeper.
be noted that this will only work on Linux as Windows does not provide any user-space APIs to address the PC speaker directly!

View File

@ -1,14 +1,14 @@
# Capcom QSound (DL-1425)
This chip was used in Capcom's CP System Dash, CP System II and ZN arcade PCBs.
this chip was used in Capcom's CP System Dash, CP System II and ZN arcade PCBs.
It supports 16 PCM channels and uses the patented (now expired) QSound stereo expansion algorithm, as the name implies.
it supports 16 PCM channels and uses the patented (now expired) QSound stereo expansion algorithm, as the name implies.
Because the chip lacks sample interpolation, it's recommended that you try to play samples at around 24038 Hz to avoid aliasing. This is especially important for e.g. cymbals.
because the chip lacks sample interpolation, it is recommended that you try to play samples at around 24038 Hz to avoid aliasing. this is especially important for e.g. cymbals.
The QSound chip also has a small echo buffer, somewhat similar to the SNES, although with a very basic (and non-adjustable) filter. It is however possible to adjust the feedback and length of the echo buffer. The initial values can be set in the "Configure system" dialog.
the QSound chip also has a small echo buffer, somewhat similar to the SNES, although with a very basic (and non-adjustable) filter. it is however possible to adjust the feedback and length of the echo buffer (the initial values can be set in the "configure chip" option in the file menu or the chip manager).
There are also 3 ADPCM channels, however they cannot be used in Furnace yet. They have been reserved in case this feature is added later. ADPCM samples are limited to 8012 Hz.
there are also 3 ADPCM channels, however they cannot be used in Furnace yet. they have been reserved in case this feature is added later. ADPCM samples are limited to 8012 Hz.
# effects

View File

@ -1,6 +1,6 @@
# Ricoh RF5C68
YM2612's sidekick - poor man's SPC-700. 8ch PCM sample-based synthesizer used in Sega Mega CD, Fujitsu FM Towns and some of Sega's arcade machines. Supports up to 64 Kbytes of external PCM data.
YM2612's sidekick - poor man's SNES DSP. 8-channel PCM sample-based synthesizer used in Sega CD, Fujitsu FM Towns and some of Sega's arcade machines. supports up to 64KB of external PCM data.
# effects

View File

@ -1,9 +1,8 @@
# Philips SAA1099
this was used by the Game Blaster and SAM Coupé. it's pretty similar to the AY-3-8910, but has stereo sound, twice the channels and two envelopes, both of which are highly flexible. The envelopes work like this:
an instrument with envelope settings is placed on channel 2 or channel 5
an instrument that is used as an "envelope output", is placed on channel 3 or channel 6. You may want to disable wave output on the output channel.
- an instrument with envelope settings is placed on channel 2 or channel 5
- an instrument that is used as an "envelope output" is placed on channel 3 or channel 6 (you may want to disable wave output on the output channel)
# effects

View File

@ -6,7 +6,12 @@ yep, that's right! 16 channels of PCM!
a chip used in the Sega OutRun/X/Y arcade boards. eventually the MultiPCM surpassed it with 28 channels, and later they joined the software mixing gang.
# 5-channel SegaPCM
Furnace also has a five channel version of this chip, but it only exists for DefleMask compatibility reasons (which doesn't expose the other channels for rather arbitrary reasons).
# effects
- `20xx`: set PCM frequency.
- `xx` is a 256th fraction of 31250Hz.
- this effect exists for mostly DefleMask compatibility - it is otherwise recommended to use Sample type instruments.

View File

@ -1,13 +1,10 @@
# Sega Master System
# TI SN76489 (e.g. Sega Master System)
the predecessor to Genesis.
a relatively simple sound chip made by Texas Instruments. a derivative of it is used in Sega's Master System, the predecessor to Genesis.
surely had better graphics than NES, but its sound (fairly weak, 4ch PSG with A-3 is a lowest tone) is subject of several jokes.
this console is powered by a derivative of the Texas Instruments SN76489.
the original iteration of the SN76489 used in the TI-99/4A computers was clocked much lower at 447 kHz, being able to play as low as 13.670 Hz (A -1). consequentially, pitch accuracy for higher notes is compromised.
the original iteration of the SN76489 used in the TI-99/4A computers was clocked at 447 KHz, being able to play as low as 13.670 Hz (A -1). consequentially, pitch accuracy for higher notes is compromised.
on the other hand, the chip was clocked at a much higher speed on Master System and Genesis, which makes it rather poor in the bass range.
# effects

View File

@ -1,50 +1,77 @@
# Super NES
# Super Nintendo Entertainment System (SNES)/Super Famicom
The successor to NES to compete with Genesis. Now packing superior graphics and sample-based audio. Also known as Super Famicom in Japan.
the successor to NES to compete with Genesis, packing superior graphics and sample-based audio.
Its audio subsystem, developed by Sony, features the DSP chip, SPC700 microcontroller and 64KB of dedicated SRAM used by both. This whole system itself is pretty much a separate computer that the main CPU needs to upload its program and samples to.
its audio system, developed by Sony, features a DSP chip, SPC700 CPU and 64KB of dedicated SRAM used by both.
this whole system itself is pretty much a separate computer that the main CPU needs to upload its program and samples to.
Furnace communicates with the DSP directly and provides a full 64KB of memory. This memory might be reduced excessively on ROM export to make up for playback engine and pattern data. As of version 0.6pre2, you can go to `window -> statistics` to see how much memory your samples are using.
Furnace communicates with the DSP directly and provides a full 64KB of memory. this memory might be reduced excessively on ROM export to make up for playback engine and pattern data. you can go to window > statistics to see how much memory your samples are using.
Some notable features of the DSP are:
- It has pitch modulation, meaning that you can use 2 channels to make a basic FM synth without eating up too much memory
- It has a built in noise generator, useful for hihats, cymbals, rides, sfx, among other things.
- It famously features per-channel echo, which unfortunately eats up a lot of memory but can be used to save channels in songs.
- It can loop samples, but the loop points have to be multiples of 16.
- It can invert the left and/or right channels, for surround sound.
- It features ADSR, similar to the Commodore 64, but its functionality is closer to the OPL(L|1|2|3)'s implementation of ADSR.
- It features an 8-tap FIR filter, which is basically a procedural low-pass filter that you can edit however you want.
- 7-bit volume, per-channel.
- Per-channel interpolation, which is basically a low-pass filter that gets affected by the pitch of the channel.
some notable features of the DSP are:
- pitch modulation, meaning that you can use 2 channels to make a basic FM synth without eating up too much memory.
- a built in noise generator, useful for hi-hats, cymbals, rides, effects, among other things.
- per-channel echo, which unfortunately eats up a lot of memory but can be used to save channels in songs.
- an 8-tap FIR filter for the echo, which is basically a procedural low-pass filter that you can edit however you want.
- sample loop, but the loop points have to be multiples of 16.
- left/right channel invert for surround sound.
- ADSR and gain envelope modes.
- 7-bit volume per channel.
- sample interpolation, which is basically a low-pass filter that gets affected by the pitch of the channel.
Furnace also allows the SNES to use wavetables (and the wavetable synthesizer) in order to create more 'animated' sounds, using less memory than regular samples. This, however, is not a hardware feature, and might be difficult to implement on real hardware.
Furnace also allows the SNES to use wavetables (and the wavetable synthesizer) in order to create more 'animated' sounds, using less memory than regular samples. this however is not a hardware feature, and might be difficult to implement on real hardware.
# effects
Note: this chip has a signed left/right level. Which can be used for inverted (surround) stereo. A signed 8-bit value means 80 - FF = -128 - -1. Other values work normally. A value of -128 is not recommended as it could cause overflows.
- `10xx`: Set waveform.
- `11xx`: Toggle noise generator mode.
- `12xx`: Toggle echo on this channel.
- `13xx`: Toggle pitch modulation.
- `14xy`: Toggle inverting the left or right channels. (x: left, y: right)
- `15xx`: Set envelope mode. (0: ADSR, 1: gain/direct, 2: decrement, 3: exponential, 4: increment, 5: bent)
- `16xx`: Set gain. (00 to 7F if direct, 00 to 1F otherwise)
- `18xx`: Enable echo buffer.
- `19xx`: Set echo delay. (0 to F)
- `1Axx`: Set left echo channel volume.
- `1Bxx`: Set right echo channel volume.
- `1Cxx`: Set echo feedback.
- `1Dxx`: Set noise generator frequency. (00 to 1F)
- `20xx`: Set attack (0 to F)
- `21xx`: Set decay (0 to 7)
- `22xx`: Set sustain (0 to 7)
- `23xx`: Set release (00 to 1F)
- `30xx`: Set echo filter coefficient 0
- `31xx`: Set echo filter coefficient 1
- `32xx`: Set echo filter coefficient 2
- `33xx`: Set echo filter coefficient 3
- `34xx`: Set echo filter coefficient 4
- `35xx`: Set echo filter coefficient 5
- `36xx`: Set echo filter coefficient 6
- `37xx`: Set echo filter coefficient 7
- `10xx`: set waveform.
- `11xx`: toggle noise mode.
- `12xx`: toggle echo on this channel.
- `13xx`: toggle pitch modulation.
- `14xy`: toggle inverting the left or right channels (x: left, y: right).
- `15xx`: set envelope mode.
- 0: ADSR.
- 1: gain (direct).
- 2: linear decrement.
- 3: exponential decrement.
- 4: linear increment.
- 5: bent line (inverse log) increment.
- `16xx`: set gain (00 to 7F if direct, 00 to 1F otherwise).
- `18xx`: enable echo buffer.
- `19xx`: set echo delay
- goes from 0 to F.
- `1Axx`: set left echo channel volume.
- this is a signed number.
- 00 to 7F for 0 to 127.
- 80 to FF for -128 to -1.
- setting this to -128 is not recommended as it may cause echo output to overflow and therefore click.
- `1Bxx`: set right echo channel volume.
- this is a signed number.
- 00 to 7F for 0 to 127.
- 80 to FF for -128 to -1.
- setting this to -128 is not recommended as it may cause echo output to overflow and therefore click.
- `1Cxx`: set echo feedback.
- this is a signed number.
- 00 to 7F for 0 to 127.
- 80 to FF for -128 to -1.
- setting this to -128 is not recommended as it may cause echo output to overflow and therefore click.
- `1Dxx`: set noise generator frequency (00 to 1F).
- `20xx`: set attack (0 to F).
- only in ADSR envelope mode.
- `21xx`: set decay (0 to 7).
- only in ADSR envelope mode.
- `22xx`: set sustain (0 to 7).
- only in ADSR envelope mode.
- `23xx`: set release (00 to 1F).
- only in ADSR envelope mode.
- `30xx`: set echo filter coefficient 0.
- `31xx`: set echo filter coefficient 1.
- `32xx`: set echo filter coefficient 2.
- `33xx`: set echo filter coefficient 3.
- `34xx`: set echo filter coefficient 4.
- `35xx`: set echo filter coefficient 5.
- `36xx`: set echo filter coefficient 6.
- `37xx`: set echo filter coefficient 7.
- all of these are signed numbers.
- 00 to 7F for 0 to 127.
- 80 to FF for -128 to -1.
- make sure the sum of these is between -128 or 127.
- failure to comply may result in overflow and therefore clicking.

View File

@ -1,5 +1,16 @@
# tildearrow Sound Unit
This is a fantasy sound chip, used in the specs2 fantasy computer designed by tildearrow. It includes native support for sample playback, but with only 8KB or 64KB of sample data, depending on the configuration used. Since 0.6pre1, this sound chip is no longer hidden by default and can be accessed through the module creation screen and can be added or removed.
a fantasy sound chip, used in the specs2 fantasy computer designed by tildearrow.
it has the following capabilities:
- 8 channels of either waveform or sample
- stereo sound
- 8 waveforms (pulse, saw, sine, triangle, noise, periodic noise, XOR sine and XOR triangle)
- 128 widths for the pulse wave
- per-channel resonant filter
- ring modulation
- volume, frequency and cutoff sweep units (per-channel)
- phase reset timer (per-channel)
# effects

View File

@ -0,0 +1,11 @@
# Toshiba T6W28
an enhanced SN76489 derivative. same 4 channels, but with stereo (soft panning!) and noise frequency being fully independent of channel 3's.
this chip was used in Neo Geo Pocket.
# effects
- `20xx`: set noise mode.
- 0: thin pulse.
- 1: noise.

View File

@ -1,11 +1,18 @@
# Commodore VIC-20
The Commodore VIC-20 was Commodore's major attempt at making a personal home computer, and is the percursor to the Commodore 64. The VIC-20 was also known as the VC-20 in Germany, and the VIC-1001 in Japan.
the Commodore VIC-20 was Commodore's major attempt at making a personal home computer, and is the percursor to the Commodore 64.
It has 4 PSG voices that has a limited but wide tuning range, and like the SN76489, the last voice is dedicated to playing pseudo-white noise.
it was also known as the VC-20 in Germany, and the VIC-1001 in Japan.
The 3 pulse wave channels also have different octaves that they can play notes on. The first channel is the bass channel, and it can play notes from octave 1. The next is the 'mid/chord' channel, and it plays notes from octave 2. And rather obviously, the 3rd pulse channel is typically the lead channel, can play notes from octave 3.
it has 4 voices that have a limited but wide tuning range, and like the SN76489 and T6W28, the last voice is dedicated to playing noise.
the 3 pulse wave channels also have different octaves that they can play notes on:
- the first channel is the bass channel, and it can play notes from octave 1.
- the next is the 'mid/chord' channel, and it plays notes from octave 2.
- and rather obviously, the 3rd pulse channel is typically the lead channel, can play notes from octave 3.
these channels are not referred as "square" wave channels since a technique to play 15 additional pulse-like waveforms has been discovered long after the VIC-20's release.
## effect commands
- `10xx` Switch waveform (`xx` from `00` to `0F`)
- `10xx` Switch waveform (`xx` from `00` to `0F`)

View File

@ -0,0 +1,43 @@
# Virtual Boy
a "portable" video game console made by Nintendo in the '90's.
it supposedly was the beginning of virtual reality... nah, instead it failed to sell well because you use it for 15 minutes and then you get a headache.
its sound generation chip is called Virtual Sound Unit (VSU), a wavetable chip that is a lot like PC Engine, but unlike that, the waves are twice as tall, it doesn't go too low in terms of frequency (~D-2), and the last channel (yep, it has 6 channels) is a noise one.
additionally, channel 5 offers a modulation/sweep unit. the former is similar to FDS' but has much reduced speed control.
# effects
- `10xx`: set waveform.
- `11xx`: set noise length (0 to 7).
- only in the noise channel.
- `12xy`: setup envelope.
- `x` determines whether envelope is enabled or not.
- 0: disabled
- 1: enabled
- 3: enabled and loop
- yeah, the value 2 isn't useful.
- `y` sets the speed and direction.
- 0-7: down
- 8-F: up
- `13xy`: setup sweep.
- `x` sets the speed.
- 0 and 8 are "speed 0" - sweep is ineffective.
- `y` sets the shift (0 to 7).
- 8 and higher will mute the channel.
- only in channel 5.
- `14xy`: setup modulation.
- `x` determines whether it's enabled or not.
- 0: disabled
- 1: enabled
- 3: enabled and loop
- 2 isn't useful here either.
- `y` sets the speed.
- 0 and 8 are "speed 0" - modulation is ineffective.
- no, you can't really do Yamaha FM using this.
- only in channel 5.
- `15xx`: set modulation wave.
- `xx` points to a wavetable. it should have a height of 255.
- this is an alternative to setting the modulation wave through the instrument.

View File

@ -1,11 +1,11 @@
# Konami VRC6
the most popular expansion chip to the NES' sound system.
the most popular expansion chip to the Famicom's sound system.
the chip has 2 pulse wave channels and one sawtooth channel.
volume register is 4 bit for pulse wave and 6 bit for sawtooth, but sawtooth output is corrupted when volume register value is too high. because this register is actually an 8 bit accumulator, its output may wrap around.
For that reason, the sawtooth channel has it's own instrument type. Setting volume macro and pattern editor volume setting too high (above 42/2A) will distort the waveform.
for that reason, the sawtooth channel has its own instrument type. setting volume macro and/or pattern editor volume setting too high (above 42/2A) may distort the waveform.
pulse wave duty cycle is 8-level. it can be ignored and it has potential for DAC at this case: volume register in this mode is DAC output and it can be PCM playback through this mode.
Furnace supports this routine for PCM playback, but it consumes a lot of CPU time in real hardware (even if conjunction with VRC6's integrated IRQ timer).

View File

@ -1,8 +1,12 @@
# WonderSwan
A handheld console released only in Japan by Bandai. Designed by the same people behind Game Boy and Virtual Boy, it has lots of similar elements from those two systems in the sound department.
a handheld console released only in Japan by Bandai, designed by the same people behind Game Boy and Virtual Boy.
for this reason it has lots of similar elements from those two systems in the sound department.
It has 4 wavetable channels, channel #2 could play PCM, channel #3 has hardware sweep and channel #4 could play noise.
it has 4 wavetable channels. some of them have additional capabilities:
- the second channel could play samples
- the third one has hardware sweep
- the fourth one also does noise
# effects

View File

@ -4,6 +4,8 @@ the sound chip powering several arcade boards and the Sharp X1/X68000. Eight 4-o
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.
in most arcade boards the chip was used in combination with a PCM chip, like [SegaPCM](segapcm.md) or [OKI's line of ADPCM chips](oki.md).
# effects
- `10xx`: set noise frequency of channel 8 operator 4. 00 disables noise while 01 to 20 enables it.
@ -63,4 +65,4 @@ it also was present on several pinball machines and synthesizers of the era, and
- `5Cxx`: set D2R/SR of operator 1.
- `5Dxx`: set D2R/SR of operator 2.
- `5Exx`: set D2R/SR of operator 3.
- `5Fxx`: set D2R/SR of operator 4.
- `5Fxx`: set D2R/SR of operator 4.

View File

@ -21,7 +21,7 @@ several variants of this chip were released as well, with more features.
- `y` is the mutliplier.
- `18xx`: toggle extended channel 3 mode.
- 0 disables it and 1 enables it.
- only in extended channel 3 system.
- only in extended channel 3 chip.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.

View File

@ -21,7 +21,7 @@ the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built-
- `y` is the mutliplier.
- `18xx`: toggle extended channel 3 mode.
- 0 disables it and 1 enables it.
- only in extended channel 3 system.
- only in extended channel 3 chip.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.

View File

@ -19,7 +19,7 @@ its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and 2 different
- `y` is the mutliplier.
- `18xx`: toggle extended channel 2 mode.
- 0 disables it and 1 enables it.
- only in extended channel 2 system.
- only in extended channel 2 chip.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.
@ -96,4 +96,4 @@ its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and 2 different
- `5Cxx`: set D2R/SR of operator 1.
- `5Dxx`: set D2R/SR of operator 2.
- `5Exx`: set D2R/SR of operator 3.
- `5Fxx`: set D2R/SR of operator 4.
- `5Fxx`: set D2R/SR of operator 4.

View File

@ -18,7 +18,7 @@ it is backward compatible with the original chip.
- `y` is the mutliplier.
- `18xx`: toggle extended channel 3 mode.
- 0 disables it and 1 enables it.
- only in extended channel 3 system.
- only in extended channel 3 chip.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.

View File

@ -21,7 +21,7 @@ For 0.6pre1, Furnace can now support advanced YM2612 features that [Fractal](ht
- this only works on channel 6.
- `18xx`: toggle extended channel 3 mode.
- 0 disables it and 1 enables it.
- only in extended channel 3 system.
- only in extended channel 3 chip.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.

View File

@ -1,6 +1,8 @@
# Yamaha YMZ280B (PCMD8)
8ch PCM/ADPCM sample-based synthesizer designed for use with arcade machines. Lived throughout mid to late 90s. It has 16-level stereo panning, up to 16-bit PCM and up to 16Mbytes of external PCM data.
8-channel PCM/ADPCM sample-based sound chip designed for use with arcade machines. it lived throughout mid to late 90s.
it has 16-level stereo panning, up to 16-bit PCM and up to 16MB of external PCM data.
# effects

View File

@ -1,8 +1,8 @@
# ZX Spectrum Speaker
# ZX Spectrum beeper
Rather than having a dedicated sound synthesizer, early ZX Spectrum models had one piezo beeper, controlled by Z80 CPU and ULA chip. It's capabilities should be on par with an IBM PC speaker... right?
rather than having a dedicated sound synthesizer, early ZX Spectrum models had one piezo beeper, controlled by Z80 CPU and ULA chip. its capabilities should be on par with an IBM PC speaker... right?
Not really - very soon talented programmers found out ways to output much more than one square wave channel. A lot of ZX beeper routines do exist, but as of 0.6pre1 Furnace supports only one - Follin-like engine with 6 channels of narrow pulse wave and click drums.
not really - very soon talented programmers found out ways to output much more than one square wave channel. a lot of ZX beeper routines do exist, but as of 0.6 Furnace supports only a Follin/SFX-like engine with 6 channels of narrow pulse wave and click drums.
# effects
@ -10,4 +10,4 @@ Not really - very soon talented programmers found out ways to output much more t
- `17xx`: trigger overlay drum.
- `xx` is the sample number.
- overlay drums are 1-bit and always play at 55930Hz (NTSC) or 55420Hz (PAL).
- the maximum length is 2048!
- the maximum length is 2048!

View File

@ -8,7 +8,7 @@ this documentation is a work in progress! expect several sections to be incomple
4. [instruments](4-instrument/README.md)
5. [wavetables](5-wave/README.md)
6. [samples](6-sample/README.md)
7. [list of systems](7-systems/README.md)
7. [list of sound chips](7-systems/README.md)
# attribution

View File

@ -275,6 +275,14 @@ struct DivRegWrite {
addr(a), val(v) {}
};
struct DivDelayedWrite {
int time;
DivRegWrite write;
DivDelayedWrite(int t, unsigned int a, unsigned short v):
time(t),
write(a,v) {}
};
struct DivDispatchOscBuffer {
bool follow;
unsigned int rate;
@ -327,6 +335,14 @@ class DivDispatch {
*/
virtual void acquire(short* bufL, short* bufR, size_t start, size_t len);
/**
* fill a write stream with data (e.g. for software-mixed PCM).
* @param stream the write stream.
* @param rate stream rate (e.g. 44100 for VGM).
* @param len number of samples.
*/
virtual void fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len);
/**
* send a command to this dispatch.
* @param c a DivCommand.

View File

@ -47,8 +47,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev121"
#define DIV_ENGINE_VERSION 121
#define DIV_VERSION "dev122"
#define DIV_ENGINE_VERSION 122
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -431,7 +431,7 @@ class DivEngine {
void processRow(int i, bool afterDelay);
void nextOrder();
void nextRow();
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond);
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, bool directStream);
// returns true if end of song.
bool nextTick(bool noAccum=false, bool inhibitLowLat=false);
bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal);
@ -515,7 +515,7 @@ 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);
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false, bool directStream=false);
// dump to ZSM.
SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true);
// dump command stream.

View File

@ -937,13 +937,13 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.systemLen=2;
ds.system[0]=DIV_SYSTEM_YM2612;
ds.system[1]=DIV_SYSTEM_SMS;
ds.systemVol[1]=24;
ds.systemVol[1]=32;
}
if (ds.system[0]==DIV_SYSTEM_GENESIS_EXT) {
ds.systemLen=2;
ds.system[0]=DIV_SYSTEM_YM2612_EXT;
ds.system[1]=DIV_SYSTEM_SMS;
ds.systemVol[1]=24;
ds.systemVol[1]=32;
}
if (ds.system[0]==DIV_SYSTEM_ARCADE) {
ds.systemLen=2;

View File

@ -22,6 +22,9 @@
void DivDispatch::acquire(short* bufL, short* bufR, size_t start, size_t len) {
}
void DivDispatch::fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len) {
}
void DivDispatch::tick(bool sysTick) {
}

View File

@ -27,11 +27,11 @@
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
void DivPlatformGenesis::processDAC() {
void DivPlatformGenesis::processDAC(int iRate) {
if (softPCM) {
softPCMTimer+=chipClock/576;
if (softPCMTimer>rate) {
softPCMTimer-=rate;
if (softPCMTimer>iRate) {
softPCMTimer-=iRate;
int sample=0;
for (int i=5; i<7; i++) {
@ -75,14 +75,14 @@ void DivPlatformGenesis::processDAC() {
} else {
if (!chan[5].dacReady) {
chan[5].dacDelay+=32000;
if (chan[5].dacDelay>=rate) {
chan[5].dacDelay-=rate;
if (chan[5].dacDelay>=iRate) {
chan[5].dacDelay-=iRate;
chan[5].dacReady=true;
}
}
if (chan[5].dacMode && chan[5].dacSample!=-1) {
chan[5].dacPeriod+=chan[5].dacRate;
if (chan[5].dacPeriod>=rate) {
if (chan[5].dacPeriod>=iRate) {
DivSample* s=parent->getSample(chan[5].dacSample);
if (s->samples>0) {
if (!isMuted[5]) {
@ -106,7 +106,7 @@ void DivPlatformGenesis::processDAC() {
rWrite(0x2b,0);
}
}
while (chan[5].dacPeriod>=rate) chan[5].dacPeriod-=rate;
while (chan[5].dacPeriod>=iRate) chan[5].dacPeriod-=iRate;
} else {
chan[5].dacSample=-1;
}
@ -120,7 +120,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
static int os[2];
for (size_t h=start; h<start+len; h++) {
processDAC();
processDAC(rate);
os[0]=0; os[1]=0;
for (int i=0; i<6; i++) {
@ -180,7 +180,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
ymfm::ym2612::fm_engine* fme=fm_ymfm->debug_engine();
for (size_t h=start; h<start+len; h++) {
processDAC();
processDAC(rate);
os[0]=0; os[1]=0;
if (!writes.empty()) {
@ -237,6 +237,20 @@ void DivPlatformGenesis::acquire(short* bufL, short* bufR, size_t start, size_t
}
}
void DivPlatformGenesis::fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len) {
while (!writes.empty()) writes.pop_front();
for (size_t i=0; i<len; i++) {
processDAC(sRate);
while (!writes.empty()) {
QueuedWrite& w=writes.front();
stream.push_back(DivDelayedWrite(i,w.addr,w.val));
writes.pop_front();
}
}
regWrites.clear();
}
void DivPlatformGenesis::tick(bool sysTick) {
for (int i=0; i<(softPCM?7:6); i++) {
if (i==2 && extMode) continue;

View File

@ -124,12 +124,13 @@ class DivPlatformGenesis: public DivPlatformOPN {
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
inline void processDAC();
inline void processDAC(int iRate);
void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len);
void acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
void fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);

View File

@ -416,12 +416,10 @@ int DivPlatformNES::dispatch(DivCommand c) {
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (!parent->song.brokenOutVol2) {
if (c.chan==2) {
rWrite(0x4000+c.chan*4,0xff);
} else {
rWrite(0x4000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
}
if (c.chan==2) {
rWrite(0x4000+c.chan*4,0xff);
} else if (!parent->song.brokenOutVol2) {
rWrite(0x4000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
}
break;
case DIV_CMD_NOTE_OFF:

View File

@ -25,8 +25,6 @@
//#define rWrite(a,v) pendingWrites[a]=v;
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define CHIP_DIVIDER 16
const char* regCheatSheetT6W28[]={
"Data0", "0",
"Data1", "1",
@ -72,14 +70,21 @@ void DivPlatformT6W28::acquire(short* bufL, short* bufR, size_t start, size_t le
}
void DivPlatformT6W28::writeOutVol(int ch) {
int left=15-CLAMP(chan[ch].outVol+chan[ch].panL-15,0,15);
int right=15-CLAMP(chan[ch].outVol+chan[ch].panR-15,0,15);
rWrite(0,0x90|(ch<<5)|(isMuted[ch]?15:left));
rWrite(1,0x90|(ch<<5)|(isMuted[ch]?15:right));
if (chan[ch].active) {
int left=15-CLAMP(chan[ch].outVol+chan[ch].panL-15,0,15);
int right=15-CLAMP(chan[ch].outVol+chan[ch].panR-15,0,15);
rWrite(0,0x90|(ch<<5)|(isMuted[ch]?15:left));
rWrite(1,0x90|(ch<<5)|(isMuted[ch]?15:right));
} else {
rWrite(0,0x9f|(ch<<5));
rWrite(1,0x9f|(ch<<5));
}
}
void DivPlatformT6W28::tick(bool sysTick) {
for (int i=0; i<4; i++) {
double CHIP_DIVIDER=16;
if (i==3) CHIP_DIVIDER=15;
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=VOL_SCALE_LOG(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15);
@ -91,6 +96,12 @@ void DivPlatformT6W28::tick(bool sysTick) {
}
chan[i].freqChanged=true;
}
if (i==3 && chan[i].std.duty.had) {
if (chan[i].duty!=chan[i].std.duty.val) {
chan[i].duty=((chan[i].std.duty.val==1)?4:0)|3;
rWrite(1,0xe0+chan[i].duty);
}
}
if (chan[i].std.panL.had) {
chan[i].panL=chan[i].std.panL.val&15;
}
@ -109,12 +120,13 @@ void DivPlatformT6W28::tick(bool sysTick) {
}
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
rWrite(1,0xe0+chan[i].duty);
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].freq>1023) chan[i].freq=1023;
if (i==3) {
rWrite(1,0xe7);
rWrite(1,0x80|(2<<5)|(chan[3].freq&15));
rWrite(1,chan[3].freq>>4);
} else {
@ -129,6 +141,8 @@ void DivPlatformT6W28::tick(bool sysTick) {
}
int DivPlatformT6W28::dispatch(DivCommand c) {
double CHIP_DIVIDER=16;
if (c.chan==3) CHIP_DIVIDER=15;
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE);
@ -142,6 +156,7 @@ int DivPlatformT6W28::dispatch(DivCommand c) {
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
writeOutVol(c.chan);
}
chan[c.chan].insChanged=false;
break;
@ -150,6 +165,7 @@ int DivPlatformT6W28::dispatch(DivCommand c) {
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(NULL);
writeOutVol(c.chan);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
@ -166,8 +182,7 @@ int DivPlatformT6W28::dispatch(DivCommand c) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
if (chan[c.chan].active) {
}
writeOutVol(c.chan);
}
}
break;
@ -205,7 +220,9 @@ int DivPlatformT6W28::dispatch(DivCommand c) {
break;
}
case DIV_CMD_STD_NOISE_MODE:
chan[c.chan].noise=c.value;
if (c.chan!=3) break;
chan[c.chan].duty=(((c.value&15)==1)?4:0)|3;
rWrite(1,0xe0+chan[c.chan].duty);
break;
case DIV_CMD_PANNING: {
chan[c.chan].panL=c.value>>4;
@ -226,7 +243,7 @@ int DivPlatformT6W28::dispatch(DivCommand c) {
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 31;
return 15;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
@ -289,6 +306,8 @@ void DivPlatformT6W28::reset() {
cycles=0;
curChan=-1;
delay=0;
// default noise mode
rWrite(1,0xe7);
}
bool DivPlatformT6W28::isStereo() {

View File

@ -29,8 +29,8 @@ class DivPlatformT6W28: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2, note;
int ins;
unsigned char panL, panR;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise;
unsigned char panL, panR, duty;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
signed char vol, outVol;
DivMacroInt std;
void macroInit(DivInstrument* which) {
@ -46,13 +46,13 @@ class DivPlatformT6W28: public DivDispatch {
ins(-1),
panL(15),
panR(15),
duty(7),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
inPorta(false),
noise(false),
vol(15),
outVol(15) {}
};

View File

@ -84,6 +84,8 @@ const char* regCheatSheetVB[]={
"S6EV0", "550",
"S6EV1", "554",
"S6RAM", "558",
"RESET", "580",
NULL
};
@ -148,6 +150,13 @@ void DivPlatformVB::tick(bool sysTick) {
}
chan[i].freqChanged=true;
}
if (i==5 && chan[i].std.duty.had) {
if ((chan[i].std.duty.val&7)!=((chan[i].envHigh>>4)&7)) {
chan[i].envHigh&=~0x70;
chan[i].envHigh|=(chan[i].std.duty.val&7)<<4;
writeEnv(i,true);
}
}
if (chan[i].std.wave.had) {
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave.val;
@ -176,7 +185,7 @@ void DivPlatformVB::tick(bool sysTick) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
// ???
chWrite(i,0x00,0x80);
}
if (chan[i].active) {
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) {
@ -310,12 +319,12 @@ int DivPlatformVB::dispatch(DivCommand c) {
break;
case DIV_CMD_FDS_MOD_DEPTH: // set modulation
if (c.chan!=4) break;
modulation=(c.value<<4)&15;
modulation=(c.value&15)<<4;
modType=true;
chWrite(4,0x07,modulation);
if (modulation!=0) {
chan[c.chan].envHigh&=~0x70;
chan[c.chan].envHigh|=0x40|((c.value&15)<<4);
chan[c.chan].envHigh|=0x40|(c.value&0xf0);
} else {
chan[c.chan].envHigh&=~0x70;
}
@ -328,7 +337,7 @@ int DivPlatformVB::dispatch(DivCommand c) {
chWrite(4,0x07,modulation);
if (modulation!=0) {
chan[c.chan].envHigh&=~0x70;
chan[c.chan].envHigh|=0x10;
chan[c.chan].envHigh|=0x40;
} else {
chan[c.chan].envHigh&=~0x70;
}

View File

@ -174,6 +174,7 @@ void DivPlatformVERA::tick(bool sysTick) {
if (i<16) {
if (chan[i].std.panL.had) {
chan[i].pan=chan[i].std.panL.val&3;
chan[i].pan=((chan[i].pan&1)<<1)|((chan[i].pan&2)>>1);
rWriteHi(i,2,isMuted[i]?0:chan[i].pan);
}
}
@ -329,7 +330,7 @@ int DivPlatformVERA::dispatch(DivCommand c) {
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VERA));
}
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=calcNoteFreq(c.chan,chan[c.chan].note);
if (!chan[c.chan].inPorta && c.value && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=calcNoteFreq(c.chan,chan[c.chan].note);
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_STD_NOISE_MODE:

View File

@ -796,6 +796,7 @@ void DivEngine::registerSystems() {
{0x10, {DIV_CMD_AMIGA_FILTER, "10xx: Toggle filter (0 disables; 1 enables)"}},
{0x11, {DIV_CMD_AMIGA_AM, "11xx: Toggle AM with next channel"}},
{0x12, {DIV_CMD_AMIGA_PM, "12xx: Toggle period modulation with next channel"}},
{0x13, {DIV_CMD_WAVE, "13xx: Set waveform"}}
}
);
@ -1594,7 +1595,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_MSM6258]=new DivSysDef(
"OKI MSM6258", NULL, 0xab, 0, 1, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_VOX,
"OKI MSM6258", NULL, 0xab, 0, 1, false, true, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_VOX,
"an ADPCM sound chip manufactured by OKI and used in the Sharp X68000.",
{"Sample"},
{"PCM"},
@ -1673,7 +1674,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2612_FRAC]=new DivSysDef(
"Yamaha YM2612 (OPN2) with DualPCM", NULL, 0xbe, 0, 7, true, false, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Yamaha YM2612 (OPN2) with DualPCM", NULL, 0xbe, 0, 7, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6/PCM 1", "PCM 2"},
{"F1", "F2", "F3", "F4", "F5", "P1", "P2"},
@ -1685,7 +1686,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2612_FRAC_EXT]=new DivSysDef(
"Yamaha YM2612 (OPN2) Extended Channel 3 with DualPCM and CSM", NULL, 0xbd, 0, 11, true, false, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Yamaha YM2612 (OPN2) Extended Channel 3 with DualPCM and CSM", NULL, 0xbd, 0, 11, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6/PCM 1", "PCM 2", "CSM Timer"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "P1", "P2", "CSM"},
@ -1697,7 +1698,8 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_T6W28]=new DivSysDef(
"T6W28", NULL, 0xbf, 0, 4, false, true, 0, false, 0,
// 0x0a = wild guess. it may as well be 0x83
"T6W28", NULL, 0xbf, 0x0a, 4, false, true, 0x160, false, 0,
"an SN76489 derivative used in Neo Geo Pocket, has independent stereo volume and noise channel frequency.",
{"Square 1", "Square 2", "Square 3", "Noise"},
{"S1", "S2", "S3", "NO"},
@ -1705,7 +1707,7 @@ void DivEngine::registerSystems() {
{DIV_INS_T6W28, DIV_INS_T6W28, DIV_INS_T6W28, DIV_INS_T6W28},
{},
{
{0x20, {DIV_CMD_STD_NOISE_MODE, "20xy: Set noise mode (x: preset/variable; y: thin pulse/noise)"}}
{0x20, {DIV_CMD_STD_NOISE_MODE, "20xx: Set noise length (0: short, 1: long)"}}
}
);

View File

@ -24,7 +24,7 @@
constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond) {
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, bool directStream) {
unsigned char baseAddr1=isSecond?0xa0:0x50;
unsigned char baseAddr2=isSecond?0x80:0;
unsigned short baseAddr2S=isSecond?0x8000:0;
@ -34,6 +34,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
switch (sys) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
for (int i=0; i<3; i++) { // set SL and RR to highest
w->writeC(2|baseAddr1);
w->writeC(0x80+i);
@ -79,6 +81,14 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0x90|(i<<5)|15);
}
break;
case DIV_SYSTEM_T6W28:
for (int i=0; i<4; i++) {
w->writeC(0x30);
w->writeC(0x90|(i<<5)|15);
w->writeC(0x50);
w->writeC(0x90|(i<<5)|15);
}
break;
case DIV_SYSTEM_GB:
// square 1
w->writeC(0xb3);
@ -531,47 +541,57 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(baseAddr2|12);
w->writeC(1);
break;
case DIV_SYSTEM_VBOY:
// isn't it amazing when a chip has a built-in reset command?
w->writeC(0xc7);
w->writeS_BE(baseAddr2S|(0x580>>2));
w->writeC(0xff);
break;
default:
break;
}
}
if (write.addr>=0xffff0000) { // Furnace special command
unsigned char streamID=streamOff+((write.addr&0xff00)>>8);
logD("writing stream command %x:%x with stream ID %d",write.addr,write.val,streamID);
switch (write.addr&0xff) {
case 0: // play sample
if (write.val<song.sampleLen) {
DivSample* sample=song.sample[write.val];
w->writeC(0x95);
w->writeC(streamID);
w->writeS(write.val); // sample number
w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0)|(sampleDir[streamID]?0x10:0)); // flags
if (sample->isLoopable() && !sampleDir[streamID]) {
loopTimer[streamID]=sample->length8;
loopSample[streamID]=write.val;
if (!directStream) {
unsigned char streamID=streamOff+((write.addr&0xff00)>>8);
logD("writing stream command %x:%x with stream ID %d",write.addr,write.val,streamID);
switch (write.addr&0xff) {
case 0: // play sample
if (write.val<song.sampleLen) {
DivSample* sample=song.sample[write.val];
w->writeC(0x95);
w->writeC(streamID);
w->writeS(write.val); // sample number
w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0)|(sampleDir[streamID]?0x10:0)); // flags
if (sample->isLoopable() && !sampleDir[streamID]) {
loopTimer[streamID]=sample->length8;
loopSample[streamID]=write.val;
}
}
}
break;
case 1: // set sample freq
w->writeC(0x92);
w->writeC(streamID);
w->writeI(write.val);
loopFreq[streamID]=write.val;
break;
case 2: // stop sample
w->writeC(0x94);
w->writeC(streamID);
loopSample[streamID]=-1;
break;
case 3: // set sample direction
sampleDir[streamID]=write.val;
break;
break;
case 1: // set sample freq
w->writeC(0x92);
w->writeC(streamID);
w->writeI(write.val);
loopFreq[streamID]=write.val;
break;
case 2: // stop sample
w->writeC(0x94);
w->writeC(streamID);
loopSample[streamID]=-1;
break;
case 3: // set sample direction
sampleDir[streamID]=write.val;
break;
}
}
return;
}
switch (sys) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
switch (write.addr>>8) {
case 0: // port 0
w->writeC(2|baseAddr1);
@ -593,6 +613,14 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(smsAddr);
w->writeC(write.val);
break;
case DIV_SYSTEM_T6W28:
if (write.addr) {
w->writeC(0x30);
} else {
w->writeC(0x50);
}
w->writeC(write.val);
break;
case DIV_SYSTEM_GB:
w->writeC(0xb3);
w->writeC(baseAddr2|((write.addr-16)&0xff));
@ -847,20 +875,20 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
}
#define CHIP_VOL(_id,_mult) { \
double _vol=fabs(song.systemVol[i])*4.0*_mult; \
double _vol=fabs((float)song.systemVol[i])*4.0*_mult; \
if (_vol<0.0) _vol=0.0; \
if (_vol>32767.0) _vol=32767.0; \
chipVol.push_back((_id)|(0x80000000)|(((unsigned int)_vol)<<16)); \
}
#define CHIP_VOL_SECOND(_id,_mult) { \
double _vol=fabs(song.systemVol[i])*4.0*_mult; \
double _vol=fabs((float)song.systemVol[i])*4.0*_mult; \
if (_vol<0.0) _vol=0.0; \
if (_vol>32767.0) _vol=32767.0; \
chipVol.push_back((_id)|(0x80000100)|(((unsigned int)_vol)<<16)); \
}
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints) {
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints, bool directStream) {
if (version<0x150) {
lastError="VGM version is too low";
return NULL;
@ -964,6 +992,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
int loopSample[DIV_MAX_CHANS];
bool sampleDir[DIV_MAX_CHANS];
std::vector<unsigned int> chipVol;
std::vector<DivDelayedWrite> delayedWrites[32];
std::vector<std::pair<int,DivDelayedWrite>> sortedWrites;
for (int i=0; i<DIV_MAX_CHANS; i++) {
loopTimer[i]=0;
@ -1162,6 +1192,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
break;
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
if (!hasOPN2) {
hasOPN2=disCont[i].dispatch->chipClock;
willExport[i]=true;
@ -1416,6 +1448,15 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
howManyChips++;
}
break;
case DIV_SYSTEM_T6W28:
if (!hasSN) {
hasSN=0xc0000000|disCont[i].dispatch->chipClock;
CHIP_VOL(0,1.0);
snNoiseConfig=3;
snNoiseSize=15;
willExport[i]=true;
}
break;
default:
break;
}
@ -1580,7 +1621,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
unsigned int exHeaderOff=w->tell();
if (version>=0x170) {
logD("writing extended header...");
w->writeI(8);
w->writeI(12);
w->writeI(0);
w->writeI(4);
@ -1608,7 +1649,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
sampleSeek+=sample->length8;
}
if (writeDACSamples) for (int i=0; i<song.sampleLen; i++) {
if (writeDACSamples && !directStream) for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
w->writeC(0x67);
w->writeC(0x66);
@ -1619,7 +1660,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
}
}
if (writeNESSamples) for (int i=0; i<song.sampleLen; i++) {
if (writeNESSamples && !directStream) for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
w->writeC(0x67);
w->writeC(0x66);
@ -1630,7 +1671,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
}
}
if (writePCESamples) for (int i=0; i<song.sampleLen; i++) {
if (writePCESamples && !directStream) for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
w->writeC(0x67);
w->writeC(0x66);
@ -1821,87 +1862,91 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
// initialize streams
int streamID=0;
for (int i=0; i<song.systemLen; i++) {
if (!willExport[i]) continue;
streamIDs[i]=streamID;
switch (song.system[i]) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
w->writeC(0x90);
w->writeC(streamID);
w->writeC(0x02);
w->writeC(0); // port
w->writeC(0x2a); // DAC
w->writeC(0x91);
w->writeC(streamID);
w->writeC(0);
w->writeC(1);
w->writeC(0);
w->writeC(0x92);
w->writeC(streamID);
w->writeI(32000); // default
streamID++;
break;
case DIV_SYSTEM_NES:
w->writeC(0x90);
w->writeC(streamID);
w->writeC(20);
w->writeC(0); // port
w->writeC(0x11); // DAC
w->writeC(0x91);
w->writeC(streamID);
w->writeC(7);
w->writeC(1);
w->writeC(0);
w->writeC(0x92);
w->writeC(streamID);
w->writeI(32000); // default
streamID++;
break;
case DIV_SYSTEM_PCE:
for (int j=0; j<6; j++) {
if (!directStream) {
for (int i=0; i<song.systemLen; i++) {
if (!willExport[i]) continue;
streamIDs[i]=streamID;
switch (song.system[i]) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
w->writeC(0x90);
w->writeC(streamID);
w->writeC(27);
w->writeC(j); // port
w->writeC(0x06); // select+DAC
w->writeC(0x02);
w->writeC(0); // port
w->writeC(0x2a); // DAC
w->writeC(0x91);
w->writeC(streamID);
w->writeC(5);
w->writeC(0);
w->writeC(1);
w->writeC(0);
w->writeC(0x92);
w->writeC(streamID);
w->writeI(16000); // default
w->writeI(32000); // default
streamID++;
}
break;
case DIV_SYSTEM_SWAN:
w->writeC(0x90);
w->writeC(streamID);
w->writeC(isSecond[i]?0xa1:0x21);
w->writeC(0); // port
w->writeC(0x09); // DAC
break;
case DIV_SYSTEM_NES:
w->writeC(0x90);
w->writeC(streamID);
w->writeC(20);
w->writeC(0); // port
w->writeC(0x11); // DAC
w->writeC(0x91);
w->writeC(streamID);
w->writeC(0);
w->writeC(1);
w->writeC(0);
w->writeC(0x91);
w->writeC(streamID);
w->writeC(7);
w->writeC(1);
w->writeC(0);
w->writeC(0x92);
w->writeC(streamID);
w->writeI(24000); // default
streamID++;
break;
default:
break;
w->writeC(0x92);
w->writeC(streamID);
w->writeI(32000); // default
streamID++;
break;
case DIV_SYSTEM_PCE:
for (int j=0; j<6; j++) {
w->writeC(0x90);
w->writeC(streamID);
w->writeC(27);
w->writeC(j); // port
w->writeC(0x06); // select+DAC
w->writeC(0x91);
w->writeC(streamID);
w->writeC(5);
w->writeC(1);
w->writeC(0);
w->writeC(0x92);
w->writeC(streamID);
w->writeI(16000); // default
streamID++;
}
break;
case DIV_SYSTEM_SWAN:
w->writeC(0x90);
w->writeC(streamID);
w->writeC(isSecond[i]?0xa1:0x21);
w->writeC(0); // port
w->writeC(0x09); // DAC
w->writeC(0x91);
w->writeC(streamID);
w->writeC(0);
w->writeC(1);
w->writeC(0);
w->writeC(0x92);
w->writeC(streamID);
w->writeI(24000); // default
streamID++;
break;
default:
break;
}
}
}
@ -1930,10 +1975,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
break;
}
// stop all streams
for (int i=0; i<streamID; i++) {
w->writeC(0x94);
w->writeC(i);
loopSample[i]=-1;
if (!directStream) {
for (int i=0; i<streamID; i++) {
w->writeC(0x94);
w->writeC(i);
loopSample[i]=-1;
}
}
if (!playing) {
@ -1965,63 +2012,103 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
for (int i=0; i<song.systemLen; i++) {
std::vector<DivRegWrite>& writes=disCont[i].dispatch->getRegisterWrites();
for (DivRegWrite& j: writes) {
performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i]);
performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i],directStream);
writeCount++;
}
writes.clear();
}
// check whether we need to loop
int totalWait=cycles>>MASTER_CLOCK_PREC;
for (int i=0; i<streamID; i++) {
if (loopSample[i]>=0) {
loopTimer[i]-=(loopFreq[i]/44100.0)*(double)totalWait;
if (directStream) {
// render stream of all chips
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->fillStream(delayedWrites[i],44100,totalWait);
for (DivDelayedWrite& j: delayedWrites[i]) {
sortedWrites.push_back(std::pair<int,DivDelayedWrite>(i,j));
}
delayedWrites[i].clear();
}
}
bool haveNegatives=false;
for (int i=0; i<streamID; i++) {
if (loopSample[i]>=0) {
if (loopTimer[i]<0) {
haveNegatives=true;
if (!sortedWrites.empty()) {
// sort if more than one chip
if (song.systemLen>1) {
std::sort(sortedWrites.begin(),sortedWrites.end(),[](const std::pair<int,DivDelayedWrite>& a, const std::pair<int,DivDelayedWrite>& b) -> bool {
return a.second.time<b.second.time;
});
}
// write it out
int lastOne=0;
for (std::pair<int,DivDelayedWrite>& i: sortedWrites) {
if (i.second.time>lastOne) {
// write delay
int delay=i.second.time-lastOne;
if (delay>16) {
w->writeC(0x61);
w->writeS(totalWait);
} else if (delay>0) {
w->writeC(0x70+delay-1);
}
lastOne=i.second.time;
}
// write write
performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],directStream);
}
sortedWrites.clear();
totalWait-=lastOne;
}
} else {
for (int i=0; i<streamID; i++) {
if (loopSample[i]>=0) {
loopTimer[i]-=(loopFreq[i]/44100.0)*(double)totalWait;
}
}
}
while (haveNegatives) {
// finish all negatives
int nextToTouch=-1;
bool haveNegatives=false;
for (int i=0; i<streamID; i++) {
if (loopSample[i]>=0) {
if (loopTimer[i]<0) {
if (nextToTouch>=0) {
if (loopTimer[nextToTouch]>loopTimer[i]) nextToTouch=i;
} else {
nextToTouch=i;
}
haveNegatives=true;
}
}
}
if (nextToTouch>=0) {
double waitTime=totalWait+(loopTimer[nextToTouch]*(44100.0/MAX(1,loopFreq[nextToTouch])));
if (waitTime>0) {
w->writeC(0x61);
w->writeS(waitTime);
logV("wait is: %f",waitTime);
totalWait-=waitTime;
tickCount+=waitTime;
}
if (loopSample[nextToTouch]<song.sampleLen) {
DivSample* sample=song.sample[loopSample[nextToTouch]];
// insert loop
if (sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)<sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)) {
w->writeC(0x93);
w->writeC(nextToTouch);
w->writeI(sampleOff8[loopSample[nextToTouch]]+sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT));
w->writeC(0x81);
w->writeI(sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)-sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT));
while (haveNegatives) {
// finish all negatives
int nextToTouch=-1;
for (int i=0; i<streamID; i++) {
if (loopSample[i]>=0) {
if (loopTimer[i]<0) {
if (nextToTouch>=0) {
if (loopTimer[nextToTouch]>loopTimer[i]) nextToTouch=i;
} else {
nextToTouch=i;
}
}
}
}
loopSample[nextToTouch]=-1;
} else {
haveNegatives=false;
if (nextToTouch>=0) {
double waitTime=totalWait+(loopTimer[nextToTouch]*(44100.0/MAX(1,loopFreq[nextToTouch])));
if (waitTime>0) {
w->writeC(0x61);
w->writeS(waitTime);
logV("wait is: %f",waitTime);
totalWait-=waitTime;
tickCount+=waitTime;
}
if (loopSample[nextToTouch]<song.sampleLen) {
DivSample* sample=song.sample[loopSample[nextToTouch]];
// insert loop
if (sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)<sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)) {
w->writeC(0x93);
w->writeC(nextToTouch);
w->writeI(sampleOff8[loopSample[nextToTouch]]+sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT));
w->writeC(0x81);
w->writeI(sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)-sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT));
}
}
loopSample[nextToTouch]=-1;
} else {
haveNegatives=false;
}
}
}
// write wait

View File

@ -23,18 +23,13 @@
const char* aboutLine[]={
"tildearrow",
"is not so happy to present",
"is proud to present",
"",
("Furnace " DIV_VERSION),
"",
"the biggest multi-system chiptune tracker!",
"featuring DefleMask song compatibility.",
"",
"what a mess of a versioning scheme we have...",
"I mean it! these pre-releases are like normal releases",
"by now but only because I promised you to have SNES in",
"0.6pre2 I am doing this whole mess...",
"",
"> CREDITS <",
"",
"-- program --",
@ -45,6 +40,7 @@ const char* aboutLine[]={
"laoo",
"OPNA2608",
"superctr",
"System64",
"",
"-- graphics/UI design --",
"tildearrow",
@ -186,7 +182,7 @@ void FurnaceGUI::drawAbout() {
// do stuff
if (ImGui::Begin("About Furnace",NULL,ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoDocking|ImGuiWindowFlags_NoTitleBar)) {
ImGui::SetWindowPos(ImVec2(0,0));
ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale));
ImGui::SetWindowSize(ImVec2(canvasW,canvasH));
ImGui::PushFont(bigFont);
ImDrawList* dl=ImGui::GetWindowDrawList();
float r=0;
@ -194,47 +190,47 @@ void FurnaceGUI::drawAbout() {
float b=0;
float peakMix=settings.partyTime?((peak[0]+peak[1])*0.5):0.3;
ImGui::ColorConvertHSVtoRGB(aboutHue,1.0,0.25+MIN(0.75f,peakMix*0.75f),r,g,b);
dl->AddRectFilled(ImVec2(0,0),ImVec2(scrW*dpiScale,scrH*dpiScale),0xff000000);
dl->AddRectFilled(ImVec2(0,0),ImVec2(canvasW,canvasH),0xff000000);
bool skip=false;
bool skip2=false;
for (int i=(-80-sin(double(aboutSin)*2*M_PI/120.0)*80.0)*2; i<scrW; i+=160) {
for (int i=(-80-sin(double(aboutSin)*2*M_PI/120.0)*80.0)*2*dpiScale; i<canvasW; i+=160*dpiScale) {
skip2=!skip2;
skip=skip2;
for (int j=(-80-cos(double(aboutSin)*2*M_PI/150.0)*80.0)*2; j<scrH; j+=160) {
for (int j=(-80-cos(double(aboutSin)*2*M_PI/150.0)*80.0)*2*dpiScale; j<canvasH; j+=160*dpiScale) {
skip=!skip;
if (skip) continue;
dl->AddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.25,g*0.25,b*0.25,1.0)));
dl->AddRectFilled(ImVec2(i,j),ImVec2(i+160*dpiScale,j+160*dpiScale),ImGui::GetColorU32(ImVec4(r*0.25,g*0.25,b*0.25,1.0)));
}
}
skip=false;
skip2=false;
for (int i=(-80-cos(double(aboutSin)*2*M_PI/120.0)*80.0)*2; i<scrW; i+=160) {
for (int i=(-80-cos(double(aboutSin)*2*M_PI/120.0)*80.0)*2*dpiScale; i<canvasW; i+=160*dpiScale) {
skip2=!skip2;
skip=skip2;
for (int j=(-80-sin(double(aboutSin)*2*M_PI/150.0)*80.0)*2; j<scrH; j+=160) {
for (int j=(-80-sin(double(aboutSin)*2*M_PI/150.0)*80.0)*2*dpiScale; j<canvasH; j+=160*dpiScale) {
skip=!skip;
if (skip) continue;
dl->AddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.5,g*0.5,b*0.5,1.0)));
dl->AddRectFilled(ImVec2(i,j),ImVec2(i+160*dpiScale,j+160*dpiScale),ImGui::GetColorU32(ImVec4(r*0.5,g*0.5,b*0.5,1.0)));
}
}
skip=false;
skip2=false;
for (int i=(-160+fmod(aboutSin*2,160))*2; i<scrW; i+=160) {
for (int i=(-160+fmod(aboutSin*2,160))*2*dpiScale; i<canvasW; i+=160*dpiScale) {
skip2=!skip2;
skip=skip2;
for (int j=(-240-cos(double(aboutSin*M_PI/300.0))*240.0)*2; j<scrH; j+=160) {
for (int j=(-240-cos(double(aboutSin*M_PI/300.0))*240.0)*2*dpiScale; j<canvasH; j+=160*dpiScale) {
skip=!skip;
if (skip) continue;
dl->AddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.75,g*0.75,b*0.75,1.0)));
dl->AddRectFilled(ImVec2(i,j),ImVec2(i+160*dpiScale,j+160*dpiScale),ImGui::GetColorU32(ImVec4(r*0.75,g*0.75,b*0.75,1.0)));
}
}
for (size_t i=0; i<aboutCount; i++) {
double posX=(scrW*dpiScale/2.0)+(sin(double(i)*0.5+double(aboutScroll)/90.0)*120*dpiScale)-(ImGui::CalcTextSize(aboutLine[i]).x*0.5);
double posY=(scrH-aboutScroll+42*i)*dpiScale;
if (posY<-80*dpiScale || posY>scrH*dpiScale) continue;
double posX=(canvasW/2.0)+(sin(double(i)*0.5+double(aboutScroll)/(90.0*dpiScale))*120*dpiScale)-(ImGui::CalcTextSize(aboutLine[i]).x*0.5);
double posY=(canvasH-aboutScroll+42*i*dpiScale);
if (posY<-80*dpiScale || posY>canvasH) continue;
dl->AddText(bigFont,bigFont->FontSize,
ImVec2(posX+dpiScale,posY+dpiScale),
0xff000000,aboutLine[i]);
@ -256,12 +252,12 @@ void FurnaceGUI::drawAbout() {
float timeScale=60.0f*ImGui::GetIO().DeltaTime;
aboutHue+=(0.001+peakMix*0.004)*timeScale;
aboutScroll+=(2+(peakMix>0.78)*3)*timeScale;
aboutScroll+=(2+(peakMix>0.78)*3)*timeScale*dpiScale;
aboutSin+=(1+(peakMix>0.75)*2)*timeScale;
while (aboutHue>1) aboutHue--;
while (aboutSin>=2400) aboutSin-=2400;
if (aboutScroll>(42*aboutCount+scrH)) aboutScroll=-20;
if (aboutScroll>(42*dpiScale*aboutCount+canvasH)) aboutScroll=-20*dpiScale;
WAKE_UP;
}

View File

@ -109,7 +109,7 @@ void FurnaceGUI::drawChanOsc() {
nextWindow=GUI_WINDOW_NOTHING;
}
if (!chanOscOpen) return;
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH));
if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen,globalWinFlags|((chanOscOptions)?0:ImGuiWindowFlags_NoTitleBar))) {
bool centerSettingReset=false;
ImDrawList* dl=ImGui::GetWindowDrawList();

View File

@ -42,7 +42,7 @@ void FurnaceGUI::drawDebug() {
nextWindow=GUI_WINDOW_NOTHING;
}
if (!debugOpen) return;
ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(canvasW,canvasH));
if (ImGui::Begin("Debug",&debugOpen,globalWinFlags|ImGuiWindowFlags_NoDocking)) {
ImGui::Text("NOTE: use with caution.");
if (ImGui::TreeNode("Debug Controls")) {

View File

@ -38,8 +38,8 @@ void FurnaceGUI::drawMobileControls() {
mobileMenuPos=0.0f;
}
}
ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*scrH*dpiScale)-(0.16*scrW*dpiScale)):ImVec2(0.5*scrW*dpiScale*mobileMenuPos,0.0f));
ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.16*scrW*dpiScale):ImVec2(0.16*scrH*dpiScale,scrH*dpiScale));
ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*canvasH)-(0.16*canvasW)):ImVec2(0.5*canvasW*mobileMenuPos,0.0f));
ImGui::SetNextWindowSize(portrait?ImVec2(canvasW,0.16*canvasW):ImVec2(0.16*canvasH,canvasH));
if (ImGui::Begin("Mobile Controls",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) {
float avail=portrait?ImGui::GetContentRegionAvail().y:ImGui::GetContentRegionAvail().x;
ImVec2 buttonSize=ImVec2(avail,avail);
@ -101,8 +101,8 @@ void FurnaceGUI::drawMobileControls() {
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS;
ImGui::End();
ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*scrH*dpiScale)):ImVec2(0.5*scrW*dpiScale*(mobileMenuPos-1.0),0.0f));
ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.65*scrH*dpiScale):ImVec2(0.5*scrW*dpiScale,scrH*dpiScale));
ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*canvasH)):ImVec2(0.5*canvasW*(mobileMenuPos-1.0),0.0f));
ImGui::SetNextWindowSize(portrait?ImVec2(canvasW,0.65*canvasH):ImVec2(0.5*canvasW,canvasH));
if (ImGui::Begin("Mobile Menu",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) {
if (ImGui::BeginTable("SceneSel",5)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,1.0f);

View File

@ -9,6 +9,7 @@ void FurnaceGUI::drawEffectList() {
nextWindow=GUI_WINDOW_NOTHING;
}
if (!effectListOpen) return;
ImGui::SetNextWindowSizeConstraints(ImVec2(60.0f*dpiScale,20.0f*dpiScale),ImVec2(canvasW,canvasH));
if (ImGui::Begin("Effect List",&effectListOpen,globalWinFlags)) {
ImGui::Text("Chip at cursor: %s",e->getSystemName(e->sysOfChan[cursor.xCoarse]));
if (ImGui::BeginTable("effectList",2)) {

View File

@ -494,7 +494,7 @@ void FurnaceGUI::drawFindReplace() {
nextWindow=GUI_WINDOW_NOTHING;
}
if (!findOpen) return;
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH));
if (ImGui::Begin("Find/Replace",&findOpen,globalWinFlags)) {
if (curQuery.empty()) {
curQuery.push_back(FurnaceGUIFindQuery());

View File

@ -1,3 +1,5 @@
#define _USE_MATH_DEFINES
// OK, sorry for inserting the define here but I'm so tired of this extension
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
@ -20,7 +22,7 @@
// I hate you clangd extension!
// how about you DON'T insert random headers before this freaking important
// define!!!!!!
#define _USE_MATH_DEFINES
#include "gui.h"
#include "util.h"
#include "icon.h"
@ -36,17 +38,12 @@
#include "plot_nolerp.h"
#include "guiConst.h"
#include "intConst.h"
#include "scaling.h"
#include <stdint.h>
#include <zlib.h>
#include <fmt/printf.h>
#include <stdexcept>
#ifdef __APPLE__
extern "C" {
#include "macstuff.h"
}
#endif
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
@ -1759,7 +1756,14 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"all files", ".*"},
"compatible files{.fur,.dmf,.mod},another option{.wav,.ttf},.*",
workingDirTest,
dpiScale
dpiScale,
[](const char* path) {
if (path!=NULL) {
logI("Callback Result: %s",path);
} else {
logI("Callback Result: NULL");
}
}
);
break;
case GUI_FILE_TEST_OPEN_MULTI:
@ -1772,7 +1776,13 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"compatible files{.fur,.dmf,.mod},another option{.wav,.ttf},.*",
workingDirTest,
dpiScale,
NULL,
[](const char* path) {
if (path!=NULL) {
logI("Callback Result: %s",path);
} else {
logI("Callback Result: NULL");
}
},
true
);
break;
@ -2741,12 +2751,8 @@ void FurnaceGUI::processPoint(SDL_Event& ev) {
TouchPoint* point=NULL;
FIND_POINT(point,-1);
if (point!=NULL) {
point->x=ev.motion.x;
point->y=ev.motion.y;
#ifdef __APPLE__
point->x*=dpiScale;
point->y*=dpiScale;
#endif
point->x=(double)ev.motion.x*((double)canvasW/(double)scrW);
point->y=(double)ev.motion.y*((double)canvasH/(double)scrH);
}
break;
}
@ -2787,8 +2793,8 @@ void FurnaceGUI::processPoint(SDL_Event& ev) {
if (point!=NULL) {
float prevX=point->x;
float prevY=point->y;
point->x=ev.tfinger.x*scrW*dpiScale;
point->y=ev.tfinger.y*scrH*dpiScale;
point->x=ev.tfinger.x*canvasW;
point->y=ev.tfinger.y*canvasH;
point->z=ev.tfinger.pressure;
if (point->id==0) {
@ -2807,7 +2813,7 @@ void FurnaceGUI::processPoint(SDL_Event& ev) {
break;
}
}
TouchPoint newPoint(ev.tfinger.fingerId,ev.tfinger.x*scrW*dpiScale,ev.tfinger.y*scrH*dpiScale,ev.tfinger.pressure);
TouchPoint newPoint(ev.tfinger.fingerId,ev.tfinger.x*canvasW,ev.tfinger.y*canvasH,ev.tfinger.pressure);
activePoints.push_back(newPoint);
pressedPoints.push_back(newPoint);
@ -2973,16 +2979,10 @@ bool FurnaceGUI::loop() {
if (!doThreadedInput) processEvent(&ev);
switch (ev.type) {
case SDL_MOUSEMOTION: {
int motionX=ev.motion.x;
int motionY=ev.motion.y;
int motionXrel=ev.motion.xrel;
int motionYrel=ev.motion.yrel;
#ifdef __APPLE__
motionX*=dpiScale;
motionY*=dpiScale;
motionXrel*=dpiScale;
motionYrel*=dpiScale;
#endif
int motionX=(double)ev.motion.x*((double)canvasW/(double)scrW);
int motionY=(double)ev.motion.y*((double)canvasH/(double)scrH);
int motionXrel=(double)ev.motion.xrel*((double)canvasW/(double)scrW);
int motionYrel=(double)ev.motion.yrel*((double)canvasH/(double)scrH);
pointMotion(motionX,motionY,motionXrel,motionYrel);
break;
}
@ -2999,13 +2999,8 @@ bool FurnaceGUI::loop() {
case SDL_WINDOWEVENT:
switch (ev.window.event) {
case SDL_WINDOWEVENT_RESIZED:
#ifdef __APPLE__
scrW=ev.window.data1;
scrH=ev.window.data2;
#else
scrW=ev.window.data1/dpiScale;
scrH=ev.window.data2/dpiScale;
#endif
portrait=(scrW<scrH);
logV("portrait: %d (%dx%d)",portrait,scrW,scrH);
updateWindow=true;
@ -3085,6 +3080,9 @@ bool FurnaceGUI::loop() {
scrConfW=scrW;
scrConfH=scrH;
}
// update canvas size as well
SDL_GetRendererOutputSize(sdlRend,&canvasW,&canvasH);
}
wantCaptureKeyboard=ImGui::GetIO().WantTextInput;
@ -3377,6 +3375,14 @@ bool FurnaceGUI::loop() {
"pattern indexes are ordered as they appear in the song."
);
}
ImGui::Checkbox("direct stream mode",&vgmExportDirectStream);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"required for DualPCM and MSM6258 export.\n\n"
"allows for volume/direction changes when playing samples,\n"
"at the cost of a massive increase in file size."
);
}
ImGui::Text("chips to export:");
bool hasOneAtLeast=false;
for (int i=0; i<e->song.systemLen; i++) {
@ -3446,6 +3452,8 @@ bool FurnaceGUI::loop() {
if (picked!=DIV_SYSTEM_NULL) {
if (!e->addSystem(picked)) {
showError("cannot add chip! ("+e->getLastError()+")");
} else {
MARK_MODIFIED;
}
ImGui::CloseCurrentPopup();
if (e->song.autoSystem) {
@ -3471,6 +3479,7 @@ bool FurnaceGUI::loop() {
DivSystem picked=systemPicker();
if (picked!=DIV_SYSTEM_NULL) {
e->changeSystem(i,picked,preserveChanPos);
MARK_MODIFIED;
if (e->song.autoSystem) {
autoDetectSystem();
}
@ -3488,6 +3497,8 @@ bool FurnaceGUI::loop() {
if (ImGui::MenuItem(fmt::sprintf("%d. %s##_SYSR%d",i+1,getSystemName(e->song.system[i]),i).c_str())) {
if (!e->removeSystem(i,preserveChanPos)) {
showError("cannot remove chip! ("+e->getLastError()+")");
} else {
MARK_MODIFIED;
}
if (e->song.autoSystem) {
autoDetectSystem();
@ -3750,10 +3761,10 @@ bool FurnaceGUI::loop() {
firstFrame=false;
#ifdef IS_MOBILE
SDL_GetWindowSize(sdlWin,&scrW,&scrH);
scrW/=dpiScale;
scrH/=dpiScale;
portrait=(scrW<scrH);
logV("portrait: %d (%dx%d)",portrait,scrW,scrH);
SDL_GetRendererOutputSize(sdlRend,&canvasW,&canvasH);
#endif
if (patternOpen) nextWindow=GUI_WINDOW_PATTERN;
#ifdef __APPLE__
@ -3771,12 +3782,12 @@ bool FurnaceGUI::loop() {
ImGui::CloseCurrentPopup();
}
ImDrawList* dl=ImGui::GetForegroundDrawList();
dl->AddRectFilled(ImVec2(0.0f,0.0f),ImVec2(scrW*dpiScale,scrH*dpiScale),ImGui::ColorConvertFloat4ToU32(uiColors[GUI_COLOR_MODAL_BACKDROP]));
dl->AddRectFilled(ImVec2(0.0f,0.0f),ImVec2(canvasW,canvasH),ImGui::ColorConvertFloat4ToU32(uiColors[GUI_COLOR_MODAL_BACKDROP]));
ImGui::EndPopup();
}
#endif
if (fileDialog->render(ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) {
if (fileDialog->render(ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH))) {
bool openOpen=false;
//ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavEnableKeyboard;
if ((curFileDialog==GUI_FILE_INS_OPEN || curFileDialog==GUI_FILE_INS_OPEN_REPLACE) && prevIns!=-3) {
@ -4143,7 +4154,7 @@ bool FurnaceGUI::loop() {
break;
}
case GUI_FILE_EXPORT_VGM: {
SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion,vgmExportPatternHints);
SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion,vgmExportPatternHints,vgmExportDirectStream);
if (w!=NULL) {
FILE* f=ps_fopen(copyOfName.c_str(),"wb");
if (f!=NULL) {
@ -4322,9 +4333,9 @@ bool FurnaceGUI::loop() {
ImGui::EndPopup();
}
ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(canvasW,canvasH));
if (ImGui::BeginPopupModal("New Song",NULL,ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) {
ImGui::SetWindowPos(ImVec2(((scrW*dpiScale)-ImGui::GetWindowSize().x)*0.5,((scrH*dpiScale)-ImGui::GetWindowSize().y)*0.5));
ImGui::SetWindowPos(ImVec2(((canvasW)-ImGui::GetWindowSize().x)*0.5,((canvasH)-ImGui::GetWindowSize().y)*0.5));
drawNewSong();
ImGui::EndPopup();
}
@ -4640,6 +4651,7 @@ bool FurnaceGUI::loop() {
if (e->song.autoSystem) {
autoDetectSystem();
updateWindowTitle();
MARK_MODIFIED;
}
ImGui::CloseCurrentPopup();
}
@ -4691,8 +4703,8 @@ bool FurnaceGUI::loop() {
}
bool anySelected=false;
float sizeY=ImGui::GetFrameHeightWithSpacing()*pendingIns.size();
if (sizeY>(scrH-180.0)*dpiScale) {
sizeY=(scrH-180.0)*dpiScale;
if (sizeY>(canvasH-180.0*dpiScale)) {
sizeY=canvasH-180.0*dpiScale;
if (sizeY<60.0*dpiScale) sizeY=60.0*dpiScale;
}
if (ImGui::BeginTable("PendingInsList",1,ImGuiTableFlags_ScrollY,ImVec2(0.0f,sizeY))) {
@ -4871,9 +4883,7 @@ bool FurnaceGUI::loop() {
}
bool FurnaceGUI::init() {
#ifndef __APPLE__
float dpiScaleF;
#endif
logI("initializing GUI.");
String homeDir=getHomeDir();
workingDir=e->getConfString("lastDir",homeDir);
@ -4977,15 +4987,49 @@ bool FurnaceGUI::init() {
}
}
if (settings.dpiScale>=0.5f) {
dpiScale=settings.dpiScale;
}
initSystemPresets();
e->setAutoNotePoly(noteInputPoly);
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER,"1");
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS,"0");
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS,"0");
// don't disable compositing on KWin
#if SDL_VERSION_ATLEAST(2,0,22)
logV("setting window type to NORMAL.");
SDL_SetHint(SDL_HINT_X11_WINDOW_TYPE,"_NET_WM_WINDOW_TYPE_NORMAL");
#endif
// initialize SDL
SDL_Init(SDL_INIT_VIDEO);
const char* videoBackend=SDL_GetCurrentVideoDriver();
if (videoBackend!=NULL) {
logV("video backend: %s",videoBackend);
if (strcmp(videoBackend,"wayland")==0 ||
strcmp(videoBackend,"cocoa")==0 ||
strcmp(videoBackend,"uikit")==0) {
sysManagedScale=true;
logV("scaling managed by system.");
} else {
logV("scaling managed by application.");
}
} else {
logV("could not get video backend name!");
}
// get scale factor
if (settings.dpiScale>=0.5f) {
logD("setting UI scale factor from config (%f).",settings.dpiScale);
dpiScale=settings.dpiScale;
} else {
logD("auto-detecting UI scale factor.");
dpiScale=getScaleFactor(videoBackend);
logD("scale factor: %f",dpiScale);
}
#if !(defined(__APPLE__) || defined(_WIN32))
// get the icon (on macOS and Windows the icon is bundled with the app)
unsigned char* furIcon=getFurnaceIcon();
SDL_Surface* icon=SDL_CreateRGBSurfaceFrom(furIcon,256,256,32,256*4,0xff,0xff00,0xff0000,0xff000000);
#endif
@ -5005,20 +5049,26 @@ bool FurnaceGUI::init() {
portrait=(scrW<scrH);
logV("portrait: %d (%dx%d)",portrait,scrW,scrH);
#if !defined(__APPLE__) && !defined(IS_MOBILE)
// if old config, scale size as it was stored unscaled before
if (e->getConfInt("configVersion",0)<122 && !sysManagedScale) {
logD("scaling window size to scale factor because configVersion is not present.");
scrW*=dpiScale;
scrH*=dpiScale;
}
// predict the canvas size
if (sysManagedScale) {
canvasW=scrW*dpiScale;
canvasH=scrH*dpiScale;
} else {
canvasW=scrW;
canvasH=scrH;
}
#ifndef IS_MOBILE
SDL_Rect displaySize;
#endif
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER,"1");
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS,"0");
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS,"0");
// don't disable compositing on KWin
#if SDL_VERSION_ATLEAST(2,0,22)
SDL_SetHint(SDL_HINT_X11_WINDOW_TYPE,"_NET_WM_WINDOW_TYPE_NORMAL");
#endif
SDL_Init(SDL_INIT_VIDEO);
#ifndef IS_MOBILE
// if window would spawn out of bounds, force it to be get default position
if (!detectOutOfBoundsWindow()) {
@ -5028,46 +5078,43 @@ bool FurnaceGUI::init() {
}
#endif
sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0));
logV("window size: %dx%d",scrW,scrH);
sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW,scrH,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0));
if (sdlWin==NULL) {
lastError=fmt::sprintf("could not open window! %s",SDL_GetError());
return false;
}
#ifndef __APPLE__
if (settings.dpiScale<0.5f) {
// TODO: replace with a function to actually detect the display scaling factor as it's unreliable.
SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(sdlWin),&dpiScaleF,NULL,NULL);
dpiScale=round(dpiScaleF/96.0f);
if (dpiScale<1) dpiScale=1;
#ifndef IS_MOBILE
if (dpiScale!=1) {
if (!fullScreen) {
SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale);
}
if (SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(sdlWin),&displaySize)==0) {
bool mustChange=false;
if (scrW>((displaySize.w)-48) && scrH>((displaySize.h)-64)) {
// maximize
SDL_MaximizeWindow(sdlWin);
logD("maximizing as it doesn't fit (%dx%d+%d+%d).",displaySize.w,displaySize.h,displaySize.x,displaySize.y);
}
if (SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(sdlWin),&displaySize)==0) {
if (scrW>((displaySize.w/dpiScale)-48) && scrH>((displaySize.h/dpiScale)-64)) {
// maximize
SDL_MaximizeWindow(sdlWin);
}
if (scrW>displaySize.w/dpiScale) scrW=(displaySize.w/dpiScale)-32;
if (scrH>displaySize.h/dpiScale) scrH=(displaySize.h/dpiScale)-32;
if (scrW>displaySize.w) {
scrW=(displaySize.w)-32;
mustChange=true;
}
if (scrH>displaySize.h) {
scrH=(displaySize.h)-32;
mustChange=true;
}
if (mustChange) {
portrait=(scrW<scrH);
logV("portrait: %d (%dx%d)",portrait,scrW,scrH);
if (!fullScreen) {
SDL_SetWindowSize(sdlWin,scrW*dpiScale,scrH*dpiScale);
logD("setting window size to %dx%d as it goes off bounds (%dx%d+%d+%d).",scrW,scrH,displaySize.w,displaySize.h,displaySize.x,displaySize.y);
SDL_SetWindowSize(sdlWin,scrW,scrH);
}
}
#endif
}
#endif
#ifdef IS_MOBILE
SDL_GetWindowSize(sdlWin,&scrW,&scrH);
scrW/=dpiScale;
scrH/=dpiScale;
portrait=(scrW<scrH);
logV("portrait: %d (%dx%d)",portrait,scrW,scrH);
#endif
@ -5089,9 +5136,19 @@ bool FurnaceGUI::init() {
return false;
}
#ifdef __APPLE__
dpiScale=getMacDPIScale();
#endif
// try acquiring the canvas size
if (SDL_GetRendererOutputSize(sdlRend,&canvasW,&canvasH)!=0) {
logW("could not get renderer output size! %s",SDL_GetError());
} else {
logV("canvas size: %dx%d",canvasW,canvasH);
}
// special consideration for Wayland
if (settings.dpiScale<0.5f) {
if (strcmp(videoBackend,"wayland")==0) {
dpiScale=(double)canvasW/(double)scrW;
}
}
IMGUI_CHECKVERSION();
ImGui::CreateContext();
@ -5155,6 +5212,8 @@ bool FurnaceGUI::finish() {
SDL_DestroyRenderer(sdlRend);
SDL_DestroyWindow(sdlWin);
e->setConf("configVersion",(int)DIV_ENGINE_VERSION);
e->setConf("lastDir",workingDir);
e->setConf("lastDirSong",workingDirSong);
e->setConf("lastDirIns",workingDirIns);
@ -5290,6 +5349,7 @@ FurnaceGUI::FurnaceGUI():
vgmExportLoop(true),
zsmExportLoop(true),
vgmExportPatternHints(false),
vgmExportDirectStream(false),
portrait(false),
mobileMenuOpen(false),
wantCaptureKeyboard(false),
@ -5325,11 +5385,14 @@ FurnaceGUI::FurnaceGUI():
scrH(800),
scrConfW(1280),
scrConfH(800),
canvasW(1280),
canvasH(800),
scrX(SDL_WINDOWPOS_CENTERED),
scrY(SDL_WINDOWPOS_CENTERED),
scrConfX(SDL_WINDOWPOS_CENTERED),
scrConfY(SDL_WINDOWPOS_CENTERED),
scrMax(false),
sysManagedScale(false),
dpiScale(1),
aboutScroll(0),
aboutSin(0),
@ -5608,6 +5671,7 @@ FurnaceGUI::FurnaceGUI():
#endif
hasACED(false),
waveGenBaseShape(0),
waveInterpolation(0),
waveGenDuty(0.5f),
waveGenPower(1),
waveGenInvertPoint(1.0f),

View File

@ -1035,6 +1035,7 @@ class FurnaceGUI {
std::deque<String> recentFile;
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, zsmExportLoop, vgmExportPatternHints;
bool vgmExportDirectStream;
bool portrait, mobileMenuOpen;
bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly;
@ -1061,9 +1062,9 @@ class FurnaceGUI {
FurnaceGUIFileDialog* fileDialog;
int scrW, scrH, scrConfW, scrConfH;
int scrW, scrH, scrConfW, scrConfH, canvasW, canvasH;
int scrX, scrY, scrConfX, scrConfY;
bool scrMax;
bool scrMax, sysManagedScale;
double dpiScale;
@ -1615,6 +1616,7 @@ class FurnaceGUI {
// wave generator
int waveGenBaseShape;
int waveInterpolation;
float waveGenDuty;
int waveGenPower;
float waveGenInvertPoint;

View File

@ -1907,12 +1907,12 @@ void FurnaceGUI::drawInsEdit() {
}
if (!insEditOpen) return;
if (mobileUI) {
patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f));
patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f)));
patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*canvasH)):ImVec2((0.16*canvasH)+0.5*canvasW*mobileMenuPos,0.0f));
patWindowSize=(portrait?ImVec2(canvasW,canvasH-(0.16*canvasW)-(pianoOpen?(0.4*canvasW):0.0f)):ImVec2(canvasW-(0.16*canvasH),canvasH-(pianoOpen?(0.3*canvasH):0.0f)));
ImGui::SetNextWindowPos(patWindowPos);
ImGui::SetNextWindowSize(patWindowSize);
} else {
ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH));
}
if (ImGui::Begin("Instrument Editor",&insEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) {
if (curIns<0 || curIns>=(int)e->song.ins.size()) {
@ -2575,7 +2575,7 @@ void FurnaceGUI::drawInsEdit() {
int detune=(op.dt&7)-(settings.unsignedDetune?0:3);
ImGui::TableNextColumn();
CENTER_VSLIDER;
if (CWVSliderInt("##DT",ImVec2(20.0f*dpiScale,sliderHeight),&detune,-3,4)) { PARAMETER
if (CWVSliderInt("##DT",ImVec2(20.0f*dpiScale,sliderHeight),&detune,settings.unsignedDetune?0:-3,settings.unsignedDetune?7:4)) { PARAMETER
op.dt=detune+(settings.unsignedDetune?0:3);
}
@ -2908,7 +2908,7 @@ void FurnaceGUI::drawInsEdit() {
int detune=(op.dt&7)-(settings.unsignedDetune?0:3);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_DT));
if (CWSliderInt("##DT",&detune,-3,4,tempID)) { PARAMETER
if (CWSliderInt("##DT",&detune,settings.unsignedDetune?0:-3,settings.unsignedDetune?7:4,tempID)) { PARAMETER
op.dt=detune+(settings.unsignedDetune?0:3);
} rightClickable
@ -2930,7 +2930,7 @@ void FurnaceGUI::drawInsEdit() {
int detune=(op.dt&7)-(settings.unsignedDetune?0:3);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_DT));
if (CWSliderInt("##DT",&detune,-3,4,tempID)) { PARAMETER
if (CWSliderInt("##DT",&detune,settings.unsignedDetune?0:-3,settings.unsignedDetune?7:4,tempID)) { PARAMETER
op.dt=detune+(settings.unsignedDetune?0:3);
} rightClickable
@ -3071,7 +3071,7 @@ void FurnaceGUI::drawInsEdit() {
int detune=(op.dt&7)-(settings.unsignedDetune?0:3);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_DT));
if (CWSliderInt("##DT",&detune,-3,4,tempID)) { PARAMETER
if (CWSliderInt("##DT",&detune,settings.unsignedDetune?0:-3,settings.unsignedDetune?7:4,tempID)) { PARAMETER
op.dt=detune+(settings.unsignedDetune?0:3);
} rightClickable
}
@ -3446,7 +3446,7 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderInt("##DT",&detune,-3,4)) { PARAMETER
if (CWSliderInt("##DT",&detune,settings.unsignedDetune?0:-3,settings.unsignedDetune?7:4)) { PARAMETER
op.dt=detune+(settings.unsignedDetune?0:3);
} rightClickable
ImGui::TableNextColumn();
@ -4899,14 +4899,22 @@ void FurnaceGUI::drawInsEdit() {
dutyLabel="Pulse Width";
dutyMax=255;
}
if (ins->type==DIV_INS_T6W28) {
dutyLabel="Noise Type";
dutyMax=1;
}
if (ins->type==DIV_INS_AY8930) {
dutyMax=ins->amiga.useSample?0:255;
}
if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC ||
ins->type==DIV_INS_PET || ins->type==DIV_INS_VIC || ins->type==DIV_INS_SEGAPCM ||
ins->type==DIV_INS_FM || ins->type==DIV_INS_VBOY) {
ins->type==DIV_INS_FM) {
dutyMax=0;
}
if (ins->type==DIV_INS_VBOY) {
dutyLabel="Noise Length";
dutyMax=7;
}
if (ins->type==DIV_INS_PCE || ins->type==DIV_INS_NAMCO) {
dutyLabel="Noise";
dutyMax=(ins->type==DIV_INS_PCE && !ins->amiga.useSample)?1:0;
@ -5203,6 +5211,8 @@ void FurnaceGUI::drawInsEdit() {
ins->type==DIV_INS_SU ||
ins->type==DIV_INS_MIKEY ||
ins->type==DIV_INS_ES5506 ||
ins->type==DIV_INS_T6W28 ||
ins->type==DIV_INS_VBOY ||
(ins->type==DIV_INS_X1_010 && ins->amiga.useSample)) {
macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
}

View File

@ -27,12 +27,13 @@ void FurnaceGUI::drawMixer() {
nextWindow=GUI_WINDOW_NOTHING;
}
if (!mixerOpen) return;
ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(canvasW,canvasH));
if (ImGui::Begin("Mixer",&mixerOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) {
char id[32];
if (ImGui::SliderFloat("Master Volume",&e->song.masterVol,0,3,"%.2fx")) {
if (e->song.masterVol<0) e->song.masterVol=0;
if (e->song.masterVol>3) e->song.masterVol=3;
MARK_MODIFIED;
} rightClickable
for (int i=0; i<e->song.systemLen; i++) {
snprintf(id,31,"MixS%d",i);
@ -43,13 +44,17 @@ void FurnaceGUI::drawMixer() {
ImGui::SameLine(ImGui::GetWindowWidth()-(82.0f*dpiScale));
if (ImGui::Checkbox("Invert",&doInvert)) {
e->song.systemVol[i]^=128;
MARK_MODIFIED;
}
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale));
if (CWSliderScalar("Volume",ImGuiDataType_S8,&vol,&_ZERO,&_ONE_HUNDRED_TWENTY_SEVEN)) {
e->song.systemVol[i]=(e->song.systemVol[i]&128)|vol;
MARK_MODIFIED;
} rightClickable
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale));
CWSliderScalar("Panning",ImGuiDataType_S8,&e->song.systemPan[i],&_MINUS_ONE_HUNDRED_TWENTY_SEVEN,&_ONE_HUNDRED_TWENTY_SEVEN); rightClickable
if (CWSliderScalar("Panning",ImGuiDataType_S8,&e->song.systemPan[i],&_MINUS_ONE_HUNDRED_TWENTY_SEVEN,&_ONE_HUNDRED_TWENTY_SEVEN)) {
MARK_MODIFIED;
} rightClickable
ImGui::PopID();
}

View File

@ -84,7 +84,7 @@ void FurnaceGUI::drawOsc() {
nextWindow=GUI_WINDOW_NOTHING;
}
if (!oscOpen) return;
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH));
if (settings.oscTakesEntireWindow) {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0));

View File

@ -376,8 +376,8 @@ void FurnaceGUI::drawPattern() {
}
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f));
if (mobileUI) {
patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*scrH*dpiScale)):ImVec2((0.16*scrH*dpiScale)+0.5*scrW*dpiScale*mobileMenuPos,0.0f));
patWindowSize=(portrait?ImVec2(scrW*dpiScale,scrH*dpiScale-(0.16*scrW*dpiScale)-(pianoOpen?(0.4*scrW*dpiScale):0.0f)):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),scrH*dpiScale-(pianoOpen?(0.3*scrH*dpiScale):0.0f)));
patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*canvasH)):ImVec2((0.16*canvasH)+0.5*canvasW*mobileMenuPos,0.0f));
patWindowSize=(portrait?ImVec2(canvasW,canvasH-(0.16*canvasW)-(pianoOpen?(0.4*canvasW):0.0f)):ImVec2(canvasW-(0.16*canvasH),canvasH-(pianoOpen?(0.3*canvasH):0.0f)));
ImGui::SetNextWindowPos(patWindowPos);
ImGui::SetNextWindowSize(patWindowSize);
}

View File

@ -54,7 +54,7 @@ void FurnaceGUI::drawPiano() {
if (!pianoOpen) return;
if (mobileUI) {
ImGui::SetNextWindowPos(ImVec2(patWindowPos.x,patWindowPos.y+patWindowSize.y));
ImGui::SetNextWindowSize(portrait?ImVec2(scrW*dpiScale,0.4*scrW*dpiScale):ImVec2(scrW*dpiScale-(0.16*scrH*dpiScale),0.3*scrH*dpiScale));
ImGui::SetNextWindowSize(portrait?ImVec2(canvasW,0.4*canvasW):ImVec2(canvasW-(0.16*canvasH),0.3*canvasH));
}
if (ImGui::Begin("Piano",&pianoOpen,((pianoOptions)?0:ImGuiWindowFlags_NoTitleBar)|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) {
bool oldPianoKeyPressed[180];

View File

@ -270,6 +270,12 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Toshiba T6W28", {
DIV_SYSTEM_T6W28, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"AY-3-8910", {
DIV_SYSTEM_AY8910, 64, 0, 0,
@ -527,35 +533,35 @@ void FurnaceGUI::initSystemPresets() {
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis", {
DIV_SYSTEM_YM2612, 64, 0, 0,
DIV_SYSTEM_SMS, 24, 0, 0,
DIV_SYSTEM_SMS, 32, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis (extended channel 3)", {
DIV_SYSTEM_YM2612_EXT, 64, 0, 0,
DIV_SYSTEM_SMS, 24, 0, 0,
DIV_SYSTEM_SMS, 32, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis (Fractal Sound template)", {
DIV_SYSTEM_YM2612_FRAC, 64, 0, 0,
DIV_SYSTEM_SMS, 24, 0, 0,
DIV_SYSTEM_SMS, 32, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis (Fractal Sound template, extended channel 3)", {
DIV_SYSTEM_YM2612_FRAC_EXT, 64, 0, 0,
DIV_SYSTEM_SMS, 24, 0, 0,
DIV_SYSTEM_SMS, 32, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis (with Sega CD)", {
DIV_SYSTEM_YM2612, 64, 0, 0,
DIV_SYSTEM_SMS, 24, 0, 0,
DIV_SYSTEM_SMS, 32, 0, 0,
DIV_SYSTEM_RF5C68, 64, 0, 18,
0
}
@ -563,7 +569,7 @@ void FurnaceGUI::initSystemPresets() {
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis (extended channel 3 with Sega CD)", {
DIV_SYSTEM_YM2612_EXT, 64, 0, 0,
DIV_SYSTEM_SMS, 24, 0, 0,
DIV_SYSTEM_SMS, 32, 0, 0,
DIV_SYSTEM_RF5C68, 64, 0, 18,
0
}
@ -2246,14 +2252,14 @@ void FurnaceGUI::initSystemPresets() {
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis", {
DIV_SYSTEM_YM2612, 64, 0, 0,
DIV_SYSTEM_SMS, 24, 0, 0,
DIV_SYSTEM_SMS, 32, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis (extended channel 3)", {
DIV_SYSTEM_YM2612_EXT, 64, 0, 0,
DIV_SYSTEM_SMS, 24, 0, 0,
DIV_SYSTEM_SMS, 32, 0, 0,
0
}
));

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