Merge branch 'master' of https://github.com/tildearrow/furnace into mod-import

This commit is contained in:
Natt Akuma 2022-03-14 21:57:54 +07:00
commit c7fb5df206
83 changed files with 7113 additions and 372 deletions

View File

@ -234,6 +234,8 @@ src/engine/platform/sound/gb/apu.c
src/engine/platform/sound/gb/timing.c
src/engine/platform/sound/pce_psg.cpp
src/engine/platform/sound/nes/apu.c
src/engine/platform/sound/vera_psg.c
src/engine/platform/sound/vera_pcm.c
src/engine/platform/sound/c64/sid.cc
src/engine/platform/sound/c64/voice.cc
@ -265,6 +267,8 @@ src/engine/platform/sound/lynx/Mikey.cpp
src/engine/platform/sound/qsound.c
src/engine/platform/sound/x1_010/x1_010.cpp
src/engine/platform/sound/swan.cpp
src/engine/platform/ym2610Interface.cpp
@ -308,8 +312,10 @@ src/engine/platform/amiga.cpp
src/engine/platform/pcspkr.cpp
src/engine/platform/segapcm.cpp
src/engine/platform/qsound.cpp
src/engine/platform/x1_010.cpp
src/engine/platform/lynx.cpp
src/engine/platform/swan.cpp
src/engine/platform/vera.cpp
src/engine/platform/dummy.cpp
)
@ -341,6 +347,7 @@ src/gui/font_unifont.cpp
src/gui/font_icon.cpp
src/gui/fonts.cpp
src/gui/debug.cpp
src/gui/fileDialog.cpp
src/gui/intConst.cpp
src/gui/guiConst.cpp
@ -392,6 +399,9 @@ endif()
if (NOT MSVC)
set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND WARNING_FLAGS -Wno-cast-function-type)
endif()
if (WARNINGS_ARE_ERRORS)
list(APPEND WARNING_FLAGS -Werror)
endif()

View File

@ -68,6 +68,8 @@ some people have provided packages for Unix/Unix-like distributions. here's a li
# developer info
[![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml)
**NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building.**
## dependencies

View File

@ -3877,6 +3877,7 @@ namespace IGFD
static ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_AllowDoubleClick |
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SpanAvailWidth;
// TODO BUG?!
va_list args;
va_start(args, vFmt);
vsnprintf(fdi.puVariadicBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFmt, args);
@ -4074,6 +4075,7 @@ namespace IGFD
if (ImGui::TableNextColumn()) // file name
{
// TODO BUG?!?!?!
needToBreakTheloop = prSelectableItem(i, infos, selected, _str.c_str());
if (needToBreakTheloop==2) escape=true;
}

4
extern/pfd-fixed/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
CMakeCache.txt
CMakeFiles
Makefile
cmake_install.cmake

5
extern/pfd-fixed/.lgtm.yml vendored Normal file
View File

@ -0,0 +1,5 @@
extraction:
cpp:
index:
build_command:
- make -C examples

6
extern/pfd-fixed/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.1.0)
project(portable_file_dialogs VERSION 1.00 LANGUAGES CXX)
add_library(${PROJECT_NAME} INTERFACE)
target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

14
extern/pfd-fixed/COPYING vendored Normal file
View File

@ -0,0 +1,14 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

64
extern/pfd-fixed/README.md vendored Normal file
View File

@ -0,0 +1,64 @@
# Portable File Dialogs
A free C++11 file dialog library.
- works on Windows, Mac OS X, Linux
- **single-header**, no extra library dependencies
- **synchronous *or* asynchronous** (does not block the rest of your program!)
- **cancelable** (kill asynchronous dialogues without user interaction)
- **secure** (immune to shell-quote vulnerabilities)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/a25d3fd6959a4333871f630ac70b6e09)](https://www.codacy.com/manual/samhocevar/portable-file-dialogs?utm_source=github.com&utm_medium=referral&utm_content=samhocevar/portable-file-dialogs&utm_campaign=Badge_Grade)
## Status
The library is now pretty robust. It is not as feature-complete as
[Tiny File Dialogs](https://sourceforge.net/projects/tinyfiledialogs/),
but has asynchonous dialogs, more maintainable code, and fewer potential
security issues.
The currently available backends are:
- Win32 API (all known versions of Windows)
- Mac OS X (using AppleScript)
- GNOME desktop (using [Zenity](https://en.wikipedia.org/wiki/Zenity) or its clones Matedialog and Qarma)
- KDE desktop (using [KDialog](https://github.com/KDE/kdialog))
Experimental support for Emscripten is on its way.
## Documentation
- [`pfd`](doc/pfd.md) general documentation
- [`pfd::message`](doc/message.md) message box
- [`pfd::notify`](doc/notify.md) notification
- [`pfd::open_file`](doc/open_file.md) file open
- [`pfd::save_file`](doc/save_file.md) file save
- [`pfd::select_folder`](doc/select_folder.md) folder selection
## History
- 0.1.0 (July 16, 2020): first public release
## Screenshots (Windows 10)
![warning-win32](https://user-images.githubusercontent.com/245089/47136607-76919a00-d2b4-11e8-8f42-e2d62c4f9570.png)
![notify-win32](https://user-images.githubusercontent.com/245089/47142453-2ff76c00-d2c3-11e8-871a-1a110ac91eb2.png)
![open-win32](https://user-images.githubusercontent.com/245089/47155865-0f8cd900-d2e6-11e8-8041-1e20b6f77dee.png)
## Screenshots (Mac OS X, dark theme)
![warning-osxdark](https://user-images.githubusercontent.com/245089/56053001-22dba700-5d53-11e9-8233-ca7a2c58188d.png)
![notify-osxdark](https://user-images.githubusercontent.com/245089/56053188-bc0abd80-5d53-11e9-8298-68aa96315c6c.png)
![open-osxdark](https://user-images.githubusercontent.com/245089/56053378-39363280-5d54-11e9-9583-9f1c978fa0db.png)
## Screenshots (Linux, GNOME desktop)
![warning-gnome](https://user-images.githubusercontent.com/245089/47136608-772a3080-d2b4-11e8-9e1d-60a7e743e908.png)
![notify-gnome](https://user-images.githubusercontent.com/245089/47142455-30900280-d2c3-11e8-8b76-ea16c7e502d4.png)
![open-gnome](https://user-images.githubusercontent.com/245089/47155867-0f8cd900-d2e6-11e8-93af-275636491ec4.png)
## Screenshots (Linux, KDE Plasma desktop)
![warning-kde](https://user-images.githubusercontent.com/245089/47149255-4dcccd00-d2d3-11e8-84c9-f85612784680.png)
![notify-kde](https://user-images.githubusercontent.com/245089/47149206-27a72d00-d2d3-11e8-8f1b-96e462f08c2b.png)
![open-kde](https://user-images.githubusercontent.com/245089/47155866-0f8cd900-d2e6-11e8-8006-f14b948afc55.png)

97
extern/pfd-fixed/doc/message.md vendored Normal file
View File

@ -0,0 +1,97 @@
## Message Box API
Displaying a message box is done using the `pfd::message` class. It can be provided a title, a
message text, a `choice` representing which buttons need to be rendered, and an `icon` for the
message:
```cpp
pfd::message::message(std::string const &title,
std::string const &text,
pfd::choice choice = pfd::choice::ok_cancel,
pfd::icon icon = pfd::icon::info);
enum class pfd::choice { ok, ok_cancel, yes_no, yes_no_cancel };
enum class pfd::icon { info, warning, error, question };
```
The pressed button is queried using `pfd::message::result()`. If the dialog box is closed by any
other means, the `pfd::button::cancel` is assumed:
```cpp
pfd::button pfd::message::result();
enum class pfd::button { ok, cancel, yes, no };
```
It is possible to ask the dialog box whether the user took action using the `pfd::message::ready()`
method, with an optional `timeout` argument. If the user did not press a button within `timeout`
milliseconds, the function will return `false`:
```cpp
bool pfd::message::ready(int timeout = pfd::default_wait_timeout);
```
## Example 1: simple notification
The `pfd::message` destructor waits for user action, so this operation will block until the user
closes the message box:
```cpp
pfd::message("Problem", "An error occurred while doing things",
pfd::choice::ok, pfd::icon::error);
```
## Example 2: retrieving the pressed button
Using `pfd::message::result()` will also wait for user action before returning. This operation will block and return the user choice:
```cpp
// Ask for user opinion
auto button = pfd::message("Action requested", "Do you want to proceed with things?",
pfd::choice::yes_no, pfd::icon::question).result();
// Do something with button…
```
## Example 3: asynchronous message box
Using `pfd::message::ready()` allows the application to perform other tasks while waiting for
user input:
```cpp
// Message box with nice message
auto box = pfd::message("Unsaved Files", "Do you want to save the current "
"document before closing the application?",
pfd::choice::yes_no_cancel,
pfd::icon::warning);
// Do something while waiting for user input
while (!box.ready(1000))
std::cout << "Waited 1 second for user input...\n";
// Act depending on the selected button
switch (box.result())
{
case pfd::button::yes: std::cout << "User agreed.\n"; break;
case pfd::button::no: std::cout << "User disagreed.\n"; break;
case pfd::button::cancel: std::cout << "User freaked out.\n"; break;
}
```
## Screenshots
#### Windows 10
![warning-win32](https://user-images.githubusercontent.com/245089/47136607-76919a00-d2b4-11e8-8f42-e2d62c4f9570.png)
#### Mac OS X
![warning-osx-dark](https://user-images.githubusercontent.com/245089/56053001-22dba700-5d53-11e9-8233-ca7a2c58188d.png) ![warning-osx-light](https://user-images.githubusercontent.com/245089/56053055-49014700-5d53-11e9-8306-e9a03a25e044.png)
#### Linux (GNOME desktop)
![warning-gnome](https://user-images.githubusercontent.com/245089/47140824-8662ab80-d2bf-11e8-9c87-2742dd5b58af.png)
#### Linux (KDE desktop)
![warning-kde](https://user-images.githubusercontent.com/245089/47149255-4dcccd00-d2d3-11e8-84c9-f85612784680.png)

40
extern/pfd-fixed/doc/notify.md vendored Normal file
View File

@ -0,0 +1,40 @@
## Notification API
Displaying a desktop notification is done using the `pfd::notify` class. It can be provided a
title, a message text, and an `icon` for the notification style:
```cpp
pfd::notify::notify(std::string const &title,
std::string const &text,
pfd::icon icon = pfd::icon::info);
enum class pfd::icon { info, warning, error };
```
## Example
Displaying a notification is straightforward. Emoji are supported:
```cpp
pfd::notify("System event", "Something might be on fire 🔥",
pfd::icon::warning);
```
The `pfd::notify` object needs not be kept around, letting the object clean up itself is enough.
## Screenshots
Windows 10:
![notify-win32](https://user-images.githubusercontent.com/245089/47142453-2ff76c00-d2c3-11e8-871a-1a110ac91eb2.png)
Mac OS X (dark theme):
![image](https://user-images.githubusercontent.com/245089/56053188-bc0abd80-5d53-11e9-8298-68aa96315c6c.png)
Mac OS X (light theme):
![image](https://user-images.githubusercontent.com/245089/56053137-92ea2d00-5d53-11e9-8cf2-049486c45713.png)
Linux (GNOME desktop):
![notify-gnome](https://user-images.githubusercontent.com/245089/47142455-30900280-d2c3-11e8-8b76-ea16c7e502d4.png)
Linux (KDE desktop):
![notify-kde](https://user-images.githubusercontent.com/245089/47149206-27a72d00-d2d3-11e8-8f1b-96e462f08c2b.png)

90
extern/pfd-fixed/doc/open_file.md vendored Normal file
View File

@ -0,0 +1,90 @@
## File Open API
The `pfd::open_file` class handles file opening dialogs. It can be provided a title, a starting
directory and/or pre-selected file, an optional filter for recognised file types, and an optional
flag to allow multiple selection:
```cpp
pfd::open_file::open_file(std::string const &title,
std::string const &initial_path,
std::vector<std::string> filters = { "All Files", "*" },
pfd::opt option = pfd::opt::none);
```
The `option` parameter can be `pfd::opt::multiselect` to allow selecting multiple files.
The selected files are queried using `pfd::open_file::result()`. If the user canceled the
operation, the returned list is empty:
```cpp
std::vector<std::string> pfd::open_file::result();
```
It is possible to ask the file open dialog whether the user took action using the
`pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate
the dialog within `timeout` milliseconds, the function will return `false`:
```cpp
bool pfd::open_file::ready(int timeout = pfd::default_wait_timeout);
```
## Example 1: simple file selection
Using `pfd::open_file::result()` will wait for user action before returning. This operation will
block and return the user choice:
```cpp
auto selection = pfd::open_file("Select a file").result();
if (!selection.empty())
std::cout << "User selected file " << selection[0] << "\n";
```
## Example 2: filters
The filter list enumerates filter names and corresponded space-separated wildcard lists. It
defaults to `{ "All Files", "*" }`, but here is how to use other options:
```cpp
auto selection = pfd::open_file("Select a file", ".",
{ "Image Files", "*.png *.jpg *.jpeg *.bmp",
"Audio Files", "*.wav *.mp3",
"All Files", "*" },
pfd::opt::multiselect).result();
// Do something with selection
for (auto const &filename : dialog.result())
std::cout << "Selected file: " << filename << "\n";
```
## Example 3: asynchronous file open
Using `pfd::open_file::ready()` allows the application to perform other tasks while waiting for
user input:
```cpp
// File open dialog
auto dialog = pfd::open_file("Select file to open");
// Do something while waiting for user input
while (!dialog.ready(1000))
std::cout << "Waited 1 second for user input...\n";
// Act depending on the user choice
std::cout << "Number of selected files: " << dialog.result().size() << "\n";
```
## Screenshots
Windows 10:
![open-win32](https://user-images.githubusercontent.com/245089/47155865-0f8cd900-d2e6-11e8-8041-1e20b6f77dee.png)
Mac OS X (dark theme):
![image](https://user-images.githubusercontent.com/245089/56053378-39363280-5d54-11e9-9583-9f1c978fa0db.png)
Mac OS X (light theme):
![image](https://user-images.githubusercontent.com/245089/56053413-4fdc8980-5d54-11e9-85e3-e9e5d0e10772.png)
Linux (GNOME desktop):
![open-gnome](https://user-images.githubusercontent.com/245089/47155867-0f8cd900-d2e6-11e8-93af-275636491ec4.png)
Linux (KDE desktop):
![open-kde](https://user-images.githubusercontent.com/245089/47155866-0f8cd900-d2e6-11e8-8006-f14b948afc55.png)

120
extern/pfd-fixed/doc/pfd.md vendored Normal file
View File

@ -0,0 +1,120 @@
## Portable File Dialogs documentation
The library can be used either as a [header-only library](https://en.wikipedia.org/wiki/Header-only),
or as a [single file library](https://github.com/nothings/single_file_libs).
### Use as header-only library
Just include the main header file wherever needed:
```cpp
#include "portable-file-dialogs.h"
/* ... */
pfd::message::message("Hello", "This is a test");
/* ... */
```
### Use as a single-file library
Defining the `PFD_SKIP_IMPLEMENTATION` macro before including `portable-file-dialogs.h` will
skip all the implementation code and reduce compilation times. You still need to include the
header without the macro at least once, typically in a `pfd-impl.cpp` file.
```cpp
// In pfd-impl.cpp
#include "portable-file-dialogs.h"
```
```cpp
// In all other files
#define PFD_SKIP_IMPLEMENTATION 1
#include "portable-file-dialogs.h"
```
### General concepts
Dialogs inherit from `pfd::dialog` and are created by calling their class constructor. Their
destructor will block until the window is closed by user interaction. So for instance this
will block until the end of the line:
```cpp
pfd::message::message("Hi", "there");
```
Whereas this will only block until the end of the scope, allowing the program to perform
additional operations while the dialog is open:
```cpp
{
auto m = pfd::message::message("Hi", "there");
// ... perform asynchronous operations here
}
```
It is possible to call `bool pfd::dialog::ready(timeout)` on the dialog in order to query its
status and perform asynchronous operations as long as the user has not interacted:
```cpp
{
auto m = pfd::message::message("Hi", "there");
while (!m.ready())
{
// ... perform asynchronous operations here
}
}
```
If necessary, a dialog can be forcibly closed using `bool pfd::dialog::kill()`. Note that this
may be confusing to the user and should only be used in very specific situations. It is also not
possible to close a Windows message box that provides no _Cancel_ button.
```cpp
{
auto m = pfd::message::message("Hi", "there");
while (!m.ready())
{
// ... perform asynchronous operations here
if (too_much_time_has_passed())
m.kill();
}
}
```
Finally, the user response can be retrieved using `pfd::dialog::result()`. The return value of
this function depends on which dialog is being used. See their respective documentation for more
information:
* [`pfd::message`](message.md) (message box)
* [`pfd::notify`](notify.md) (notification)
* [`pfd::open_file`](open_file.md) (file open)
* [`pfd::save_file`](save_file.md) (file save)
* [`pfd::select_folder`](select_folder.md) (folder selection)
### Settings
The library can be queried and configured through the `pfd::settings` class.
```cpp
bool pfd::settings::available();
void pfd::settings::verbose(bool value);
void pfd::settings::rescan();
```
The return value of `pfd::settings::available()` indicates whether a suitable dialog backend (such
as Zenity or KDialog on Linux) has been found. If not, the library will not work and all dialog
invocations will be no-ops. The program will not crash but you should account for this situation
and add a fallback mechanism or exit gracefully.
Calling `pfd::settings::rescan()` will force a rescan of available backends. This may change the
result of `pfd::settings::available()` if a backend was installed on the system in the meantime.
This is probably only useful for debugging purposes.
Calling `pfd::settings::verbose(true)` may help debug the library. It will output debug information
to `std::cout` about some operations being performed.

73
extern/pfd-fixed/doc/save_file.md vendored Normal file
View File

@ -0,0 +1,73 @@
## File Open API
The `pfd::save_file` class handles file saving dialogs. It can be provided a title, a starting
directory and/or pre-selected file, an optional filter for recognised file types, and an optional
flag to allow multiple selection:
```cpp
pfd::save_file::save_file(std::string const &title,
std::string const &initial_path,
std::vector<std::string> filters = { "All Files", "*" },
pfd::opt option = pfd::opt::none);
```
The `option` parameter can be `pfd::opt::force_overwrite` to disable a potential warning when
saving to an existing file.
The selected file is queried using `pfd::save_file::result()`. If the user canceled the
operation, the returned file name is empty:
```cpp
std::string pfd::save_file::result();
```
It is possible to ask the file save dialog whether the user took action using the
`pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate
the dialog within `timeout` milliseconds, the function will return `false`:
```cpp
bool pfd::save_file::ready(int timeout = pfd::default_wait_timeout);
```
## Example 1: simple file selection
Using `pfd::save_file::result()` will wait for user action before returning. This operation will
block and return the user choice:
```cpp
auto destination = pfd::save_file("Select a file").result();
if (!destination.empty())
std::cout << "User selected file " << destination << "\n";
```
## Example 2: filters
The filter list enumerates filter names and corresponded space-separated wildcard lists. It
defaults to `{ "All Files", "*" }`, but here is how to use other options:
```cpp
auto destination = pfd::save_file("Select a file", ".",
{ "Image Files", "*.png *.jpg *.jpeg *.bmp",
"Audio Files", "*.wav *.mp3",
"All Files", "*" },
pfd::opt::force_overwrite).result();
// Do something with destination
std::cout << "Selected file: " << destination << "\n";
```
## Example 3: asynchronous file save
Using `pfd::save_file::ready()` allows the application to perform other tasks while waiting for
user input:
```cpp
// File save dialog
auto dialog = pfd::save_file("Select file to save");
// Do something while waiting for user input
while (!dialog.ready(1000))
std::cout << "Waited 1 second for user input...\n";
// Act depending on the user choice
std::cout << "User selected file: " << dialog.result() << "\n";
```

55
extern/pfd-fixed/doc/select_folder.md vendored Normal file
View File

@ -0,0 +1,55 @@
## Folder Selection API
The `pfd::select_folder` class handles folder opening dialogs. It can be provided a title, and an
optional starting directory:
```cpp
pfd::select_folder::select_folder(std::string const &title,
std::string const &default_path = "",
pfd::opt option = pfd::opt::none);
```
The `option` parameter can be `pfd::opt::force_path` to force the operating system to use the
provided path. Some systems default to the most recently used path, if applicable.
The selected folder is queried using `pfd::select_folder::result()`. If the user canceled the
operation, the returned string is empty:
```cpp
std::string pfd::select_folder::result();
```
It is possible to ask the folder selection dialog whether the user took action using the
`pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate
the dialog within `timeout` milliseconds, the function will return `false`:
```cpp
bool pfd::select_folder::ready(int timeout = pfd::default_wait_timeout);
```
## Example 1: simple folder selection
Using `pfd::select_folder::result()` will wait for user action before returning. This operation
will block and return the user choice:
```cpp
auto selection = pfd::select_folder("Select a folder").result();
if (!selection.empty())
std::cout << "User selected folder " << selection << "\n";
```
## Example 2: asynchronous folder open
Using `pfd::select_folder::ready()` allows the application to perform other tasks while waiting for user input:
```cpp
// Folder selection dialog
auto dialog = pfd::select_folder("Select folder to open");
// Do something while waiting for user input
while (!dialog.ready(1000))
std::cout << "Waited 1 second for user input...\n";
// Act depending on the user choice
std::cout << "Selected folder: " << dialog.result() << "\n";
```

11
extern/pfd-fixed/examples/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
example
example.exe
kill
kill.exe
Debug
Release
*.vcxproj.user
.idea
cmake-build-*

110
extern/pfd-fixed/examples/example.cpp vendored Normal file
View File

@ -0,0 +1,110 @@
//
// Portable File Dialogs
//
// Copyright © 2018—2020 Sam Hocevar <sam@hocevar.net>
//
// This program is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What the Fuck You Want
// to Public License, Version 2, as published by the WTFPL Task Force.
// See http://www.wtfpl.net/ for more details.
//
#include "portable-file-dialogs.h"
#include <iostream>
#if _WIN32
#define DEFAULT_PATH "C:\\"
#else
#define DEFAULT_PATH "/tmp"
#endif
int main()
{
// Check that a backend is available
if (!pfd::settings::available())
{
std::cout << "Portable File Dialogs are not available on this platform.\n";
return 1;
}
// Set verbosity to true
pfd::settings::verbose(true);
// Notification
pfd::notify("Important Notification",
"This is ' a message, pay \" attention \\ to it!",
pfd::icon::info);
// Message box with nice message
auto m = pfd::message("Personal Message",
"You are an amazing person, dont let anyone make you think otherwise.",
pfd::choice::yes_no_cancel,
pfd::icon::warning);
// Optional: do something while waiting for user action
for (int i = 0; i < 10 && !m.ready(1000); ++i)
std::cout << "Waited 1 second for user input...\n";
// Do something according to the selected button
switch (m.result())
{
case pfd::button::yes: std::cout << "User agreed.\n"; break;
case pfd::button::no: std::cout << "User disagreed.\n"; break;
case pfd::button::cancel: std::cout << "User freaked out.\n"; break;
default: break; // Should not happen
}
// Directory selection
auto dir = pfd::select_folder("Select any directory", DEFAULT_PATH).result();
std::cout << "Selected dir: " << dir << "\n";
// File open
auto f = pfd::open_file("Choose files to read", DEFAULT_PATH,
{ "Text Files (.txt .text)", "*.txt *.text",
"All Files", "*" },
pfd::opt::multiselect);
std::cout << "Selected files:";
for (auto const &name : f.result())
std::cout << " " + name;
std::cout << "\n";
}
// Unused function that just tests the whole API
void api()
{
// pfd::settings
pfd::settings::verbose(true);
pfd::settings::rescan();
// pfd::notify
pfd::notify("", "");
pfd::notify("", "", pfd::icon::info);
pfd::notify("", "", pfd::icon::warning);
pfd::notify("", "", pfd::icon::error);
pfd::notify("", "", pfd::icon::question);
pfd::notify a("", "");
(void)a.ready();
(void)a.ready(42);
// pfd::message
pfd::message("", "");
pfd::message("", "", pfd::choice::ok);
pfd::message("", "", pfd::choice::ok_cancel);
pfd::message("", "", pfd::choice::yes_no);
pfd::message("", "", pfd::choice::yes_no_cancel);
pfd::message("", "", pfd::choice::retry_cancel);
pfd::message("", "", pfd::choice::abort_retry_ignore);
pfd::message("", "", pfd::choice::ok, pfd::icon::info);
pfd::message("", "", pfd::choice::ok, pfd::icon::warning);
pfd::message("", "", pfd::choice::ok, pfd::icon::error);
pfd::message("", "", pfd::choice::ok, pfd::icon::question);
pfd::message b("", "");
(void)b.ready();
(void)b.ready(42);
(void)b.result();
}

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="example.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="../portable-file-dialogs.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{10F4364D-27C4-4C74-8079-7C42971E81E7}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>example</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

42
extern/pfd-fixed/examples/kill.cpp vendored Normal file
View File

@ -0,0 +1,42 @@
//
// Portable File Dialogs
//
// Copyright © 2018—2020 Sam Hocevar <sam@hocevar.net>
//
// This program is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What the Fuck You Want
// to Public License, Version 2, as published by the WTFPL Task Force.
// See http://www.wtfpl.net/ for more details.
//
#include "portable-file-dialogs.h"
#include <cstdio>
int main()
{
// Set verbosity to true
pfd::settings::verbose(true);
// Message box with nice message
auto m = pfd::message("Upgrade software?",
"Press OK to upgrade this software.\n"
"\n"
"By default, the software will update itself\n"
"automatically in 10 seconds.",
pfd::choice::ok_cancel,
pfd::icon::warning);
// Wait for an answer for up to 10 seconds
for (int i = 0; i < 10 && !m.ready(1000); ++i)
;
// Upgrade software if user clicked OK, or if user didnt interact
bool upgrade = m.ready() ? m.result() == pfd::button::ok : m.kill();
if (upgrade)
std::cout << "Upgrading software!\n";
else
std::cout << "Not upgrading software.\n";
}

96
extern/pfd-fixed/examples/kill.vcxproj vendored Normal file
View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="kill.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="../portable-file-dialogs.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{B94D26B1-7EF7-43A2-A973-9A96A08E2E17}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>kill</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

1731
extern/pfd-fixed/portable-file-dialogs.h vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ double-click to open the instrument editor.
every instrument can be renamed and have its type changed.
depending on the instrument type, there are currently 12 different types of an instrument editor:
depending on the instrument type, there are currently 13 different types of an instrument editor:
- [FM synthesis](fm.md) - for use with YM2612, YM2151 and FM block portion of YM2610.
- [Standard](standard.md) - for use with NES and Sega Master System's PSG sound source and its derivatives.
@ -22,8 +22,10 @@ depending on the instrument type, there are currently 12 different types of an i
- [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source.
- [TIA](tia.md) - for use with Atari 2600 system.
- [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 and PC Engine's sample playback mode.
- [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console.
- [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.
- [VERA](vera.md) - for use with Commander X16 VERA.
- [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010.
# macros

View File

@ -0,0 +1,8 @@
# VERA instrument editor
VERA instrument editor consists of only four macros:
- [Volume] - volume sequence
- [Arpeggio] - pitch sequence
- [Duty cycle] - pulse duty cycle sequence
- [Waveform] - select the waveform used by instrument

View File

@ -0,0 +1,11 @@
# X1-010 instrument editor
X1-010 instrument editor consists of 7 macros.
- [Volume] - volume levels sequence
- [Arpeggio]- pitch sequence
- [Waveform] - spicifies wavetables sequence
- [Envelope Mode] - allows shaping an envelope
- [Envelope] - spicifies envelope shape sequence, it's also wavetable.
- [Auto envelope numerator] - sets the envelope to the channel's frequency multiplied by numerator
- [Auto envelope denominator] - ets the envelope to the channel's frequency multiplied by denominator

View File

@ -2,4 +2,4 @@
Wavetable synthizers, 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 and WonderSwan can handle max 32 byte waveforms as of now, with 16-level height for GB and WS, and 32-level height for PCE. If larger wave will be defined for these two 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 and WonderSwan 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 and WS, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system.

View File

@ -12,7 +12,8 @@ As of Furnace 0.5.5, the following sound chips have sample support:
- PC Engine/TurboGrafx 16/Huc6280 (same conditions as above)
- Amiga/Paula (on all channels AND resamplable, but you need to make an instrument with the Amiga format and tie it to a sample first)
- Arcade/SEGA PCM (same as above but you don't need to make an instrument for it and you have to use the `20xx` effect command to resample your samples)
- Neo Geo/Neo Geo EXT-Ch2 (on the last 5 channels only and can be resampled the same way as above)
- Neo Geo/Neo Geo EXT-Ch2 (on the last 7 channels only and can be resampled the same way as above)
- Seta/Allumer X1-010 (same as above, and both `1701` and `20xx` effect commands are affected on all 16 channels)
Furnace also has a feature where you can make an Amiga formarted instrument on the YM2612 and Huc6280 to resample a sample you have in the module.

View File

@ -18,6 +18,7 @@ this is a list of systems that Furnace supports, including each system's effects
- [Atari 2600](tia.md)
- [Philips SAA1099](saa1099.md)
- [Microchip AY8930](ay8930.md)
- [Seta/Allumer X1-010](x1_010.md)
- [WonderSwan](wonderswan.md)
Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all.

View File

@ -4,6 +4,8 @@ this chip was used in several home computers (ZX Spectrum, MSX, Amstrad CPC, Ata
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.
# effects
- `20xx`: set channel mode. `xx` may be one of the following:

View File

@ -0,0 +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 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 failiure, 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.
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.
## Sound capabilities
- 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). 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,47 @@
# Seta/Allumer X1-010
One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-80s to early-2000s.
It has 2 output channels, but no known hardware using this feature for stereo sound.
Later hardware paired this with external bankswitching logic, but its logic is not emulated now in current furnace revision.
Allumer one is just rebadged Seta's thing for use in their arcade hardwares.
It has 16 channels, and all channels can be switchable to PCM sample or wavetable playback mode.
Wavetable needs to paired with envelope, this feature is similar as AY PSG, but its shape are stored at RAM: it means it is user-definable.
In furnace, this chip is can be configurable for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output.
# waveform type
This chip supports 2 type waveforms, needs to paired external 8 KB RAM for use these features:
One is signed 8 bit mono waveform, it's operated like other wavetable based sound systems.
These are stored at the bottom half of RAM at common case.
Another one ("Envelope") is 4 bit stereo waveform, it's multiplied with above and calculates final output, Each nibble is used for each output channels.
These are stored at the upper half of RAM at common case.
Both waveforms are 128 byte fixed size, it's freely allocated at each half of RAM except channel register area: each half can be stored total 32/31 waveforms at once.
In furnace, You can set envelope shape split mode. When it sets, its waveform will be split to left half and right half for each outputs. each max size are 128 bytes, total 256 bytes.
# effects
- `10xx`: change wave.
- `11xx`: change envelope shape. (also wavetable)
- `17xx`: toggle PCM mode.
- `20xx`: set PCM frequency. (1 to FF)*
- `22xx`: set envelope mode.
- bit 0 sets whether envelope will affect this channel.
- bit 1 sets whether envelope one-shot mode. when it sets, channel is halted after envelope cycle is ended.
- bit 2 sets whether envelope shape split mode. when it sets, envelope shape will splitted to left half and right half.
- bit 3/5 sets whether the right/left shape will mirror the original one.
- bit 4/6 sets whether the right/left output will mirror the original one.
- `23xx`: set envelope period.
- `25xx`: slide envelope period up.
- `26xx`: slide envelope period down.
- `29xy`: enable auto-envelope mode.
- in this mode the envelope period is set to the channel's notes, multiplied by a fraction.
- `x` is the numerator.
- `y` is the denominator.
- if `x` or `y` are 0 this will disable auto-envelope mode.
* PCM frequency: 255 step, fomula: `step * (Chip clock / 8192)`; 1.95KHz to 498KHz if Chip clock is 16MHz.

View File

@ -29,6 +29,9 @@ furthermore, an `or reserved` indicates this field is always present, but is res
the format versions are:
- 66: Furnace dev66
- 65: Furnace dev65
- 64: Furnace dev64
- 63: Furnace dev63
- 62: Furnace dev62
- 61: Furnace dev61
@ -203,7 +206,10 @@ size | description
1 | ignore duplicate slides (>=50) or reserved
1 | stop portamento on note off (>=62) or reserved
1 | continuous vibrato (>=62) or reserved
4 | reserved
1 | broken DAC mode (>=64) or reserved
1 | one tick cut (>=65) or reserved
1 | instrument change allowed during porta (>=66) or reserved
1 | reserved
4?? | pointers to instruments
4?? | pointers to wavetables
4?? | pointers to samples

View File

@ -1,6 +1,6 @@
#!/bin/bash
# make Windows release
# this script shall be run from Linux with MinGW installed!
# this script shall be run from Arch Linux with MinGW installed!
if [ ! -e /tmp/furnace ]; then
ln -s "$PWD" /tmp/furnace || exit 1
@ -14,7 +14,8 @@ fi
cd win32build
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" -DBUILD_SHARED_LIBS=OFF .. || exit 1
# TODO: potential Arch-ism?
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF .. || exit 1
make -j8 || exit 1
i686-w64-mingw32-strip -s furnace.exe || exit 1
@ -30,3 +31,7 @@ cp -r ../../papers papers || exit 1
cp -r ../../demos demos || exit 1
zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos
furName=$(git describe --tags | sed "s/v0/0/")
mv furnace.zip furnace-"$furName"-win32.zip

View File

@ -1,6 +1,6 @@
#!/bin/bash
# make Windows release
# this script shall be run from Linux with MinGW installed!
# this script shall be run from Arch Linux with MinGW installed!
if [ ! -e /tmp/furnace ]; then
ln -s "$PWD" /tmp/furnace || exit 1
@ -14,7 +14,8 @@ fi
cd winbuild
x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" .. || exit 1
# TODO: potential Arch-ism?
x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" .. || exit 1
make -j8 || exit 1
x86_64-w64-mingw32-strip -s furnace.exe || exit 1
@ -30,3 +31,7 @@ cp -r ../../papers papers || exit 1
cp -r ../../demos demos || exit 1
zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos
furName=$(git describe --tags | sed "s/v0/0/")
mv furnace.zip furnace-"$furName"-win64.zip

View File

@ -110,6 +110,13 @@ enum DivDispatchCmds {
DIV_CMD_QSOUND_ECHO_DELAY,
DIV_CMD_QSOUND_ECHO_LEVEL,
DIV_CMD_X1_010_ENVELOPE_SHAPE,
DIV_CMD_X1_010_ENVELOPE_ENABLE,
DIV_CMD_X1_010_ENVELOPE_MODE,
DIV_CMD_X1_010_ENVELOPE_PERIOD,
DIV_CMD_X1_010_ENVELOPE_SLIDE,
DIV_CMD_X1_010_AUTO_ENVELOPE,
DIV_CMD_WS_SWEEP_TIME,
DIV_CMD_WS_SWEEP_AMOUNT,

View File

@ -41,9 +41,11 @@
#include "platform/pcspkr.h"
#include "platform/segapcm.h"
#include "platform/qsound.h"
#include "platform/vera.h"
#include "platform/x1_010.h"
#include "platform/swan.h"
#include "platform/dummy.h"
#include "platform/lynx.h"
#include "platform/dummy.h"
#include "../ta-log.h"
#include "song.h"
@ -237,7 +239,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
((DivPlatformOPL*)dispatch)->setOPLType(3,true);
break;
case DIV_SYSTEM_SAA1099: {
int saaCore=eng->getConfInt("saaCore",0);
int saaCore=eng->getConfInt("saaCore",1);
if (saaCore<0 || saaCore>2) saaCore=0;
dispatch=new DivPlatformSAA1099;
((DivPlatformSAA1099*)dispatch)->setCore((DivSAACores)saaCore);
@ -256,9 +258,15 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_SEGAPCM_COMPAT:
dispatch=new DivPlatformSegaPCM;
break;
case DIV_SYSTEM_X1_010:
dispatch=new DivPlatformX1_010;
break;
case DIV_SYSTEM_SWAN:
dispatch=new DivPlatformSwan;
break;
case DIV_SYSTEM_VERA:
dispatch=new DivPlatformVERA;
break;
default:
logW("this system is not supported yet! using dummy platform.\n");
dispatch=new DivPlatformDummy;

View File

@ -19,7 +19,6 @@
#include "dataErrors.h"
#include "song.h"
#include <cstddef>
#define _USE_MATH_DEFINES
#include "engine.h"
#include "instrument.h"
@ -547,6 +546,36 @@ void DivEngine::renderSamples() {
memPos+=length+16;
}
qsoundMemLen=memPos+256;
// step 4: allocate x1-010 pcm samples
if (x1_010Mem==NULL) x1_010Mem=new unsigned char[1048576];
memset(x1_010Mem,0,1048576);
memPos=0;
for (int i=0; i<song.sampleLen; i++) {
DivSample* s=song.sample[i];
int paddedLen=(s->length8+4095)&(~0xfff);
// fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!)
if (paddedLen>131072) {
paddedLen=131072;
}
if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) {
memPos=(memPos+0x1ffff)&0xfe0000;
}
if (memPos>=1048576) {
logW("out of X1-010 memory for sample %d!\n",i);
break;
}
if (memPos+paddedLen>=1048576) {
memcpy(x1_010Mem+memPos,s->data8,1048576-memPos);
logW("out of X1-010 memory for sample %d!\n",i);
} else {
memcpy(x1_010Mem+memPos,s->data8,paddedLen);
}
s->offX1_010=memPos;
memPos+=paddedLen;
}
x1_010MemLen=memPos+256;
}
void DivEngine::createNew(const int* description) {
@ -965,10 +994,14 @@ int DivEngine::getEffectiveSampleRate(int rate) {
return 1789773/(1789773/rate);
case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT:
return (31250*MIN(255,(rate*255/31250)))/255;
case DIV_SYSTEM_QSOUND:
case DIV_SYSTEM_QSOUND:
return (24038*MIN(65535,(rate*4096/24038)))/4096;
case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT:
return 18518;
case DIV_SYSTEM_VERA:
return (48828*MIN(128,(rate*128/48828)))/128;
case DIV_SYSTEM_X1_010:
return (31250*MIN(255,(rate*16/31250)))/16; // TODO: support variable clock case
default:
break;
}

View File

@ -37,8 +37,8 @@
warnings+=(String("\n")+x); \
}
#define DIV_VERSION "dev63"
#define DIV_ENGINE_VERSION 63
#define DIV_VERSION "dev66"
#define DIV_ENGINE_VERSION 66
enum DivStatusView {
DIV_STATUS_NOTHING=0,
@ -69,7 +69,7 @@ enum DivHaltPositions {
struct DivChannelState {
std::vector<DivDelayedCommand> delayed;
int note, oldNote, pitch, portaSpeed, portaNote;
int note, oldNote, lastIns, pitch, portaSpeed, portaNote;
int volume, volSpeed, cut, rowDelay, volMax;
int delayOrder, delayRow, retrigSpeed, retrigTick;
int vibratoDepth, vibratoRate, vibratoPos, vibratoDir, vibratoFine;
@ -80,6 +80,7 @@ struct DivChannelState {
DivChannelState():
note(-1),
oldNote(-1),
lastIns(-1),
pitch(0),
portaSpeed(-1),
portaNote(-1),
@ -633,6 +634,8 @@ class DivEngine {
size_t qsoundAMemLen;
unsigned char* dpcmMem;
size_t dpcmMemLen;
unsigned char* x1_010Mem;
size_t x1_010MemLen;
DivEngine():
output(NULL),

View File

@ -140,6 +140,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.algMacroBehavior=false;
ds.brokenShortcutSlides=false;
ds.ignoreDuplicateSlides=true;
ds.brokenDACMode=true;
ds.oneTickCut=false;
ds.newInsTriggersInPorta=true;
// 1.1 compat flags
if (ds.version>24) {
@ -149,8 +152,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
// Neo Geo detune
if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT
|| ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT
|| ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) {
|| ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT
|| ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) {
ds.tuning=443.23;
}
@ -259,8 +262,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ins->type=DIV_INS_C64;
}
if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT
|| ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT
|| ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) {
|| ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT
|| ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) {
if (!ins->mode) {
ins->type=DIV_INS_AY;
}
@ -799,6 +802,15 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<62) {
ds.stopPortaOnNoteOff=true;
}
if (ds.version<64) {
ds.brokenDACMode=false;
}
if (ds.version<65) {
ds.oneTickCut=false;
}
if (ds.version<66) {
ds.newInsTriggersInPorta=false;
}
ds.isDMF=false;
reader.readS(); // reserved
@ -975,7 +987,22 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
reader.readC();
reader.readC();
}
for (int i=0; i<4; i++) reader.readC();
if (ds.version>=64) {
ds.brokenDACMode=reader.readC();
} else {
reader.readC();
}
if (ds.version>=65) {
ds.oneTickCut=reader.readC();
} else {
reader.readC();
}
if (ds.version>=66) {
ds.newInsTriggersInPorta=reader.readC();
} else {
reader.readC();
}
for (int i=0; i<1; i++) reader.readC();
} else {
for (int i=0; i<20; i++) reader.readC();
}
@ -1746,7 +1773,10 @@ SafeWriter* DivEngine::saveFur() {
w->writeC(song.ignoreDuplicateSlides);
w->writeC(song.stopPortaOnNoteOff);
w->writeC(song.continuousVibrato);
for (int i=0; i<4; i++) {
w->writeC(song.brokenDACMode);
w->writeC(song.oneTickCut);
w->writeC(song.newInsTriggersInPorta);
for (int i=0; i<1; i++) {
w->writeC(0);
}

View File

@ -51,6 +51,9 @@ enum DivInstrumentType {
DIV_INS_BEEPER=21,
DIV_INS_SWAN=22,
DIV_INS_MIKEY=23,
DIV_INS_VERA=24,
DIV_INS_X1_010=25,
DIV_INS_MAX,
};
// FM operator structure:

View File

@ -24,7 +24,7 @@
#include <math.h>
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} }
#define CHIP_DIVIDER 8
@ -48,8 +48,28 @@ const char* regCheatSheetAY[]={
NULL
};
const char* regCheatSheetAY8914[]={
"FreqL_A", "0",
"FreqL_B", "1",
"FreqL_C", "2",
"FreqL_Env", "3",
"FreqH_A", "4",
"FreqH_B", "5",
"FreqH_C", "6",
"FreqH_Env", "7",
"Enable", "8",
"FreqNoise", "9",
"Control_Env", "A",
"Volume_A", "B",
"Volume_B", "C",
"Volume_C", "D",
"PortA", "E",
"PortB", "F",
NULL
};
const char** DivPlatformAY8910::getRegisterSheet() {
return regCheatSheetAY;
return intellivision?regCheatSheetAY8914:regCheatSheetAY;
}
const char* DivPlatformAY8910::getEffectName(unsigned char effect) {
@ -92,8 +112,13 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l
}
while (!writes.empty()) {
QueuedWrite w=writes.front();
ay->address_w(w.addr);
ay->data_w(w.val);
if (intellivision) {
ay8914_device* ay8914=(ay8914_device*)ay;
ay8914->write(w.addr,w.val);
} else {
ay->address_w(w.addr);
ay->data_w(w.val);
}
regPool[w.addr&0x0f]=w.val;
writes.pop();
}
@ -125,6 +150,8 @@ void DivPlatformAY8910::tick() {
if (chan[i].outVol<0) chan[i].outVol=0;
if (isMuted[i]) {
rWrite(0x08+i,0);
} else if (intellivision && (chan[i].psgMode&4)) {
rWrite(0x08+i,(chan[i].outVol&0xc)<<2);
} else {
rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2));
}
@ -151,6 +178,8 @@ void DivPlatformAY8910::tick() {
chan[i].psgMode=(chan[i].std.wave+1)&7;
if (isMuted[i]) {
rWrite(0x08+i,0);
} else if (intellivision && (chan[i].psgMode&4)) {
rWrite(0x08+i,(chan[i].outVol&0xc)<<2);
} else {
rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2));
}
@ -242,6 +271,8 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
chan[c.chan].std.init(ins);
if (isMuted[c.chan]) {
rWrite(0x08+c.chan,0);
} else if (intellivision && (chan[c.chan].psgMode&4)) {
rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2);
} else {
rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2));
}
@ -264,7 +295,13 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
if (isMuted[c.chan]) {
rWrite(0x08+c.chan,0);
} else {
if (chan[c.chan].active) rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2));
if (chan[c.chan].active) {
if (intellivision && (chan[c.chan].psgMode&4)) {
rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2);
} else {
rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2));
}
}
}
break;
}
@ -317,7 +354,11 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
if (isMuted[c.chan]) {
rWrite(0x08+c.chan,0);
} else if (chan[c.chan].active) {
rWrite(0x08+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode&4)<<2));
if (intellivision && (chan[c.chan].psgMode&4)) {
rWrite(0x08+c.chan,(chan[c.chan].outVol&0xc)<<2);
} else {
rWrite(0x08+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode&4)<<2));
}
}
}
break;
@ -334,6 +375,8 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
}
if (isMuted[c.chan]) {
rWrite(0x08+c.chan,0);
} else if (intellivision && (chan[c.chan].psgMode&4)) {
rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2);
} else {
rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2));
}
@ -383,6 +426,8 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (isMuted[ch]) {
rWrite(0x08+ch,0);
} else if (intellivision && (chan[ch].psgMode&4)) {
rWrite(0x08+ch,(chan[ch].vol&0xc)<<2);
} else {
rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2));
}
@ -508,14 +553,22 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
case 1:
ay=new ym2149_device(rate);
sunsoft=false;
intellivision=false;
break;
case 2:
ay=new sunsoft_5b_sound_device(rate);
sunsoft=true;
intellivision=false;
break;
case 3:
ay=new ay8914_device(rate);
sunsoft=false;
intellivision=true;
break;
default:
ay=new ay8910_device(rate);
sunsoft=false;
intellivision=false;
break;
}
ay->device_start();

View File

@ -26,6 +26,10 @@
class DivPlatformAY8910: public DivDispatch {
protected:
const unsigned char AY8914RegRemap[16]={
0,4,1,5,2,6,9,8,11,12,13,3,7,10,14,15
};
inline unsigned char regRemap(unsigned char reg) { return intellivision?AY8914RegRemap[reg&0x0f]:reg&0x0f; }
struct Channel {
unsigned char freqH, freqL;
int freq, baseFreq, note, pitch;
@ -60,7 +64,7 @@ class DivPlatformAY8910: public DivDispatch {
int delay;
bool extMode;
bool stereo, sunsoft;
bool stereo, sunsoft, intellivision;
short oldWrites[16];
short pendingWrites[16];

View File

@ -509,10 +509,17 @@ void DivPlatformC64::setChipModel(bool is6581) {
}
void DivPlatformC64::setFlags(unsigned int flags) {
if (flags&1) {
rate=COLOR_PAL*2.0/9.0;
} else {
rate=COLOR_NTSC*2.0/7.0;
switch (flags&0xf) {
case 0x0: // NTSC C64
rate=COLOR_NTSC*2.0/7.0;
break;
case 0x1: // PAL C64
rate=COLOR_PAL*2.0/9.0;
break;
case 0x2: // SSI 2001
default:
rate=14318180.0/16.0;
break;
}
chipClock=rate;
}

View File

@ -97,8 +97,12 @@ void DivPlatformGB::updateWave() {
if (wt->max<1 || wt->len<1) {
rWrite(0x30+i,0);
} else {
unsigned char nibble1=15-((wt->data[(i*2)*wt->len/32]*15)/wt->max);
unsigned char nibble2=15-((wt->data[(1+i*2)*wt->len/32]*15)/wt->max);
int nibble1=15-((wt->data[(i*2)*wt->len/32]*15)/wt->max);
int nibble2=15-((wt->data[(1+i*2)*wt->len/32]*15)/wt->max);
if (nibble1<0) nibble1=0;
if (nibble1>15) nibble1=15;
if (nibble2<0) nibble2=0;
if (nibble2>15) nibble2=15;
rWrite(0x30+i,(nibble1<<4)|nibble2);
}
}

View File

@ -92,13 +92,16 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
DivSample* s=parent->getSample(dacSample);
if (s->samples>0) {
if (!isMuted[5]) {
immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
}
if (++dacPos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<=(int)s->samples) {
dacPos=s->loopStart;
} else {
dacSample=-1;
if (parent->song.brokenDACMode) {
rWrite(0x2b,0);
}
}
}
dacPeriod+=MAX(40,dacRate);
@ -118,7 +121,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
//printf("write: %x = %.2x\n",w.addr,w.val);
lastBusy=0;
regPool[w.addr&0x1ff]=w.val;
writes.pop();
writes.pop_front();
} else {
lastBusy++;
if (fm.write_busy==0) {
@ -156,13 +159,16 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
DivSample* s=parent->getSample(dacSample);
if (s->samples>0) {
if (!isMuted[5]) {
immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
}
if (++dacPos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<=(int)s->samples) {
dacPos=s->loopStart;
} else {
dacSample=-1;
if (parent->song.brokenDACMode) {
rWrite(0x2b,0);
}
}
}
dacPeriod+=MAX(40,dacRate);
@ -178,7 +184,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr);
fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val);
regPool[w.addr&0x1ff]=w.val;
writes.pop();
writes.pop_front();
lastBusy=1;
}
@ -460,6 +466,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
if (dumpWrites) addWrite(0xffff0002,0);
break;
} else {
rWrite(0x2b,1<<7);
if (dumpWrites) addWrite(0xffff0000,dacSample);
}
dacPos=0;
@ -477,6 +484,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
if (dumpWrites) addWrite(0xffff0002,0);
break;
} else {
rWrite(0x2b,1<<7);
if (dumpWrites) addWrite(0xffff0000,dacSample);
}
dacPos=0;
@ -541,6 +549,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
if (c.chan==5) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);
if (parent->song.brokenDACMode) {
rWrite(0x2b,0);
if (dacMode) break;
}
}
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
@ -770,7 +782,7 @@ int DivPlatformGenesis::getRegisterPoolSize() {
}
void DivPlatformGenesis::reset() {
while (!writes.empty()) writes.pop();
while (!writes.empty()) writes.pop_front();
memset(regPool,0,512);
if (useYMFM) {
fm_ymfm->reset();

View File

@ -20,7 +20,7 @@
#ifndef _GENESIS_H
#define _GENESIS_H
#include "../dispatch.h"
#include <queue>
#include <deque>
#include "../../../extern/Nuked-OPN2/ym3438.h"
#include "sound/ymfm/ymfm_opn.h"
@ -68,7 +68,7 @@ class DivPlatformGenesis: public DivDispatch {
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
std::deque<QueuedWrite> writes;
ym3438_t fm;
int delay;
unsigned char lastBusy;

View File

@ -43,6 +43,7 @@ static int orderedOps[4]={
};
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define immWrite(a,v) if (!skipRegisterWrites) {writes.push_back(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} }
#define urgentWrite(a,v) if (!skipRegisterWrites) {if (writes.front().addrOrVal) {writes.push_back(QueuedWrite(a,v));} else {writes.push_front(QueuedWrite(a,v));}; if (dumpWrites) {addWrite(a,v);} }
#include "fmshared_OPN.h"

View File

@ -82,7 +82,44 @@ static int32_t clamp(int32_t v, int32_t lo, int32_t hi)
}
const char* regCheatSheetLynx[]={
"DATA", "0",
"AUDIO0_VOLCNTRL", "20",
"AUDIO0_FEEDBACK", "21",
"AUDIO0_OUTPUT", "22",
"AUDIO0_SHIFT", "23",
"AUDIO0_BACKUP", "24",
"AUDIO0_CONTROL", "25",
"AUDIO0_COUNTER", "26",
"AUDIO0_OTHER", "27",
"AUDIO1_VOLCNTRL", "28",
"AUDIO1_FEEDBACK", "29",
"AUDIO1_OUTPUT", "2a",
"AUDIO1_SHIFT", "2b",
"AUDIO1_BACKUP", "2c",
"AUDIO1_CONTROL", "2d",
"AUDIO1_COUNTER", "2e",
"AUDIO1_OTHER", "2f",
"AUDIO2_VOLCNTRL", "30",
"AUDIO2_FEEDBACK", "31",
"AUDIO2_OUTPUT", "32",
"AUDIO2_SHIFT", "33",
"AUDIO2_BACKUP", "34",
"AUDIO2_CONTROL", "35",
"AUDIO2_COUNTER", "36",
"AUDIO2_OTHER", "37",
"AUDIO3_VOLCNTRL", "38",
"AUDIO3_FEEDBACK", "39",
"AUDIO3_OUTPUT", "3a",
"AUDIO3_SHIFT", "3b",
"AUDIO3_BACKUP", "3c",
"AUDIO3_CONTROL", "3d",
"AUDIO3_COUNTER", "3e",
"AUDIO3_OTHER", "3f",
"ATTENREG0", "40",
"ATTENREG1", "41",
"ATTENREG2", "42",
"ATTENREG3", "43",
"MPAN", "44",
"MSTEREO", "50",
NULL
};
@ -198,7 +235,7 @@ int DivPlatformLynx::dispatch(DivCommand c) {
}
break;
case DIV_CMD_PANNING:
chan[c.chan].pan=((c.value&0x0f)<<4)|((c.value&0xf0)>>4);
chan[c.chan].pan=c.value;
WRITE_ATTEN(c.chan,chan[c.chan].pan);
break;
case DIV_CMD_GET_VOLUME:

View File

@ -201,6 +201,7 @@ void DivPlatformNES::tick() {
} else {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1;
if (chan[i].freq>2047) chan[i].freq=2047;
if (chan[i].freq<0) chan[i].freq=0;
}
if (chan[i].keyOn) {
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));

View File

@ -222,7 +222,7 @@ void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len)
}
void DivPlatformOPL::tick() {
for (int i=0; i<20; i++) {
for (int i=0; i<melodicChans; i++) {
chan[i].std.next();
/*
@ -349,10 +349,22 @@ void DivPlatformOPL::tick() {
if (chan[i].keyOn || chan[i].keyOff) {
immWrite(chanMap[i]+ADDR_FREQH,0x00|(chan[i].freqH&31));
if (chan[i].state.ops==4 && i<6) {
immWrite(chanMap[i+1]+ADDR_FREQH,0x00|(chan[i].freqH&31));
}
chan[i].keyOff=false;
}
}
if (update4OpMask) {
update4OpMask=false;
if (oplType==3) {
unsigned char opMask=chan[0].fourOp|(chan[2].fourOp<<1)|(chan[4].fourOp<<2)|(chan[6].fourOp<<3)|(chan[8].fourOp<<4)|(chan[10].fourOp<<5);
immWrite(0x104,opMask);
//printf("updating opMask to %.2x\n",opMask);
}
}
for (int i=0; i<512; i++) {
if (pendingWrites[i]!=oldWrites[i]) {
immWrite(i,pendingWrites[i]&0xff);
@ -360,7 +372,7 @@ void DivPlatformOPL::tick() {
}
}
for (int i=0; i<20; i++) {
for (int i=0; i<melodicChans; i++) {
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq));
if (chan[i].freq>131071) chan[i].freq=131071;
@ -368,12 +380,21 @@ void DivPlatformOPL::tick() {
chan[i].freqH=freqt>>8;
chan[i].freqL=freqt&0xff;
immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL);
if (chan[i].state.ops==4 && i<6) {
immWrite(chanMap[i+1]+ADDR_FREQ,chan[i].freqL);
}
}
if (chan[i].keyOn) {
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(0x20));
if (chan[i].state.ops==4 && i<6) {
immWrite(chanMap[i+1]+ADDR_FREQH,chan[i].freqH|(0x20));
}
chan[i].keyOn=false;
} else if (chan[i].freqChanged) {
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5));
if (chan[i].state.ops==4 && i<6) {
immWrite(chanMap[i+1]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5));
}
}
chan[i].freqChanged=false;
}
@ -424,25 +445,42 @@ int DivPlatformOPL::toFreq(int freq) {
void DivPlatformOPL::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
/*
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[ch]|opOffs[j];
DivInstrumentFM::Operator& op=chan[ch].state.op[j];
int ops=(slots[3][ch]!=255 && chan[ch].state.ops==4 && oplType==3)?4:2;
chan[ch].fourOp=(ops==4);
update4OpMask=true;
for (int i=0; i<ops; i++) {
unsigned char slot=slots[i][ch];
if (slot==255) continue;
unsigned short baseAddr=slotMap[slot];
DivInstrumentFM::Operator& op=chan[ch].state.op[(ops==4)?orderedOpsL[i]:i];
if (isMuted[ch]) {
rWrite(baseAddr+ADDR_TL,127);
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
} else {
if (isOutput[chan[ch].state.alg][j]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[ch].outVol&0x7f))/127));
if (isOutputL[ops==4][chan[ch].state.alg][i]) {
rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[ch].outVol&0x3f))/63))|(op.ksl<<6));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6));
}
}
}
rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4));
*/
if (isMuted[ch]) {
rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1));
if (ops==4) {
rWrite(chanMap[ch+1]+ADDR_LR_FB_ALG,((chan[ch].state.alg>>1)&1)|(chan[ch].state.fb<<1));
}
} else {
rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&3)<<4));
if (ops==4) {
rWrite(chanMap[ch+1]+ADDR_LR_FB_ALG,((chan[ch].state.alg>>1)&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&3)<<4));
}
}
}
int DivPlatformOPL::dispatch(DivCommand c) {
// TODO: drums mode!
if (c.chan>=melodicChans) return 0;
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
@ -456,7 +494,9 @@ int DivPlatformOPL::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (chan[c.chan].insChanged) {
int ops=(slots[3][c.chan]!=255 && ins->fm.ops==4 && oplType==3)?4:2;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
chan[c.chan].fourOp=(ops==4);
update4OpMask=true;
for (int i=0; i<ops; i++) {
unsigned char slot=slots[i][c.chan];
if (slot==255) continue;
@ -524,21 +564,23 @@ int DivPlatformOPL::dispatch(DivCommand c) {
if (!chan[c.chan].std.hasVol) {
chan[c.chan].outVol=c.value;
}
/*
for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
for (int i=0; i<ops; i++) {
unsigned char slot=slots[i][c.chan];
if (slot==255) continue;
unsigned short baseAddr=slotMap[slot];
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i];
if (isMuted[c.chan]) {
rWrite(baseAddr+ADDR_TL,127);
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
} else {
if (isOutput[chan[c.chan].state.alg][i]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
if (isOutputL[ops==4][chan[c.chan].state.alg][i]) {
rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[c.chan].outVol&0x3f))/63))|(op.ksl<<6));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6));
}
}
}
*/
break;
}
case DIV_CMD_GET_VOLUME: {
@ -552,16 +594,22 @@ int DivPlatformOPL::dispatch(DivCommand c) {
chan[c.chan].ins=c.value;
break;
case DIV_CMD_PANNING: {
switch (c.value) {
case 0x01:
chan[c.chan].pan=1;
break;
case 0x10:
chan[c.chan].pan=2;
break;
default:
chan[c.chan].pan=3;
break;
if (c.value==0) {
chan[c.chan].pan=3;
} else {
chan[c.chan].pan=(((c.value&15)>0)<<1)|((c.value>>4)>0);
}
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (isMuted[c.chan]) {
rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1));
if (ops==4) {
rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1));
}
} else {
rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4));
if (ops==4) {
rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4));
}
}
//rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4));
break;
@ -683,39 +731,46 @@ int DivPlatformOPL::dispatch(DivCommand c) {
}
void DivPlatformOPL::forceIns() {
/*
for (int i=0; i<20; i++) {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
for (int i=0; i<melodicChans; i++) {
int ops=(slots[3][i]!=255 && chan[i].state.ops==4 && oplType==3)?4:2;
chan[i].fourOp=(ops==4);
for (int j=0; j<ops; j++) {
unsigned char slot=slots[j][i];
if (slot==255) continue;
unsigned short baseAddr=slotMap[slot];
DivInstrumentFM::Operator& op=chan[i].state.op[(ops==4)?orderedOpsL[j]:j];
if (isMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
} else {
if (isOutput[chan[i].state.alg][j]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
if (isOutputL[ops==4][chan[i].state.alg][j]) {
rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[i].outVol&0x3f))/63))|(op.ksl<<6));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6));
}
}
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr);
rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr);
if (oplType>1) {
rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3));
}
}
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
if (chan[i].active) {
chan[i].keyOn=true;
chan[i].freqChanged=true;
if (isMuted[i]) {
rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1));
if (ops==4) {
rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1));
}
} else {
rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)|((chan[i].pan&3)<<4));
if (ops==4) {
rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)|((chan[i].pan&3)<<4));
}
}
}
if (dacMode) {
rWrite(0x2b,0x80);
}
immWrite(0x22,lfoValue);
*/
update4OpMask=true;
}
void DivPlatformOPL::toggleRegisterDump(bool enable) {
@ -746,7 +801,7 @@ void DivPlatformOPL::reset() {
if (dumpWrites) {
addWrite(0xffffffff,0);
}
for (int i=0; i<20; i++) {
for (int i=0; i<totalChans; i++) {
chan[i]=DivPlatformOPL::Channel();
chan[i].vol=0x3f;
chan[i].outVol=0x3f;
@ -761,10 +816,15 @@ void DivPlatformOPL::reset() {
lfoValue=8;
properDrums=properDrumsSys;
if (oplType==1) { // disable waveforms
immWrite(0x01,0x20);
}
if (oplType==3) { // enable OPL3 features
immWrite(0x105,1);
}
update4OpMask=true;
delay=0;
}
@ -781,7 +841,7 @@ bool DivPlatformOPL::keyOffAffectsPorta(int ch) {
}
void DivPlatformOPL::notifyInsChange(int ins) {
for (int i=0; i<20; i++) {
for (int i=0; i<totalChans; i++) {
if (chan[i].ins==ins) {
chan[i].insChanged=true;
}
@ -815,6 +875,9 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
slots=drums?slotsDrums:slotsNonDrums;
chanMap=chanMapOPL2;
chipFreqBase=9440540*0.25;
chans=9;
melodicChans=drums?6:9;
totalChans=drums?11:9;
break;
case 3:
slotsNonDrums=slotsOPL3;
@ -822,6 +885,9 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
slots=drums?slotsDrums:slotsNonDrums;
chanMap=chanMapOPL3;
chipFreqBase=9440540;
chans=18;
melodicChans=drums?15:18;
totalChans=drums?20:18;
break;
}
oplType=type;

View File

@ -32,7 +32,7 @@ class DivPlatformOPL: public DivDispatch {
unsigned char freqH, freqL;
int freq, baseFreq, pitch, note;
unsigned char ins;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, fourOp;
int vol, outVol;
unsigned char pan;
Channel():
@ -51,6 +51,7 @@ class DivPlatformOPL: public DivDispatch {
portaPause(false),
furnaceDac(false),
inPorta(false),
fourOp(false),
vol(0),
pan(3) {}
};
@ -69,7 +70,7 @@ class DivPlatformOPL: public DivDispatch {
const unsigned char** slots;
const unsigned short* chanMap;
double chipFreqBase;
int delay, oplType;
int delay, oplType, chans, melodicChans, totalChans;
unsigned char lastBusy;
unsigned char regPool[512];
@ -78,7 +79,7 @@ class DivPlatformOPL: public DivDispatch {
unsigned char lfoValue;
bool useYMFM;
bool useYMFM, update4OpMask;
short oldWrites[512];
short pendingWrites[512];

View File

@ -138,7 +138,10 @@ void DivPlatformPCE::updateWave(int ch) {
if (wt->max<1 || wt->len<1) {
chWrite(ch,0x06,0);
} else {
chWrite(ch,0x06,wt->data[i*wt->len/32]*31/wt->max);
int data=wt->data[i*wt->len/32]*31/wt->max;
if (data<0) data=0;
if (data>31) data=31;
chWrite(ch,0x06,data);
}
}
if (chan[ch].active) {

View File

@ -435,8 +435,8 @@ public:
case ATTENREG2:
case ATTENREG3:
mRegisterPool[8*4+idx] = value;
mAttenuationLeft[idx] = ( value & 0x0f ) << 2;
mAttenuationRight[idx] = ( value & 0xf0 ) >> 2;
mAttenuationRight[idx] = ( value & 0x0f ) << 2;
mAttenuationLeft[idx] = ( value & 0xf0 ) >> 2;
break;
case MPAN:
mPan = value;

View File

@ -0,0 +1,135 @@
// Commander X16 Emulator
// Copyright (c) 2020 Frank van den Hoef
// All rights reserved. License: 2-clause BSD
#include "vera_pcm.h"
#include <stdio.h>
#include <string.h>
static uint8_t volume_lut[16] = {0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64};
static void
fifo_reset(struct VERA_PCM* pcm)
{
pcm->fifo_wridx = 0;
pcm->fifo_rdidx = 0;
pcm->fifo_cnt = 0;
memset(pcm->fifo,0,sizeof(pcm->fifo));
}
void
pcm_reset(struct VERA_PCM* pcm)
{
fifo_reset(pcm);
pcm->ctrl = 0;
pcm->rate = 0;
pcm->cur_l = 0;
pcm->cur_r = 0;
pcm->phase = 0;
}
void
pcm_write_ctrl(struct VERA_PCM* pcm, uint8_t val)
{
if (val & 0x80) {
fifo_reset(pcm);
}
pcm->ctrl = val & 0x3F;
}
uint8_t
pcm_read_ctrl(struct VERA_PCM* pcm)
{
uint8_t result = pcm->ctrl;
if (pcm->fifo_cnt == sizeof(pcm->fifo)) {
result |= 0x80;
}
return result;
}
void
pcm_write_rate(struct VERA_PCM* pcm, uint8_t val)
{
pcm->rate = val;
}
uint8_t
pcm_read_rate(struct VERA_PCM* pcm)
{
return pcm->rate;
}
void
pcm_write_fifo(struct VERA_PCM* pcm, uint8_t val)
{
if (pcm->fifo_cnt < sizeof(pcm->fifo)) {
pcm->fifo[pcm->fifo_wridx++] = val;
if (pcm->fifo_wridx == sizeof(pcm->fifo)) {
pcm->fifo_wridx = 0;
}
pcm->fifo_cnt++;
}
}
static uint8_t
read_fifo(struct VERA_PCM* pcm)
{
if (pcm->fifo_cnt == 0) {
return 0;
}
uint8_t result = pcm->fifo[pcm->fifo_rdidx++];
if (pcm->fifo_rdidx == sizeof(pcm->fifo)) {
pcm->fifo_rdidx = 0;
}
pcm->fifo_cnt--;
return result;
}
bool
pcm_is_fifo_almost_empty(struct VERA_PCM* pcm)
{
return pcm->fifo_cnt < 1024;
}
void
pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_samples)
{
while (num_samples--) {
uint8_t old_phase = pcm->phase;
pcm->phase += pcm->rate;
if ((old_phase & 0x80) != (pcm->phase & 0x80)) {
switch ((pcm->ctrl >> 4) & 3) {
case 0: { // mono 8-bit
pcm->cur_l = (int16_t)read_fifo(pcm) << 8;
pcm->cur_r = pcm->cur_l;
break;
}
case 1: { // stereo 8-bit
pcm->cur_l = read_fifo(pcm) << 8;
pcm->cur_r = read_fifo(pcm) << 8;
break;
}
case 2: { // mono 16-bit
pcm->cur_l = read_fifo(pcm);
pcm->cur_l |= read_fifo(pcm) << 8;
pcm->cur_r = pcm->cur_l;
break;
}
case 3: { // stereo 16-bit
pcm->cur_l = read_fifo(pcm);
pcm->cur_l |= read_fifo(pcm) << 8;
pcm->cur_r = read_fifo(pcm);
pcm->cur_r |= read_fifo(pcm) << 8;
break;
}
}
}
*(buf_l) += ((int)pcm->cur_l * (int)volume_lut[pcm->ctrl & 0xF]) >> 6;
*(buf_r) += ((int)pcm->cur_r * (int)volume_lut[pcm->ctrl & 0xF]) >> 6;
buf_l++;
buf_r++;
}
}

View File

@ -0,0 +1,31 @@
// Commander X16 Emulator
// Copyright (c) 2020 Frank van den Hoef
// All rights reserved. License: 2-clause BSD
#pragma once
#include <stdint.h>
#include <stdbool.h>
struct VERA_PCM {
uint8_t fifo[4096 - 1]; // Actual hardware FIFO is 4kB, but you can only use 4095 bytes.
unsigned fifo_wridx;
unsigned fifo_rdidx;
unsigned fifo_cnt;
uint8_t ctrl;
uint8_t rate;
int16_t cur_l, cur_r;
uint8_t phase;
};
void pcm_reset(struct VERA_PCM* pcm);
void pcm_write_ctrl(struct VERA_PCM* pcm, uint8_t val);
uint8_t pcm_read_ctrl(struct VERA_PCM* pcm);
void pcm_write_rate(struct VERA_PCM* pcm, uint8_t val);
uint8_t pcm_read_rate(struct VERA_PCM* pcm);
void pcm_write_fifo(struct VERA_PCM* pcm, uint8_t val);
void pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_samples);
bool pcm_is_fifo_almost_empty(struct VERA_PCM* pcm);

View File

@ -0,0 +1,106 @@
// Commander X16 Emulator
// Copyright (c) 2020 Frank van den Hoef
// All rights reserved. License: 2-clause BSD
#include "vera_psg.h"
#include <stdlib.h>
#include <string.h>
enum waveform {
WF_PULSE = 0,
WF_SAWTOOTH,
WF_TRIANGLE,
WF_NOISE,
};
static uint8_t volume_lut[64] = {0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35, 37, 39, 42, 44, 47, 50, 52, 56, 59, 63};
void
psg_reset(struct VERA_PSG* psg)
{
memset(psg->channels, 0, sizeof(psg->channels));
psg->noiseState=1;
psg->noiseOut=0;
}
void
psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val)
{
reg &= 0x3f;
int ch = reg / 4;
int idx = reg & 3;
switch (idx) {
case 0: psg->channels[ch].freq = (psg->channels[ch].freq & 0xFF00) | val; break;
case 1: psg->channels[ch].freq = (psg->channels[ch].freq & 0x00FF) | (val << 8); break;
case 2: {
psg->channels[ch].right = (val & 0x80) != 0;
psg->channels[ch].left = (val & 0x40) != 0;
psg->channels[ch].volume = volume_lut[val & 0x3F];
break;
}
case 3: {
psg->channels[ch].pw = val & 0x3F;
psg->channels[ch].waveform = val >> 6;
break;
}
}
}
static inline void
render(struct VERA_PSG* psg, int16_t *left, int16_t *right)
{
int l = 0;
int r = 0;
// TODO this is a currently speculated noise generation
// as the hardware and sources for it are not out in the public
// and the official emulator just uses rand()
psg->noiseOut=((psg->noiseOut<<1)|(psg->noiseState&1))&63;
psg->noiseState=(psg->noiseState<<1)|(((psg->noiseState>>1)^(psg->noiseState>>2)^(psg->noiseState>>4)^(psg->noiseState>>15))&1);
for (int i = 0; i < 16; i++) {
struct VERAChannel *ch = &psg->channels[i];
unsigned new_phase = (ch->phase + ch->freq) & 0x1FFFF;
if ((ch->phase & 0x10000) != (new_phase & 0x10000)) {
ch->noiseval = psg->noiseOut;
}
ch->phase = new_phase;
uint8_t v = 0;
switch (ch->waveform) {
case WF_PULSE: v = (ch->phase >> 10) > ch->pw ? 0 : 63; break;
case WF_SAWTOOTH: v = ch->phase >> 11; break;
case WF_TRIANGLE: v = (ch->phase & 0x10000) ? (~(ch->phase >> 10) & 0x3F) : ((ch->phase >> 10) & 0x3F); break;
case WF_NOISE: v = ch->noiseval; break;
}
int8_t sv = (v ^ 0x20);
if (sv & 0x20) {
sv |= 0xC0;
}
int val = (int)sv * (int)ch->volume;
if (ch->left) {
l += val;
}
if (ch->right) {
r += val;
}
}
*left = l;
*right = r;
}
void
psg_render(struct VERA_PSG* psg, int16_t *bufL, int16_t *bufR, unsigned num_samples)
{
while (num_samples--) {
render(psg, bufL, bufR);
bufL++;
bufR++;
}
}

View File

@ -0,0 +1,28 @@
// Commander X16 Emulator
// Copyright (c) 2020 Frank van den Hoef
// All rights reserved. License: 2-clause BSD
#pragma once
#include <stdint.h>
#include <stdbool.h>
struct VERAChannel {
uint16_t freq;
uint8_t volume;
bool left, right;
uint8_t pw;
uint8_t waveform;
unsigned phase;
uint8_t noiseval;
};
struct VERA_PSG {
unsigned int noiseState, noiseOut;
struct VERAChannel channels[16];
};
void psg_reset(struct VERA_PSG* psg);
void psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val);
void psg_render(struct VERA_PSG* psg, int16_t *bufL, int16_t *bufR, unsigned num_samples);

View File

@ -0,0 +1,224 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holders: cam900
Seta/Allumer X1-010 Emulation core
the chip has 16 voices, all voices can be switchable to Wavetable or PCM sample playback mode.
It has also 2 output channels, but no known hardware using this feature for stereo sound.
Wavetable needs to paired with envelope, it's always enabled and similar as AY PSG's one
but its shape is stored at RAM.
PCM volume is stored by each register.
Both volume is 4bit per output.
Everything except PCM sample is stored at paired 8 bit RAM.
RAM layout (common case: Address bit 12 is swapped when RAM is shared with CPU)
-----------------------------
0000...007f Voice Registers
0000...0007 Voice 0 Register
Address Bits Description
7654 3210
0 x--- ---- Frequency divider*
---- -x-- Envelope one-shot mode
---- --x- Sound format
---- --0- PCM
---- --1- Wavetable
---- ---x Keyon/off
PCM case:
1 xxxx xxxx Volume (Each nibble is for each output)
2 xxxx xxxx Frequency*
4 xxxx xxxx Start address / 4096
5 xxxx xxxx 0x100 - (End address / 4096)
Wavetable case:
1 ---x xxxx Wavetable data select
2 xxxx xxxx Frequency LSB*
3 xxxx xxxx "" MSB
4 xxxx xxxx Envelope period (.10 fixed point, Low 8 bit)
5 ---x xxxx Envelope shape select (!= 0 : Reserved for Voice registers)
0008...000f Voice 1 Register
...
0078...007f Voice 15 Register
-----------------------------
0080...0fff Envelope shape data (Same as volume; Each nibble is for each output)
0080...00ff Envelope shape data 1
0100...017f Envelope shape data 2
...
0f80...0fff Envelope shape data 31
-----------------------------
1000...1fff Wavetable data
1000...107f Wavetable data 0
1080...10ff Wavetable data 1
...
1f80...1fff Wavetable data 31
-----------------------------
* Frequency is 4.4 fixed point for PCM,
6.10 for Wavetable.
Frequency divider is higher precision or just right shift?
needs verification.
*/
#include "x1_010.hpp"
void x1_010_core::tick()
{
// reset output
m_out[0] = m_out[1] = 0;
for (int i = 0; i < 16; i++)
{
voice_t &v = m_voice[i];
v.tick();
m_out[0] += v.data * v.vol_out[0];
m_out[1] += v.data * v.vol_out[1];
}
}
void x1_010_core::voice_t::tick()
{
data = vol_out[0] = vol_out[1] = 0;
if (flag.keyon)
{
if (flag.wavetable) // Wavetable
{
// envelope, each nibble is for each output
u8 vol = m_host.m_envelope[(bitfield(end_envshape, 0, 5) << 7) | bitfield(env_acc, 10, 7)];
vol_out[0] = bitfield(vol, 4, 4);
vol_out[1] = bitfield(vol, 0, 4);
env_acc += start_envfreq;
if (flag.env_oneshot && bitfield(env_acc, 17))
flag.keyon = false;
else
env_acc = bitfield(env_acc, 0, 17);
// get wavetable data
data = m_host.m_wave[(bitfield(vol_wave, 0, 5) << 7) | bitfield(acc, 10, 7)];
acc = bitfield(acc + (freq >> flag.div), 0, 17);
}
else // PCM sample
{
// volume register, each nibble is for each output
vol_out[0] = bitfield(vol_wave, 4, 4);
vol_out[1] = bitfield(vol_wave, 0, 4);
// get PCM sample
data = m_host.m_intf.read_byte(bitfield(acc, 4, 20));
acc += bitfield(freq, 0, 8) >> flag.div;
if ((acc >> 16) > (0xff ^ end_envshape))
flag.keyon = false;
}
}
}
u8 x1_010_core::ram_r(u16 offset)
{
if (offset & 0x1000) // wavetable data
return m_wave[offset & 0xfff];
else if (offset & 0xf80) // envelope shape data
return m_envelope[offset & 0xfff];
else // channel register
return m_voice[bitfield(offset, 3, 4)].reg_r(offset & 0x7);
}
void x1_010_core::ram_w(u16 offset, u8 data)
{
if (offset & 0x1000) // wavetable data
m_wave[offset & 0xfff] = data;
else if (offset & 0xf80) // envelope shape data
m_envelope[offset & 0xfff] = data;
else // channel register
m_voice[bitfield(offset, 3, 4)].reg_w(offset & 0x7, data);
}
u8 x1_010_core::voice_t::reg_r(u8 offset)
{
switch (offset & 0x7)
{
case 0x00: return (flag.div << 7)
| (flag.env_oneshot << 2)
| (flag.wavetable << 1)
| (flag.keyon << 0);
case 0x01: return vol_wave;
case 0x02: return bitfield(freq, 0, 8);
case 0x03: return bitfield(freq, 8, 8);
case 0x04: return start_envfreq;
case 0x05: return end_envshape;
default: break;
}
return 0;
}
void x1_010_core::voice_t::reg_w(u8 offset, u8 data)
{
switch (offset & 0x7)
{
case 0x00:
{
const bool prev_keyon = flag.keyon;
flag.div = bitfield(data, 7);
flag.env_oneshot = bitfield(data, 2);
flag.wavetable = bitfield(data, 1);
flag.keyon = bitfield(data, 0);
if (!prev_keyon && flag.keyon) // Key on
{
acc = flag.wavetable ? 0 : (u32(start_envfreq) << 16);
env_acc = 0;
}
break;
}
case 0x01:
vol_wave = data;
break;
case 0x02:
freq = (freq & 0xff00) | data;
break;
case 0x03:
freq = (freq & 0x00ff) | (u16(data) << 8);
break;
case 0x04:
start_envfreq = data;
break;
case 0x05:
end_envshape = data;
break;
default:
break;
}
}
void x1_010_core::voice_t::reset()
{
flag.reset();
vol_wave = 0;
freq = 0;
start_envfreq = 0;
end_envshape = 0;
acc = 0;
env_acc = 0;
data = 0;
vol_out[0] = vol_out[1] = 0;
}
void x1_010_core::reset()
{
for (auto & elem : m_voice)
elem.reset();
std::fill_n(&m_envelope[0], 0x1000, 0);
std::fill_n(&m_wave[0], 0x1000, 0);
m_out[0] = m_out[1] = 0;
}

View File

@ -0,0 +1,127 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holders: cam900
Seta/Allumer X1-010 Emulation core
See x1_010.cpp for more info.
*/
#include <algorithm>
#include <memory>
#ifndef _VGSOUND_EMU_X1_010_HPP
#define _VGSOUND_EMU_X1_010_HPP
#pragma once
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef signed char s8;
typedef signed int s32;
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
{
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
}
class x1_010_mem_intf
{
public:
virtual u8 read_byte(u32 address) { return 0; }
};
class x1_010_core
{
friend class x1_010_mem_intf;
public:
// constructor
x1_010_core(x1_010_mem_intf &intf)
: m_voice{*this,*this,*this,*this,
*this,*this,*this,*this,
*this,*this,*this,*this,
*this,*this,*this,*this}
, m_intf(intf)
{
m_envelope = std::make_unique<u8[]>(0x1000);
m_wave = std::make_unique<u8[]>(0x1000);
std::fill_n(&m_envelope[0], 0x1000, 0);
std::fill_n(&m_wave[0], 0x1000, 0);
}
// register accessor
u8 ram_r(u16 offset);
void ram_w(u16 offset, u8 data);
// getters
s32 output(u8 channel) { return m_out[channel & 1]; }
// internal state
void reset();
void tick();
private:
// 16 voices in chip
struct voice_t
{
// constructor
voice_t(x1_010_core &host) : m_host(host) {}
// internal state
void reset();
void tick();
// register accessor
u8 reg_r(u8 offset);
void reg_w(u8 offset, u8 data);
// registers
x1_010_core &m_host;
struct flag_t
{
u8 div : 1;
u8 env_oneshot : 1;
u8 wavetable : 1;
u8 keyon : 1;
void reset()
{
div = 0;
env_oneshot = 0;
wavetable = 0;
keyon = 0;
}
flag_t()
: div(0)
, env_oneshot(0)
, wavetable(0)
, keyon(0)
{ }
};
flag_t flag;
u8 vol_wave = 0;
u16 freq = 0;
u8 start_envfreq = 0;
u8 end_envshape = 0;
// internal registers
u32 acc = 0;
u32 env_acc = 0;
s8 data = 0;
u8 vol_out[2] = {0};
};
voice_t m_voice[16];
// RAM
std::unique_ptr<u8[]> m_envelope = nullptr;
std::unique_ptr<u8[]> m_wave = nullptr;
// output data
s32 m_out[2] = {0};
x1_010_mem_intf &m_intf;
};
#endif

View File

@ -119,8 +119,12 @@ void DivPlatformSwan::updateWave(int ch) {
}
} else {
for (int i=0; i<16; i++) {
unsigned char nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max;
unsigned char nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max;
int nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max;
int nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max;
if (nibble1<0) nibble1=0;
if (nibble1>15) nibble1=15;
if (nibble2<0) nibble2=0;
if (nibble2>15) nibble2=15;
rWrite(addr+i,nibble1|(nibble2<<4));
}
}

View File

@ -0,0 +1,347 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "vera.h"
#include "../engine.h"
#include <string.h>
#include <math.h>
extern "C" {
#include "sound/vera_psg.h"
#include "sound/vera_pcm.h"
}
#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d); psg_writereg(psg,((c)*4+(a)),(d));}
#define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f))
#define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0))
#define rWriteFIFOVol(d) rWrite(16,0,(regPool[64]&(~0x3f))|((d)&0x3f))
const char* regCheatSheetVERA[]={
"CHxFreq", "00+x*4",
"CHxVol", "02+x*4",
"CHxWave", "03+x*4",
"AUDIO_CTRL", "40",
"AUDIO_RATE", "41",
NULL
};
const char** DivPlatformVERA::getRegisterSheet() {
return regCheatSheetVERA;
}
const char* DivPlatformVERA::getEffectName(unsigned char effect) {
switch (effect) {
case 0x20:
return "20xx: Change waveform";
break;
case 0x22:
return "22xx: Set duty cycle (0 to 63)";
break;
}
return NULL;
}
// TODO: wire up PCM.
void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) {
psg_render(psg,bufL+start,bufR+start,len);
pcm_render(pcm,bufL+start,bufR+start,len);
}
void DivPlatformVERA::reset() {
for (int i=0; i<17; i++) {
chan[i]=Channel();
}
psg_reset(psg);
pcm_reset(pcm);
memset(regPool,0,66);
for (int i=0; i<16; i++) {
chan[i].vol=63;
chan[i].pan=3;
rWriteHi(i,2,isMuted[i]?0:3);
}
chan[16].vol=15;
chan[16].pan=3;
}
int DivPlatformVERA::calcNoteFreq(int ch, int note) {
if (ch<16) {
return parent->calcBaseFreq(chipClock,2097152,note,false);
} else {
double off=1.0;
if (chan[ch].pcm.sample>=0 && chan[ch].pcm.sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[ch].pcm.sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=s->centerRate/8363.0;
}
}
return (int)(off*parent->calcBaseFreq(chipClock,65536,note,false));
}
}
void DivPlatformVERA::tick() {
for (int i=0; i<16; i++) {
chan[i].std.next();
if (chan[i].std.hadVol) {
chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0);
rWriteLo(i,2,chan[i].outVol);
}
if (chan[i].std.hadArp) {
if (!chan[i].inPorta) {
if (chan[i].std.arpMode) {
chan[i].baseFreq=calcNoteFreq(0,chan[i].std.arp);
} else {
chan[i].baseFreq=calcNoteFreq(0,chan[i].note+chan[i].std.arp);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
chan[i].baseFreq=calcNoteFreq(0,chan[i].note);
chan[i].freqChanged=true;
}
}
if (chan[i].std.hadDuty) {
rWriteLo(i,3,chan[i].std.duty);
}
if (chan[i].std.hadWave) {
rWriteHi(i,3,chan[i].std.wave);
}
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8);
if (chan[i].freq>65535) chan[i].freq=65535;
rWrite(i,0,chan[i].freq&0xff);
rWrite(i,1,(chan[i].freq>>8)&0xff);
chan[i].freqChanged=false;
}
}
// PCM
chan[16].std.next();
if (chan[16].std.hadVol) {
chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0);
rWriteFIFOVol(chan[16].outVol&15);
}
if (chan[16].std.hadArp) {
if (!chan[16].inPorta) {
if (chan[16].std.arpMode) {
chan[16].baseFreq=calcNoteFreq(16,chan[16].std.arp);
} else {
chan[16].baseFreq=calcNoteFreq(16,chan[16].note+chan[16].std.arp);
}
}
chan[16].freqChanged=true;
} else {
if (chan[16].std.arpMode && chan[16].std.finishedArp) {
chan[16].baseFreq=calcNoteFreq(16,chan[16].note);
chan[16].freqChanged=true;
}
}
if (chan[16].freqChanged) {
chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8);
if (chan[16].freq>128) chan[16].freq=128;
rWrite(16,1,chan[16].freq&0xff);
chan[16].freqChanged=false;
}
}
int DivPlatformVERA::dispatch(DivCommand c) {
int tmp;
switch (c.cmd) {
case DIV_CMD_NOTE_ON:
if(c.chan<16) {
rWriteLo(c.chan,2,chan[c.chan].vol)
} else {
chan[c.chan].pcm.sample=parent->getIns(chan[16].ins)->amiga.initSample;
if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) {
chan[c.chan].pcm.sample=-1;
}
chan[16].pcm.pos=0;
rWriteFIFOVol(chan[c.chan].vol);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
break;
case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false;
if(c.chan<16) {
rWriteLo(c.chan,2,0)
} else {
chan[16].pcm.sample=-1;
rWriteFIFOVol(0);
rWrite(16,1,0);
}
chan[c.chan].std.init(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
chan[c.chan].ins=(unsigned char)c.value;
break;
case DIV_CMD_VOLUME:
if (c.chan<16) {
tmp=c.value&0x3f;
chan[c.chan].vol=tmp;
rWriteLo(c.chan,2,tmp);
} else {
tmp=c.value&0x0f;
chan[c.chan].vol=tmp;
rWriteFIFOVol(tmp);
}
break;
case DIV_CMD_GET_VOLUME:
return chan[c.chan].vol;
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=calcNoteFreq(c.chan,c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
}
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_STD_NOISE_MODE:
if (c.chan<16) rWriteLo(c.chan,3,c.value);
break;
case DIV_CMD_WAVE:
if (c.chan<16) rWriteHi(c.chan,3,c.value);
break;
case DIV_CMD_PANNING: {
tmp=0;
tmp|=(c.value&0x10)?1:0;
tmp|=(c.value&0x01)?2:0;
chan[c.chan].pan=tmp&3;
if (c.chan<16) {
rWriteHi(c.chan,2,isMuted[c.chan]?0:chan[c.chan].pan);
}
break;
}
case DIV_CMD_GET_VOLMAX:
if(c.chan<16) {
return 63;
} else {
return 15;
}
break;
case DIV_ALWAYS_SET_VOLUME:
return 0;
break;
default:
break;
}
return 1;
}
void* DivPlatformVERA::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformVERA::getRegisterPool() {
return regPool;
}
int DivPlatformVERA::getRegisterPoolSize() {
return 66;
}
void DivPlatformVERA::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (ch<16) {
rWriteHi(ch,2,mute?0:chan[ch].pan);
}
}
bool DivPlatformVERA::isStereo() {
return true;
}
void DivPlatformVERA::notifyInsDeletion(void* ins) {
for (int i=0; i<2; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformVERA::poke(unsigned int addr, unsigned short val) {
regPool[addr] = (unsigned char)val;
}
void DivPlatformVERA::poke(std::vector<DivRegWrite>& wlist) {
for (auto &i: wlist) regPool[i.addr] = (unsigned char)i.val;
}
int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
for (int i=0; i<17; i++) {
isMuted[i]=false;
}
parent=p;
psg=new struct VERA_PSG;
pcm=new struct VERA_PCM;
dumpWrites=false;
skipRegisterWrites=false;
chipClock=25000000;
rate=chipClock/512;
reset();
return 17;
}
void DivPlatformVERA::quit() {
delete psg;
delete pcm;
}
DivPlatformVERA::~DivPlatformVERA() {
}

View File

@ -0,0 +1,78 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _VERA_H
#define _VERA_H
#include "../dispatch.h"
#include "../instrument.h"
#include "../macroInt.h"
struct VERA_PSG;
struct VERA_PCM;
class DivPlatformVERA: public DivDispatch {
protected:
struct Channel {
int freq, baseFreq, pitch, note;
unsigned char ins, pan;
bool active, freqChanged, inPorta;
int vol, outVol;
unsigned accum;
int noiseval;
DivMacroInt std;
struct PCMChannel {
int sample;
int out_l, out_r;
unsigned pos;
unsigned len;
unsigned char freq;
PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0) {}
} pcm;
Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), pan(0), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {}
};
Channel chan[17];
bool isMuted[17];
unsigned char regPool[66];
struct VERA_PSG* psg;
struct VERA_PCM* pcm;
int calcNoteFreq(int ch, int note);
friend void putDispatchChan(void*,int,int);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void tick();
void muteChannel(int ch, bool mute);
void notifyInsDeletion(void* ins);
bool isStereo();
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformVERA();
};
#endif

View File

@ -0,0 +1,893 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "x1_010.h"
#include "../engine.h"
#include <math.h>
//#define rWrite(a,v) pendingWrites[a]=v;
#define rWrite(a,v) if (!skipRegisterWrites) { x1_010->ram_w(a,v); if (dumpWrites) { addWrite(a,v); } }
#define chRead(c,a) x1_010->ram_r((c<<3)|(a&7))
#define chWrite(c,a,v) rWrite((c<<3)|(a&7),v)
#define waveWrite(c,a,v) rWrite(0x1000|(chan[c].waveBank<<11)|(c<<7)|(a&0x7f),(v-128)&0xff)
#define envFill(c,a) rWrite(0x800|(c<<7)|(a&0x7f),(chan[c].lvol<<4)|chan[c].rvol)
#define envWrite(c,a,l,r) rWrite(0x800|(c<<7)|(a&0x7f),(((chan[c].lvol*(l))/15)<<4)|((chan[c].rvol*(r))/15))
#define refreshControl(c) chWrite(c,0,chan[c].active?(chan[c].pcm?1:((chan[c].env.flag.envEnable && chan[c].env.flag.envOneshot)?7:3)):0);
#define CHIP_FREQBASE 4194304
const char* regCheatSheetX1_010[]={
// Channel registers
"Ch00_Control", "0000",
"Ch00_PCMVol_WavSel", "0001",
"Ch00_FreqL", "0002",
"Ch00_FreqH", "0003",
"Ch00_Start_EnvFrq", "0004",
"Ch00_End_EnvSel", "0005",
"Ch01_Control", "0008",
"Ch01_PCMVol_WavSel", "0009",
"Ch01_FreqL", "000A",
"Ch01_FreqH", "000B",
"Ch01_Start_EnvFrq", "000C",
"Ch01_End_EnvSel", "000D",
"Ch02_Control", "0010",
"Ch02_PCMVol_WavSel", "0011",
"Ch02_FreqL", "0012",
"Ch02_FreqH", "0013",
"Ch02_Start_EnvFrq", "0014",
"Ch02_End_EnvSel", "0015",
"Ch03_Control", "0018",
"Ch03_PCMVol_WavSel", "0019",
"Ch03_FreqL", "001A",
"Ch03_FreqH", "001B",
"Ch03_Start_EnvFrq", "001C",
"Ch03_End_EnvSel", "001D",
"Ch04_Control", "0020",
"Ch04_PCMVol_WavSel", "0021",
"Ch04_FreqL", "0022",
"Ch04_FreqH", "0023",
"Ch04_Start_EnvFrq", "0024",
"Ch04_End_EnvSel", "0025",
"Ch05_Control", "0028",
"Ch05_PCMVol_WavSel", "0029",
"Ch05_FreqL", "002A",
"Ch05_FreqH", "002B",
"Ch05_Start_EnvFrq", "002C",
"Ch05_End_EnvSel", "002D",
"Ch06_Control", "0030",
"Ch06_PCMVol_WavSel", "0031",
"Ch06_FreqL", "0032",
"Ch06_FreqH", "0033",
"Ch06_Start_EnvFrq", "0034",
"Ch06_End_EnvSel", "0035",
"Ch07_Control", "0038",
"Ch07_PCMVol_WavSel", "0039",
"Ch07_FreqL", "003A",
"Ch07_FreqH", "003B",
"Ch07_Start_EnvFrq", "003C",
"Ch07_End_EnvSel", "003D",
"Ch08_Control", "0040",
"Ch08_PCMVol_WavSel", "0041",
"Ch08_FreqL", "0042",
"Ch08_FreqH", "0043",
"Ch08_Start_EnvFrq", "0044",
"Ch08_End_EnvSel", "0045",
"Ch09_Control", "0048",
"Ch09_PCMVol_WavSel", "0049",
"Ch09_FreqL", "004A",
"Ch09_FreqH", "004B",
"Ch09_Start_EnvFrq", "004C",
"Ch09_End_EnvSel", "004D",
"Ch10_Control", "0050",
"Ch10_PCMVol_WavSel", "0051",
"Ch10_FreqL", "0052",
"Ch10_FreqH", "0053",
"Ch10_Start_EnvFrq", "0054",
"Ch10_End_EnvSel", "0055",
"Ch11_Control", "0058",
"Ch11_PCMVol_WavSel", "0059",
"Ch11_FreqL", "005A",
"Ch11_FreqH", "005B",
"Ch11_Start_EnvFrq", "005C",
"Ch11_End_EnvSel", "005D",
"Ch12_Control", "0060",
"Ch12_PCMVol_WavSel", "0061",
"Ch12_FreqL", "0062",
"Ch12_FreqH", "0063",
"Ch12_Start_EnvFrq", "0064",
"Ch12_End_EnvSel", "0065",
"Ch13_Control", "0068",
"Ch13_PCMVol_WavSel", "0069",
"Ch13_FreqL", "006A",
"Ch13_FreqH", "006B",
"Ch13_Start_EnvFrq", "006C",
"Ch13_End_EnvSel", "006D",
"Ch14_Control", "0070",
"Ch14_PCMVol_WavSel", "0071",
"Ch14_FreqL", "0072",
"Ch14_FreqH", "0073",
"Ch14_Start_EnvFrq", "0074",
"Ch14_End_EnvSel", "0075",
"Ch15_Control", "0078",
"Ch15_PCMVol_WavSel", "0079",
"Ch15_FreqL", "007A",
"Ch15_FreqH", "007B",
"Ch15_Start_EnvFrq", "007C",
"Ch15_End_EnvSel", "007D",
// Envelope data
"Env01Data", "0080",
"Env02Data", "0100",
"Env03Data", "0180",
"Env04Data", "0200",
"Env05Data", "0280",
"Env06Data", "0300",
"Env07Data", "0380",
"Env08Data", "0400",
"Env09Data", "0480",
"Env10Data", "0500",
"Env11Data", "0580",
"Env12Data", "0600",
"Env13Data", "0680",
"Env14Data", "0700",
"Env15Data", "0780",
"Env16Data", "0800",
"Env17Data", "0880",
"Env18Data", "0900",
"Env19Data", "0980",
"Env20Data", "0A00",
"Env21Data", "0A80",
"Env22Data", "0B00",
"Env23Data", "0B80",
"Env24Data", "0C00",
"Env25Data", "0C80",
"Env26Data", "0D00",
"Env27Data", "0D80",
"Env28Data", "0E00",
"Env29Data", "0E80",
"Env30Data", "0F00",
"Env31Data", "0F80",
// Wavetable data
"Wave00Data", "1000",
"Wave01Data", "1080",
"Wave02Data", "1100",
"Wave03Data", "1180",
"Wave04Data", "1200",
"Wave05Data", "1280",
"Wave06Data", "1300",
"Wave07Data", "1380",
"Wave08Data", "1400",
"Wave09Data", "1480",
"Wave10Data", "1500",
"Wave11Data", "1580",
"Wave12Data", "1600",
"Wave13Data", "1680",
"Wave14Data", "1700",
"Wave15Data", "1780",
"Wave16Data", "1800",
"Wave17Data", "1880",
"Wave18Data", "1900",
"Wave19Data", "1980",
"Wave20Data", "1A00",
"Wave21Data", "1A80",
"Wave22Data", "1B00",
"Wave23Data", "1B80",
"Wave24Data", "1C00",
"Wave25Data", "1C80",
"Wave26Data", "1D00",
"Wave27Data", "1D80",
"Wave28Data", "1E00",
"Wave29Data", "1E80",
"Wave30Data", "1F00",
"Wave31Data", "1F80",
NULL
};
const char** DivPlatformX1_010::getRegisterSheet() {
return regCheatSheetX1_010;
}
const char* DivPlatformX1_010::getEffectName(unsigned char effect) {
switch (effect) {
case 0x10:
return "10xx: Change waveform";
break;
case 0x11:
return "11xx: Change envelope shape";
break;
case 0x17:
return "17xx: Toggle PCM mode";
break;
case 0x20:
return "20xx: Set PCM frequency (1 to FF)";
break;
case 0x22:
return "22xx: Set envelope mode (bit 0: enable, bit 1: one-shot, bit 2: split shape to L/R, bit 3/5: H.invert right/left, bit 4/6: V.invert right/left)";
break;
case 0x23:
return "23xx: Set envelope period";
break;
case 0x25:
return "25xx: Envelope slide up";
break;
case 0x26:
return "26xx: Envelope slide down";
break;
case 0x29:
return "29xy: Set auto-envelope (x: numerator; y: denominator)";
break;
}
return NULL;
}
void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t len) {
for (size_t h=start; h<start+len; h++) {
x1_010->tick();
signed int tempL=x1_010->output(0);
signed int tempR=x1_010->output(1);
if (tempL<-32768) tempL=-32768;
if (tempL>32767) tempL=32767;
if (tempR<-32768) tempR=-32768;
if (tempR>32767) tempR=32767;
//printf("tempL: %d tempR: %d\n",tempL,tempR);
bufL[h]=stereo?tempL:((tempL+tempR)>>1);
bufR[h]=stereo?tempR:bufL[h];
}
}
double DivPlatformX1_010::NoteX1_010(int ch, int note) {
if (chan[ch].pcm) { // PCM note
double off=1.0;
int sample=chan[ch].sample;
if (sample>=0 && sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=s->centerRate/8363.0;
}
}
return off*parent->calcBaseFreq(chipClock,8192,note,false);
}
// Wavetable note
return NOTE_FREQUENCY(note);
}
void DivPlatformX1_010::updateWave(int ch) {
DivWavetable* wt=parent->getWave(chan[ch].wave);
if (chan[ch].active) {
chan[ch].waveBank ^= 1;
}
for (int i=0; i<128; i++) {
if (wt->max<1 || wt->len<1) {
waveWrite(ch,i,0);
} else {
int data=wt->data[i*wt->len/128]*255/wt->max;
if (data<0) data=0;
if (data>255) data=255;
waveWrite(ch,i,data);
}
}
if (!chan[ch].pcm) {
chWrite(ch,1,(chan[ch].waveBank<<4)|(ch&0xf));
}
}
void DivPlatformX1_010::updateEnvelope(int ch) {
if (!chan[ch].pcm) {
if (isMuted[ch]) {
for (int i=0; i<128; i++) {
rWrite(0x800|(ch<<7)|(i&0x7f),0);
}
} else {
if (!chan[ch].env.flag.envEnable) {
for (int i=0; i<128; i++) {
envFill(ch,i);
}
} else {
DivWavetable* wt=parent->getWave(chan[ch].env.shape);
for (int i=0; i<128; i++) {
if (wt->max<1 || wt->len<1) {
envFill(ch,i);
} else if (chan[ch].env.flag.envSplit || chan[ch].env.flag.envHinvR || chan[ch].env.flag.envVinvR || chan[ch].env.flag.envHinvL || chan[ch].env.flag.envVinvL) { // Stereo config
int la=i,ra=i;
int lo,ro;
if (chan[ch].env.flag.envHinvR) { ra=127-i; } // horizontal invert right envelope
if (chan[ch].env.flag.envHinvL) { la=127-i; } // horizontal invert left envelope
if (chan[ch].env.flag.envSplit) { // Split shape to left and right half
lo=wt->data[la*(wt->len/128/2)]*15/wt->max;
ro=wt->data[(ra+128)*(wt->len/128/2)]*15/wt->max;
} else {
lo=wt->data[la*wt->len/128]*15/wt->max;
ro=wt->data[ra*wt->len/128]*15/wt->max;
}
if (chan[ch].env.flag.envVinvR) { ro=15-ro; } // vertical invert right envelope
if (chan[ch].env.flag.envVinvL) { lo=15-lo; } // vertical invert left envelope
if (lo<0) lo=0;
if (lo>15) lo=15;
if (ro<0) ro=0;
if (ro>15) ro=15;
envWrite(ch,i,lo,ro);
} else {
int out=wt->data[i*wt->len/128]*15/wt->max;
if (out<0) out=0;
if (out>15) out=15;
envWrite(ch,i,out,out);
}
}
}
}
chWrite(ch,5,0x10|(ch&0xf));
} else {
chWrite(ch,1,(chan[ch].lvol<<4)|chan[ch].rvol);
}
}
void DivPlatformX1_010::tick() {
for (int i=0; i<16; i++) {
chan[i].std.next();
if (chan[i].std.hadVol) {
signed char macroVol=((chan[i].vol&15)*MIN(chan[i].furnacePCM?64:15,chan[i].std.vol))/(chan[i].furnacePCM?64:15);
if ((!isMuted[i]) && (macroVol!=chan[i].outVol)) {
chan[i].outVol=macroVol;
chan[i].envChanged=true;
}
}
if ((!chan[i].pcm) || chan[i].furnacePCM) {
if (chan[i].std.hadArp) {
if (!chan[i].inPorta) {
if (chan[i].std.arpMode) {
chan[i].baseFreq=NoteX1_010(i,chan[i].std.arp);
} else {
chan[i].baseFreq=NoteX1_010(i,chan[i].note+chan[i].std.arp);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
chan[i].baseFreq=NoteX1_010(i,chan[i].note);
chan[i].freqChanged=true;
}
}
}
if (chan[i].std.hadWave && !chan[i].pcm) {
if (chan[i].wave!=chan[i].std.wave) {
chan[i].wave=chan[i].std.wave;
if (!chan[i].pcm) {
updateWave(i);
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
}
if (chan[i].std.hadEx1) {
bool nextEnable=(chan[i].std.ex1&1);
if (nextEnable!=(chan[i].env.flag.envEnable)) {
chan[i].env.flag.envEnable=nextEnable;
if (!chan[i].pcm) {
if (!isMuted[i]) {
chan[i].envChanged=true;
}
refreshControl(i);
}
}
bool nextOneshot=(chan[i].std.ex1&2);
if (nextOneshot!=(chan[i].env.flag.envOneshot)) {
chan[i].env.flag.envOneshot=nextOneshot;
if (!chan[i].pcm) {
refreshControl(i);
}
}
bool nextSplit=(chan[i].std.ex1&4);
if (nextSplit!=(chan[i].env.flag.envSplit)) {
chan[i].env.flag.envSplit=nextSplit;
if (!isMuted[i] && !chan[i].pcm) {
chan[i].envChanged=true;
}
}
bool nextHinvR=(chan[i].std.ex1&8);
if (nextHinvR!=(chan[i].env.flag.envHinvR)) {
chan[i].env.flag.envHinvR=nextHinvR;
if (!isMuted[i] && !chan[i].pcm) {
chan[i].envChanged=true;
}
}
bool nextVinvR=(chan[i].std.ex1&16);
if (nextVinvR!=(chan[i].env.flag.envVinvR)) {
chan[i].env.flag.envVinvR=nextVinvR;
if (!isMuted[i] && !chan[i].pcm) {
chan[i].envChanged=true;
}
}
bool nextHinvL=(chan[i].std.ex1&32);
if (nextHinvL!=(chan[i].env.flag.envHinvL)) {
chan[i].env.flag.envHinvL=nextHinvL;
if (!isMuted[i] && !chan[i].pcm) {
chan[i].envChanged=true;
}
}
bool nextVinvL=(chan[i].std.ex1&64);
if (nextVinvL!=(chan[i].env.flag.envVinvL)) {
chan[i].env.flag.envVinvL=nextVinvL;
if (!isMuted[i] && !chan[i].pcm) {
chan[i].envChanged=true;
}
}
}
if (chan[i].std.hadEx2) {
if (chan[i].env.shape!=chan[i].std.ex2) {
chan[i].env.shape=chan[i].std.ex2;
if (!chan[i].pcm) {
if (chan[i].env.flag.envEnable && (!isMuted[i])) {
chan[i].envChanged=true;
}
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
}
if (chan[i].std.hadEx3) {
chan[i].autoEnvNum=chan[i].std.ex3;
if (!chan[i].pcm) {
chan[i].freqChanged=true;
if (!chan[i].std.willAlg) chan[i].autoEnvDen=1;
}
}
if (chan[i].std.hadAlg) {
chan[i].autoEnvDen=chan[i].std.alg;
if (!chan[i].pcm) {
chan[i].freqChanged=true;
if (!chan[i].std.willEx3) chan[i].autoEnvNum=1;
}
}
if (chan[i].envChanged) {
if (!isMuted[i]) {
chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15;
chan[i].rvol=((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15;
}
updateEnvelope(i);
chan[i].envChanged=false;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false);
if (chan[i].pcm) {
if (chan[i].freq<1) chan[i].freq=1;
if (chan[i].freq>255) chan[i].freq=255;
chWrite(i,2,chan[i].freq&0xff);
} else {
if (chan[i].freq>65535) chan[i].freq=65535;
chWrite(i,2,chan[i].freq&0xff);
chWrite(i,3,(chan[i].freq>>8)&0xff);
if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) {
chan[i].env.period=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>12;
chWrite(i,4,chan[i].env.period);
}
}
if (chan[i].keyOn || chan[i].keyOff || (chRead(i,0)&1)) {
refreshControl(i);
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
if (chan[i].env.slide!=0) {
chan[i].env.slidefrac+=chan[i].env.slide;
while (chan[i].env.slidefrac>0xf) {
chan[i].env.slidefrac-=0x10;
if (chan[i].env.period<0xff) {
chan[i].env.period++;
if (!chan[i].pcm) {
chWrite(i,4,chan[i].env.period);
}
}
}
while (chan[i].env.slidefrac<-0xf) {
chan[i].env.slidefrac+=0x10;
if (chan[i].env.period>0) {
chan[i].env.period--;
if (!chan[i].pcm) {
chWrite(i,4,chan[i].env.period);
}
}
}
}
}
}
int DivPlatformX1_010::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
chWrite(c.chan,0,0); // reset previous note
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
if ((ins->type==DIV_INS_AMIGA) || chan[c.chan].pcm) {
if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].furnacePCM=true;
} else {
chan[c.chan].furnacePCM=false;
}
if (skipRegisterWrites) break;
if (chan[c.chan].furnacePCM) {
chan[c.chan].pcm=true;
chan[c.chan].std.init(ins);
chan[c.chan].sample=ins->amiga.initSample;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
chWrite(c.chan,4,(s->offX1_010>>12)&0xff);
int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded
chWrite(c.chan,5,(0x100-(end>>12))&0xff);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
} else {
chan[c.chan].std.init(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
chWrite(c.chan,0,0); // reset
chWrite(c.chan,1,0);
chWrite(c.chan,2,0);
chWrite(c.chan,4,0);
chWrite(c.chan,5,0);
break;
}
}
} else {
chan[c.chan].std.init(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
chWrite(c.chan,0,0); // reset
chWrite(c.chan,1,0);
chWrite(c.chan,2,0);
chWrite(c.chan,4,0);
chWrite(c.chan,5,0);
break;
}
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
chWrite(c.chan,4,(s->offX1_010>>12)&0xff);
int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded
chWrite(c.chan,5,(0x100-(end>>12))&0xff);
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<4)/(chipClock/512);
chan[c.chan].freqChanged=true;
}
} else if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].envChanged=true;
chan[c.chan].std.init(ins);
refreshControl(c.chan);
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].pcm=false;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].std.init(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.hasVol) {
if (chan[c.chan].outVol!=c.value) {
chan[c.chan].outVol=c.value;
if (!isMuted[c.chan]) {
chan[c.chan].envChanged=true;
}
}
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.hasVol) {
return chan[c.chan].vol;
}
return chan[c.chan].outVol;
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_WAVE:
chan[c.chan].wave=c.value;
updateWave(c.chan);
chan[c.chan].keyOn=true;
break;
case DIV_CMD_X1_010_ENVELOPE_SHAPE:
if (chan[c.chan].env.shape!=c.value) {
chan[c.chan].env.shape=c.value;
if (!chan[c.chan].pcm) {
if (chan[c.chan].env.flag.envEnable && (!isMuted[c.chan])) {
chan[c.chan].envChanged=true;
}
chan[c.chan].keyOn=true;
}
}
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NoteX1_010(c.chan,c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_SAMPLE_MODE:
if (chan[c.chan].pcm!=(c.value&1)) {
chan[c.chan].pcm=c.value&1;
chan[c.chan].freqChanged=true;
if (!isMuted[c.chan]) {
chan[c.chan].envChanged=true;
}
}
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_PANNING: {
if (chan[c.chan].pan!=c.value) {
chan[c.chan].pan=c.value;
if (!isMuted[c.chan]) {
chan[c.chan].envChanged=true;
}
}
break;
}
case DIV_CMD_LEGATO:
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note+((chan[c.chan].std.willArp&&!chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0)));
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
}
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_SAMPLE_FREQ:
if (chan[c.chan].pcm) {
chan[c.chan].freq=MAX(1,c.value&0xff);
chWrite(c.chan,2,chan[c.chan].freq&0xff);
if (chRead(c.chan,0)&1) {
refreshControl(c.chan);
}
}
break;
case DIV_CMD_X1_010_ENVELOPE_MODE: {
bool nextEnable=c.value&1;
if (nextEnable!=(chan[c.chan].env.flag.envEnable)) {
chan[c.chan].env.flag.envEnable=nextEnable;
if (!chan[c.chan].pcm) {
if (!isMuted[c.chan]) {
chan[c.chan].envChanged=true;
}
refreshControl(c.chan);
}
}
bool nextOneshot=c.value&2;
if (nextOneshot!=(chan[c.chan].env.flag.envOneshot)) {
chan[c.chan].env.flag.envOneshot=nextOneshot;
if (!chan[c.chan].pcm) {
refreshControl(c.chan);
}
}
bool nextSplit=c.value&4;
if (nextSplit!=(chan[c.chan].env.flag.envSplit)) {
chan[c.chan].env.flag.envSplit=nextSplit;
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
chan[c.chan].envChanged=true;
}
}
bool nextHinvR=c.value&8;
if (nextHinvR!=(chan[c.chan].env.flag.envHinvR)) {
chan[c.chan].env.flag.envHinvR=nextHinvR;
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
chan[c.chan].envChanged=true;
}
}
bool nextVinvR=c.value&16;
if (nextVinvR!=(chan[c.chan].env.flag.envVinvR)) {
chan[c.chan].env.flag.envVinvR=nextVinvR;
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
chan[c.chan].envChanged=true;
}
}
bool nextHinvL=c.value&32;
if (nextHinvL!=(chan[c.chan].env.flag.envHinvL)) {
chan[c.chan].env.flag.envHinvL=nextHinvL;
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
chan[c.chan].envChanged=true;
}
}
bool nextVinvL=c.value&64;
if (nextVinvL!=(chan[c.chan].env.flag.envVinvL)) {
chan[c.chan].env.flag.envVinvL=nextVinvL;
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
chan[c.chan].envChanged=true;
}
}
break;
}
case DIV_CMD_X1_010_ENVELOPE_PERIOD:
chan[c.chan].env.period=c.value;
if (!chan[c.chan].pcm) {
chWrite(c.chan,4,chan[c.chan].env.period);
}
break;
case DIV_CMD_X1_010_ENVELOPE_SLIDE:
chan[c.chan].env.slide=c.value;
break;
case DIV_CMD_X1_010_AUTO_ENVELOPE:
chan[c.chan].autoEnvNum=c.value>>4;
chan[c.chan].autoEnvDen=c.value&15;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_GET_VOLMAX:
return 15;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformX1_010::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
chan[ch].envChanged=true;
}
void DivPlatformX1_010::forceIns() {
for (int i=0; i<16; i++) {
chan[i].insChanged=true;
chan[i].envChanged=true;
chan[i].freqChanged=true;
updateWave(i);
}
}
void* DivPlatformX1_010::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformX1_010::getRegisterPool() {
for (int i=0; i<0x2000; i++) {
regPool[i]=x1_010->ram_r(i);
}
return regPool;
}
int DivPlatformX1_010::getRegisterPoolSize() {
return 0x2000;
}
void DivPlatformX1_010::reset() {
memset(regPool,0,0x2000);
for (int i=0; i<16; i++) {
chan[i]=DivPlatformX1_010::Channel();
chan[i].reset();
}
x1_010->reset();
sampleBank=0;
// set per-channel initial panning
for (int i=0; i<16; i++) {
chWrite(i,0,0);
}
}
bool DivPlatformX1_010::isStereo() {
return stereo;
}
bool DivPlatformX1_010::keyOffAffectsArp(int ch) {
return true;
}
void DivPlatformX1_010::notifyWaveChange(int wave) {
for (int i=0; i<16; i++) {
if (chan[i].wave==wave) {
updateWave(i);
}
}
}
void DivPlatformX1_010::notifyInsDeletion(void* ins) {
for (int i=0; i<16; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformX1_010::setFlags(unsigned int flags) {
switch (flags&15) {
case 0: // 16MHz (earlier hardwares)
chipClock=16000000;
break;
case 1: // 16.67MHz (later hardwares)
chipClock=50000000.0/3.0;
break;
// Other clock is used?
}
rate=chipClock/512;
stereo=flags&16;
}
void DivPlatformX1_010::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformX1_010::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
stereo=false;
for (int i=0; i<16; i++) {
isMuted[i]=false;
}
setFlags(flags);
intf.parent=parent;
x1_010=new x1_010_core(intf);
x1_010->reset();
reset();
return 16;
}
void DivPlatformX1_010::quit() {
delete x1_010;
}
DivPlatformX1_010::~DivPlatformX1_010() {
}

View File

@ -0,0 +1,144 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _X1_010_H
#define _X1_010_H
#include <queue>
#include "../dispatch.h"
#include "../engine.h"
#include "../macroInt.h"
#include "sound/x1_010/x1_010.hpp"
class DivX1_010Interface: public x1_010_mem_intf {
public:
DivEngine* parent;
int sampleBank;
virtual u8 read_byte(u32 address) override {
if (parent->x1_010Mem==NULL) return 0;
return parent->x1_010Mem[address & 0xfffff];
}
DivX1_010Interface(): parent(NULL), sampleBank(0) {}
};
class DivPlatformX1_010: public DivDispatch {
struct Channel {
struct Envelope {
struct EnvFlag {
unsigned char envEnable : 1;
unsigned char envOneshot : 1;
unsigned char envSplit : 1;
unsigned char envHinvR : 1;
unsigned char envVinvR : 1;
unsigned char envHinvL : 1;
unsigned char envVinvL : 1;
void reset() {
envEnable=0;
envOneshot=0;
envSplit=0;
envHinvR=0;
envVinvR=0;
envHinvL=0;
envVinvL=0;
}
EnvFlag():
envEnable(0),
envOneshot(0),
envSplit(0),
envHinvR(0),
envVinvR(0),
envHinvL(0),
envVinvL(0) {}
};
int shape, period, slide, slidefrac;
EnvFlag flag;
void reset() {
shape=-1;
period=0;
flag.reset();
}
Envelope():
shape(-1),
period(0),
slide(0),
slidefrac(0) {}
};
int freq, baseFreq, pitch, note;
int wave, sample, ins;
unsigned char pan, autoEnvNum, autoEnvDen;
bool active, insChanged, envChanged, freqChanged, keyOn, keyOff, inPorta, furnacePCM, pcm;
int vol, outVol, lvol, rvol;
unsigned char waveBank;
Envelope env;
DivMacroInt std;
void reset() {
freq = baseFreq = pitch = note = 0;
wave = sample = ins = -1;
pan = 255;
autoEnvNum = autoEnvDen = 0;
active = false;
insChanged = envChanged = freqChanged = true;
keyOn = keyOff = inPorta = furnacePCM = pcm = false;
vol = outVol = lvol = rvol = 15;
waveBank = 0;
}
Channel():
freq(0), baseFreq(0), pitch(0), note(0),
wave(-1), sample(-1), ins(-1),
pan(255), autoEnvNum(0), autoEnvDen(0),
active(false), insChanged(true), envChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), furnacePCM(false), pcm(false),
vol(15), outVol(15), lvol(15), rvol(15),
waveBank(0) {}
};
Channel chan[16];
bool isMuted[16];
bool stereo=false;
unsigned char sampleBank;
DivX1_010Interface intf;
x1_010_core* x1_010;
unsigned char regPool[0x2000];
double NoteX1_010(int ch, int note);
void updateWave(int ch);
void updateEnvelope(int ch);
friend void putDispatchChan(void*,int,int);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick();
void muteChannel(int ch, bool mute);
bool isStereo();
bool keyOffAffectsArp(int ch);
void setFlags(unsigned int flags);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformX1_010();
};
#endif

View File

@ -313,11 +313,22 @@ const char* DivPlatformYM2610::getEffectName(unsigned char effect) {
return NULL;
}
double DivPlatformYM2610::NOTE_OPNB(int ch, int note) {
if (ch>6) { // ADPCM
return NOTE_ADPCMB(note);
} else if (ch>3) { // PSG
return NOTE_PERIODIC(note);
}
// FM
return NOTE_FREQUENCY(note);
}
double DivPlatformYM2610::NOTE_ADPCMB(int note) {
DivInstrument* ins=parent->getIns(chan[13].ins);
if (ins->type!=DIV_INS_AMIGA) return 0;
double off=(double)(parent->getSample(ins->amiga.initSample)->centerRate)/8363.0;
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false);
if (chan[13].sample>=0 && chan[13].sample<parent->song.sampleLen) {
double off=(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0;
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false);
}
return 0;
}
void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t len) {
@ -691,22 +702,33 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x1b,chan[c.chan].outVol);
}
DivSample* s=parent->getSample(ins->amiga.initSample);
immWrite(0x12,(s->offB>>8)&0xff);
immWrite(0x13,s->offB>>16);
int end=s->offB+s->lengthB-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
chan[c.chan].sample=ins->amiga.initSample;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(s->offB>>8)&0xff);
immWrite(0x13,s->offB>>16);
int end=s->offB+s->lengthB-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(0x10,0x01); // reset
immWrite(0x12,0);
immWrite(0x13,0);
immWrite(0x14,0);
immWrite(0x15,0);
break;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
chan[c.chan].sample=-1;
chan[c.chan].std.init(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
@ -915,8 +937,8 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
if (c.chan>3) { // PSG
int destFreq=NOTE_PERIODIC(c.value2);
if (c.chan>3) { // PSG, ADPCM-B
int destFreq=NOTE_OPNB(c.chan,c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
@ -975,11 +997,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
if (c.chan>3) { // PSG
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
} else {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
}
chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value);
chan[c.chan].freqChanged=true;
break;
}
@ -1210,11 +1228,6 @@ void DivPlatformYM2610::reset() {
}
lastBusy=60;
dacMode=0;
dacPeriod=0;
dacPos=0;
dacRate=0;
dacSample=-1;
sampleBank=0;
ayEnvPeriod=0;
ayEnvMode=0;

View File

@ -47,6 +47,7 @@ class DivPlatformYM2610: public DivDispatch {
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM;
int vol, outVol;
int sample;
unsigned char pan;
DivMacroInt std;
Channel():
@ -70,6 +71,7 @@ class DivPlatformYM2610: public DivDispatch {
furnacePCM(false),
vol(0),
outVol(15),
sample(-1),
pan(3) {}
};
Channel chan[14];
@ -87,11 +89,6 @@ class DivPlatformYM2610: public DivDispatch {
unsigned char regPool[512];
unsigned char lastBusy;
bool dacMode;
int dacPeriod;
int dacRate;
int dacPos;
int dacSample;
int ayNoiseFreq;
unsigned char sampleBank;
@ -108,6 +105,7 @@ class DivPlatformYM2610: public DivDispatch {
int octave(int freq);
int toFreq(int freq);
double NOTE_OPNB(int ch, int note);
double NOTE_ADPCMB(int note);
friend void putDispatchChan(void*,int,int);

View File

@ -377,11 +377,22 @@ const char* DivPlatformYM2610B::getEffectName(unsigned char effect) {
return NULL;
}
double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) {
if (ch>8) { // ADPCM-B
return NOTE_ADPCMB(note);
} else if (ch>5) { // PSG
return NOTE_PERIODIC(note);
}
// FM
return NOTE_FREQUENCY(note);
}
double DivPlatformYM2610B::NOTE_ADPCMB(int note) {
DivInstrument* ins=parent->getIns(chan[15].ins);
if (ins->type!=DIV_INS_AMIGA) return 0;
double off=(double)(parent->getSample(ins->amiga.initSample)->centerRate)/8363.0;
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false);
if (chan[15].sample>=0 && chan[15].sample<parent->song.sampleLen) {
double off=(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0;
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false);
}
return 0;
}
void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) {
@ -752,24 +763,35 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
chan[c.chan].std.init(ins);
if (!chan[c.chan].std.willVol) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x1b,chan[c.chan].outVol);
immWrite(0x1b,chan[c.chan].outVol);
}
DivSample* s=parent->getSample(ins->amiga.initSample);
immWrite(0x12,(s->offB>>8)&0xff);
immWrite(0x13,s->offB>>16);
int end=s->offB+s->lengthB-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
chan[c.chan].sample=ins->amiga.initSample;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(s->offB>>8)&0xff);
immWrite(0x13,s->offB>>16);
int end=s->offB+s->lengthB-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(0x10,0x01); // reset
immWrite(0x12,0);
immWrite(0x13,0);
immWrite(0x14,0);
immWrite(0x15,0);
break;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
chan[c.chan].sample=-1;
chan[c.chan].std.init(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
@ -978,8 +1000,8 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
if (c.chan>5) { // PSG
int destFreq=NOTE_PERIODIC(c.value2);
if (c.chan>5) { // PSG, ADPCM-B
int destFreq=NOTE_OPNB(c.chan,c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
@ -1038,11 +1060,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
if (c.chan>5) { // PSG
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
} else {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
}
chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value);
chan[c.chan].freqChanged=true;
break;
}
@ -1273,11 +1291,6 @@ void DivPlatformYM2610B::reset() {
}
lastBusy=60;
dacMode=0;
dacPeriod=0;
dacPos=0;
dacRate=0;
dacSample=-1;
sampleBank=0;
ayEnvPeriod=0;
ayEnvMode=0;

View File

@ -40,6 +40,7 @@ class DivPlatformYM2610B: public DivDispatch {
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM;
int vol, outVol;
int sample;
unsigned char pan;
DivMacroInt std;
Channel():
@ -63,6 +64,7 @@ class DivPlatformYM2610B: public DivDispatch {
furnacePCM(false),
vol(0),
outVol(15),
sample(-1),
pan(3) {}
};
Channel chan[16];
@ -80,11 +82,6 @@ class DivPlatformYM2610B: public DivDispatch {
unsigned char regPool[512];
unsigned char lastBusy;
bool dacMode;
int dacPeriod;
int dacRate;
int dacPos;
int dacSample;
int ayNoiseFreq;
unsigned char sampleBank;
@ -101,6 +98,7 @@ class DivPlatformYM2610B: public DivDispatch {
int octave(int freq);
int toFreq(int freq);
double NOTE_OPNB(int ch, int note);
double NOTE_ADPCMB(int note);
friend void putDispatchChan(void*,int,int);

View File

@ -117,6 +117,16 @@ const char* cmdName[DIV_CMD_MAX]={
"QSOUND_ECHO_DELAY",
"QSOUND_ECHO_LEVEL",
"X1_010_ENVELOPE_SHAPE",
"X1_010_ENVELOPE_ENABLE",
"X1_010_ENVELOPE_MODE",
"X1_010_ENVELOPE_PERIOD",
"X1_010_ENVELOPE_SLIDE",
"X1_010_AUTO_ENVELOPE",
"WS_SWEEP_TIME",
"WS_SWEEP_AMOUNT",
"ALWAYS_SET_VOLUME"
};
@ -251,6 +261,21 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
break;
}
break;
case DIV_SYSTEM_X1_010:
switch (effect) {
case 0x10: // select waveform
dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal));
break;
case 0x11: // select envelope shape
dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SHAPE,ch,effectVal));
break;
case 0x17: // PCM enable
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0)));
break;
default:
return false;
}
break;
case DIV_SYSTEM_SWAN:
switch (effect) {
case 0x10: // select waveform
@ -272,6 +297,18 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
return false;
}
break;
case DIV_SYSTEM_VERA:
switch (effect) {
case 0x20: // select waveform
dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal));
break;
case 0x22: // duty
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
break;
default:
return false;
}
break;
default:
return false;
}
@ -555,6 +592,30 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char
}
return false;
break;
case DIV_SYSTEM_X1_010:
switch (effect) {
case 0x20: // PCM frequency
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal));
break;
case 0x22: // envelope mode
dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_MODE,ch,effectVal));
break;
case 0x23: // envelope period
dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_PERIOD,ch,effectVal));
break;
case 0x25: // envelope slide up
dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,effectVal));
break;
case 0x26: // envelope slide down
dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,-effectVal));
break;
case 0x29: // auto-envelope
dispatchCmd(DivCommand(DIV_CMD_X1_010_AUTO_ENVELOPE,ch,effectVal));
break;
default:
return false;
}
break;
default:
return false;
}
@ -593,6 +654,12 @@ void DivEngine::processRow(int i, bool afterDelay) {
// instrument
if (pat->data[whatRow][2]!=-1) {
dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2]));
if (chan[i].lastIns!=pat->data[whatRow][2]) {
chan[i].lastIns=pat->data[whatRow][2];
if (chan[i].inPorta && song.newInsTriggersInPorta) {
dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL));
}
}
}
// note
if (pat->data[whatRow][0]==100) { // note off
@ -1052,8 +1119,28 @@ void DivEngine::nextRow() {
for (int i=0; i<chans; i++) {
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][curOrder],false);
if (!(pat->data[curRow][0]==0 && pat->data[curRow][1]==0)) {
if (pat->data[curRow][0]!=100) {
if (!chan[i].legato) dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks));
if (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) {
if (!chan[i].legato) {
dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks));
if (song.oneTickCut) {
bool doPrepareCut=true;
for (int j=0; j<song.pat[i].effectRows; j++) {
if (pat->data[curRow][4+(j<<1)]==0x03) {
doPrepareCut=false;
break;
}
if (pat->data[curRow][4+(j<<1)]==0xea) {
if (pat->data[curRow][5+(j<<1)]>0) {
doPrepareCut=false;
break;
}
}
}
if (doPrepareCut) chan[i].cut=ticks;
}
}
}
}
}

View File

@ -112,9 +112,9 @@ int SafeReader::readI() {
int SafeReader::readI_BE() {
if (curSeek+4>len) throw EndOfFileException(this,len);
int ret=*(int*)(&buf[curSeek]);
unsigned int ret=*(unsigned int*)(&buf[curSeek]);
curSeek+=4;
return (ret>>24)|((ret&0xff0000)>>8)|((ret&0xff00)<<8)|((ret&0xff)<<24);
return (int)((ret>>24)|((ret&0xff0000)>>8)|((ret&0xff00)<<8)|((ret&0xff)<<24));
}
int64_t SafeReader::readL() {

View File

@ -115,8 +115,9 @@ bool DivSample::initInternal(unsigned char d, int count) {
case 8: // 8-bit
if (data8!=NULL) delete[] data8;
length8=count;
data8=new signed char[length8];
memset(data8,0,length8);
// for padding X1-010 sample
data8=new signed char[(count+4095)&(~0xfff)];
memset(data8,0,(count+4095)&(~0xfff));
break;
case 9: // BRR
if (dataBRR!=NULL) delete[] dataBRR;

View File

@ -49,7 +49,7 @@ struct DivSample {
unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX;
unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX;
unsigned int offSegaPCM, offQSound;
unsigned int offSegaPCM, offQSound, offX1_010;
unsigned int samples;

View File

@ -90,8 +90,10 @@ enum DivSystem {
DIV_SYSTEM_OPLL_DRUMS,
DIV_SYSTEM_LYNX,
DIV_SYSTEM_QSOUND,
DIV_SYSTEM_VERA,
DIV_SYSTEM_YM2610B_EXT,
DIV_SYSTEM_SEGAPCM_COMPAT
DIV_SYSTEM_SEGAPCM_COMPAT,
DIV_SYSTEM_X1_010
};
struct DivSong {
@ -241,6 +243,13 @@ struct DivSong {
// - 2: YM2423
// - 3: VRC7
// - 4: custom (TODO)
// - X1-010:
// - bit 0-3: clock rate
// - 0: 16MHz (Seta 1)
// - 1: 16.67MHz (Seta 2)
// - bit 4: stereo
// - 0: mono
// - 1: stereo
unsigned int systemFlags[32];
// song information
@ -286,6 +295,9 @@ struct DivSong {
bool ignoreDuplicateSlides;
bool stopPortaOnNoteOff;
bool continuousVibrato;
bool brokenDACMode;
bool oneTickCut;
bool newInsTriggersInPorta;
DivOrders orders;
std::vector<DivInstrument*> ins;
@ -350,7 +362,10 @@ struct DivSong {
brokenShortcutSlides(false),
ignoreDuplicateSlides(false),
stopPortaOnNoteOff(false),
continuousVibrato(false) {
continuousVibrato(false),
brokenDACMode(false),
oneTickCut(false),
newInsTriggersInPorta(true) {
for (int i=0; i<32; i++) {
system[i]=DIV_SYSTEM_NULL;
systemVol[i]=64;

View File

@ -135,6 +135,10 @@ DivSystem DivEngine::systemFromFile(unsigned char val) {
return DIV_SYSTEM_LYNX;
case 0xa9:
return DIV_SYSTEM_SEGAPCM_COMPAT;
case 0xac:
return DIV_SYSTEM_VERA;
case 0xb0:
return DIV_SYSTEM_X1_010;
case 0xde:
return DIV_SYSTEM_YM2610B_EXT;
case 0xe0:
@ -258,6 +262,10 @@ unsigned char DivEngine::systemToFile(DivSystem val) {
return 0xa8;
case DIV_SYSTEM_SEGAPCM_COMPAT:
return 0xa9;
case DIV_SYSTEM_VERA:
return 0xac;
case DIV_SYSTEM_X1_010:
return 0xb0;
case DIV_SYSTEM_YM2610B_EXT:
return 0xde;
case DIV_SYSTEM_QSOUND:
@ -335,7 +343,7 @@ int DivEngine::getChannelCount(DivSystem sys) {
case DIV_SYSTEM_OPL3:
return 18;
case DIV_SYSTEM_MULTIPCM:
return 24;
return 28;
case DIV_SYSTEM_PCSPKR:
return 1;
case DIV_SYSTEM_POKEY:
@ -351,6 +359,7 @@ int DivEngine::getChannelCount(DivSystem sys) {
case DIV_SYSTEM_POKEMINI:
return 1;
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_X1_010:
return 16;
case DIV_SYSTEM_VBOY:
return 6;
@ -383,6 +392,8 @@ int DivEngine::getChannelCount(DivSystem sys) {
case DIV_SYSTEM_YM2610B_EXT:
case DIV_SYSTEM_QSOUND:
return 19;
case DIV_SYSTEM_VERA:
return 17;
}
return 0;
}
@ -408,10 +419,6 @@ const char* DivEngine::getSongSystemName() {
return "Vectrex";
case 5: // AY-3-8910, 1MHz
return "Amstrad CPC";
case 6: // AY-3-8910, 0.somethingMhz
return "Intellivision";
case 8: // AY-3-8910, 0.somethingMhz
return "Intellivision (PAL)";
case 0x10: // YM2149, 1.79MHz
return "MSX";
@ -421,7 +428,12 @@ const char* DivEngine::getSongSystemName() {
return "Sunsoft 5B standalone";
case 0x28: // 5B PAL
return "Sunsoft 5B standalone (PAL)";
case 0x30: // AY-3-8914, 1.79MHz
return "Intellivision";
case 0x33: // AY-3-8914, 2MHz
return "Intellivision (PAL)";
default:
if ((song.systemFlags[0]&0x30)==0x00) {
return "AY-3-8910";
@ -429,6 +441,8 @@ const char* DivEngine::getSongSystemName() {
return "Yamaha YM2149";
} else if ((song.systemFlags[0]&0x30)==0x20) {
return "Overclocked Sunsoft 5B";
} else if ((song.systemFlags[0]&0x30)==0x30) {
return "Intellivision";
}
}
} else if (song.system[0]==DIV_SYSTEM_SMS) {
@ -522,6 +536,10 @@ const char* DivEngine::getSongSystemName() {
if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910) {
return "Bally Midway MCR";
}
if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_VERA) {
return "Commander X16";
}
break;
case 3:
break;
@ -650,6 +668,10 @@ const char* DivEngine::getSystemName(DivSystem sys) {
return "Taito Arcade Extended Channel 3";
case DIV_SYSTEM_QSOUND:
return "Capcom QSound";
case DIV_SYSTEM_VERA:
return "VERA";
case DIV_SYSTEM_X1_010:
return "Seta/Allumer X1-010";
}
return "Unknown";
}
@ -775,6 +797,10 @@ const char* DivEngine::getSystemChips(DivSystem sys) {
return "Yamaha YM2610B Extended Channel 3";
case DIV_SYSTEM_QSOUND:
return "Capcom DL-1425";
case DIV_SYSTEM_VERA:
return "VERA";
case DIV_SYSTEM_X1_010:
return "Seta/Allumer X1-010";
}
return "Unknown";
}
@ -857,7 +883,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) {
sys!=DIV_SYSTEM_YM2151);
}
const char* chanNames[38][24]={
const char* chanNames[40][32]={
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3)
@ -870,7 +896,7 @@ const char* chanNames[38][24]={
{"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610
{"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610 (extended channel 2)
{"PSG 1", "PSG 2", "PSG 3"}, // AY-3-8910
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, // Amiga/POKEY/Swan/Lynx
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, // Amiga/POKEY/Lynx
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"}, // YM2151/YM2414
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"}, // YM2612
{"Channel 1", "Channel 2"}, // TIA
@ -886,7 +912,7 @@ const char* chanNames[38][24]={
{"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, // OPN
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, // PC-98
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "FM 10", "FM 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"}, // OPL3
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24"}, // MultiPCM
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"}, // MultiPCM
{"Square"}, // PC Speaker/Pokémon Mini
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"}, // Virtual Boy/SCC
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B
@ -896,9 +922,11 @@ const char* chanNames[38][24]={
{"FM 1", "FM 2", "FM 3", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 4-op + drums
{"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"}, // QSound
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3)
{"Wave", "Wave/PCM", "Wave", "Wave/Noise"}, // Swan
{"PSG 1", "PSG 2", "PSG 3", "PSG 4", "PSG 5", "PSG 6", "PSG 7", "PSG 8", "PSG 9", "PSG 10", "PSG 11", "PSG 12", "PSG 13", "PSG 14", "PSG 15", "PSG 16", "PCM"}, // VERA
};
const char* chanShortNames[38][24]={
const char* chanShortNames[38][32]={
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3)
@ -927,7 +955,7 @@ const char* chanShortNames[38][24]={
{"F1", "F2", "F3", "S1", "S2", "S3"}, // OPN
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"}, // PC-98
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"}, // OPL3
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24"}, // MultiPCM
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"}, // MultiPCM
{"SQ"}, // PC Speaker/Pokémon Mini
{"CH1", "CH2", "CH3", "CH4", "CH5", "NO"}, // Virtual Boy/SCC
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B
@ -939,7 +967,7 @@ const char* chanShortNames[38][24]={
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3)
};
const int chanTypes[38][24]={
const int chanTypes[41][32]={
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759
{0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis
{0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3)
@ -968,7 +996,7 @@ const int chanTypes[38][24]={
{0, 0, 0, 1, 1, 1}, // OPN
{0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 4}, // PC-98
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound
{1}, // PC Speaker/Pokémon Mini
{3, 3, 3, 3, 3, 2}, // Virtual Boy/SCC
{0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B
@ -976,11 +1004,14 @@ const int chanTypes[38][24]={
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 drums
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 4-op
{0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums
{3, 3, 3, 3}, //Lynx
{3, 3, 3, 3}, // Lynx
{0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3)
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4}, // VERA
{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, // X1-010
{3, 4, 3, 2}, // Swan
};
const DivInstrumentType chanPrefType[44][24]={
const DivInstrumentType chanPrefType[46][28]={
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3)
@ -1009,7 +1040,7 @@ const DivInstrumentType chanPrefType[44][24]={
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, // OPN
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // PC-98
{DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, // OPL/OPL2/OPL3
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM/QSound
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM/QSound
{DIV_INS_STD}, // PC Speaker/Pokémon Mini
{DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY}, // Virtual Boy
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B
@ -1025,6 +1056,8 @@ const DivInstrumentType chanPrefType[44][24]={
{DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // Z
{DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B (extended channel 3)
{DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_AMIGA}, // VERA
{DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010}, // X1-010
};
const char* DivEngine::getChannelName(int chan) {
@ -1078,10 +1111,12 @@ const char* DivEngine::getChannelName(int chan) {
break;
case DIV_SYSTEM_AMIGA:
case DIV_SYSTEM_POKEY:
case DIV_SYSTEM_SWAN:
case DIV_SYSTEM_LYNX:
return chanNames[12][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_SWAN:
return chanNames[38][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_YM2151:
return chanNames[13][dispatchChanOfChan[chan]];
break;
@ -1129,6 +1164,7 @@ const char* DivEngine::getChannelName(int chan) {
case DIV_SYSTEM_MULTIPCM:
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_SEGAPCM_COMPAT:
case DIV_SYSTEM_X1_010:
return chanNames[28][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_PCSPKR:
@ -1162,6 +1198,9 @@ const char* DivEngine::getChannelName(int chan) {
case DIV_SYSTEM_QSOUND:
return chanNames[36][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_VERA:
return chanNames[39][dispatchChanOfChan[chan]];
break;
}
return "??";
}
@ -1268,6 +1307,7 @@ const char* DivEngine::getChannelShortName(int chan) {
case DIV_SYSTEM_MULTIPCM:
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_SEGAPCM_COMPAT:
case DIV_SYSTEM_X1_010:
return chanShortNames[28][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_PCSPKR:
@ -1301,6 +1341,9 @@ const char* DivEngine::getChannelShortName(int chan) {
case DIV_SYSTEM_QSOUND:
return chanShortNames[36][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_VERA:
return chanShortNames[0][dispatchChanOfChan[chan]];
break;
}
return "??";
}
@ -1354,7 +1397,6 @@ int DivEngine::getChannelType(int chan) {
break;
case DIV_SYSTEM_AMIGA:
case DIV_SYSTEM_POKEY:
case DIV_SYSTEM_SWAN:
return chanTypes[12][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_YM2151:
@ -1438,6 +1480,15 @@ int DivEngine::getChannelType(int chan) {
case DIV_SYSTEM_LYNX:
return chanTypes[36][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_VERA:
return chanTypes[38][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_X1_010:
return chanTypes[39][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_SWAN:
return chanTypes[40][dispatchChanOfChan[chan]];
break;
}
return 1;
}
@ -1588,6 +1639,12 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) {
case DIV_SYSTEM_LYNX:
return chanPrefType[42][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_VERA:
return chanPrefType[44][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_X1_010:
return chanPrefType[45][dispatchChanOfChan[chan]];
break;
}
return DIV_INS_FM;
}
@ -1616,6 +1673,7 @@ bool DivEngine::isVGMExportable(DivSystem which) {
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7:
case DIV_SYSTEM_X1_010:
case DIV_SYSTEM_SWAN:
return true;
default:

View File

@ -154,6 +154,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(3);
}
break;
case DIV_SYSTEM_X1_010:
for (int i=0; i<16; i++) {
w->writeC(0xc8);
w->writeS_BE(baseAddr2S+(i<<3));
w->writeC(0);
}
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610B:
@ -395,6 +402,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeS(baseAddr2S|(write.addr&0xffff));
w->writeC(write.val);
break;
case DIV_SYSTEM_X1_010:
w->writeC(0xc8);
w->writeS_BE(baseAddr2S|(write.addr&0x1fff));
w->writeC(write.val);
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610B:
@ -568,6 +580,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
bool writePCESamples=false;
bool writeADPCM=false;
bool writeSegaPCM=false;
bool writeX1010=false;
bool writeQSound=false;
for (int i=0; i<song.systemLen; i++) {
@ -651,6 +664,18 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
howManyChips++;
}
break;
case DIV_SYSTEM_X1_010:
if (!hasX1) {
hasX1=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeX1010=true;
} else if (!(hasX1&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasX1|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610B:
@ -669,7 +694,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
}
if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag
hasOPNB|=0x80000000;
}
}
break;
case DIV_SYSTEM_AY8910:
case DIV_SYSTEM_AY8930:
@ -995,6 +1020,16 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
w->write(qsoundMem,blockSize);
}
if (writeX1010 && x1_010MemLen>0) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x91);
w->writeI(x1_010MemLen+8);
w->writeI(x1_010MemLen);
w->writeI(0);
w->write(x1_010Mem,x1_010MemLen);
}
// initialize streams
int streamID=0;
for (int i=0; i<song.systemLen; i++) {

View File

@ -36,6 +36,7 @@
#include "../engine/platform/tia.h"
#include "../engine/platform/saa.h"
#include "../engine/platform/amiga.h"
#include "../engine/platform/x1_010.h"
#include "../engine/platform/dummy.h"
#define GENESIS_DEBUG \
@ -232,6 +233,47 @@ void putDispatchChan(void* data, int chanNum, int type) {
ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta");
break;
}
case DIV_SYSTEM_X1_010: {
DivPlatformX1_010::Channel* ch=(DivPlatformX1_010::Channel*)data;
ImGui::Text("> X1-010");
ImGui::Text("* freq: %.4x",ch->freq);
ImGui::Text(" - base: %d",ch->baseFreq);
ImGui::Text(" - pitch: %d",ch->pitch);
ImGui::Text("- note: %d",ch->note);
ImGui::Text("- wave: %d",ch->wave);
ImGui::Text("- sample: %d",ch->sample);
ImGui::Text("- ins: %d",ch->ins);
ImGui::Text("- pan: %d",ch->pan);
ImGui::Text("* envelope:");
ImGui::Text(" - shape: %d",ch->env.shape);
ImGui::Text(" - period: %.2x",ch->env.period);
ImGui::Text(" - slide: %.2x",ch->env.slide);
ImGui::Text(" - slidefrac: %.2x",ch->env.slidefrac);
ImGui::Text(" - autoEnvNum: %.2x",ch->autoEnvNum);
ImGui::Text(" - autoEnvDen: %.2x",ch->autoEnvDen);
ImGui::Text("- WaveBank: %d",ch->waveBank);
ImGui::Text("- vol: %.2x",ch->vol);
ImGui::Text("- outVol: %.2x",ch->outVol);
ImGui::Text("- Lvol: %.2x",ch->lvol);
ImGui::Text("- Rvol: %.2x",ch->rvol);
ImGui::TextColored(ch->active?colorOn:colorOff,">> Active");
ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged");
ImGui::TextColored(ch->envChanged?colorOn:colorOff,">> EnvChanged");
ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged");
ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn");
ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff");
ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta");
ImGui::TextColored(ch->furnacePCM?colorOn:colorOff,">> FurnacePCM");
ImGui::TextColored(ch->pcm?colorOn:colorOff,">> PCM");
ImGui::TextColored(ch->env.flag.envEnable?colorOn:colorOff,">> EnvEnable");
ImGui::TextColored(ch->env.flag.envOneshot?colorOn:colorOff,">> EnvOneshot");
ImGui::TextColored(ch->env.flag.envSplit?colorOn:colorOff,">> EnvSplit");
ImGui::TextColored(ch->env.flag.envHinvR?colorOn:colorOff,">> EnvHinvR");
ImGui::TextColored(ch->env.flag.envVinvR?colorOn:colorOff,">> EnvVinvR");
ImGui::TextColored(ch->env.flag.envHinvL?colorOn:colorOff,">> EnvHinvL");
ImGui::TextColored(ch->env.flag.envVinvL?colorOn:colorOff,">> EnvVinvL");
break;
}
default:
ImGui::Text("Unknown system! Help!");
break;

106
src/gui/fileDialog.cpp Normal file
View File

@ -0,0 +1,106 @@
#include "fileDialog.h"
#include "ImGuiFileDialog.h"
#include "../ta-log.h"
#include "../../extern/pfd-fixed/portable-file-dialogs.h"
bool FurnaceGUIFileDialog::openLoad(String header, std::vector<String> filter, const char* noSysFilter, String path, double dpiScale) {
if (opened) return false;
saving=false;
curPath=path;
if (sysDialog) {
dialogO=new pfd::open_file(header,path,filter);
} else {
ImGuiFileDialog::Instance()->DpiScale=dpiScale;
ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path);
}
opened=true;
return true;
}
bool FurnaceGUIFileDialog::openSave(String header, std::vector<String> filter, const char* noSysFilter, String path, double dpiScale) {
if (opened) return false;
saving=true;
curPath=path;
if (sysDialog) {
dialogS=new pfd::save_file(header,path,filter);
} else {
ImGuiFileDialog::Instance()->DpiScale=dpiScale;
ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite);
}
opened=true;
return true;
}
bool FurnaceGUIFileDialog::accepted() {
if (sysDialog) {
return (fileName!="");
} else {
return ImGuiFileDialog::Instance()->IsOk();
}
}
void FurnaceGUIFileDialog::close() {
if (sysDialog) {
if (saving) {
if (dialogS!=NULL) {
delete dialogS;
dialogS=NULL;
}
} else {
if (dialogO!=NULL) {
delete dialogO;
dialogO=NULL;
}
}
} else {
ImGuiFileDialog::Instance()->Close();
}
opened=false;
}
bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) {
if (sysDialog) {
if (saving) {
if (dialogS!=NULL) {
if (dialogS->ready(1)) {
fileName=dialogS->result();
logD("returning %s\n",fileName.c_str());
return true;
}
}
} else {
if (dialogO!=NULL) {
if (dialogO->ready(1)) {
if (dialogO->result().empty()) {
fileName="";
logD("returning nothing\n");
} else {
fileName=dialogO->result()[0];
logD("returning %s\n",fileName.c_str());
}
return true;
}
}
}
return false;
} else {
return ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,min,max);
}
}
String FurnaceGUIFileDialog::getPath() {
if (sysDialog) {
return curPath;
} else {
return ImGuiFileDialog::Instance()->GetCurrentPath();
}
}
String FurnaceGUIFileDialog::getFileName() {
if (sysDialog) {
return fileName;
} else {
return ImGuiFileDialog::Instance()->GetFilePathName();
}
}

32
src/gui/fileDialog.h Normal file
View File

@ -0,0 +1,32 @@
#include "../ta-utils.h"
#include "imgui.h"
#include <vector>
namespace pfd {
class open_file;
class save_file;
}
class FurnaceGUIFileDialog {
bool sysDialog;
bool opened;
bool saving;
String curPath;
String fileName;
pfd::open_file* dialogO;
pfd::save_file* dialogS;
public:
bool openLoad(String header, std::vector<String> filter, const char* noSysFilter, String path, double dpiScale);
bool openSave(String header, std::vector<String> filter, const char* noSysFilter, String path, double dpiScale);
bool accepted();
void close();
bool render(const ImVec2& min, const ImVec2& max);
String getPath();
String getFileName();
FurnaceGUIFileDialog(bool system):
sysDialog(system),
opened(false),
saving(false),
dialogO(NULL),
dialogS(NULL) {}
};

File diff suppressed because it is too large Load Diff

View File

@ -27,6 +27,8 @@
#include <map>
#include <vector>
#include "fileDialog.h"
#define rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::SetKeyboardFocusHere(-1);
#define handleUnimportant if (settings.insFocusesPattern && patternOpen) {nextWindow=GUI_WINDOW_PATTERN;}
@ -73,6 +75,8 @@ enum FurnaceGUIColors {
GUI_COLOR_INSTR_BEEPER,
GUI_COLOR_INSTR_SWAN,
GUI_COLOR_INSTR_MIKEY,
GUI_COLOR_INSTR_VERA,
GUI_COLOR_INSTR_X1_010,
GUI_COLOR_INSTR_UNKNOWN,
GUI_COLOR_CHANNEL_FM,
GUI_COLOR_CHANNEL_PULSE,
@ -233,6 +237,10 @@ enum FurnaceGUIActions {
GUI_ACTION_PAT_CUT,
GUI_ACTION_PAT_COPY,
GUI_ACTION_PAT_PASTE,
GUI_ACTION_PAT_PASTE_MIX,
GUI_ACTION_PAT_PASTE_MIX_BG,
GUI_ACTION_PAT_PASTE_FLOOD,
GUI_ACTION_PAT_PASTE_OVERFLOW,
GUI_ACTION_PAT_CURSOR_UP,
GUI_ACTION_PAT_CURSOR_DOWN,
GUI_ACTION_PAT_CURSOR_LEFT,
@ -268,6 +276,17 @@ enum FurnaceGUIActions {
GUI_ACTION_PAT_COLLAPSE,
GUI_ACTION_PAT_INCREASE_COLUMNS,
GUI_ACTION_PAT_DECREASE_COLUMNS,
GUI_ACTION_PAT_INTERPOLATE,
GUI_ACTION_PAT_FADE,
GUI_ACTION_PAT_INVERT_VALUES,
GUI_ACTION_PAT_FLIP_SELECTION,
GUI_ACTION_PAT_COLLAPSE_ROWS,
GUI_ACTION_PAT_EXPAND_ROWS,
GUI_ACTION_PAT_COLLAPSE_PAT,
GUI_ACTION_PAT_EXPAND_PAT,
GUI_ACTION_PAT_COLLAPSE_SONG,
GUI_ACTION_PAT_EXPAND_SONG,
GUI_ACTION_PAT_LATCH,
GUI_ACTION_PAT_MAX,
GUI_ACTION_INS_LIST_MIN,
@ -334,6 +353,14 @@ enum FurnaceGUIActions {
GUI_ACTION_MAX
};
enum PasteMode {
GUI_PASTE_MODE_NORMAL=0,
GUI_PASTE_MODE_MIX_FG,
GUI_PASTE_MODE_MIX_BG,
GUI_PASTE_MODE_FLOOD,
GUI_PASTE_MODE_OVERFLOW
};
#define FURKMOD_CTRL (1<<31)
#define FURKMOD_SHIFT (1<<29)
#define FURKMOD_META (1<<28)
@ -354,7 +381,16 @@ enum ActionType {
GUI_UNDO_PATTERN_PULL,
GUI_UNDO_PATTERN_PUSH,
GUI_UNDO_PATTERN_CUT,
GUI_UNDO_PATTERN_PASTE
GUI_UNDO_PATTERN_PASTE,
GUI_UNDO_PATTERN_CHANGE_INS,
GUI_UNDO_PATTERN_INTERPOLATE,
GUI_UNDO_PATTERN_FADE,
GUI_UNDO_PATTERN_SCALE,
GUI_UNDO_PATTERN_RANDOMIZE,
GUI_UNDO_PATTERN_INVERT_VAL,
GUI_UNDO_PATTERN_FLIP,
GUI_UNDO_PATTERN_COLLAPSE,
GUI_UNDO_PATTERN_EXPAND
};
struct UndoPatternData {
@ -442,6 +478,8 @@ class FurnaceGUI {
FurnaceGUIFileDialogs curFileDialog;
FurnaceGUIWarnings warnAction;
FurnaceGUIFileDialog* fileDialog;
int scrW, scrH;
double dpiScale;
@ -499,6 +537,11 @@ class FurnaceGUI {
int guiColorsBase;
int avoidRaisingPattern;
int insFocusesPattern;
int stepOnInsert;
// TODO flags
int unifiedDataView;
int sysFileDialog;
// end
unsigned int maxUndoSteps;
String mainFontPath;
String patFontPath;
@ -512,7 +555,7 @@ class FurnaceGUI {
audioQuality(0),
arcadeCore(0),
ym2612Core(0),
saaCore(0),
saaCore(1),
mainFont(0),
patFont(0),
audioRate(44100),
@ -542,6 +585,9 @@ class FurnaceGUI {
guiColorsBase(0),
avoidRaisingPattern(0),
insFocusesPattern(1),
stepOnInsert(0),
unifiedDataView(0),
sysFileDialog(1),
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),
@ -558,13 +604,16 @@ class FurnaceGUI {
bool pianoOpen, notesOpen, channelsOpen, regViewOpen;
SelectionPoint selStart, selEnd, cursor;
bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders;
bool collapseWindow, demandScrollX, fancyPattern, wantPatName;
bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame;
FurnaceGUIWindows curWindow, nextWindow;
float peak[2];
float patChanX[DIV_MAX_CHANS+1];
float patChanSlideY[DIV_MAX_CHANS+1];
const int* nextDesc;
bool opMaskNote, opMaskIns, opMaskVol, opMaskEffect, opMaskEffectVal;
short latchNote, latchIns, latchVol, latchEffect, latchEffectVal;
// bit 31: ctrl
// bit 30: reserved for SDL scancode mask
// bit 29: shift
@ -624,6 +673,7 @@ class FurnaceGUI {
bool macroDragInitialValueSet;
bool macroDragInitialValue;
bool macroDragChar;
bool macroDragLineMode; // TODO
bool macroDragActive;
ImVec2 macroLoopDragStart;
@ -650,6 +700,9 @@ class FurnaceGUI {
ImVec2 threeChars, twoChars;
SelectionPoint sel1, sel2;
int dummyRows, demandX;
int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax;
float scaleMax;
bool fadeMode, randomMode;
int oldOrdersLen;
DivOrders oldOrders;
@ -668,6 +721,9 @@ class FurnaceGUI {
void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord);
void actualWaveList();
void actualSampleList();
void drawEditControls();
void drawSongInfo();
void drawOrders();
@ -718,9 +774,19 @@ class FurnaceGUI {
void doInsert();
void doTranspose(int amount);
void doCopy(bool cut);
void doPaste();
void doPaste(PasteMode mode=GUI_PASTE_MODE_NORMAL);
void doChangeIns(int ins);
void doInterpolate();
void doFade(int p0, int p1, bool mode);
void doInvertValues();
void doScale(float top);
void doRandomize(int bottom, int top, bool mode);
void doFlip();
void doCollapse(int divider);
void doExpand(int multiplier);
void doUndo();
void doRedo();
void editOptions(bool topMenu);
void play(int row=0);
void stop();

View File

@ -19,6 +19,7 @@
// guiConst: constants used in the GUI like arrays, strings and other stuff
#include "guiConst.h"
#include "../engine/instrument.h"
const int opOrder[4]={
0, 2, 1, 3
@ -64,3 +65,31 @@ const char* pitchLabel[11]={
"1/6", "1/5", "1/4", "1/3", "1/2", "1x", "2x", "3x", "4x", "5x", "6x"
};
const char* insTypes[DIV_INS_MAX]={
"Standard",
"FM (4-operator)",
"Game Boy",
"C64",
"Amiga/Sample",
"PC Engine",
"AY-3-8910/SSG",
"AY8930",
"TIA",
"SAA1099",
"VIC",
"PET",
"VRC6",
"FM (OPLL)",
"FM (OPL)",
"FDS",
"Virtual Boy",
"Namco 163",
"Konami SCC",
"FM (OPZ)",
"POKEY",
"PC Beeper",
"WonderSwan",
"Atari Lynx",
"VERA",
"X1-010"
};

View File

@ -23,3 +23,4 @@ extern const int opOrder[4];
extern const char* noteNames[180];
extern const char* noteNamesG[180];
extern const char* pitchLabel[11];
extern const char* insTypes[];

View File

@ -27,33 +27,6 @@
#include <imgui.h>
#include "plot_nolerp.h"
const char* insTypes[24]={
"Standard",
"FM (4-operator)",
"Game Boy",
"C64",
"Amiga/Sample",
"PC Engine",
"AY-3-8910/SSG",
"AY8930",
"TIA",
"SAA1099",
"VIC",
"PET",
"VRC6",
"FM (OPLL)",
"FM (OPL)",
"FDS",
"Virtual Boy",
"Namco 163",
"Konami SCC",
"FM (OPZ)",
"POKEY",
"PC Beeper",
"WonderSwan",
"Atari Lynx"
};
const char* ssgEnvTypes[8]={
"Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN"
};
@ -156,6 +129,10 @@ const char* mikeyFeedbackBits[11] = {
"0", "1", "2", "3", "4", "5", "7", "10", "11", "int", NULL
};
const char* x1_010EnvBits[8]={
"enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL
};
const char* oneBit[2]={
"on", NULL
};
@ -791,9 +768,9 @@ void FurnaceGUI::drawInsEdit() {
} else {
DivInstrument* ins=e->song.ins[curIns];
ImGui::InputText("Name",&ins->name);
if (ins->type<0 || ins->type>23) ins->type=DIV_INS_FM;
if (ins->type<0 || ins->type>=DIV_INS_MAX) ins->type=DIV_INS_FM;
int insType=ins->type;
if (ImGui::Combo("Type",&insType,insTypes,24,24)) {
if (ImGui::Combo("Type",&insType,insTypes,DIV_INS_MAX,DIV_INS_MAX)) {
ins->type=(DivInstrumentType)insType;
}
@ -1355,7 +1332,7 @@ void FurnaceGUI::drawInsEdit() {
float loopIndicator[256];
const char* volumeLabel="Volume";
int volMax=(ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)?31:15;
int volMax=15;
int volMin=0;
if (ins->type==DIV_INS_C64) {
if (ins->c64.volIsCutoff) {
@ -1368,6 +1345,12 @@ void FurnaceGUI::drawInsEdit() {
}
}
}
if ((ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)) {
volMax=31;
}
if (ins->type==DIV_INS_VERA) {
volMax=63;
}
if (ins->type==DIV_INS_AMIGA) {
volMax=64;
}
@ -1381,7 +1364,7 @@ void FurnaceGUI::drawInsEdit() {
bool arpMode=ins->std.arpMacroMode;
const char* dutyLabel="Duty/Noise";
int dutyMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?31:3;
int dutyMax=3;
if (ins->type==DIV_INS_C64) {
dutyLabel="Duty";
if (ins->c64.dutyIsAbs) {
@ -1393,6 +1376,9 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_FM) {
dutyMax=32;
}
if ((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)) {
dutyMax=31;
}
if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_FM) {
dutyLabel="Noise Freq";
}
@ -1416,9 +1402,13 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) {
dutyMax=0;
}
if (ins->type==DIV_INS_VERA) {
dutyLabel="Duty";
dutyMax=63;
}
bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs);
int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?3:63;
int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:63;
bool bitMode=false;
if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) {
bitMode=true;
@ -1435,11 +1425,18 @@ void FurnaceGUI::drawInsEdit() {
int ex1Max=(ins->type==DIV_INS_AY8930)?8:0;
int ex2Max=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?4:0;
bool ex2Bit=true;
if (ins->type==DIV_INS_C64) {
ex1Max=4;
ex2Max=15;
}
if (ins->type==DIV_INS_X1_010) {
dutyMax=0;
ex1Max=7;
ex2Max=63;
ex2Bit=false;
}
if (ins->type==DIV_INS_SAA1099) ex1Max=8;
if (settings.macroView==0) { // modern view
@ -1464,6 +1461,8 @@ void FurnaceGUI::drawInsEdit() {
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Filter Mode",64,ins->std.ex1MacroOpen,true,filtModeBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false);
} else if (ins->type==DIV_INS_SAA1099) {
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope",160,ins->std.ex1MacroOpen,true,saaEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false);
} else if (ins->type==DIV_INS_X1_010) {
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope Mode",160,ins->std.ex1MacroOpen,true,x1_010EnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false);
} else {
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Duty",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false);
}
@ -1472,13 +1471,13 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_C64) {
NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Resonance",64,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false);
} else {
NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",64,ins->std.ex2MacroOpen,true,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false);
NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",ex2Bit?64:160,ins->std.ex2MacroOpen,ex2Bit,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false);
}
}
if (ins->type==DIV_INS_C64) {
NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,2,"ex3","Special",32,ins->std.ex3MacroOpen,true,c64SpecialBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false);
}
if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930) {
if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_X1_010) {
NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,15,"ex3","AutoEnv Num",96,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,15,NULL,false);
NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,15,"alg","AutoEnv Den",96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,15,NULL,false);
}
@ -1759,7 +1758,6 @@ void FurnaceGUI::drawWaveList() {
nextWindow=GUI_WINDOW_NOTHING;
}
if (!waveListOpen) return;
float wavePreview[256];
if (ImGui::Begin("Wavetables",&waveListOpen)) {
if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) {
doAction(GUI_ACTION_WAVE_LIST_ADD);
@ -1790,25 +1788,7 @@ void FurnaceGUI::drawWaveList() {
}
ImGui::Separator();
if (ImGui::BeginTable("WaveListScroll",1,ImGuiTableFlags_ScrollY)) {
for (int i=0; i<(int)e->song.wave.size(); i++) {
DivWavetable* wave=e->song.wave[i];
for (int i=0; i<wave->len; i++) {
wavePreview[i]=wave->data[i];
}
if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1];
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(fmt::sprintf("%d##_WAVE%d\n",i,i).c_str(),curWave==i)) {
curWave=i;
}
if (ImGui::IsItemHovered()) {
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
waveEditOpen=true;
}
}
ImGui::SameLine();
PlotNoLerp(fmt::sprintf("##_WAVEP%d",i).c_str(),wavePreview,wave->len+1,0,NULL,0,wave->max);
}
actualWaveList();
ImGui::EndTable();
}
}
@ -1857,6 +1837,7 @@ void FurnaceGUI::drawWaveEdit() {
modified=true;
}
for (int i=0; i<wave->len; i++) {
if (wave->data[i]>wave->max) wave->data[i]=wave->max;
wavePreview[i]=wave->data[i];
}
if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1];

View File

@ -830,22 +830,8 @@ void FurnaceGUI::drawPattern() {
ImGui::PopStyleVar();
if (patternOpen) {
if (!inhibitMenu && ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::OpenPopup("patternActionMenu");
if (ImGui::BeginPopup("patternActionMenu",ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) {
char id[4096];
ImGui::Selectable("cut");
ImGui::Selectable("copy");
ImGui::Selectable("paste");
if (ImGui::BeginMenu("change instrument...")) {
if (e->song.ins.empty()) {
ImGui::Text("no instruments available");
}
for (size_t i=0; i<e->song.ins.size(); i++) {
snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str());
if (ImGui::Selectable(id)) { // TODO
}
}
ImGui::EndMenu();
}
if (ImGui::BeginPopup("patternActionMenu",ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) {
editOptions(false);
ImGui::EndPopup();
}
}

View File

@ -157,6 +157,11 @@ void FurnaceGUI::drawSettings() {
settings.stepOnDelete=stepOnDeleteB;
}
bool stepOnInsertB=settings.stepOnInsert;
if (ImGui::Checkbox("Move cursor by edit step on insert (push)",&stepOnInsertB)) {
settings.stepOnInsert=stepOnInsertB;
}
bool allowEditDockingB=settings.allowEditDocking;
if (ImGui::Checkbox("Allow docking editors",&allowEditDockingB)) {
settings.allowEditDocking=allowEditDockingB;
@ -177,6 +182,11 @@ void FurnaceGUI::drawSettings() {
settings.restartOnFlagChange=restartOnFlagChangeB;
}
bool sysFileDialogB=settings.sysFileDialog;
if (ImGui::Checkbox("Use system file picker",&sysFileDialogB)) {
settings.sysFileDialog=sysFileDialogB;
}
ImGui::Text("Wrap pattern cursor horizontally:");
if (ImGui::RadioButton("No##wrapH0",settings.wrapHorizontal==0)) {
settings.wrapHorizontal=0;
@ -206,6 +216,7 @@ void FurnaceGUI::drawSettings() {
if (ImGui::RadioButton("Move by Edit Step##cmk1",settings.scrollStep==1)) {
settings.scrollStep=1;
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Audio")) {
@ -397,6 +408,11 @@ void FurnaceGUI::drawSettings() {
settings.macroView=macroViewB;
}
bool unifiedDataViewB=settings.unifiedDataView;
if (ImGui::Checkbox("Unified instrument/wavetable/sample list",&unifiedDataViewB)) {
settings.unifiedDataView=unifiedDataViewB;
}
bool chipNamesB=settings.chipNames;
if (ImGui::Checkbox("Use chip names instead of system names",&chipNamesB)) {
settings.chipNames=chipNamesB;
@ -492,6 +508,8 @@ void FurnaceGUI::drawSettings() {
UI_COLOR_CONFIG(GUI_COLOR_INSTR_BEEPER,"PC Beeper");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_SWAN,"WonderSwan");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_MIKEY,"Lynx");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_VERA,"VERA");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_X1_010,"X1-010");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown");
ImGui::TreePop();
}
@ -853,7 +871,7 @@ void FurnaceGUI::syncSettings() {
settings.audioRate=e->getConfInt("audioRate",44100);
settings.arcadeCore=e->getConfInt("arcadeCore",0);
settings.ym2612Core=e->getConfInt("ym2612Core",0);
settings.saaCore=e->getConfInt("saaCore",0);
settings.saaCore=e->getConfInt("saaCore",1);
settings.mainFont=e->getConfInt("mainFont",0);
settings.patFont=e->getConfInt("patFont",0);
settings.mainFontPath=e->getConfString("mainFontPath","");
@ -883,6 +901,9 @@ void FurnaceGUI::syncSettings() {
settings.guiColorsBase=e->getConfInt("guiColorsBase",0);
settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0);
settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1);
settings.stepOnInsert=e->getConfInt("stepOnInsert",0);
settings.unifiedDataView=e->getConfInt("unifiedDataView",0);
settings.sysFileDialog=e->getConfInt("sysFileDialog",1);
clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.patFontSize,2,96);
@ -920,6 +941,9 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.guiColorsBase,0,1);
clampSetting(settings.avoidRaisingPattern,0,1);
clampSetting(settings.insFocusesPattern,0,1);
clampSetting(settings.stepOnInsert,0,1);
clampSetting(settings.unifiedDataView,0,1);
clampSetting(settings.sysFileDialog,0,1);
// keybinds
LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o);
@ -1118,6 +1142,9 @@ void FurnaceGUI::commitSettings() {
e->setConf("guiColorsBase",settings.guiColorsBase);
e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern);
e->setConf("insFocusesPattern",settings.insFocusesPattern);
e->setConf("stepOnInsert",settings.stepOnInsert);
e->setConf("unifiedDataView",settings.unifiedDataView);
e->setConf("sysFileDialog",settings.sysFileDialog);
PUT_UI_COLOR(GUI_COLOR_BACKGROUND);
PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND);
@ -1159,6 +1186,8 @@ void FurnaceGUI::commitSettings() {
PUT_UI_COLOR(GUI_COLOR_INSTR_BEEPER);
PUT_UI_COLOR(GUI_COLOR_INSTR_SWAN);
PUT_UI_COLOR(GUI_COLOR_INSTR_MIKEY);
PUT_UI_COLOR(GUI_COLOR_INSTR_VERA);
PUT_UI_COLOR(GUI_COLOR_INSTR_X1_010);
PUT_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN);
PUT_UI_COLOR(GUI_COLOR_CHANNEL_FM);
PUT_UI_COLOR(GUI_COLOR_CHANNEL_PULSE);