Merge branch 'master' of https://github.com/tildearrow/furnace into scc
This commit is contained in:
commit
2c6267bd6b
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
Makefile
|
||||
cmake_install.cmake
|
|
@ -0,0 +1,5 @@
|
|||
extraction:
|
||||
cpp:
|
||||
index:
|
||||
build_command:
|
||||
- make -C examples
|
|
@ -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})
|
|
@ -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.
|
||||
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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.
|
|
@ -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";
|
||||
```
|
|
@ -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";
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
example
|
||||
example.exe
|
||||
kill
|
||||
kill.exe
|
||||
|
||||
Debug
|
||||
Release
|
||||
*.vcxproj.user
|
||||
|
||||
.idea
|
||||
cmake-build-*
|
|
@ -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, don’t 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();
|
||||
}
|
||||
|
|
@ -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>
|
|
@ -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 didn’t 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";
|
||||
}
|
||||
|
|
@ -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>
|
File diff suppressed because it is too large
Load Diff
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
};
|
412
src/gui/gui.cpp
412
src/gui/gui.cpp
|
@ -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
|
||||
}
|
||||
));
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
};
|
|
@ -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[];
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue