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/font_icon.cpp
|
||||||
src/gui/fonts.cpp
|
src/gui/fonts.cpp
|
||||||
src/gui/debug.cpp
|
src/gui/debug.cpp
|
||||||
|
src/gui/fileDialog.cpp
|
||||||
|
|
||||||
src/gui/intConst.cpp
|
src/gui/intConst.cpp
|
||||||
src/gui/guiConst.cpp
|
src/gui/guiConst.cpp
|
||||||
|
@ -401,6 +402,9 @@ endif()
|
||||||
|
|
||||||
if (NOT MSVC)
|
if (NOT MSVC)
|
||||||
set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter)
|
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)
|
if (WARNINGS_ARE_ERRORS)
|
||||||
list(APPEND WARNING_FLAGS -Werror)
|
list(APPEND WARNING_FLAGS -Werror)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -68,6 +68,8 @@ some people have provided packages for Unix/Unix-like distributions. here's a li
|
||||||
|
|
||||||
# developer info
|
# 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.**
|
**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
|
## dependencies
|
||||||
|
|
|
@ -3877,6 +3877,7 @@ namespace IGFD
|
||||||
static ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_AllowDoubleClick |
|
static ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_AllowDoubleClick |
|
||||||
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SpanAvailWidth;
|
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SpanAvailWidth;
|
||||||
|
|
||||||
|
// TODO BUG?!
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, vFmt);
|
va_start(args, vFmt);
|
||||||
vsnprintf(fdi.puVariadicBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFmt, args);
|
vsnprintf(fdi.puVariadicBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFmt, args);
|
||||||
|
@ -4074,6 +4075,7 @@ namespace IGFD
|
||||||
|
|
||||||
if (ImGui::TableNextColumn()) // file name
|
if (ImGui::TableNextColumn()) // file name
|
||||||
{
|
{
|
||||||
|
// TODO BUG?!?!?!
|
||||||
needToBreakTheloop = prSelectableItem(i, infos, selected, _str.c_str());
|
needToBreakTheloop = prSelectableItem(i, infos, selected, _str.c_str());
|
||||||
if (needToBreakTheloop==2) escape=true;
|
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...
|
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
|
# effects
|
||||||
|
|
||||||
- `20xx`: set channel mode. `xx` may be one of the following:
|
- `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:
|
the format versions are:
|
||||||
|
|
||||||
|
- 66: Furnace dev66
|
||||||
- 65: Furnace dev65
|
- 65: Furnace dev65
|
||||||
- 64: Furnace dev64
|
- 64: Furnace dev64
|
||||||
- 63: Furnace dev63
|
- 63: Furnace dev63
|
||||||
|
@ -207,7 +208,8 @@ size | description
|
||||||
1 | continuous vibrato (>=62) or reserved
|
1 | continuous vibrato (>=62) or reserved
|
||||||
1 | broken DAC mode (>=64) or reserved
|
1 | broken DAC mode (>=64) or reserved
|
||||||
1 | one tick cut (>=65) 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 instruments
|
||||||
4?? | pointers to wavetables
|
4?? | pointers to wavetables
|
||||||
4?? | pointers to samples
|
4?? | pointers to samples
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# make Windows release
|
# 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
|
if [ ! -e /tmp/furnace ]; then
|
||||||
ln -s "$PWD" /tmp/furnace || exit 1
|
ln -s "$PWD" /tmp/furnace || exit 1
|
||||||
|
@ -14,7 +14,8 @@ fi
|
||||||
|
|
||||||
cd win32build
|
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
|
make -j8 || exit 1
|
||||||
i686-w64-mingw32-strip -s furnace.exe || 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
|
cp -r ../../demos demos || exit 1
|
||||||
|
|
||||||
zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos
|
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
|
#!/bin/bash
|
||||||
# make Windows release
|
# 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
|
if [ ! -e /tmp/furnace ]; then
|
||||||
ln -s "$PWD" /tmp/furnace || exit 1
|
ln -s "$PWD" /tmp/furnace || exit 1
|
||||||
|
@ -14,7 +14,8 @@ fi
|
||||||
|
|
||||||
cd winbuild
|
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
|
make -j8 || exit 1
|
||||||
x86_64-w64-mingw32-strip -s furnace.exe || 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
|
cp -r ../../demos demos || exit 1
|
||||||
|
|
||||||
zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos
|
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); \
|
warnings+=(String("\n")+x); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define DIV_VERSION "dev65"
|
#define DIV_VERSION "dev66"
|
||||||
#define DIV_ENGINE_VERSION 65
|
#define DIV_ENGINE_VERSION 66
|
||||||
|
|
||||||
enum DivStatusView {
|
enum DivStatusView {
|
||||||
DIV_STATUS_NOTHING=0,
|
DIV_STATUS_NOTHING=0,
|
||||||
|
@ -69,7 +69,7 @@ enum DivHaltPositions {
|
||||||
|
|
||||||
struct DivChannelState {
|
struct DivChannelState {
|
||||||
std::vector<DivDelayedCommand> delayed;
|
std::vector<DivDelayedCommand> delayed;
|
||||||
int note, oldNote, pitch, portaSpeed, portaNote;
|
int note, oldNote, lastIns, pitch, portaSpeed, portaNote;
|
||||||
int volume, volSpeed, cut, rowDelay, volMax;
|
int volume, volSpeed, cut, rowDelay, volMax;
|
||||||
int delayOrder, delayRow, retrigSpeed, retrigTick;
|
int delayOrder, delayRow, retrigSpeed, retrigTick;
|
||||||
int vibratoDepth, vibratoRate, vibratoPos, vibratoDir, vibratoFine;
|
int vibratoDepth, vibratoRate, vibratoPos, vibratoDir, vibratoFine;
|
||||||
|
@ -80,6 +80,7 @@ struct DivChannelState {
|
||||||
DivChannelState():
|
DivChannelState():
|
||||||
note(-1),
|
note(-1),
|
||||||
oldNote(-1),
|
oldNote(-1),
|
||||||
|
lastIns(-1),
|
||||||
pitch(0),
|
pitch(0),
|
||||||
portaSpeed(-1),
|
portaSpeed(-1),
|
||||||
portaNote(-1),
|
portaNote(-1),
|
||||||
|
|
|
@ -142,6 +142,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
||||||
ds.ignoreDuplicateSlides=true;
|
ds.ignoreDuplicateSlides=true;
|
||||||
ds.brokenDACMode=true;
|
ds.brokenDACMode=true;
|
||||||
ds.oneTickCut=false;
|
ds.oneTickCut=false;
|
||||||
|
ds.newInsTriggersInPorta=true;
|
||||||
|
|
||||||
// 1.1 compat flags
|
// 1.1 compat flags
|
||||||
if (ds.version>24) {
|
if (ds.version>24) {
|
||||||
|
@ -807,6 +808,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
||||||
if (ds.version<65) {
|
if (ds.version<65) {
|
||||||
ds.oneTickCut=false;
|
ds.oneTickCut=false;
|
||||||
}
|
}
|
||||||
|
if (ds.version<66) {
|
||||||
|
ds.newInsTriggersInPorta=false;
|
||||||
|
}
|
||||||
ds.isDMF=false;
|
ds.isDMF=false;
|
||||||
|
|
||||||
reader.readS(); // reserved
|
reader.readS(); // reserved
|
||||||
|
@ -993,7 +997,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
||||||
} else {
|
} else {
|
||||||
reader.readC();
|
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 {
|
} else {
|
||||||
for (int i=0; i<20; i++) reader.readC();
|
for (int i=0; i<20; i++) reader.readC();
|
||||||
}
|
}
|
||||||
|
@ -1437,7 +1446,8 @@ SafeWriter* DivEngine::saveFur() {
|
||||||
w->writeC(song.continuousVibrato);
|
w->writeC(song.continuousVibrato);
|
||||||
w->writeC(song.brokenDACMode);
|
w->writeC(song.brokenDACMode);
|
||||||
w->writeC(song.oneTickCut);
|
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);
|
w->writeC(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
#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
|
#define CHIP_DIVIDER 8
|
||||||
|
|
||||||
|
@ -48,8 +48,28 @@ const char* regCheatSheetAY[]={
|
||||||
NULL
|
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() {
|
const char** DivPlatformAY8910::getRegisterSheet() {
|
||||||
return regCheatSheetAY;
|
return intellivision?regCheatSheetAY8914:regCheatSheetAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* DivPlatformAY8910::getEffectName(unsigned char effect) {
|
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()) {
|
while (!writes.empty()) {
|
||||||
QueuedWrite w=writes.front();
|
QueuedWrite w=writes.front();
|
||||||
ay->address_w(w.addr);
|
if (intellivision) {
|
||||||
ay->data_w(w.val);
|
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;
|
regPool[w.addr&0x0f]=w.val;
|
||||||
writes.pop();
|
writes.pop();
|
||||||
}
|
}
|
||||||
|
@ -125,6 +150,8 @@ void DivPlatformAY8910::tick() {
|
||||||
if (chan[i].outVol<0) chan[i].outVol=0;
|
if (chan[i].outVol<0) chan[i].outVol=0;
|
||||||
if (isMuted[i]) {
|
if (isMuted[i]) {
|
||||||
rWrite(0x08+i,0);
|
rWrite(0x08+i,0);
|
||||||
|
} else if (intellivision && (chan[i].psgMode&4)) {
|
||||||
|
rWrite(0x08+i,(chan[i].outVol&0xc)<<2);
|
||||||
} else {
|
} else {
|
||||||
rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2));
|
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;
|
chan[i].psgMode=(chan[i].std.wave+1)&7;
|
||||||
if (isMuted[i]) {
|
if (isMuted[i]) {
|
||||||
rWrite(0x08+i,0);
|
rWrite(0x08+i,0);
|
||||||
|
} else if (intellivision && (chan[i].psgMode&4)) {
|
||||||
|
rWrite(0x08+i,(chan[i].outVol&0xc)<<2);
|
||||||
} else {
|
} else {
|
||||||
rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2));
|
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);
|
chan[c.chan].std.init(ins);
|
||||||
if (isMuted[c.chan]) {
|
if (isMuted[c.chan]) {
|
||||||
rWrite(0x08+c.chan,0);
|
rWrite(0x08+c.chan,0);
|
||||||
|
} else if (intellivision && (chan[c.chan].psgMode&4)) {
|
||||||
|
rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2);
|
||||||
} else {
|
} else {
|
||||||
rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2));
|
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]) {
|
if (isMuted[c.chan]) {
|
||||||
rWrite(0x08+c.chan,0);
|
rWrite(0x08+c.chan,0);
|
||||||
} else {
|
} 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;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -317,7 +354,11 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
||||||
if (isMuted[c.chan]) {
|
if (isMuted[c.chan]) {
|
||||||
rWrite(0x08+c.chan,0);
|
rWrite(0x08+c.chan,0);
|
||||||
} else if (chan[c.chan].active) {
|
} 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;
|
break;
|
||||||
|
@ -334,6 +375,8 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
||||||
}
|
}
|
||||||
if (isMuted[c.chan]) {
|
if (isMuted[c.chan]) {
|
||||||
rWrite(0x08+c.chan,0);
|
rWrite(0x08+c.chan,0);
|
||||||
|
} else if (intellivision && (chan[c.chan].psgMode&4)) {
|
||||||
|
rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2);
|
||||||
} else {
|
} else {
|
||||||
rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2));
|
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;
|
isMuted[ch]=mute;
|
||||||
if (isMuted[ch]) {
|
if (isMuted[ch]) {
|
||||||
rWrite(0x08+ch,0);
|
rWrite(0x08+ch,0);
|
||||||
|
} else if (intellivision && (chan[ch].psgMode&4)) {
|
||||||
|
rWrite(0x08+ch,(chan[ch].vol&0xc)<<2);
|
||||||
} else {
|
} else {
|
||||||
rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2));
|
rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2));
|
||||||
}
|
}
|
||||||
|
@ -508,14 +553,22 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
|
||||||
case 1:
|
case 1:
|
||||||
ay=new ym2149_device(rate);
|
ay=new ym2149_device(rate);
|
||||||
sunsoft=false;
|
sunsoft=false;
|
||||||
|
intellivision=false;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
ay=new sunsoft_5b_sound_device(rate);
|
ay=new sunsoft_5b_sound_device(rate);
|
||||||
sunsoft=true;
|
sunsoft=true;
|
||||||
|
intellivision=false;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
ay=new ay8914_device(rate);
|
||||||
|
sunsoft=false;
|
||||||
|
intellivision=true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ay=new ay8910_device(rate);
|
ay=new ay8910_device(rate);
|
||||||
sunsoft=false;
|
sunsoft=false;
|
||||||
|
intellivision=false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ay->device_start();
|
ay->device_start();
|
||||||
|
|
|
@ -26,6 +26,10 @@
|
||||||
|
|
||||||
class DivPlatformAY8910: public DivDispatch {
|
class DivPlatformAY8910: public DivDispatch {
|
||||||
protected:
|
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 {
|
struct Channel {
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, note, pitch;
|
int freq, baseFreq, note, pitch;
|
||||||
|
@ -60,7 +64,7 @@ class DivPlatformAY8910: public DivDispatch {
|
||||||
int delay;
|
int delay;
|
||||||
|
|
||||||
bool extMode;
|
bool extMode;
|
||||||
bool stereo, sunsoft;
|
bool stereo, sunsoft, intellivision;
|
||||||
|
|
||||||
short oldWrites[16];
|
short oldWrites[16];
|
||||||
short pendingWrites[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);
|
DivSample* s=parent->getSample(dacSample);
|
||||||
if (s->samples>0) {
|
if (s->samples>0) {
|
||||||
if (!isMuted[5]) {
|
if (!isMuted[5]) {
|
||||||
immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
|
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
|
||||||
}
|
}
|
||||||
if (++dacPos>=s->samples) {
|
if (++dacPos>=s->samples) {
|
||||||
if (s->loopStart>=0 && s->loopStart<=(int)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);
|
//printf("write: %x = %.2x\n",w.addr,w.val);
|
||||||
lastBusy=0;
|
lastBusy=0;
|
||||||
regPool[w.addr&0x1ff]=w.val;
|
regPool[w.addr&0x1ff]=w.val;
|
||||||
writes.pop();
|
writes.pop_front();
|
||||||
} else {
|
} else {
|
||||||
lastBusy++;
|
lastBusy++;
|
||||||
if (fm.write_busy==0) {
|
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);
|
DivSample* s=parent->getSample(dacSample);
|
||||||
if (s->samples>0) {
|
if (s->samples>0) {
|
||||||
if (!isMuted[5]) {
|
if (!isMuted[5]) {
|
||||||
immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
|
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
|
||||||
}
|
}
|
||||||
if (++dacPos>=s->samples) {
|
if (++dacPos>=s->samples) {
|
||||||
if (s->loopStart>=0 && s->loopStart<=(int)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(0x0+((w.addr>>8)<<1),w.addr);
|
||||||
fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val);
|
fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val);
|
||||||
regPool[w.addr&0x1ff]=w.val;
|
regPool[w.addr&0x1ff]=w.val;
|
||||||
writes.pop();
|
writes.pop_front();
|
||||||
lastBusy=1;
|
lastBusy=1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -782,7 +782,7 @@ int DivPlatformGenesis::getRegisterPoolSize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformGenesis::reset() {
|
void DivPlatformGenesis::reset() {
|
||||||
while (!writes.empty()) writes.pop();
|
while (!writes.empty()) writes.pop_front();
|
||||||
memset(regPool,0,512);
|
memset(regPool,0,512);
|
||||||
if (useYMFM) {
|
if (useYMFM) {
|
||||||
fm_ymfm->reset();
|
fm_ymfm->reset();
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
#ifndef _GENESIS_H
|
#ifndef _GENESIS_H
|
||||||
#define _GENESIS_H
|
#define _GENESIS_H
|
||||||
#include "../dispatch.h"
|
#include "../dispatch.h"
|
||||||
#include <queue>
|
#include <deque>
|
||||||
#include "../../../extern/Nuked-OPN2/ym3438.h"
|
#include "../../../extern/Nuked-OPN2/ym3438.h"
|
||||||
#include "sound/ymfm/ymfm_opn.h"
|
#include "sound/ymfm/ymfm_opn.h"
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ class DivPlatformGenesis: public DivDispatch {
|
||||||
bool addrOrVal;
|
bool addrOrVal;
|
||||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||||
};
|
};
|
||||||
std::queue<QueuedWrite> writes;
|
std::deque<QueuedWrite> writes;
|
||||||
ym3438_t fm;
|
ym3438_t fm;
|
||||||
int delay;
|
int delay;
|
||||||
unsigned char lastBusy;
|
unsigned char lastBusy;
|
||||||
|
|
|
@ -43,6 +43,7 @@ static int orderedOps[4]={
|
||||||
};
|
};
|
||||||
|
|
||||||
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
#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"
|
#include "fmshared_OPN.h"
|
||||||
|
|
|
@ -235,7 +235,7 @@ int DivPlatformLynx::dispatch(DivCommand c) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_PANNING:
|
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);
|
WRITE_ATTEN(c.chan,chan[c.chan].pan);
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_GET_VOLUME:
|
case DIV_CMD_GET_VOLUME:
|
||||||
|
|
|
@ -201,6 +201,7 @@ void DivPlatformNES::tick() {
|
||||||
} else {
|
} else {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1;
|
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>2047) chan[i].freq=2047;
|
||||||
|
if (chan[i].freq<0) chan[i].freq=0;
|
||||||
}
|
}
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
//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() {
|
void DivPlatformOPL::tick() {
|
||||||
for (int i=0; i<20; i++) {
|
for (int i=0; i<melodicChans; i++) {
|
||||||
chan[i].std.next();
|
chan[i].std.next();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -349,10 +349,22 @@ void DivPlatformOPL::tick() {
|
||||||
|
|
||||||
if (chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].keyOn || chan[i].keyOff) {
|
||||||
immWrite(chanMap[i]+ADDR_FREQH,0x00|(chan[i].freqH&31));
|
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;
|
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++) {
|
for (int i=0; i<512; i++) {
|
||||||
if (pendingWrites[i]!=oldWrites[i]) {
|
if (pendingWrites[i]!=oldWrites[i]) {
|
||||||
immWrite(i,pendingWrites[i]&0xff);
|
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) {
|
if (chan[i].freqChanged) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq));
|
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;
|
if (chan[i].freq>131071) chan[i].freq=131071;
|
||||||
|
@ -368,12 +380,21 @@ void DivPlatformOPL::tick() {
|
||||||
chan[i].freqH=freqt>>8;
|
chan[i].freqH=freqt>>8;
|
||||||
chan[i].freqL=freqt&0xff;
|
chan[i].freqL=freqt&0xff;
|
||||||
immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL);
|
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) {
|
if (chan[i].keyOn) {
|
||||||
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(0x20));
|
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;
|
chan[i].keyOn=false;
|
||||||
} else if (chan[i].freqChanged) {
|
} else if (chan[i].freqChanged) {
|
||||||
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5));
|
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;
|
chan[i].freqChanged=false;
|
||||||
}
|
}
|
||||||
|
@ -424,25 +445,42 @@ int DivPlatformOPL::toFreq(int freq) {
|
||||||
|
|
||||||
void DivPlatformOPL::muteChannel(int ch, bool mute) {
|
void DivPlatformOPL::muteChannel(int ch, bool mute) {
|
||||||
isMuted[ch]=mute;
|
isMuted[ch]=mute;
|
||||||
/*
|
int ops=(slots[3][ch]!=255 && chan[ch].state.ops==4 && oplType==3)?4:2;
|
||||||
for (int j=0; j<4; j++) {
|
chan[ch].fourOp=(ops==4);
|
||||||
unsigned short baseAddr=chanOffs[ch]|opOffs[j];
|
update4OpMask=true;
|
||||||
DivInstrumentFM::Operator& op=chan[ch].state.op[j];
|
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]) {
|
if (isMuted[ch]) {
|
||||||
rWrite(baseAddr+ADDR_TL,127);
|
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
|
||||||
} else {
|
} else {
|
||||||
if (isOutput[chan[ch].state.alg][j]) {
|
if (isOutputL[ops==4][chan[ch].state.alg][i]) {
|
||||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[ch].outVol&0x7f))/127));
|
rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[ch].outVol&0x3f))/63))|(op.ksl<<6));
|
||||||
} else {
|
} 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) {
|
int DivPlatformOPL::dispatch(DivCommand c) {
|
||||||
|
// TODO: drums mode!
|
||||||
|
if (c.chan>=melodicChans) return 0;
|
||||||
switch (c.cmd) {
|
switch (c.cmd) {
|
||||||
case DIV_CMD_NOTE_ON: {
|
case DIV_CMD_NOTE_ON: {
|
||||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
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;
|
chan[c.chan].outVol=chan[c.chan].vol;
|
||||||
}
|
}
|
||||||
if (chan[c.chan].insChanged) {
|
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++) {
|
for (int i=0; i<ops; i++) {
|
||||||
unsigned char slot=slots[i][c.chan];
|
unsigned char slot=slots[i][c.chan];
|
||||||
if (slot==255) continue;
|
if (slot==255) continue;
|
||||||
|
@ -524,21 +564,23 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
||||||
if (!chan[c.chan].std.hasVol) {
|
if (!chan[c.chan].std.hasVol) {
|
||||||
chan[c.chan].outVol=c.value;
|
chan[c.chan].outVol=c.value;
|
||||||
}
|
}
|
||||||
/*
|
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<ops; i++) {
|
||||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
unsigned char slot=slots[i][c.chan];
|
||||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
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]) {
|
if (isMuted[c.chan]) {
|
||||||
rWrite(baseAddr+ADDR_TL,127);
|
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
|
||||||
} else {
|
} else {
|
||||||
if (isOutput[chan[c.chan].state.alg][i]) {
|
if (isOutputL[ops==4][chan[c.chan].state.alg][i]) {
|
||||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
|
rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[c.chan].outVol&0x3f))/63))|(op.ksl<<6));
|
||||||
} else {
|
} else {
|
||||||
rWrite(baseAddr+ADDR_TL,op.tl);
|
rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_GET_VOLUME: {
|
case DIV_CMD_GET_VOLUME: {
|
||||||
|
@ -552,16 +594,22 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
||||||
chan[c.chan].ins=c.value;
|
chan[c.chan].ins=c.value;
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_PANNING: {
|
case DIV_CMD_PANNING: {
|
||||||
switch (c.value) {
|
if (c.value==0) {
|
||||||
case 0x01:
|
chan[c.chan].pan=3;
|
||||||
chan[c.chan].pan=1;
|
} else {
|
||||||
break;
|
chan[c.chan].pan=(((c.value&15)>0)<<1)|((c.value>>4)>0);
|
||||||
case 0x10:
|
}
|
||||||
chan[c.chan].pan=2;
|
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||||
break;
|
if (isMuted[c.chan]) {
|
||||||
default:
|
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;
|
if (ops==4) {
|
||||||
break;
|
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));
|
//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;
|
break;
|
||||||
|
@ -683,39 +731,46 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformOPL::forceIns() {
|
void DivPlatformOPL::forceIns() {
|
||||||
/*
|
for (int i=0; i<melodicChans; i++) {
|
||||||
for (int i=0; i<20; i++) {
|
int ops=(slots[3][i]!=255 && chan[i].state.ops==4 && oplType==3)?4:2;
|
||||||
for (int j=0; j<4; j++) {
|
chan[i].fourOp=(ops==4);
|
||||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
for (int j=0; j<ops; j++) {
|
||||||
DivInstrumentFM::Operator& op=chan[i].state.op[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]) {
|
if (isMuted[i]) {
|
||||||
rWrite(baseAddr+ADDR_TL,127);
|
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
|
||||||
} else {
|
} else {
|
||||||
if (isOutput[chan[i].state.alg][j]) {
|
if (isOutputL[ops==4][chan[i].state.alg][j]) {
|
||||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[i].outVol&0x3f))/63))|(op.ksl<<6));
|
||||||
} else {
|
} 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_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
|
||||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr);
|
||||||
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr);
|
||||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
if (oplType>1) {
|
||||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
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 (isMuted[i]) {
|
||||||
if (chan[i].active) {
|
rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1));
|
||||||
chan[i].keyOn=true;
|
if (ops==4) {
|
||||||
chan[i].freqChanged=true;
|
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) {
|
update4OpMask=true;
|
||||||
rWrite(0x2b,0x80);
|
|
||||||
}
|
|
||||||
immWrite(0x22,lfoValue);
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformOPL::toggleRegisterDump(bool enable) {
|
void DivPlatformOPL::toggleRegisterDump(bool enable) {
|
||||||
|
@ -746,7 +801,7 @@ void DivPlatformOPL::reset() {
|
||||||
if (dumpWrites) {
|
if (dumpWrites) {
|
||||||
addWrite(0xffffffff,0);
|
addWrite(0xffffffff,0);
|
||||||
}
|
}
|
||||||
for (int i=0; i<20; i++) {
|
for (int i=0; i<totalChans; i++) {
|
||||||
chan[i]=DivPlatformOPL::Channel();
|
chan[i]=DivPlatformOPL::Channel();
|
||||||
chan[i].vol=0x3f;
|
chan[i].vol=0x3f;
|
||||||
chan[i].outVol=0x3f;
|
chan[i].outVol=0x3f;
|
||||||
|
@ -761,10 +816,15 @@ void DivPlatformOPL::reset() {
|
||||||
lfoValue=8;
|
lfoValue=8;
|
||||||
properDrums=properDrumsSys;
|
properDrums=properDrumsSys;
|
||||||
|
|
||||||
|
if (oplType==1) { // disable waveforms
|
||||||
|
immWrite(0x01,0x20);
|
||||||
|
}
|
||||||
|
|
||||||
if (oplType==3) { // enable OPL3 features
|
if (oplType==3) { // enable OPL3 features
|
||||||
immWrite(0x105,1);
|
immWrite(0x105,1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update4OpMask=true;
|
||||||
delay=0;
|
delay=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -781,7 +841,7 @@ bool DivPlatformOPL::keyOffAffectsPorta(int ch) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformOPL::notifyInsChange(int ins) {
|
void DivPlatformOPL::notifyInsChange(int ins) {
|
||||||
for (int i=0; i<20; i++) {
|
for (int i=0; i<totalChans; i++) {
|
||||||
if (chan[i].ins==ins) {
|
if (chan[i].ins==ins) {
|
||||||
chan[i].insChanged=true;
|
chan[i].insChanged=true;
|
||||||
}
|
}
|
||||||
|
@ -815,6 +875,9 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
|
||||||
slots=drums?slotsDrums:slotsNonDrums;
|
slots=drums?slotsDrums:slotsNonDrums;
|
||||||
chanMap=chanMapOPL2;
|
chanMap=chanMapOPL2;
|
||||||
chipFreqBase=9440540*0.25;
|
chipFreqBase=9440540*0.25;
|
||||||
|
chans=9;
|
||||||
|
melodicChans=drums?6:9;
|
||||||
|
totalChans=drums?11:9;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
slotsNonDrums=slotsOPL3;
|
slotsNonDrums=slotsOPL3;
|
||||||
|
@ -822,6 +885,9 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
|
||||||
slots=drums?slotsDrums:slotsNonDrums;
|
slots=drums?slotsDrums:slotsNonDrums;
|
||||||
chanMap=chanMapOPL3;
|
chanMap=chanMapOPL3;
|
||||||
chipFreqBase=9440540;
|
chipFreqBase=9440540;
|
||||||
|
chans=18;
|
||||||
|
melodicChans=drums?15:18;
|
||||||
|
totalChans=drums?20:18;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
oplType=type;
|
oplType=type;
|
||||||
|
|
|
@ -32,7 +32,7 @@ class DivPlatformOPL: public DivDispatch {
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, note;
|
int freq, baseFreq, pitch, note;
|
||||||
unsigned char ins;
|
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;
|
int vol, outVol;
|
||||||
unsigned char pan;
|
unsigned char pan;
|
||||||
Channel():
|
Channel():
|
||||||
|
@ -51,6 +51,7 @@ class DivPlatformOPL: public DivDispatch {
|
||||||
portaPause(false),
|
portaPause(false),
|
||||||
furnaceDac(false),
|
furnaceDac(false),
|
||||||
inPorta(false),
|
inPorta(false),
|
||||||
|
fourOp(false),
|
||||||
vol(0),
|
vol(0),
|
||||||
pan(3) {}
|
pan(3) {}
|
||||||
};
|
};
|
||||||
|
@ -69,7 +70,7 @@ class DivPlatformOPL: public DivDispatch {
|
||||||
const unsigned char** slots;
|
const unsigned char** slots;
|
||||||
const unsigned short* chanMap;
|
const unsigned short* chanMap;
|
||||||
double chipFreqBase;
|
double chipFreqBase;
|
||||||
int delay, oplType;
|
int delay, oplType, chans, melodicChans, totalChans;
|
||||||
unsigned char lastBusy;
|
unsigned char lastBusy;
|
||||||
|
|
||||||
unsigned char regPool[512];
|
unsigned char regPool[512];
|
||||||
|
@ -78,7 +79,7 @@ class DivPlatformOPL: public DivDispatch {
|
||||||
|
|
||||||
unsigned char lfoValue;
|
unsigned char lfoValue;
|
||||||
|
|
||||||
bool useYMFM;
|
bool useYMFM, update4OpMask;
|
||||||
|
|
||||||
short oldWrites[512];
|
short oldWrites[512];
|
||||||
short pendingWrites[512];
|
short pendingWrites[512];
|
||||||
|
|
|
@ -435,8 +435,8 @@ public:
|
||||||
case ATTENREG2:
|
case ATTENREG2:
|
||||||
case ATTENREG3:
|
case ATTENREG3:
|
||||||
mRegisterPool[8*4+idx] = value;
|
mRegisterPool[8*4+idx] = value;
|
||||||
mAttenuationLeft[idx] = ( value & 0x0f ) << 2;
|
mAttenuationRight[idx] = ( value & 0x0f ) << 2;
|
||||||
mAttenuationRight[idx] = ( value & 0xf0 ) >> 2;
|
mAttenuationLeft[idx] = ( value & 0xf0 ) >> 2;
|
||||||
break;
|
break;
|
||||||
case MPAN:
|
case MPAN:
|
||||||
mPan = value;
|
mPan = value;
|
||||||
|
|
|
@ -663,6 +663,12 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
// instrument
|
// instrument
|
||||||
if (pat->data[whatRow][2]!=-1) {
|
if (pat->data[whatRow][2]!=-1) {
|
||||||
dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2]));
|
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
|
// note
|
||||||
if (pat->data[whatRow][0]==100) { // note off
|
if (pat->data[whatRow][0]==100) { // note off
|
||||||
|
|
|
@ -298,6 +298,7 @@ struct DivSong {
|
||||||
bool continuousVibrato;
|
bool continuousVibrato;
|
||||||
bool brokenDACMode;
|
bool brokenDACMode;
|
||||||
bool oneTickCut;
|
bool oneTickCut;
|
||||||
|
bool newInsTriggersInPorta;
|
||||||
|
|
||||||
DivOrders orders;
|
DivOrders orders;
|
||||||
std::vector<DivInstrument*> ins;
|
std::vector<DivInstrument*> ins;
|
||||||
|
@ -364,7 +365,8 @@ struct DivSong {
|
||||||
stopPortaOnNoteOff(false),
|
stopPortaOnNoteOff(false),
|
||||||
continuousVibrato(false),
|
continuousVibrato(false),
|
||||||
brokenDACMode(false),
|
brokenDACMode(false),
|
||||||
oneTickCut(false) {
|
oneTickCut(false),
|
||||||
|
newInsTriggersInPorta(true) {
|
||||||
for (int i=0; i<32; i++) {
|
for (int i=0; i<32; i++) {
|
||||||
system[i]=DIV_SYSTEM_NULL;
|
system[i]=DIV_SYSTEM_NULL;
|
||||||
systemVol[i]=64;
|
systemVol[i]=64;
|
||||||
|
|
|
@ -421,10 +421,6 @@ const char* DivEngine::getSongSystemName() {
|
||||||
return "Vectrex";
|
return "Vectrex";
|
||||||
case 5: // AY-3-8910, 1MHz
|
case 5: // AY-3-8910, 1MHz
|
||||||
return "Amstrad CPC";
|
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
|
case 0x10: // YM2149, 1.79MHz
|
||||||
return "MSX";
|
return "MSX";
|
||||||
|
@ -435,6 +431,11 @@ const char* DivEngine::getSongSystemName() {
|
||||||
case 0x28: // 5B PAL
|
case 0x28: // 5B PAL
|
||||||
return "Sunsoft 5B standalone (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:
|
default:
|
||||||
if ((song.systemFlags[0]&0x30)==0x00) {
|
if ((song.systemFlags[0]&0x30)==0x00) {
|
||||||
return "AY-3-8910";
|
return "AY-3-8910";
|
||||||
|
@ -442,6 +443,8 @@ const char* DivEngine::getSongSystemName() {
|
||||||
return "Yamaha YM2149";
|
return "Yamaha YM2149";
|
||||||
} else if ((song.systemFlags[0]&0x30)==0x20) {
|
} else if ((song.systemFlags[0]&0x30)==0x20) {
|
||||||
return "Overclocked Sunsoft 5B";
|
return "Overclocked Sunsoft 5B";
|
||||||
|
} else if ((song.systemFlags[0]&0x30)==0x30) {
|
||||||
|
return "Intellivision";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (song.system[0]==DIV_SYSTEM_SMS) {
|
} 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 "ImGuiFileDialog.h"
|
||||||
#include "IconsFontAwesome4.h"
|
#include "IconsFontAwesome4.h"
|
||||||
#include "misc/cpp/imgui_stdlib.h"
|
#include "misc/cpp/imgui_stdlib.h"
|
||||||
|
#include "plot_nolerp.h"
|
||||||
#include "guiConst.h"
|
#include "guiConst.h"
|
||||||
#include "intConst.h"
|
#include "intConst.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
@ -1090,6 +1091,13 @@ void FurnaceGUI::drawInsList() {
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (ImGui::BeginTable("InsListScroll",1,ImGuiTableFlags_ScrollY)) {
|
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++) {
|
for (int i=0; i<(int)e->song.ins.size(); i++) {
|
||||||
DivInstrument* ins=e->song.ins[i];
|
DivInstrument* ins=e->song.ins[i];
|
||||||
String name;
|
String name;
|
||||||
|
@ -1214,12 +1222,32 @@ void FurnaceGUI::drawInsList() {
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("%s",(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]);
|
||||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||||
insEditOpen=true;
|
insEditOpen=true;
|
||||||
nextWindow=GUI_WINDOW_INS_EDIT;
|
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();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1231,6 +1259,47 @@ const char* sampleNote[12]={
|
||||||
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
|
"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() {
|
void FurnaceGUI::drawSampleList() {
|
||||||
if (nextWindow==GUI_WINDOW_SAMPLE_LIST) {
|
if (nextWindow==GUI_WINDOW_SAMPLE_LIST) {
|
||||||
sampleListOpen=true;
|
sampleListOpen=true;
|
||||||
|
@ -1272,24 +1341,7 @@ void FurnaceGUI::drawSampleList() {
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (ImGui::BeginTable("SampleListScroll",1,ImGuiTableFlags_ScrollY)) {
|
if (ImGui::BeginTable("SampleListScroll",1,ImGuiTableFlags_ScrollY)) {
|
||||||
for (int i=0; i<(int)e->song.sample.size(); i++) {
|
actualSampleList();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
ImGui::Unindent();
|
ImGui::Unindent();
|
||||||
|
@ -2164,6 +2216,10 @@ void FurnaceGUI::drawCompatFlags() {
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("behavior changed in 0.6");
|
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;
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS;
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
@ -3330,6 +3386,31 @@ void FurnaceGUI::doFlip() {
|
||||||
finishSelection();
|
finishSelection();
|
||||||
prepareUndo(GUI_UNDO_PATTERN_FLIP);
|
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);
|
makeUndo(GUI_UNDO_PATTERN_FLIP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3337,13 +3418,99 @@ void FurnaceGUI::doCollapse(int divider) {
|
||||||
finishSelection();
|
finishSelection();
|
||||||
prepareUndo(GUI_UNDO_PATTERN_COLLAPSE);
|
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);
|
makeUndo(GUI_UNDO_PATTERN_COLLAPSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::doExpand(int multiplier) {
|
void FurnaceGUI::doExpand(int multiplier) {
|
||||||
|
if (multiplier<1) return;
|
||||||
|
|
||||||
finishSelection();
|
finishSelection();
|
||||||
prepareUndo(GUI_UNDO_PATTERN_EXPAND);
|
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);
|
makeUndo(GUI_UNDO_PATTERN_EXPAND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4221,6 +4388,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
|
||||||
int num=12*curOctave+key;
|
int num=12*curOctave+key;
|
||||||
|
|
||||||
if (edit) {
|
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);
|
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true);
|
||||||
|
|
||||||
prepareUndo(GUI_UNDO_PATTERN_EDIT);
|
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]--;
|
||||||
}
|
}
|
||||||
pat->data[cursor.y][1]=(unsigned char)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);
|
previewNote(cursor.xCoarse,num);
|
||||||
}
|
}
|
||||||
makeUndo(GUI_UNDO_PATTERN_EDIT);
|
makeUndo(GUI_UNDO_PATTERN_EDIT);
|
||||||
|
@ -4463,73 +4641,168 @@ bool dirExists(String what) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
||||||
ImGuiFileDialog::Instance()->DpiScale=dpiScale;
|
bool hasOpened=false;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case GUI_FILE_OPEN:
|
case GUI_FILE_OPEN:
|
||||||
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_SAVE:
|
case GUI_FILE_SAVE:
|
||||||
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_SAVE_DMF_LEGACY:
|
case GUI_FILE_SAVE_DMF_LEGACY:
|
||||||
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_INS_OPEN:
|
case GUI_FILE_INS_OPEN:
|
||||||
if (!dirExists(workingDirIns)) workingDirIns=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_INS_SAVE:
|
case GUI_FILE_INS_SAVE:
|
||||||
if (!dirExists(workingDirIns)) workingDirIns=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_WAVE_OPEN:
|
case GUI_FILE_WAVE_OPEN:
|
||||||
if (!dirExists(workingDirWave)) workingDirWave=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_WAVE_SAVE:
|
case GUI_FILE_WAVE_SAVE:
|
||||||
if (!dirExists(workingDirWave)) workingDirWave=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_SAMPLE_OPEN:
|
case GUI_FILE_SAMPLE_OPEN:
|
||||||
if (!dirExists(workingDirSample)) workingDirSample=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_SAMPLE_SAVE:
|
case GUI_FILE_SAMPLE_SAVE:
|
||||||
if (!dirExists(workingDirSample)) workingDirSample=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_EXPORT_AUDIO_ONE:
|
case GUI_FILE_EXPORT_AUDIO_ONE:
|
||||||
if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_EXPORT_AUDIO_PER_SYS:
|
case GUI_FILE_EXPORT_AUDIO_PER_SYS:
|
||||||
if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL:
|
case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL:
|
||||||
if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_EXPORT_VGM:
|
case GUI_FILE_EXPORT_VGM:
|
||||||
if (!dirExists(workingDirVGMExport)) workingDirVGMExport=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_EXPORT_ROM:
|
case GUI_FILE_EXPORT_ROM:
|
||||||
showError("Coming soon!");
|
showError("Coming soon!");
|
||||||
break;
|
break;
|
||||||
case GUI_FILE_LOAD_MAIN_FONT:
|
case GUI_FILE_LOAD_MAIN_FONT:
|
||||||
if (!dirExists(workingDirFont)) workingDirFont=getHomeDir();
|
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;
|
break;
|
||||||
case GUI_FILE_LOAD_PAT_FONT:
|
case GUI_FILE_LOAD_PAT_FONT:
|
||||||
if (!dirExists(workingDirFont)) workingDirFont=getHomeDir();
|
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;
|
break;
|
||||||
}
|
}
|
||||||
curFileDialog=type;
|
if (hasOpened) curFileDialog=type;
|
||||||
//ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard;
|
//ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4654,7 +4927,7 @@ int FurnaceGUI::load(String path) {
|
||||||
}
|
}
|
||||||
if (len<1) {
|
if (len<1) {
|
||||||
if (len==0) {
|
if (len==0) {
|
||||||
printf("that file is empty!\n");
|
logE("that file is empty!\n");
|
||||||
lastError="file is empty";
|
lastError="file is empty";
|
||||||
} else {
|
} else {
|
||||||
perror("tell error");
|
perror("tell error");
|
||||||
|
@ -4810,6 +5083,15 @@ void FurnaceGUI::processDrags(int dragX, int dragY) {
|
||||||
fileName+=x; \
|
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()
|
#define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str()
|
||||||
|
|
||||||
void FurnaceGUI::editOptions(bool topMenu) {
|
void FurnaceGUI::editOptions(bool topMenu) {
|
||||||
|
@ -5475,6 +5757,10 @@ bool FurnaceGUI::loop() {
|
||||||
e->setSysFlags(i,(flags&(~0x30))|32,restart);
|
e->setSysFlags(i,(flags&(~0x30))|32,restart);
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
}
|
}
|
||||||
|
if (ImGui::RadioButton("AY-3-8914",(flags&0x30)==48)) {
|
||||||
|
e->setSysFlags(i,(flags&(~0x30))|48,restart);
|
||||||
|
updateWindowTitle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bool stereo=flags&0x40;
|
bool stereo=flags&0x40;
|
||||||
ImGui::BeginDisabled((flags&0x30)==32);
|
ImGui::BeginDisabled((flags&0x30)==32);
|
||||||
|
@ -5819,49 +6105,47 @@ bool FurnaceGUI::loop() {
|
||||||
if (patternOpen) nextWindow=GUI_WINDOW_PATTERN;
|
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;
|
//ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavEnableKeyboard;
|
||||||
switch (curFileDialog) {
|
switch (curFileDialog) {
|
||||||
case GUI_FILE_OPEN:
|
case GUI_FILE_OPEN:
|
||||||
case GUI_FILE_SAVE:
|
case GUI_FILE_SAVE:
|
||||||
case GUI_FILE_SAVE_DMF_LEGACY:
|
case GUI_FILE_SAVE_DMF_LEGACY:
|
||||||
workingDirSong=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
|
workingDirSong=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||||
break;
|
break;
|
||||||
case GUI_FILE_INS_OPEN:
|
case GUI_FILE_INS_OPEN:
|
||||||
case GUI_FILE_INS_SAVE:
|
case GUI_FILE_INS_SAVE:
|
||||||
workingDirIns=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
|
workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||||
break;
|
break;
|
||||||
case GUI_FILE_WAVE_OPEN:
|
case GUI_FILE_WAVE_OPEN:
|
||||||
case GUI_FILE_WAVE_SAVE:
|
case GUI_FILE_WAVE_SAVE:
|
||||||
workingDirWave=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
|
workingDirWave=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||||
break;
|
break;
|
||||||
case GUI_FILE_SAMPLE_OPEN:
|
case GUI_FILE_SAMPLE_OPEN:
|
||||||
case GUI_FILE_SAMPLE_SAVE:
|
case GUI_FILE_SAMPLE_SAVE:
|
||||||
workingDirSample=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
|
workingDirSample=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||||
break;
|
break;
|
||||||
case GUI_FILE_EXPORT_AUDIO_ONE:
|
case GUI_FILE_EXPORT_AUDIO_ONE:
|
||||||
case GUI_FILE_EXPORT_AUDIO_PER_SYS:
|
case GUI_FILE_EXPORT_AUDIO_PER_SYS:
|
||||||
case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL:
|
case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL:
|
||||||
workingDirAudioExport=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
|
workingDirAudioExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||||
break;
|
break;
|
||||||
case GUI_FILE_EXPORT_VGM:
|
case GUI_FILE_EXPORT_VGM:
|
||||||
case GUI_FILE_EXPORT_ROM:
|
case GUI_FILE_EXPORT_ROM:
|
||||||
workingDirVGMExport=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
|
workingDirVGMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||||
break;
|
break;
|
||||||
case GUI_FILE_LOAD_MAIN_FONT:
|
case GUI_FILE_LOAD_MAIN_FONT:
|
||||||
case GUI_FILE_LOAD_PAT_FONT:
|
case GUI_FILE_LOAD_PAT_FONT:
|
||||||
workingDirFont=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR;
|
workingDirFont=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (ImGuiFileDialog::Instance()->IsOk()) {
|
if (fileDialog->accepted()) {
|
||||||
fileName=ImGuiFileDialog::Instance()->GetFilePathName();
|
fileName=fileDialog->getFileName();
|
||||||
if (fileName!="") {
|
if (fileName!="") {
|
||||||
if (curFileDialog==GUI_FILE_SAVE) {
|
if (curFileDialog==GUI_FILE_SAVE) {
|
||||||
if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song") {
|
// we can't tell whether the user chose .dmf or .fur in the system file picker
|
||||||
checkExtension(".fur");
|
const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song")?".fur":".dmf";
|
||||||
} else {
|
checkExtensionDual(".fur",".dmf",fallbackExt);
|
||||||
checkExtension(".dmf");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (curFileDialog==GUI_FILE_SAVE_DMF_LEGACY) {
|
if (curFileDialog==GUI_FILE_SAVE_DMF_LEGACY) {
|
||||||
checkExtension(".dmf");
|
checkExtension(".dmf");
|
||||||
|
@ -5888,9 +6172,13 @@ bool FurnaceGUI::loop() {
|
||||||
showError(fmt::sprintf("Error while loading file! (%s)",lastError));
|
showError(fmt::sprintf("Error while loading file! (%s)",lastError));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GUI_FILE_SAVE:
|
case GUI_FILE_SAVE: {
|
||||||
printf("saving: %s\n",copyOfName.c_str());
|
logD("saving: %s\n",copyOfName.c_str());
|
||||||
if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song") {
|
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) {
|
if (save(copyOfName,0)>0) {
|
||||||
showError(fmt::sprintf("Error while saving file! (%s)",lastError));
|
showError(fmt::sprintf("Error while saving file! (%s)",lastError));
|
||||||
}
|
}
|
||||||
|
@ -5900,8 +6188,9 @@ bool FurnaceGUI::loop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case GUI_FILE_SAVE_DMF_LEGACY:
|
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) {
|
if (save(copyOfName,24)>0) {
|
||||||
showError(fmt::sprintf("Error while saving file! (%s)",lastError));
|
showError(fmt::sprintf("Error while saving file! (%s)",lastError));
|
||||||
}
|
}
|
||||||
|
@ -5980,7 +6269,7 @@ bool FurnaceGUI::loop() {
|
||||||
curFileDialog=GUI_FILE_OPEN;
|
curFileDialog=GUI_FILE_OPEN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGuiFileDialog::Instance()->Close();
|
fileDialog->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (warnQuit) {
|
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) {
|
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");
|
logE("could not load big UI font!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fileDialog!=NULL) delete fileDialog;
|
||||||
|
fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FurnaceGUI::init() {
|
bool FurnaceGUI::init() {
|
||||||
|
@ -6587,6 +6879,7 @@ bool FurnaceGUI::init() {
|
||||||
ImGui::GetIO().IniFilename=finalLayoutPath;
|
ImGui::GetIO().IniFilename=finalLayoutPath;
|
||||||
ImGui::LoadIniSettingsFromDisk(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_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_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);
|
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),
|
displayNew(false),
|
||||||
curFileDialog(GUI_FILE_OPEN),
|
curFileDialog(GUI_FILE_OPEN),
|
||||||
warnAction(GUI_WARN_OPEN),
|
warnAction(GUI_WARN_OPEN),
|
||||||
|
fileDialog(NULL),
|
||||||
scrW(1280),
|
scrW(1280),
|
||||||
scrH(800),
|
scrH(800),
|
||||||
dpiScale(1),
|
dpiScale(1),
|
||||||
|
@ -6750,7 +7044,7 @@ FurnaceGUI::FurnaceGUI():
|
||||||
opMaskEffect(true),
|
opMaskEffect(true),
|
||||||
opMaskEffectVal(true),
|
opMaskEffectVal(true),
|
||||||
latchNote(-1),
|
latchNote(-1),
|
||||||
latchIns(-1),
|
latchIns(-2),
|
||||||
latchVol(-1),
|
latchVol(-1),
|
||||||
latchEffect(-1),
|
latchEffect(-1),
|
||||||
latchEffectVal(-1),
|
latchEffectVal(-1),
|
||||||
|
@ -7058,7 +7352,7 @@ FurnaceGUI::FurnaceGUI():
|
||||||
));
|
));
|
||||||
cat.systems.push_back(FurnaceGUISysDef(
|
cat.systems.push_back(FurnaceGUISysDef(
|
||||||
"Mattel Intellivision", {
|
"Mattel Intellivision", {
|
||||||
DIV_SYSTEM_AY8910, 64, 0, 6,
|
DIV_SYSTEM_AY8910, 64, 0, 48,
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fileDialog.h"
|
||||||
|
|
||||||
#define rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::SetKeyboardFocusHere(-1);
|
#define rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::SetKeyboardFocusHere(-1);
|
||||||
|
|
||||||
#define handleUnimportant if (settings.insFocusesPattern && patternOpen) {nextWindow=GUI_WINDOW_PATTERN;}
|
#define handleUnimportant if (settings.insFocusesPattern && patternOpen) {nextWindow=GUI_WINDOW_PATTERN;}
|
||||||
|
@ -476,6 +478,8 @@ class FurnaceGUI {
|
||||||
FurnaceGUIFileDialogs curFileDialog;
|
FurnaceGUIFileDialogs curFileDialog;
|
||||||
FurnaceGUIWarnings warnAction;
|
FurnaceGUIWarnings warnAction;
|
||||||
|
|
||||||
|
FurnaceGUIFileDialog* fileDialog;
|
||||||
|
|
||||||
int scrW, scrH;
|
int scrW, scrH;
|
||||||
|
|
||||||
double dpiScale;
|
double dpiScale;
|
||||||
|
@ -534,6 +538,10 @@ class FurnaceGUI {
|
||||||
int avoidRaisingPattern;
|
int avoidRaisingPattern;
|
||||||
int insFocusesPattern;
|
int insFocusesPattern;
|
||||||
int stepOnInsert;
|
int stepOnInsert;
|
||||||
|
// TODO flags
|
||||||
|
int unifiedDataView;
|
||||||
|
int sysFileDialog;
|
||||||
|
// end
|
||||||
unsigned int maxUndoSteps;
|
unsigned int maxUndoSteps;
|
||||||
String mainFontPath;
|
String mainFontPath;
|
||||||
String patFontPath;
|
String patFontPath;
|
||||||
|
@ -578,6 +586,8 @@ class FurnaceGUI {
|
||||||
avoidRaisingPattern(0),
|
avoidRaisingPattern(0),
|
||||||
insFocusesPattern(1),
|
insFocusesPattern(1),
|
||||||
stepOnInsert(0),
|
stepOnInsert(0),
|
||||||
|
unifiedDataView(0),
|
||||||
|
sysFileDialog(1),
|
||||||
maxUndoSteps(100),
|
maxUndoSteps(100),
|
||||||
mainFontPath(""),
|
mainFontPath(""),
|
||||||
patFontPath(""),
|
patFontPath(""),
|
||||||
|
@ -663,6 +673,7 @@ class FurnaceGUI {
|
||||||
bool macroDragInitialValueSet;
|
bool macroDragInitialValueSet;
|
||||||
bool macroDragInitialValue;
|
bool macroDragInitialValue;
|
||||||
bool macroDragChar;
|
bool macroDragChar;
|
||||||
|
bool macroDragLineMode; // TODO
|
||||||
bool macroDragActive;
|
bool macroDragActive;
|
||||||
|
|
||||||
ImVec2 macroLoopDragStart;
|
ImVec2 macroLoopDragStart;
|
||||||
|
@ -710,6 +721,9 @@ class FurnaceGUI {
|
||||||
|
|
||||||
void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord);
|
void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord);
|
||||||
|
|
||||||
|
void actualWaveList();
|
||||||
|
void actualSampleList();
|
||||||
|
|
||||||
void drawEditControls();
|
void drawEditControls();
|
||||||
void drawSongInfo();
|
void drawSongInfo();
|
||||||
void drawOrders();
|
void drawOrders();
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
// guiConst: constants used in the GUI like arrays, strings and other stuff
|
// guiConst: constants used in the GUI like arrays, strings and other stuff
|
||||||
#include "guiConst.h"
|
#include "guiConst.h"
|
||||||
|
#include "../engine/instrument.h"
|
||||||
|
|
||||||
const int opOrder[4]={
|
const int opOrder[4]={
|
||||||
0, 2, 1, 3
|
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"
|
"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* noteNames[180];
|
||||||
extern const char* noteNamesG[180];
|
extern const char* noteNamesG[180];
|
||||||
extern const char* pitchLabel[11];
|
extern const char* pitchLabel[11];
|
||||||
|
extern const char* insTypes[];
|
|
@ -27,35 +27,6 @@
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include "plot_nolerp.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]={
|
const char* ssgEnvTypes[8]={
|
||||||
"Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN"
|
"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;
|
nextWindow=GUI_WINDOW_NOTHING;
|
||||||
}
|
}
|
||||||
if (!waveListOpen) return;
|
if (!waveListOpen) return;
|
||||||
float wavePreview[256];
|
|
||||||
if (ImGui::Begin("Wavetables",&waveListOpen)) {
|
if (ImGui::Begin("Wavetables",&waveListOpen)) {
|
||||||
if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) {
|
if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) {
|
||||||
doAction(GUI_ACTION_WAVE_LIST_ADD);
|
doAction(GUI_ACTION_WAVE_LIST_ADD);
|
||||||
|
@ -1818,25 +1788,7 @@ void FurnaceGUI::drawWaveList() {
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (ImGui::BeginTable("WaveListScroll",1,ImGuiTableFlags_ScrollY)) {
|
if (ImGui::BeginTable("WaveListScroll",1,ImGuiTableFlags_ScrollY)) {
|
||||||
for (int i=0; i<(int)e->song.wave.size(); i++) {
|
actualWaveList();
|
||||||
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);
|
|
||||||
}
|
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,6 +182,11 @@ void FurnaceGUI::drawSettings() {
|
||||||
settings.restartOnFlagChange=restartOnFlagChangeB;
|
settings.restartOnFlagChange=restartOnFlagChangeB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool sysFileDialogB=settings.sysFileDialog;
|
||||||
|
if (ImGui::Checkbox("Use system file picker",&sysFileDialogB)) {
|
||||||
|
settings.sysFileDialog=sysFileDialogB;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Text("Wrap pattern cursor horizontally:");
|
ImGui::Text("Wrap pattern cursor horizontally:");
|
||||||
if (ImGui::RadioButton("No##wrapH0",settings.wrapHorizontal==0)) {
|
if (ImGui::RadioButton("No##wrapH0",settings.wrapHorizontal==0)) {
|
||||||
settings.wrapHorizontal=0;
|
settings.wrapHorizontal=0;
|
||||||
|
@ -403,6 +408,11 @@ void FurnaceGUI::drawSettings() {
|
||||||
settings.macroView=macroViewB;
|
settings.macroView=macroViewB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool unifiedDataViewB=settings.unifiedDataView;
|
||||||
|
if (ImGui::Checkbox("Unified instrument/wavetable/sample list",&unifiedDataViewB)) {
|
||||||
|
settings.unifiedDataView=unifiedDataViewB;
|
||||||
|
}
|
||||||
|
|
||||||
bool chipNamesB=settings.chipNames;
|
bool chipNamesB=settings.chipNames;
|
||||||
if (ImGui::Checkbox("Use chip names instead of system names",&chipNamesB)) {
|
if (ImGui::Checkbox("Use chip names instead of system names",&chipNamesB)) {
|
||||||
settings.chipNames=chipNamesB;
|
settings.chipNames=chipNamesB;
|
||||||
|
@ -892,6 +902,8 @@ void FurnaceGUI::syncSettings() {
|
||||||
settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0);
|
settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0);
|
||||||
settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1);
|
settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1);
|
||||||
settings.stepOnInsert=e->getConfInt("stepOnInsert",0);
|
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.mainFontSize,2,96);
|
||||||
clampSetting(settings.patFontSize,2,96);
|
clampSetting(settings.patFontSize,2,96);
|
||||||
|
@ -930,6 +942,8 @@ void FurnaceGUI::syncSettings() {
|
||||||
clampSetting(settings.avoidRaisingPattern,0,1);
|
clampSetting(settings.avoidRaisingPattern,0,1);
|
||||||
clampSetting(settings.insFocusesPattern,0,1);
|
clampSetting(settings.insFocusesPattern,0,1);
|
||||||
clampSetting(settings.stepOnInsert,0,1);
|
clampSetting(settings.stepOnInsert,0,1);
|
||||||
|
clampSetting(settings.unifiedDataView,0,1);
|
||||||
|
clampSetting(settings.sysFileDialog,0,1);
|
||||||
|
|
||||||
// keybinds
|
// keybinds
|
||||||
LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o);
|
LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o);
|
||||||
|
@ -1129,6 +1143,8 @@ void FurnaceGUI::commitSettings() {
|
||||||
e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern);
|
e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern);
|
||||||
e->setConf("insFocusesPattern",settings.insFocusesPattern);
|
e->setConf("insFocusesPattern",settings.insFocusesPattern);
|
||||||
e->setConf("stepOnInsert",settings.stepOnInsert);
|
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_BACKGROUND);
|
||||||
PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND);
|
PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND);
|
||||||
|
|
Loading…
Reference in New Issue