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

This commit is contained in:
cam900 2022-03-14 19:22:31 +09:00
commit 2c6267bd6b
47 changed files with 3473 additions and 205 deletions

View File

@ -350,6 +350,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
@ -401,6 +402,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

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

@ -29,6 +29,7 @@ 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
@ -207,7 +208,8 @@ size | description
1 | continuous vibrato (>=62) or reserved
1 | broken DAC mode (>=64) or reserved
1 | one tick cut (>=65) or reserved
2 | 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

@ -37,8 +37,8 @@
warnings+=(String("\n")+x); \
}
#define DIV_VERSION "dev65"
#define DIV_ENGINE_VERSION 65
#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),

View File

@ -142,6 +142,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.ignoreDuplicateSlides=true;
ds.brokenDACMode=true;
ds.oneTickCut=false;
ds.newInsTriggersInPorta=true;
// 1.1 compat flags
if (ds.version>24) {
@ -807,6 +808,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<65) {
ds.oneTickCut=false;
}
if (ds.version<66) {
ds.newInsTriggersInPorta=false;
}
ds.isDMF=false;
reader.readS(); // reserved
@ -993,7 +997,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
} else {
reader.readC();
}
for (int i=0; i<2; i++) 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();
}
@ -1437,7 +1446,8 @@ SafeWriter* DivEngine::saveFur() {
w->writeC(song.continuousVibrato);
w->writeC(song.brokenDACMode);
w->writeC(song.oneTickCut);
for (int i=0; i<2; i++) {
w->writeC(song.newInsTriggersInPorta);
for (int i=0; i<1; i++) {
w->writeC(0);
}

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

@ -92,7 +92,7 @@ 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) {
@ -121,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) {
@ -159,7 +159,7 @@ 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) {
@ -184,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;
}
@ -782,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

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

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

@ -663,6 +663,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

View File

@ -298,6 +298,7 @@ struct DivSong {
bool continuousVibrato;
bool brokenDACMode;
bool oneTickCut;
bool newInsTriggersInPorta;
DivOrders orders;
std::vector<DivInstrument*> ins;
@ -364,7 +365,8 @@ struct DivSong {
stopPortaOnNoteOff(false),
continuousVibrato(false),
brokenDACMode(false),
oneTickCut(false) {
oneTickCut(false),
newInsTriggersInPorta(true) {
for (int i=0; i<32; i++) {
system[i]=DIV_SYSTEM_NULL;
systemVol[i]=64;

View File

@ -421,10 +421,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";
@ -434,7 +430,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";
@ -442,6 +443,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) {

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

View File

@ -32,6 +32,7 @@
#include "ImGuiFileDialog.h"
#include "IconsFontAwesome4.h"
#include "misc/cpp/imgui_stdlib.h"
#include "plot_nolerp.h"
#include "guiConst.h"
#include "intConst.h"
#include <stdint.h>
@ -1090,6 +1091,13 @@ void FurnaceGUI::drawInsList() {
}
ImGui::Separator();
if (ImGui::BeginTable("InsListScroll",1,ImGuiTableFlags_ScrollY)) {
if (settings.unifiedDataView) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text(ICON_FA_TASKS " Instruments");
ImGui::Indent();
}
for (int i=0; i<(int)e->song.ins.size(); i++) {
DivInstrument* ins=e->song.ins[i];
String name;
@ -1214,12 +1222,32 @@ void FurnaceGUI::drawInsList() {
}
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s",(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]);
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
insEditOpen=true;
nextWindow=GUI_WINDOW_INS_EDIT;
}
}
}
if (settings.unifiedDataView) {
ImGui::Unindent();
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text(ICON_FA_AREA_CHART " Wavetables");
ImGui::Indent();
actualWaveList();
ImGui::Unindent();
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text(ICON_FA_VOLUME_UP " Samples");
ImGui::Indent();
actualSampleList();
ImGui::Unindent();
}
ImGui::EndTable();
}
}
@ -1231,6 +1259,47 @@ const char* sampleNote[12]={
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
};
void FurnaceGUI::actualWaveList() {
float wavePreview[256];
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);
}
}
void FurnaceGUI::actualSampleList() {
for (int i=0; i<(int)e->song.sample.size(); i++) {
DivSample* sample=e->song.sample[i];
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d",i,sample->name,i).c_str(),curSample==i)) {
curSample=i;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Bank %d: %s",i/12,sampleNote[i%12]);
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
sampleEditOpen=true;
}
}
}
}
void FurnaceGUI::drawSampleList() {
if (nextWindow==GUI_WINDOW_SAMPLE_LIST) {
sampleListOpen=true;
@ -1272,24 +1341,7 @@ void FurnaceGUI::drawSampleList() {
}
ImGui::Separator();
if (ImGui::BeginTable("SampleListScroll",1,ImGuiTableFlags_ScrollY)) {
for (int i=0; i<(int)e->song.sample.size(); i++) {
DivSample* sample=e->song.sample[i];
ImGui::TableNextRow();
ImGui::TableNextColumn();
if ((i%12)==0) {
if (i>0) ImGui::Unindent();
ImGui::Text("Bank %d",i/12);
ImGui::Indent();
}
if (ImGui::Selectable(fmt::sprintf("%s: %s##_SAM%d",sampleNote[i%12],sample->name,i).c_str(),curSample==i)) {
curSample=i;
}
if (ImGui::IsItemHovered()) {
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
sampleEditOpen=true;
}
}
}
actualSampleList();
ImGui::EndTable();
}
ImGui::Unindent();
@ -2164,6 +2216,10 @@ void FurnaceGUI::drawCompatFlags() {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("behavior changed in 0.6");
}
ImGui::Checkbox("Allow instrument change during slides",&e->song.newInsTriggersInPorta);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("behavior changed in 0.6");
}
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS;
ImGui::End();
@ -3330,6 +3386,31 @@ void FurnaceGUI::doFlip() {
finishSelection();
prepareUndo(GUI_UNDO_PATTERN_FLIP);
DivPattern patBuffer;
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
int ord=e->getOrder();
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true);
for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(iFine);
for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) {
patBuffer.data[j][0]=pat->data[j][0];
}
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
}
for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) {
pat->data[j][0]=patBuffer.data[selEnd.y-j+selStart.y][0];
}
pat->data[j][iFine+1]=patBuffer.data[selEnd.y-j+selStart.y][iFine+1];
}
}
iFine=0;
}
makeUndo(GUI_UNDO_PATTERN_FLIP);
}
@ -3337,13 +3418,99 @@ void FurnaceGUI::doCollapse(int divider) {
finishSelection();
prepareUndo(GUI_UNDO_PATTERN_COLLAPSE);
DivPattern patBuffer;
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
int ord=e->getOrder();
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true);
for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(iFine);
for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) {
patBuffer.data[j][0]=pat->data[j][0];
}
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
}
for (int j=0; j<=selEnd.y-selStart.y; j++) {
if (j*divider>=selEnd.y-selStart.y) {
if (iFine==0) {
pat->data[j+selStart.y][0]=0;
pat->data[j+selStart.y][1]=0;
} else {
pat->data[j+selStart.y][iFine+1]=-1;
}
} else {
if (iFine==0) {
pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y][0];
}
pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y][iFine+1];
if (iFine==0) {
for (int k=1; k<divider; k++) {
if ((j*divider+k)>=selEnd.y-selStart.y) break;
if (!(pat->data[j+selStart.y][0]==0 && pat->data[j+selStart.y][1]==0)) break;
pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y+k][0];
pat->data[j+selStart.y][1]=patBuffer.data[j*divider+selStart.y+k][1];
}
} else {
for (int k=1; k<divider; k++) {
if ((j*divider+k)>=selEnd.y-selStart.y) break;
if (pat->data[j+selStart.y][iFine+1]!=-1) break;
pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y+k][iFine+1];
}
}
}
}
}
iFine=0;
}
makeUndo(GUI_UNDO_PATTERN_COLLAPSE);
}
void FurnaceGUI::doExpand(int multiplier) {
if (multiplier<1) return;
finishSelection();
prepareUndo(GUI_UNDO_PATTERN_EXPAND);
DivPattern patBuffer;
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
int ord=e->getOrder();
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true);
for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(iFine);
for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) {
patBuffer.data[j][0]=pat->data[j][0];
}
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
}
for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) {
if ((j+selStart.y)>=e->song.patLen) break;
if ((j%multiplier)!=0) {
if (iFine==0) {
pat->data[j+selStart.y][0]=0;
pat->data[j+selStart.y][1]=0;
} else {
pat->data[j+selStart.y][iFine+1]=-1;
}
continue;
}
if (iFine==0) {
pat->data[j+selStart.y][0]=patBuffer.data[j/multiplier+selStart.y][0];
}
pat->data[j+selStart.y][iFine+1]=patBuffer.data[j/multiplier+selStart.y][iFine+1];
}
}
iFine=0;
}
makeUndo(GUI_UNDO_PATTERN_EXPAND);
}
@ -4221,6 +4388,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
int num=12*curOctave+key;
if (edit) {
// TODO: separate when adding MIDI input.
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true);
prepareUndo(GUI_UNDO_PATTERN_EDIT);
@ -4242,7 +4410,17 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
pat->data[cursor.y][1]--;
}
pat->data[cursor.y][1]=(unsigned char)pat->data[cursor.y][1];
pat->data[cursor.y][2]=curIns;
if (latchIns==-2) {
pat->data[cursor.y][2]=curIns;
} else if (latchIns!=-1 && !e->song.ins.empty()) {
pat->data[cursor.y][2]=MIN(((int)e->song.ins.size())-1,latchIns);
}
if (latchVol!=-1) {
int maxVol=e->getMaxVolumeChan(cursor.xCoarse);
pat->data[cursor.y][3]=MIN(maxVol,latchVol);
}
if (latchEffect!=-1) pat->data[cursor.y][4]=latchEffect;
if (latchEffectVal!=-1) pat->data[cursor.y][5]=latchEffectVal;
previewNote(cursor.xCoarse,num);
}
makeUndo(GUI_UNDO_PATTERN_EDIT);
@ -4463,73 +4641,168 @@ bool dirExists(String what) {
}
void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
ImGuiFileDialog::Instance()->DpiScale=dpiScale;
bool hasOpened=false;
switch (type) {
case GUI_FILE_OPEN:
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Open File","compatible files{.fur,.dmf},.*",workingDirSong);
hasOpened=fileDialog->openLoad(
"Open File",
{"compatible files", "*.fur *.dmf",
"all files", ".*"},
"compatible files{.fur,.dmf},.*",
workingDirSong,
dpiScale
);
break;
case GUI_FILE_SAVE:
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save File","Furnace song{.fur},DefleMask 1.1 module{.dmf}",workingDirSong,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite);
hasOpened=fileDialog->openSave(
"Save File",
{"Furnace song", "*.fur",
"DefleMask 1.1 module", "*.dmf"},
"Furnace song{.fur},DefleMask 1.1 module{.dmf}",
workingDirSong,
dpiScale
);
break;
case GUI_FILE_SAVE_DMF_LEGACY:
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save File","DefleMask 1.0/legacy module{.dmf}",workingDirSong,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite);
hasOpened=fileDialog->openSave(
"Save File",
{"DefleMask 1.0/legacy module", "*.dmf"},
"DefleMask 1.0/legacy module{.dmf}",
workingDirSong,
dpiScale
);
break;
case GUI_FILE_INS_OPEN:
if (!dirExists(workingDirIns)) workingDirIns=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Instrument","compatible files{.fui,.dmp,.tfi,.vgi},.*",workingDirIns);
hasOpened=fileDialog->openLoad(
"Load Instrument",
{"compatible files", "*.fui *.dmp *.tfi *.vgi",
"all files", ".*"},
"compatible files{.fui,.dmp,.tfi,.vgi},.*",
workingDirIns,
dpiScale
);
break;
case GUI_FILE_INS_SAVE:
if (!dirExists(workingDirIns)) workingDirIns=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Instrument","Furnace instrument{.fui}",workingDirIns,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite);
hasOpened=fileDialog->openSave(
"Save Instrument",
{"Furnace instrument", "*.fui"},
"Furnace instrument{.fui}",
workingDirIns,
dpiScale
);
break;
case GUI_FILE_WAVE_OPEN:
if (!dirExists(workingDirWave)) workingDirWave=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Wavetable","compatible files{.fuw,.dmw},.*",workingDirWave);
hasOpened=fileDialog->openLoad(
"Load Wavetable",
{"compatible files", "*.fuw *.dmw",
"all files", ".*"},
"compatible files{.fuw,.dmw},.*",
workingDirWave,
dpiScale
);
break;
case GUI_FILE_WAVE_SAVE:
if (!dirExists(workingDirWave)) workingDirWave=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Wavetable","Furnace wavetable{.fuw}",workingDirWave,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite);
hasOpened=fileDialog->openSave(
"Save Wavetable",
{"Furnace wavetable", ".fuw"},
"Furnace wavetable{.fuw}",
workingDirWave,
dpiScale
);
break;
case GUI_FILE_SAMPLE_OPEN:
if (!dirExists(workingDirSample)) workingDirSample=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Sample","Wave file{.wav},.*",workingDirSample);
hasOpened=fileDialog->openLoad(
"Load Sample",
{"Wave file", "*.wav",
"all files", ".*"},
"Wave file{.wav},.*",
workingDirSample,
dpiScale
);
break;
case GUI_FILE_SAMPLE_SAVE:
if (!dirExists(workingDirSample)) workingDirSample=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Sample","Wave file{.wav}",workingDirSample,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite);
hasOpened=fileDialog->openSave(
"Save Sample",
{"Wave file", "*.wav"},
"Wave file{.wav}",
workingDirSample,
dpiScale
);
break;
case GUI_FILE_EXPORT_AUDIO_ONE:
if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDirAudioExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite);
hasOpened=fileDialog->openSave(
"Export Audio",
{"Wave file", "*.wav"},
"Wave file{.wav}",
workingDirAudioExport,
dpiScale
);
break;
case GUI_FILE_EXPORT_AUDIO_PER_SYS:
if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDirAudioExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite);
hasOpened=fileDialog->openSave(
"Export Audio",
{"Wave file", "*.wav"},
"Wave file{.wav}",
workingDirAudioExport,
dpiScale
);
break;
case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL:
if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDirAudioExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite);
hasOpened=fileDialog->openSave(
"Export Audio",
{"Wave file", "*.wav"},
"Wave file{.wav}",
workingDirAudioExport,
dpiScale
);
break;
case GUI_FILE_EXPORT_VGM:
if (!dirExists(workingDirVGMExport)) workingDirVGMExport=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export VGM",".vgm",workingDirVGMExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite);
hasOpened=fileDialog->openSave(
"Export VGM",
{"VGM file", "*.vgm"},
"VGM file{.vgm}",
workingDirVGMExport,
dpiScale
);
break;
case GUI_FILE_EXPORT_ROM:
showError("Coming soon!");
break;
case GUI_FILE_LOAD_MAIN_FONT:
if (!dirExists(workingDirFont)) workingDirFont=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Select Font","compatible files{.ttf,.otf,.ttc}",workingDirFont);
hasOpened=fileDialog->openLoad(
"Select Font",
{"compatible files", "*.ttf *.otf *.ttc"},
"compatible files{.ttf,.otf,.ttc}",
workingDirFont,
dpiScale
);
break;
case GUI_FILE_LOAD_PAT_FONT:
if (!dirExists(workingDirFont)) workingDirFont=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog","Select Font","compatible files{.ttf,.otf,.ttc}",workingDirFont);
hasOpened=fileDialog->openLoad(
"Select Font",
{"compatible files", "*.ttf *.otf *.ttc"},
"compatible files{.ttf,.otf,.ttc}",
workingDirFont,
dpiScale
);
break;
}
curFileDialog=type;
if (hasOpened) curFileDialog=type;
//ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard;
}
@ -4654,7 +4927,7 @@ int FurnaceGUI::load(String path) {
}
if (len<1) {
if (len==0) {
printf("that file is empty!\n");
logE("that file is empty!\n");
lastError="file is empty";
} else {
perror("tell error");
@ -4810,6 +5083,15 @@ void FurnaceGUI::processDrags(int dragX, int dragY) {
fileName+=x; \
}
#define checkExtensionDual(x,y,fallback) \
String lowerCase=fileName; \
for (char& i: lowerCase) { \
if (i>='A' && i<='Z') i+='a'-'A'; \
} \
if (lowerCase.size()<4 || (lowerCase.rfind(x)!=lowerCase.size()-4 && lowerCase.rfind(y)!=lowerCase.size()-4)) { \
fileName+=fallback; \
}
#define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str()
void FurnaceGUI::editOptions(bool topMenu) {
@ -5475,6 +5757,10 @@ bool FurnaceGUI::loop() {
e->setSysFlags(i,(flags&(~0x30))|32,restart);
updateWindowTitle();
}
if (ImGui::RadioButton("AY-3-8914",(flags&0x30)==48)) {
e->setSysFlags(i,(flags&(~0x30))|48,restart);
updateWindowTitle();
}
}
bool stereo=flags&0x40;
ImGui::BeginDisabled((flags&0x30)==32);
@ -5819,49 +6105,47 @@ bool FurnaceGUI::loop() {
if (patternOpen) nextWindow=GUI_WINDOW_PATTERN;
}
if (ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) {
if (fileDialog->render(ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) {
//ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavEnableKeyboard;
switch (curFileDialog) {
case GUI_FILE_OPEN:
case GUI_FILE_SAVE:
case GUI_FILE_SAVE_DMF_LEGACY:
workingDirSong=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
workingDirSong=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_INS_OPEN:
case GUI_FILE_INS_SAVE:
workingDirIns=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_WAVE_OPEN:
case GUI_FILE_WAVE_SAVE:
workingDirWave=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
workingDirWave=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_SAMPLE_OPEN:
case GUI_FILE_SAMPLE_SAVE:
workingDirSample=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
workingDirSample=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_EXPORT_AUDIO_ONE:
case GUI_FILE_EXPORT_AUDIO_PER_SYS:
case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL:
workingDirAudioExport=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
workingDirAudioExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_EXPORT_VGM:
case GUI_FILE_EXPORT_ROM:
workingDirVGMExport=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
workingDirVGMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_LOAD_MAIN_FONT:
case GUI_FILE_LOAD_PAT_FONT:
workingDirFont=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
workingDirFont=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
}
if (ImGuiFileDialog::Instance()->IsOk()) {
fileName=ImGuiFileDialog::Instance()->GetFilePathName();
if (fileDialog->accepted()) {
fileName=fileDialog->getFileName();
if (fileName!="") {
if (curFileDialog==GUI_FILE_SAVE) {
if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song") {
checkExtension(".fur");
} else {
checkExtension(".dmf");
}
// we can't tell whether the user chose .dmf or .fur in the system file picker
const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song")?".fur":".dmf";
checkExtensionDual(".fur",".dmf",fallbackExt);
}
if (curFileDialog==GUI_FILE_SAVE_DMF_LEGACY) {
checkExtension(".dmf");
@ -5888,9 +6172,13 @@ bool FurnaceGUI::loop() {
showError(fmt::sprintf("Error while loading file! (%s)",lastError));
}
break;
case GUI_FILE_SAVE:
printf("saving: %s\n",copyOfName.c_str());
if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song") {
case GUI_FILE_SAVE: {
logD("saving: %s\n",copyOfName.c_str());
String lowerCase=fileName;
for (char& i: lowerCase) {
if (i>='A' && i<='Z') i+='a'-'A';
}
if ((lowerCase.size()<4 || lowerCase.rfind(".dmf")!=lowerCase.size()-4)) {
if (save(copyOfName,0)>0) {
showError(fmt::sprintf("Error while saving file! (%s)",lastError));
}
@ -5900,8 +6188,9 @@ bool FurnaceGUI::loop() {
}
}
break;
}
case GUI_FILE_SAVE_DMF_LEGACY:
printf("saving: %s\n",copyOfName.c_str());
logD("saving: %s\n",copyOfName.c_str());
if (save(copyOfName,24)>0) {
showError(fmt::sprintf("Error while saving file! (%s)",lastError));
}
@ -5980,7 +6269,7 @@ bool FurnaceGUI::loop() {
curFileDialog=GUI_FILE_OPEN;
}
}
ImGuiFileDialog::Instance()->Close();
fileDialog->close();
}
if (warnQuit) {
@ -6458,6 +6747,9 @@ void FurnaceGUI::applyUISettings() {
if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) {
logE("could not load big UI font!\n");
}
if (fileDialog!=NULL) delete fileDialog;
fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog);
}
bool FurnaceGUI::init() {
@ -6587,6 +6879,7 @@ bool FurnaceGUI::init() {
ImGui::GetIO().IniFilename=finalLayoutPath;
ImGui::LoadIniSettingsFromDisk(finalLayoutPath);
// TODO: allow changing these colors.
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir,"",ImVec4(0.0f,1.0f,1.0f,1.0f),ICON_FA_FOLDER_O);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile,"",ImVec4(0.7f,0.7f,0.7f,1.0f),ICON_FA_FILE_O);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fur",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE);
@ -6682,6 +6975,7 @@ FurnaceGUI::FurnaceGUI():
displayNew(false),
curFileDialog(GUI_FILE_OPEN),
warnAction(GUI_WARN_OPEN),
fileDialog(NULL),
scrW(1280),
scrH(800),
dpiScale(1),
@ -6750,7 +7044,7 @@ FurnaceGUI::FurnaceGUI():
opMaskEffect(true),
opMaskEffectVal(true),
latchNote(-1),
latchIns(-1),
latchIns(-2),
latchVol(-1),
latchEffect(-1),
latchEffectVal(-1),
@ -7058,7 +7352,7 @@ FurnaceGUI::FurnaceGUI():
));
cat.systems.push_back(FurnaceGUISysDef(
"Mattel Intellivision", {
DIV_SYSTEM_AY8910, 64, 0, 6,
DIV_SYSTEM_AY8910, 64, 0, 48,
0
}
));

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;}
@ -476,6 +478,8 @@ class FurnaceGUI {
FurnaceGUIFileDialogs curFileDialog;
FurnaceGUIWarnings warnAction;
FurnaceGUIFileDialog* fileDialog;
int scrW, scrH;
double dpiScale;
@ -534,6 +538,10 @@ class FurnaceGUI {
int avoidRaisingPattern;
int insFocusesPattern;
int stepOnInsert;
// TODO flags
int unifiedDataView;
int sysFileDialog;
// end
unsigned int maxUndoSteps;
String mainFontPath;
String patFontPath;
@ -578,6 +586,8 @@ class FurnaceGUI {
avoidRaisingPattern(0),
insFocusesPattern(1),
stepOnInsert(0),
unifiedDataView(0),
sysFileDialog(1),
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),
@ -663,6 +673,7 @@ class FurnaceGUI {
bool macroDragInitialValueSet;
bool macroDragInitialValue;
bool macroDragChar;
bool macroDragLineMode; // TODO
bool macroDragActive;
ImVec2 macroLoopDragStart;
@ -710,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();

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,35 +27,6 @@
#include <imgui.h>
#include "plot_nolerp.h"
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"
};
const char* ssgEnvTypes[8]={
"Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN"
};
@ -1787,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);
@ -1818,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();
}
}

View File

@ -182,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;
@ -403,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;
@ -892,6 +902,8 @@ void FurnaceGUI::syncSettings() {
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);
@ -930,6 +942,8 @@ void FurnaceGUI::syncSettings() {
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);
@ -1129,6 +1143,8 @@ void FurnaceGUI::commitSettings() {
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);