Merge branch 'master' into ZSMv1

This commit is contained in:
ZeroByteOrg 2022-06-23 13:45:12 -05:00
commit ae7b271e1d
128 changed files with 8022 additions and 1597 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
.vscode/
build/
clangbuild/
nosdl/
release/
t/

View File

@ -497,6 +497,7 @@ src/gui/editing.cpp
src/gui/editControls.cpp
src/gui/effectList.cpp
src/gui/findReplace.cpp
src/gui/gradient.cpp
src/gui/insEdit.cpp
src/gui/log.cpp
src/gui/mixer.cpp
@ -512,6 +513,7 @@ src/gui/sampleEdit.cpp
src/gui/settings.cpp
src/gui/songInfo.cpp
src/gui/songNotes.cpp
src/gui/spoiler.cpp
src/gui/stats.cpp
src/gui/subSongs.cpp
src/gui/sysConf.cpp
@ -522,8 +524,17 @@ src/gui/volMeter.cpp
src/gui/gui.cpp
)
if (WIN32 OR APPLE)
list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_common.cpp)
endif()
if (WIN32)
list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_win.cpp)
endif()
if (APPLE)
list(APPEND GUI_SOURCES src/gui/macstuff.m)
list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_cocoa.mm)
endif()
if (NOT WIN32 AND NOT APPLE)
@ -551,6 +562,11 @@ if (BUILD_GUI)
extern/IconFontCppHeaders
extern/igfd
)
if (WIN32 OR APPLE)
list(APPEND DEPENDENCIES_INCLUDE_DIRS
extern/nfd-modified/src/include
)
endif()
list(APPEND DEPENDENCIES_DEFINES HAVE_GUI)
message(STATUS "Building GUI")
else()
@ -572,7 +588,11 @@ endif()
if (NOT MSVC)
set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND WARNING_FLAGS -Wno-cast-function-type)
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.0.0)
# nothing
else()
list(APPEND WARNING_FLAGS -Wno-cast-function-type)
endif()
endif()
if (WARNINGS_ARE_ERRORS)
list(APPEND WARNING_FLAGS -Werror)

View File

@ -1,12 +1,13 @@
# to-do for 0.6pre1
- rewrite the system name detection function anyway
- add another FM editor layout
- add ability to move selection by dragging
- find and replace
- implement Defle slide bug when using E1xy/E2xy and repeating origin note (requires format change)
# to-do for 0.6pre2 (as this requires new data structures)
- rewrite the system name detection function anyway
- this involves the addition of a new "system" field in the song (which solves the problem)
- songs made in older versions will go through old system name detection for compatibility
- Game Boy envelope macro/sequence
- volume commands should work on Game Boy
- ability to customize `OFF`, `===` and `REL`
- stereo separation control for AY

Binary file not shown.

BIN
demos/E1M4OPL2.fur Normal file

Binary file not shown.

BIN
demos/FDS TEST.fur Normal file

Binary file not shown.

BIN
demos/Fake Gameboy.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/Fusion.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/c64 ring test.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/game boy thing.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demos/the_erfngjt.fur Normal file

Binary file not shown.

BIN
demos/thick bass test.fur Normal file

Binary file not shown.

View File

@ -130,7 +130,7 @@ static void YMPSG_ClockInternal1(ympsg_t *chip)
else if (noise_of && !chip->noise_of)
{
noise_bit1 = (chip->noise >> chip->noise_tap2) & 1;
noise_bit2 = (chip->noise >> 12) & 1;
noise_bit2 = (chip->noise >> chip->noise_tap1) & 1;
noise_bit1 ^= noise_bit2;
noise_next = ((noise_bit1 && ((chip->noise_data >> 2) & 1)) || ((chip->noise & chip->noise_size) == 0));
chip->noise <<= 1;
@ -257,13 +257,14 @@ uint16_t YMPSG_Read(ympsg_t *chip)
return data;
}
void YMPSG_Init(ympsg_t *chip, uint8_t real_sn)
void YMPSG_Init(ympsg_t *chip, uint8_t real_sn, uint8_t noise_tap1, uint8_t noise_tap2, uint32_t noise_size)
{
uint32_t i;
memset(chip, 0, sizeof(ympsg_t));
YMPSG_SetIC(chip, 1);
chip->noise_tap2 = real_sn ? 13 : 15;
chip->noise_size = real_sn ? 16383 : 32767;
chip->noise_tap1 = noise_tap1;
chip->noise_tap2 = noise_tap2;
chip->noise_size = noise_size;
for (i = 0; i < 17; i++)
{
chip->vol_table[i]=(real_sn?tipsg_vol[i]:ympsg_vol[i]) * 8192.0f;

View File

@ -46,8 +46,9 @@ typedef struct {
uint8_t sign_l;
uint8_t noise_sign_l;
uint16_t noise;
uint8_t noise_tap1;
uint8_t noise_tap2;
uint16_t noise_size;
uint32_t noise_size;
uint8_t test;
uint8_t volume_out[4];
@ -68,7 +69,7 @@ typedef struct {
void YMPSG_Write(ympsg_t *chip, uint8_t data);
uint16_t YMPSG_Read(ympsg_t *chip);
void YMPSG_Init(ympsg_t *chip, uint8_t real_sn);
void YMPSG_Init(ympsg_t *chip, uint8_t real_sn, uint8_t noise_tap1, uint8_t noise_tap2, uint32_t noise_size);
void YMPSG_SetIC(ympsg_t *chip, uint32_t ic);
void YMPSG_Clock(ympsg_t *chip);
int YMPSG_GetOutput(ympsg_t *chip);

View File

@ -1287,8 +1287,6 @@ namespace IGFD
std::sort(prFileList.begin(), prFileList.end(),
[](const std::shared_ptr<FileInfos>& a, const std::shared_ptr<FileInfos>& b) -> bool
{
if (a==NULL || b==NULL)
return false;
if (!a.use_count() || !b.use_count())
return false;
@ -1760,7 +1758,7 @@ namespace IGFD
struct stat statInfos = {};
char timebuf[100];
int result = stat(fpn.c_str(), &statInfos);
if (!result)
if (result!=-1)
{
if (vInfos->fileType != 'd')
{
@ -1781,7 +1779,11 @@ namespace IGFD
{
vInfos->fileModifDate = std::string(timebuf, len);
}
}
} else {
vInfos->fileSize=0;
vInfos->formatedFileSize = prFormatFileSize(vInfos->fileSize);
vInfos->fileModifDate="???";
}
}
}

View File

@ -4545,27 +4545,52 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size
// of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used
// without any storage on user's side.
IM_ASSERT(apply_new_text_length >= 0);
if (is_resizable)
{
ImGuiInputTextCallbackData callback_data;
callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
callback_data.Flags = flags;
callback_data.Buf = buf;
callback_data.BufTextLen = apply_new_text_length;
callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
callback_data.UserData = callback_user_data;
callback(&callback_data);
buf = callback_data.Buf;
buf_size = callback_data.BufSize;
apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
IM_ASSERT(apply_new_text_length <= buf_size);
}
//IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
// If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
value_changed = true;
// don't assert because maybe the user passes an invalid UTF-8 string...
if (apply_new_text_length >= 0) {
if (is_resizable)
{
ImGuiInputTextCallbackData callback_data;
callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
callback_data.Flags = flags;
callback_data.Buf = buf;
callback_data.BufTextLen = apply_new_text_length;
callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
callback_data.UserData = callback_user_data;
callback(&callback_data);
buf = callback_data.Buf;
buf_size = callback_data.BufSize;
apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
IM_ASSERT(apply_new_text_length <= buf_size);
}
//IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
// If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
value_changed = true;
} else {
printf("invalid buffer!\n");
if (is_resizable)
{
ImGuiInputTextCallbackData callback_data;
callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
callback_data.Flags = flags;
callback_data.Buf = buf;
callback_data.BufTextLen = 0;
callback_data.BufSize = ImMax(buf_size, 1);
callback_data.UserData = callback_user_data;
callback(&callback_data);
buf = callback_data.Buf;
buf_size = callback_data.BufSize;
apply_new_text_length = 0;
IM_ASSERT(apply_new_text_length <= buf_size);
}
//IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
// clear the buffer
ImStrncpy(buf, "", 1);
value_changed = true;
}
}
// Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)

181
extern/nfd-modified/.gitignore vendored Normal file
View File

@ -0,0 +1,181 @@
.sconsign.dblite
# Object files
*.o
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific folders
*.sln.ide/
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
# Roslyn cache directories
*.ide/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
#NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# If using the old MSBuild-Integrated Package Restore, uncomment this:
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/

16
extern/nfd-modified/LICENSE vendored Normal file
View File

@ -0,0 +1,16 @@
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

7
extern/nfd-modified/MODIFIED.md vendored Normal file
View File

@ -0,0 +1,7 @@
# MODIFIED
this is a modified, altered, edited and revised version of the Native File Dialog library used to display native-feeling, original and operating-system specific file archive dialog picker selectors, which is done by the Native File Dialog library.
it should not and shall NOT be mistaken for the original, authentic or actual version and revision of the library.
this is a version tailored for Furnace.

182
extern/nfd-modified/README.md vendored Normal file
View File

@ -0,0 +1,182 @@
# Native File Dialog Modified Version!!! #
A tiny, neat C library that portably invokes native file open, folder select and save dialogs. Write dialog code once and have it pop up native dialogs on all supported platforms. Avoid linking large dependencies like wxWidgets and qt.
This is a modified version of Native File Dialog, tailored for Furnace.
Features:
- Lean C API, static library -- no ObjC, no C++, no STL.
- Zlib licensed.
- Consistent UTF-8 support on all platforms.
- Simple universal file filter syntax.
- Paid support available.
- Multiple file selection support.
- 64-bit and 32-bit friendly.
- GCC, Clang, Xcode, Mingw and Visual Studio supported.
- No third party dependencies for building or linking.
- Support for Vista's modern `IFileDialog` on Windows.
- Support for non-deprecated Cocoa APIs on OS X.
- GTK3 dialog on Linux.
- Optional Zenity support on Linux to avoid linking GTK.
- Tested, works alongside [http://www.libsdl.org](SDL2) on all platforms, for the game developers out there.
# Example Usage #
```C
#include <nfd.h>
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
nfdchar_t *outPath = NULL;
nfdresult_t result = NFD_OpenDialog( NULL, NULL, &outPath );
if ( result == NFD_OKAY ) {
puts("Success!");
puts(outPath);
free(outPath);
}
else if ( result == NFD_CANCEL ) {
puts("User pressed cancel.");
}
else {
printf("Error: %s\n", NFD_GetError() );
}
return 0;
}
```
See self-documenting API [NFD.h](src/include/nfd.h) for more options.
# Screenshots #
![Windows rendering a dialog](screens/open_win.png?raw=true)
![GTK3 on Linux rendering a dialog](screens/open_gtk3.png?raw=true)
![Cocoa on MacOS rendering a dialog](screens/open_cocoa.png?raw=true)
## Changelog ##
- **Major** version increments denote API or ABI departure.
- **Minor** version increments denote build or trivial departures.
- **Micro** version increments just recompile and drop-in.
release | what's new | date
--------|-----------------------------|---------
1.0.0 | initial | oct 2014
1.1.0 | premake5; scons deprecated | aug 2016
1.1.1 | mingw support, build fixes | aug 2016
1.1.2 | test_pickfolder() added | aug 2016
1.1.3 | zenity linux backend added | nov 2017
<i></i> | fix char type in decls | nov 2017
1.1.4 | fix win32 memleaks | dec 2018
<i></i> | improve win32 errorhandling | dec 2018
<i></i> | macos fix focus bug | dec 2018
1.1.5 | win32 fix com reinitialize | aug 2019
1.1.6 | fix osx filter bug | aug 2019
<i></i> | remove deprecated scons | aug 2019
<i></i> | fix mingw compilation | aug 2019
<i></i> | -Wextra warning cleanup | aug 2019
## Building ##
NFD uses [Premake5](https://premake.github.io/download.html) generated Makefiles and IDE project files. The generated project files are checked in under `build/` so you don't have to download and use Premake in most cases.
If you need to run Premake5 directly, further [build documentation](docs/build.md) is available.
Previously, NFD used SCons to build. As of 1.1.6, SCons support has been removed entirely.
`nfd.a` will be built for release builds, and `nfd_d.a` will be built for debug builds.
### Makefiles ###
The makefile offers up to four options, with `release_x64` as the default.
make config=release_x86
make config=release_x64
make config=debug_x86
make config=debug_x64
### Compiling Your Programs ###
1. Add `src/include` to your include search path.
2. Add `nfd.lib` or `nfd_d.lib` to the list of list of static libraries to link against (for release or debug, respectively).
3. Add `build/<debug|release>/<arch>` to the library search path.
#### Linux GTK ####
`apt-get libgtk-3-dev` installs the gtk dependency for library compilation.
On Linux, you have the option of compiling and linking against GTK. If you use it, the recommended way to compile is to include the arguments of `pkg-config --cflags --libs gtk+-3.0`.
#### Linux Zenity ####
Alternatively, you can use the Zenity backend by running the Makefile in `build/gmake_linux_zenity`. Zenity runs the dialog in its own address space, but requires the user to have Zenity correctly installed and configured on their system.
#### MacOS ####
On Mac OS, add `AppKit` to the list of frameworks.
#### Windows ####
On Windows, ensure you are linking against `comctl32.lib`.
## Usage ##
See `NFD.h` for API calls. See `tests/*.c` for example code.
After compiling, `build/bin` contains compiled test programs. The appropriate subdirectory under `build/lib` contains the built library.
## File Filter Syntax ##
There is a form of file filtering in every file dialog API, but no consistent means of supporting it. NFD provides support for filtering files by groups of extensions, providing its own descriptions (where applicable) for the extensions.
A wildcard filter is always added to every dialog.
### Separators ###
- `;` Begin a new filter.
- `,` Add a separate type to the filter.
#### Examples ####
`txt` The default filter is for text files. There is a wildcard option in a dropdown.
`png,jpg;psd` The default filter is for png and jpg files. A second filter is available for psd files. There is a wildcard option in a dropdown.
`NULL` Wildcard only.
## Iterating Over PathSets ##
See [test_opendialogmultiple.c](test/test_opendialogmultiple.c).
# Known Limitations #
I accept quality code patches, or will resolve these and other matters through support. See [contributing](docs/contributing.md) for details.
- No support for Windows XP's legacy dialogs such as `GetOpenFileName`.
- No support for file filter names -- ex: "Image Files" (*.png, *.jpg). Nameless filters are supported, however.
- GTK Zenity implementation's process exec error handling does not gracefully handle numerous error cases, choosing to abort rather than cleanup and return.
- GTK 3 spams one warning per dialog created.
# Copyright and Credit #
Copyright &copy; 2014-2019 [Frogtoss Games](http://www.frogtoss.com), Inc.
File [LICENSE](LICENSE) covers all files in this repo.
Native File Dialog by Michael Labbe
<mike@frogtoss.com>
Tomasz Konojacki for [microutf8](http://puszcza.gnu.org.ua/software/microutf8/)
[Denis Kolodin](https://github.com/DenisKolodin) for mingw support.
[Tom Mason](https://github.com/wheybags) for Zenity support.
## Support ##
Directed support for this work is available from the original author under a paid agreement.
[Contact Frogtoss Games](http://www.frogtoss.com/pages/contact.html).

39
extern/nfd-modified/docs/build.md vendored Normal file
View File

@ -0,0 +1,39 @@
# Building NFD #
Most of the building instructions are included in [README.md](/README.md). This file just contains apocrypha.
## Running Premake5 Directly ##
*You shouldn't have to run Premake5 directly to build Native File Dialog. This is for package maintainers or people with exotic demands only!*
1. [Clone premake-core](https://github.com/premake/premake-core)
2. [Follow instructions on how to build premake](https://github.com/premake/premake-core/wiki/Building-Premake)
3. `cd` to `build`
4. Type `premake5 <type>`, where <type> is the build you want to create.
### Package Maintainer Only ###
I support a custom Premake action: `premake5 dist`, which generates all of the checked in project types in subdirectories. It is useful to run this command if you are submitting a pull request to test all of the supported premake configurations. Do not check in the built projects; I will do so while accepting your pull request.
## SCons build (deprecated) ##
As of 1.1.6, the deprecated and unmaintained SCons support is removed.
## Compiling with Mingw ##
Use the Makefile in `build/gmake_windows` to build Native File Dialog with mingw. Mingw has many distributions and not all of them are reliable. Here is what worked for me, the primary author of Native File Dialog:
1. Use mingw64, not mingw32. Downloaded from [sourceforge.net](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe/download).
2. When prompted in the intsaller, install the basic compiler and g++.
3. Add the installed bin dir to command prompt path.
4. Run `set CC=g++` to enforce `g++` instead of the default, `cc` for compiling and linking.
5. In `build/gmake_windows`, run `mingw32-make config=release_x64 clean`. Running clean ensures no Visual Studio build products conflict which can cause link errors.
6. Now run `mingw32-make config=release_x64`.
The author has not attempted to build or even install an x86 toolchain for mingw.
If you report an issue, be sure to run make with `verbose=1` so commands are visible.
## Adding NFD source directly to your project ##
Lots of developers add NFD source directly to their projects instead of using the included build scripts. As of 1.1.6, this is an acknowledged approach to building. Of course, everyone has a slightly different toolchain with various warnings and linters enabled. If you run a linter or catch a warning, please consider submitting a pull request to help NFD build cleanly for everyone.

View File

@ -0,0 +1,25 @@
# Pull Requests #
I have had to turn away a number of pull requests due to avoidable circumstances. Please read this file before submitting a pull request. Also look at existing, rejected pull requests which state the reason for rejection.
Here are the rules:
- **Submit pull requests to the devel branch**. The library must be tested on every compiler and OS, so there is no way I am going to just put your change in the master before it has been sync'd and tested on a number of machines. Master branch is depended upon by hundreds of projects.
- **Test your changes on all platforms and compilers that you can.** Also, state which platforms you have tested your code on. 32-bit or 64-bit. Clang or GCC. Visual Studio or Mingw. I have to test all these to accept pull requests, so I prioritize changes that respect my time.
- **Submit Premake build changes only**. As of 1.1, SCons is deprecated. Also, do not submit altered generated projects. I will re-run Premake to re-generate them to ensure that I can still generate the project prior to admitting your pull request.
- **Do not alter existing behavior to support your desired behavior**. For instance, rewriting file open dialogs to behave differently, while trading off functionality for compatibility, will get you rejected. Consider creating an additional code path. Instead of altering `nfd_win.cpp` to support Windows XP, create `nfd_win_legacy.cpp`, which exists alongside the newer file dialog.
- **Do not submit anything I can't verify or maintain**. If you add support for a compiler, include from-scratch install instructions that you have tested yourself. Accepting a pull request means I am now the maintainer of your code, so I must understand what it does and how to test it.
- **Do not change the externally facing API**. NFD needs to maintain ABI compatibility.
## Submitting Cloud Autobuild systems ##
I have received a few pull requests for Travis and AppVeyor-based autobuilding which I have not accepted. NativeFileDialog is officially covered by my private BuildBot network which supports all three target OSes, both CPU architectures and four compilers. I take the view that having a redundant, lesser autobuild system does not improve coverage: it gives a false positive when partial building succeeds. Please do not invest time into cloud-based building with the hope of a pull request being accepted.
## Contact Me ##
Despite all of the "do nots" above, I am happy to recieve new pull requests! If you have any questions about style, or what I would need to accept your specific request, please contact me ahead of submitting the pull request by opening an issue on Github with your question. I will do my best to answer you.

21
extern/nfd-modified/src/common.h vendored Normal file
View File

@ -0,0 +1,21 @@
/*
Native File Dialog
Internal, common across platforms
http://www.frogtoss.com/labs
*/
#ifndef _NFD_COMMON_H
#define _NFD_COMMON_H
#define NFD_MAX_STRLEN 256
#define _NFD_UNUSED(x) ((void)x)
void *NFDi_Malloc( size_t bytes );
void NFDi_Free( void *ptr );
void NFDi_SetError( const char *msg );
void NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy );
#endif

71
extern/nfd-modified/src/include/nfd.h vendored Normal file
View File

@ -0,0 +1,71 @@
/*
Native File Dialog
User API
http://www.frogtoss.com/labs
*/
#ifndef _NFD_H
#define _NFD_H
#include <stddef.h>
#include <functional>
/* denotes UTF-8 char */
typedef char nfdchar_t;
typedef std::function<void(const char*)> nfdselcallback_t;
/* opaque data structure -- see NFD_PathSet_* */
typedef struct {
nfdchar_t *buf;
size_t *indices; /* byte offsets into buf */
size_t count; /* number of indices into buf */
}nfdpathset_t;
typedef enum {
NFD_ERROR, /* programmatic error */
NFD_OKAY, /* user pressed okay, or successful return */
NFD_CANCEL /* user pressed cancel */
}nfdresult_t;
/* nfd_<targetplatform>.c */
/* single file open dialog */
nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath,
nfdselcallback_t selCallback = NULL );
/* multiple file open dialog */
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths,
nfdselcallback_t selCallback = NULL );
/* save dialog */
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath,
nfdselcallback_t selCallback = NULL );
/* select folder dialog */
nfdresult_t NFD_PickFolder( const nfdchar_t *defaultPath,
nfdchar_t **outPath);
/* nfd_common.c */
/* get last error -- set when nfdresult_t returns NFD_ERROR */
const char *NFD_GetError( void );
/* get the number of entries stored in pathSet */
size_t NFD_PathSet_GetCount( const nfdpathset_t *pathSet );
/* Get the UTF-8 path at offset index */
nfdchar_t *NFD_PathSet_GetPath( const nfdpathset_t *pathSet, size_t index );
/* Free the pathSet */
void NFD_PathSet_Free( nfdpathset_t *pathSet );
#endif

300
extern/nfd-modified/src/nfd_cocoa.mm vendored Normal file
View File

@ -0,0 +1,300 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#include <AppKit/AppKit.h>
#include "nfd.h"
#include "nfd_common.h"
// this language is a mess!
// who thought it was a good idea to combine Objective-C and C++ together
// when you could just have used C++ and call it a day!!!
//
// might as well make Objective-Ruswift++...
static NSArray *BuildAllowedFileTypes( const char *filterList )
{
// Commas and semicolons are the same thing on this platform
// like what about THIS INSTEAD!
// NSMutableArray *buildFilterList = NSMutableArray::alloc()->init();
NSMutableArray *buildFilterList = [[NSMutableArray alloc] init];
char typebuf[NFD_MAX_STRLEN] = {0};
size_t filterListLen = strlen(filterList);
char *p_typebuf = typebuf;
for ( size_t i = 0; i < filterListLen+1; ++i )
{
if ( filterList[i] == ',' || filterList[i] == ';' || filterList[i] == '\0' )
{
if (filterList[i] != '\0')
++p_typebuf;
*p_typebuf = '\0';
// or this: NSString::stringWithUTF8String(typebuf);
// buildFilterList->addObject(thisType);
// really? did you have to make this mess?!
NSString *thisType = [NSString stringWithUTF8String: typebuf];
[buildFilterList addObject:thisType];
p_typebuf = typebuf;
*p_typebuf = '\0';
}
else
{
*p_typebuf = filterList[i];
++p_typebuf;
}
}
NSArray *returnArray = [NSArray arrayWithArray:buildFilterList];
[buildFilterList release];
return returnArray;
}
static void AddFilterListToDialog( NSSavePanel *dialog, const char *filterList )
{
if ( !filterList || strlen(filterList) == 0 )
return;
NSArray *allowedFileTypes = BuildAllowedFileTypes( filterList );
if ( [allowedFileTypes count] != 0 )
{
[dialog setAllowedFileTypes:allowedFileTypes];
}
}
static void SetDefaultPath( NSSavePanel *dialog, const nfdchar_t *defaultPath )
{
if ( !defaultPath || strlen(defaultPath) == 0 )
return;
NSString *defaultPathString = [NSString stringWithUTF8String: defaultPath];
NSURL *url = [NSURL fileURLWithPath:defaultPathString isDirectory:YES];
[dialog setDirectoryURL:url];
}
/* fixme: pathset should be pathSet */
static nfdresult_t AllocPathSet( NSArray *urls, nfdpathset_t *pathset )
{
assert(pathset);
assert([urls count]);
pathset->count = (size_t)[urls count];
pathset->indices = (size_t*)NFDi_Malloc( sizeof(size_t)*pathset->count );
if ( !pathset->indices )
{
return NFD_ERROR;
}
// count the total space needed for buf
size_t bufsize = 0;
for ( NSURL *url in urls )
{
NSString *path = [url path];
bufsize += [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
}
pathset->buf = (nfdchar_t*)NFDi_Malloc( sizeof(nfdchar_t) * bufsize );
if ( !pathset->buf )
{
return NFD_ERROR;
}
// fill buf
nfdchar_t *p_buf = pathset->buf;
size_t count = 0;
for ( NSURL *url in urls )
{
NSString *path = [url path];
const nfdchar_t *utf8Path = [path UTF8String];
size_t byteLen = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
memcpy( p_buf, utf8Path, byteLen );
ptrdiff_t index = p_buf - pathset->buf;
assert( index >= 0 );
pathset->indices[count] = (size_t)index;
p_buf += byteLen;
++count;
}
return NFD_OKAY;
}
/* public */
nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath,
nfdselcallback_t selCallback )
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
NSOpenPanel *dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:NO];
// Build the filter list
AddFilterListToDialog(dialog, filterList);
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
nfdresult_t nfdResult = NFD_CANCEL;
if ( [dialog runModal] == NSModalResponseOK )
{
NSURL *url = [dialog URL];
const char *utf8Path = [[url path] UTF8String];
// byte count, not char count
size_t len = strlen(utf8Path);//NFDi_UTF8_Strlen(utf8Path);
*outPath = (nfdchar_t*)NFDi_Malloc( len+1 );
if ( !*outPath )
{
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return NFD_ERROR;
}
memcpy( *outPath, utf8Path, len+1 ); /* copy null term */
nfdResult = NFD_OKAY;
}
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return nfdResult;
}
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths,
nfdselcallback_t selCallback )
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
NSOpenPanel *dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:YES];
// Build the fiter list.
AddFilterListToDialog(dialog, filterList);
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
nfdresult_t nfdResult = NFD_CANCEL;
if ( [dialog runModal] == NSModalResponseOK )
{
NSArray *urls = [dialog URLs];
if ( [urls count] == 0 )
{
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return NFD_CANCEL;
}
if ( AllocPathSet( urls, outPaths ) == NFD_ERROR )
{
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return NFD_ERROR;
}
nfdResult = NFD_OKAY;
}
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return nfdResult;
}
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath,
nfdselcallback_t selCallback )
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
NSSavePanel *dialog = [NSSavePanel savePanel];
[dialog setExtensionHidden:NO];
// Build the filter list.
AddFilterListToDialog(dialog, filterList);
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
nfdresult_t nfdResult = NFD_CANCEL;
if ( [dialog runModal] == NSModalResponseOK )
{
NSURL *url = [dialog URL];
const char *utf8Path = [[url path] UTF8String];
size_t byteLen = [url.path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
*outPath = (nfdchar_t*)NFDi_Malloc( byteLen );
if ( !*outPath )
{
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return NFD_ERROR;
}
memcpy( *outPath, utf8Path, byteLen );
nfdResult = NFD_OKAY;
}
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return nfdResult;
}
nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
nfdchar_t **outPath)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
NSOpenPanel *dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:NO];
[dialog setCanChooseDirectories:YES];
[dialog setCanCreateDirectories:YES];
[dialog setCanChooseFiles:NO];
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
nfdresult_t nfdResult = NFD_CANCEL;
if ( [dialog runModal] == NSModalResponseOK )
{
NSURL *url = [dialog URL];
const char *utf8Path = [[url path] UTF8String];
// byte count, not char count
size_t len = strlen(utf8Path);//NFDi_UTF8_Strlen(utf8Path);
*outPath = (nfdchar_t*)NFDi_Malloc( len+1 );
if ( !*outPath )
{
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return NFD_ERROR;
}
memcpy( *outPath, utf8Path, len+1 ); /* copy null term */
nfdResult = NFD_OKAY;
}
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return nfdResult;
}

142
extern/nfd-modified/src/nfd_common.cpp vendored Normal file
View File

@ -0,0 +1,142 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "nfd_common.h"
static char g_errorstr[NFD_MAX_STRLEN] = {0};
/* public routines */
const char *NFD_GetError( void )
{
return g_errorstr;
}
size_t NFD_PathSet_GetCount( const nfdpathset_t *pathset )
{
assert(pathset);
return pathset->count;
}
nfdchar_t *NFD_PathSet_GetPath( const nfdpathset_t *pathset, size_t num )
{
assert(pathset);
assert(num < pathset->count);
return pathset->buf + pathset->indices[num];
}
void NFD_PathSet_Free( nfdpathset_t *pathset )
{
assert(pathset);
NFDi_Free( pathset->indices );
NFDi_Free( pathset->buf );
}
/* internal routines */
void *NFDi_Malloc( size_t bytes )
{
void *ptr = malloc(bytes);
if ( !ptr )
NFDi_SetError("NFDi_Malloc failed.");
return ptr;
}
void NFDi_Free( void *ptr )
{
assert(ptr);
free(ptr);
}
void NFDi_SetError( const char *msg )
{
int bTruncate = NFDi_SafeStrncpy( g_errorstr, msg, NFD_MAX_STRLEN );
assert( !bTruncate ); _NFD_UNUSED(bTruncate);
}
int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy )
{
size_t n = maxCopy;
char *d = dst;
assert( src );
assert( dst );
while ( n > 0 && *src != '\0' )
{
*d++ = *src++;
--n;
}
/* Truncation case -
terminate string and return true */
if ( n == 0 )
{
dst[maxCopy-1] = '\0';
return 1;
}
/* No truncation. Append a single NULL and return. */
*d = '\0';
return 0;
}
/* adapted from microutf8 */
int32_t NFDi_UTF8_Strlen( const nfdchar_t *str )
{
/* This function doesn't properly check validity of UTF-8 character
sequence, it is supposed to use only with valid UTF-8 strings. */
int32_t character_count = 0;
int32_t i = 0; /* Counter used to iterate over string. */
nfdchar_t maybe_bom[4];
/* If there is UTF-8 BOM ignore it. */
if (strlen(str) > 2)
{
strncpy(maybe_bom, str, 3);
maybe_bom[3] = 0;
if (strcmp(maybe_bom, (nfdchar_t*)NFD_UTF8_BOM) == 0)
i += 3;
}
while(str[i])
{
if (str[i] >> 7 == 0)
{
/* If bit pattern begins with 0 we have ascii character. */
++character_count;
}
else if (str[i] >> 6 == 3)
{
/* If bit pattern begins with 11 it is beginning of UTF-8 byte sequence. */
++character_count;
}
else if (str[i] >> 6 == 2)
; /* If bit pattern begins with 10 it is middle of utf-8 byte sequence. */
else
{
/* In any other case this is not valid UTF-8. */
return -1;
}
++i;
}
return character_count;
}
int NFDi_IsFilterSegmentChar( char ch )
{
return (ch==','||ch==';'||ch=='\0');
}

31
extern/nfd-modified/src/nfd_common.h vendored Normal file
View File

@ -0,0 +1,31 @@
/*
Native File Dialog
Internal, common across platforms
http://www.frogtoss.com/labs
*/
#ifndef _NFD_COMMON_H
#define _NFD_COMMON_H
#include "nfd.h"
#include <stdint.h>
#define NFD_MAX_STRLEN 256
#define _NFD_UNUSED(x) ((void)x)
#define NFD_UTF8_BOM "\xEF\xBB\xBF"
void *NFDi_Malloc( size_t bytes );
void NFDi_Free( void *ptr );
void NFDi_SetError( const char *msg );
int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy );
int32_t NFDi_UTF8_Strlen( const nfdchar_t *str );
int NFDi_IsFilterSegmentChar( char ch );
#endif

379
extern/nfd-modified/src/nfd_gtk.cpp vendored Normal file
View File

@ -0,0 +1,379 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <gtk/gtk.h>
#include "nfd.h"
#include "nfd_common.h"
const char INIT_FAIL_MSG[] = "gtk_init_check failed to initilaize GTK+";
static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize )
{
const char SEP[] = ", ";
size_t len = strlen(filterName);
if ( len != 0 )
{
strncat( filterName, SEP, bufsize - len - 1 );
len += strlen(SEP);
}
strncat( filterName, typebuf, bufsize - len - 1 );
}
static void AddFiltersToDialog( GtkWidget *dialog, const char *filterList )
{
GtkFileFilter *filter;
char typebuf[NFD_MAX_STRLEN] = {0};
const char *p_filterList = filterList;
char *p_typebuf = typebuf;
char filterName[NFD_MAX_STRLEN] = {0};
if ( !filterList || strlen(filterList) == 0 )
return;
filter = gtk_file_filter_new();
while ( 1 )
{
if ( NFDi_IsFilterSegmentChar(*p_filterList) )
{
char typebufWildcard[NFD_MAX_STRLEN];
/* add another type to the filter */
assert( strlen(typebuf) > 0 );
assert( strlen(typebuf) < NFD_MAX_STRLEN-1 );
snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf );
AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN );
gtk_file_filter_add_pattern( filter, typebufWildcard );
p_typebuf = typebuf;
memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN );
}
if ( *p_filterList == ';' || *p_filterList == '\0' )
{
/* end of filter -- add it to the dialog */
gtk_file_filter_set_name( filter, filterName );
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
filterName[0] = '\0';
if ( *p_filterList == '\0' )
break;
filter = gtk_file_filter_new();
}
if ( !NFDi_IsFilterSegmentChar( *p_filterList ) )
{
*p_typebuf = *p_filterList;
p_typebuf++;
}
p_filterList++;
}
/* always append a wildcard option to the end*/
filter = gtk_file_filter_new();
gtk_file_filter_set_name( filter, "*.*" );
gtk_file_filter_add_pattern( filter, "*" );
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
}
static void SetDefaultPath( GtkWidget *dialog, const char *defaultPath )
{
if ( !defaultPath || strlen(defaultPath) == 0 )
return;
/* GTK+ manual recommends not specifically setting the default path.
We do it anyway in order to be consistent across platforms.
If consistency with the native OS is preferred, this is the line
to comment out. -ml */
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), defaultPath );
}
static nfdresult_t AllocPathSet( GSList *fileList, nfdpathset_t *pathSet )
{
size_t bufSize = 0;
GSList *node;
nfdchar_t *p_buf;
size_t count = 0;
assert(fileList);
assert(pathSet);
pathSet->count = (size_t)g_slist_length( fileList );
assert( pathSet->count > 0 );
pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count );
if ( !pathSet->indices )
{
return NFD_ERROR;
}
/* count the total space needed for buf */
for ( node = fileList; node; node = node->next )
{
assert(node->data);
bufSize += strlen( (const gchar*)node->data ) + 1;
}
pathSet->buf = NFDi_Malloc( sizeof(nfdchar_t) * bufSize );
/* fill buf */
p_buf = pathSet->buf;
for ( node = fileList; node; node = node->next )
{
nfdchar_t *path = (nfdchar_t*)(node->data);
size_t byteLen = strlen(path)+1;
ptrdiff_t index;
memcpy( p_buf, path, byteLen );
g_free(node->data);
index = p_buf - pathSet->buf;
assert( index >= 0 );
pathSet->indices[count] = (size_t)index;
p_buf += byteLen;
++count;
}
g_slist_free( fileList );
return NFD_OKAY;
}
static void WaitForCleanup(void)
{
while (gtk_events_pending())
gtk_main_iteration();
}
/* public */
nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
GtkWidget *dialog;
nfdresult_t result;
if ( !gtk_init_check( NULL, NULL ) )
{
NFDi_SetError(INIT_FAIL_MSG);
return NFD_ERROR;
}
dialog = gtk_file_chooser_dialog_new( "Open File",
NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT,
NULL );
/* Build the filter list */
AddFiltersToDialog(dialog, filterList);
/* Set the default path */
SetDefaultPath(dialog, defaultPath);
result = NFD_CANCEL;
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
{
char *filename;
filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
{
size_t len = strlen(filename);
*outPath = NFDi_Malloc( len + 1 );
memcpy( *outPath, filename, len + 1 );
if ( !*outPath )
{
g_free( filename );
gtk_widget_destroy(dialog);
return NFD_ERROR;
}
}
g_free( filename );
result = NFD_OKAY;
}
WaitForCleanup();
gtk_widget_destroy(dialog);
WaitForCleanup();
return result;
}
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths )
{
GtkWidget *dialog;
nfdresult_t result;
if ( !gtk_init_check( NULL, NULL ) )
{
NFDi_SetError(INIT_FAIL_MSG);
return NFD_ERROR;
}
dialog = gtk_file_chooser_dialog_new( "Open Files",
NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT,
NULL );
gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), TRUE );
/* Build the filter list */
AddFiltersToDialog(dialog, filterList);
/* Set the default path */
SetDefaultPath(dialog, defaultPath);
result = NFD_CANCEL;
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
{
GSList *fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) );
if ( AllocPathSet( fileList, outPaths ) == NFD_ERROR )
{
gtk_widget_destroy(dialog);
return NFD_ERROR;
}
result = NFD_OKAY;
}
WaitForCleanup();
gtk_widget_destroy(dialog);
WaitForCleanup();
return result;
}
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
GtkWidget *dialog;
nfdresult_t result;
if ( !gtk_init_check( NULL, NULL ) )
{
NFDi_SetError(INIT_FAIL_MSG);
return NFD_ERROR;
}
dialog = gtk_file_chooser_dialog_new( "Save File",
NULL,
GTK_FILE_CHOOSER_ACTION_SAVE,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Save", GTK_RESPONSE_ACCEPT,
NULL );
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
/* Build the filter list */
AddFiltersToDialog(dialog, filterList);
/* Set the default path */
SetDefaultPath(dialog, defaultPath);
result = NFD_CANCEL;
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
{
char *filename;
filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
{
size_t len = strlen(filename);
*outPath = NFDi_Malloc( len + 1 );
memcpy( *outPath, filename, len + 1 );
if ( !*outPath )
{
g_free( filename );
gtk_widget_destroy(dialog);
return NFD_ERROR;
}
}
g_free(filename);
result = NFD_OKAY;
}
WaitForCleanup();
gtk_widget_destroy(dialog);
WaitForCleanup();
return result;
}
nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
nfdchar_t **outPath)
{
GtkWidget *dialog;
nfdresult_t result;
if (!gtk_init_check(NULL, NULL))
{
NFDi_SetError(INIT_FAIL_MSG);
return NFD_ERROR;
}
dialog = gtk_file_chooser_dialog_new( "Select folder",
NULL,
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Select", GTK_RESPONSE_ACCEPT,
NULL );
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
/* Set the default path */
SetDefaultPath(dialog, defaultPath);
result = NFD_CANCEL;
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
{
char *filename;
filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
{
size_t len = strlen(filename);
*outPath = NFDi_Malloc( len + 1 );
memcpy( *outPath, filename, len + 1 );
if ( !*outPath )
{
g_free( filename );
gtk_widget_destroy(dialog);
return NFD_ERROR;
}
}
g_free(filename);
result = NFD_OKAY;
}
WaitForCleanup();
gtk_widget_destroy(dialog);
WaitForCleanup();
return result;
}

848
extern/nfd-modified/src/nfd_win.cpp vendored Normal file
View File

@ -0,0 +1,848 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#ifdef __MINGW32__
// Explicitly setting NTDDI version, this is necessary for the MinGW compiler
#define NTDDI_VERSION NTDDI_VISTA
#define _WIN32_WINNT _WIN32_WINNT_VISTA
#endif
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
/* only locally define UNICODE in this compilation unit */
#ifndef UNICODE
#define UNICODE
#endif
#include <wchar.h>
#include <stdio.h>
#include <assert.h>
#include <windows.h>
#include <shobjidl.h>
#include "nfd_common.h"
// hack I know
#include "../../../src/utfutils.h"
class NFDWinEvents: public IFileDialogEvents {
nfdselcallback_t selCallback;
size_t refCount;
virtual ~NFDWinEvents() {
}
public:
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) {
printf("QueryInterface called DAMN IT\n");
*ppv=NULL;
return E_NOTIMPL;
}
IFACEMETHODIMP_(ULONG) AddRef() {
printf("AddRef() called\n");
return InterlockedIncrement(&refCount);
}
IFACEMETHODIMP_(ULONG) Release() {
printf("Release() called\n");
LONG ret=InterlockedDecrement(&refCount);
if (ret==0) {
printf("Destroying the final object.\n");
delete this;
}
return ret;
}
IFACEMETHODIMP OnFileOk(IFileDialog*) { return E_NOTIMPL; }
IFACEMETHODIMP OnFolderChange(IFileDialog*) { return E_NOTIMPL; }
IFACEMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return E_NOTIMPL; }
IFACEMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) { return E_NOTIMPL; }
IFACEMETHODIMP OnShareViolation(IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) { return E_NOTIMPL; }
IFACEMETHODIMP OnTypeChange(IFileDialog*) { return E_NOTIMPL; }
IFACEMETHODIMP OnSelectionChange(IFileDialog* dialog) {
// Get the file name
::IShellItem *shellItem(NULL);
HRESULT result = dialog->GetCurrentSelection(&shellItem);
if ( !SUCCEEDED(result) )
{
printf("failure!\n");
return S_OK;
}
wchar_t *filePath(NULL);
result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
if ( !SUCCEEDED(result) )
{
printf("GDN failure!\n");
shellItem->Release();
return S_OK;
}
std::string utf8FilePath=utf16To8(filePath);
if (selCallback!=NULL) selCallback(utf8FilePath.c_str());
printf("I got you for a value of %s\n",utf8FilePath.c_str());
shellItem->Release();
return S_OK;
}
NFDWinEvents(nfdselcallback_t callback):
selCallback(callback),
refCount(1) {
}
};
#define COM_INITFLAGS ::COINIT_APARTMENTTHREADED | ::COINIT_DISABLE_OLE1DDE
static BOOL COMIsInitialized(HRESULT coResult)
{
if (coResult == RPC_E_CHANGED_MODE)
{
// If COM was previously initialized with different init flags,
// NFD still needs to operate. Eat this warning.
return TRUE;
}
return SUCCEEDED(coResult);
}
static HRESULT COMInit(void)
{
return ::CoInitializeEx(NULL, COM_INITFLAGS);
}
static void COMUninit(HRESULT coResult)
{
// do not uninitialize if RPC_E_CHANGED_MODE occurred -- this
// case does not refcount COM.
if (SUCCEEDED(coResult))
::CoUninitialize();
}
// allocs the space in outPath -- call free()
static void CopyWCharToNFDChar( const wchar_t *inStr, nfdchar_t **outStr )
{
int inStrCharacterCount = static_cast<int>(wcslen(inStr));
int bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
inStr, inStrCharacterCount,
NULL, 0, NULL, NULL );
assert( bytesNeeded );
bytesNeeded += 1;
*outStr = (nfdchar_t*)NFDi_Malloc( bytesNeeded );
if ( !*outStr )
return;
int bytesWritten = WideCharToMultiByte( CP_UTF8, 0,
inStr, -1,
*outStr, bytesNeeded,
NULL, NULL );
assert( bytesWritten ); _NFD_UNUSED( bytesWritten );
}
/* includes NULL terminator byte in return */
static size_t GetUTF8ByteCountForWChar( const wchar_t *str )
{
size_t bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
str, -1,
NULL, 0, NULL, NULL );
assert( bytesNeeded );
return bytesNeeded+1;
}
// write to outPtr -- no free() necessary.
static int CopyWCharToExistingNFDCharBuffer( const wchar_t *inStr, nfdchar_t *outPtr )
{
int bytesNeeded = static_cast<int>(GetUTF8ByteCountForWChar( inStr ));
/* invocation copies null term */
int bytesWritten = WideCharToMultiByte( CP_UTF8, 0,
inStr, -1,
outPtr, bytesNeeded,
NULL, 0 );
assert( bytesWritten );
return bytesWritten;
}
// allocs the space in outStr -- call free()
static void CopyNFDCharToWChar( const nfdchar_t *inStr, wchar_t **outStr )
{
int inStrByteCount = static_cast<int>(strlen(inStr));
int charsNeeded = MultiByteToWideChar(CP_UTF8, 0,
inStr, inStrByteCount,
NULL, 0 );
assert( charsNeeded );
assert( !*outStr );
charsNeeded += 1; // terminator
*outStr = (wchar_t*)NFDi_Malloc( charsNeeded * sizeof(wchar_t) );
if ( !*outStr )
return;
int ret = MultiByteToWideChar(CP_UTF8, 0,
inStr, inStrByteCount,
*outStr, charsNeeded);
(*outStr)[charsNeeded-1] = '\0';
#ifdef _DEBUG
int inStrCharacterCount = static_cast<int>(NFDi_UTF8_Strlen(inStr));
assert( ret == inStrCharacterCount );
#else
_NFD_UNUSED(ret);
#endif
}
/* ext is in format "jpg", no wildcards or separators */
static int AppendExtensionToSpecBuf( const char *ext, char *specBuf, size_t specBufLen )
{
const char SEP[] = ";";
assert( specBufLen > strlen(ext)+3 );
if ( strlen(specBuf) > 0 )
{
strncat( specBuf, SEP, specBufLen - strlen(specBuf) - 1 );
specBufLen += strlen(SEP);
}
char extWildcard[NFD_MAX_STRLEN];
int bytesWritten = sprintf_s( extWildcard, NFD_MAX_STRLEN, "*.%s", ext );
assert( bytesWritten == (int)(strlen(ext)+2) );
_NFD_UNUSED(bytesWritten);
strncat( specBuf, extWildcard, specBufLen - strlen(specBuf) - 1 );
return NFD_OKAY;
}
static nfdresult_t AddFiltersToDialog( ::IFileDialog *fileOpenDialog, const char *filterList )
{
const wchar_t WILDCARD[] = L"*.*";
if ( !filterList || strlen(filterList) == 0 )
return NFD_OKAY;
// Count rows to alloc
UINT filterCount = 1; /* guaranteed to have one filter on a correct, non-empty parse */
const char *p_filterList;
for ( p_filterList = filterList; *p_filterList; ++p_filterList )
{
if ( *p_filterList == ';' )
++filterCount;
}
assert(filterCount);
if ( !filterCount )
{
NFDi_SetError("Error parsing filters.");
return NFD_ERROR;
}
/* filterCount plus 1 because we hardcode the *.* wildcard after the while loop */
COMDLG_FILTERSPEC *specList = (COMDLG_FILTERSPEC*)NFDi_Malloc( sizeof(COMDLG_FILTERSPEC) * ((size_t)filterCount + 1) );
if ( !specList )
{
return NFD_ERROR;
}
for (UINT i = 0; i < filterCount+1; ++i )
{
specList[i].pszName = NULL;
specList[i].pszSpec = NULL;
}
size_t specIdx = 0;
p_filterList = filterList;
char typebuf[NFD_MAX_STRLEN] = {0}; /* one per comma or semicolon */
char *p_typebuf = typebuf;
char specbuf[NFD_MAX_STRLEN] = {0}; /* one per semicolon */
while ( 1 )
{
if ( NFDi_IsFilterSegmentChar(*p_filterList) )
{
/* append a type to the specbuf (pending filter) */
AppendExtensionToSpecBuf( typebuf, specbuf, NFD_MAX_STRLEN );
p_typebuf = typebuf;
memset( typebuf, 0, sizeof(char)*NFD_MAX_STRLEN );
}
if ( *p_filterList == ';' || *p_filterList == '\0' )
{
/* end of filter -- add it to specList */
CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszName );
CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszSpec );
memset( specbuf, 0, sizeof(char)*NFD_MAX_STRLEN );
++specIdx;
if ( specIdx == filterCount )
break;
}
if ( !NFDi_IsFilterSegmentChar( *p_filterList ))
{
*p_typebuf = *p_filterList;
++p_typebuf;
}
++p_filterList;
}
/* Add wildcard */
specList[specIdx].pszSpec = WILDCARD;
specList[specIdx].pszName = WILDCARD;
fileOpenDialog->SetFileTypes( filterCount+1, specList );
/* free speclist */
for ( size_t i = 0; i < filterCount; ++i )
{
NFDi_Free( (void*)specList[i].pszSpec );
}
NFDi_Free( specList );
return NFD_OKAY;
}
static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *pathSet )
{
const char ERRORMSG[] = "Error allocating pathset.";
assert(shellItems);
assert(pathSet);
// How many items in shellItems?
DWORD numShellItems;
HRESULT result = shellItems->GetCount(&numShellItems);
if ( !SUCCEEDED(result) )
{
NFDi_SetError(ERRORMSG);
return NFD_ERROR;
}
pathSet->count = static_cast<size_t>(numShellItems);
assert( pathSet->count > 0 );
pathSet->indices = (size_t*)NFDi_Malloc( sizeof(size_t)*pathSet->count );
if ( !pathSet->indices )
{
return NFD_ERROR;
}
/* count the total bytes needed for buf */
size_t bufSize = 0;
for ( DWORD i = 0; i < numShellItems; ++i )
{
::IShellItem *shellItem;
result = shellItems->GetItemAt(i, &shellItem);
if ( !SUCCEEDED(result) )
{
NFDi_SetError(ERRORMSG);
return NFD_ERROR;
}
// Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it.
SFGAOF attribs;
result = shellItem->GetAttributes( SFGAO_FILESYSTEM, &attribs );
if ( !SUCCEEDED(result) )
{
NFDi_SetError(ERRORMSG);
return NFD_ERROR;
}
if ( !(attribs & SFGAO_FILESYSTEM) )
continue;
LPWSTR name;
shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name);
// Calculate length of name with UTF-8 encoding
bufSize += GetUTF8ByteCountForWChar( name );
CoTaskMemFree(name);
}
assert(bufSize);
pathSet->buf = (nfdchar_t*)NFDi_Malloc( sizeof(nfdchar_t) * bufSize );
memset( pathSet->buf, 0, sizeof(nfdchar_t) * bufSize );
/* fill buf */
nfdchar_t *p_buf = pathSet->buf;
for (DWORD i = 0; i < numShellItems; ++i )
{
::IShellItem *shellItem;
result = shellItems->GetItemAt(i, &shellItem);
if ( !SUCCEEDED(result) )
{
NFDi_SetError(ERRORMSG);
return NFD_ERROR;
}
// Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it.
SFGAOF attribs;
result = shellItem->GetAttributes( SFGAO_FILESYSTEM, &attribs );
if ( !SUCCEEDED(result) )
{
NFDi_SetError(ERRORMSG);
return NFD_ERROR;
}
if ( !(attribs & SFGAO_FILESYSTEM) )
continue;
LPWSTR name;
shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name);
int bytesWritten = CopyWCharToExistingNFDCharBuffer(name, p_buf);
CoTaskMemFree(name);
ptrdiff_t index = p_buf - pathSet->buf;
assert( index >= 0 );
pathSet->indices[i] = static_cast<size_t>(index);
p_buf += bytesWritten;
}
return NFD_OKAY;
}
static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath )
{
if ( !defaultPath || strlen(defaultPath) == 0 )
return NFD_OKAY;
wchar_t *defaultPathW = {0};
CopyNFDCharToWChar( defaultPath, &defaultPathW );
IShellItem *folder;
HRESULT result = SHCreateItemFromParsingName( defaultPathW, NULL, IID_PPV_ARGS(&folder) );
// Valid non results.
if ( result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || result == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE) )
{
NFDi_Free( defaultPathW );
return NFD_OKAY;
}
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Error creating ShellItem");
NFDi_Free( defaultPathW );
return NFD_ERROR;
}
// Could also call SetDefaultFolder(), but this guarantees defaultPath -- more consistency across API.
dialog->SetFolder( folder );
NFDi_Free( defaultPathW );
folder->Release();
return NFD_OKAY;
}
/* public */
nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath,
nfdselcallback_t selCallback )
{
nfdresult_t nfdResult = NFD_ERROR;
NFDWinEvents* winEvents;
bool hasEvents=true;
DWORD eventID=0;
HRESULT coResult = COMInit();
if (!COMIsInitialized(coResult))
{
NFDi_SetError("Could not initialize COM.");
return nfdResult;
}
// Create dialog
::IFileOpenDialog *fileOpenDialog(NULL);
HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
CLSCTX_ALL, ::IID_IFileOpenDialog,
reinterpret_cast<void**>(&fileOpenDialog) );
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not create dialog.");
goto end;
}
// Build the filter list
if ( !AddFiltersToDialog( fileOpenDialog, filterList ) )
{
goto end;
}
// Set the default path
if ( !SetDefaultPath( fileOpenDialog, defaultPath ) )
{
goto end;
}
// Pass the callback
winEvents=new NFDWinEvents(selCallback);
if ( !SUCCEEDED(fileOpenDialog->Advise(winEvents,&eventID)) ) {
// error... ignore
hasEvents=false;
winEvents->Release();
} else {
winEvents->Release();
}
// Show the dialog.
// TODO: pass the Furnace window here
result = fileOpenDialog->Show(NULL);
if ( SUCCEEDED(result) )
{
// Get the file name
::IShellItem *shellItem(NULL);
result = fileOpenDialog->GetResult(&shellItem);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get shell item from dialog.");
goto end;
}
wchar_t *filePath(NULL);
result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get file path for selected.");
shellItem->Release();
goto end;
}
CopyWCharToNFDChar( filePath, outPath );
CoTaskMemFree(filePath);
if ( !*outPath )
{
/* error is malloc-based, error message would be redundant */
shellItem->Release();
goto end;
}
nfdResult = NFD_OKAY;
shellItem->Release();
}
else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
{
nfdResult = NFD_CANCEL;
}
else
{
NFDi_SetError("File dialog box show failed.");
nfdResult = NFD_ERROR;
}
end:
if (fileOpenDialog) {
if (hasEvents) {
fileOpenDialog->Unadvise(eventID);
}
fileOpenDialog->Release();
}
COMUninit(coResult);
return nfdResult;
}
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths,
nfdselcallback_t selCallback )
{
nfdresult_t nfdResult = NFD_ERROR;
HRESULT coResult = COMInit();
if (!COMIsInitialized(coResult))
{
NFDi_SetError("Could not initialize COM.");
return nfdResult;
}
// Create dialog
::IFileOpenDialog *fileOpenDialog(NULL);
HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
CLSCTX_ALL, ::IID_IFileOpenDialog,
reinterpret_cast<void**>(&fileOpenDialog) );
if ( !SUCCEEDED(result) )
{
fileOpenDialog = NULL;
NFDi_SetError("Could not create dialog.");
goto end;
}
// Build the filter list
if ( !AddFiltersToDialog( fileOpenDialog, filterList ) )
{
goto end;
}
// Set the default path
if ( !SetDefaultPath( fileOpenDialog, defaultPath ) )
{
goto end;
}
// Set a flag for multiple options
DWORD dwFlags;
result = fileOpenDialog->GetOptions(&dwFlags);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get options.");
goto end;
}
result = fileOpenDialog->SetOptions(dwFlags | FOS_ALLOWMULTISELECT);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not set options.");
goto end;
}
// Show the dialog.
result = fileOpenDialog->Show(NULL);
if ( SUCCEEDED(result) )
{
IShellItemArray *shellItems;
result = fileOpenDialog->GetResults( &shellItems );
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get shell items.");
goto end;
}
if ( AllocPathSet( shellItems, outPaths ) == NFD_ERROR )
{
shellItems->Release();
goto end;
}
shellItems->Release();
nfdResult = NFD_OKAY;
}
else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
{
nfdResult = NFD_CANCEL;
}
else
{
NFDi_SetError("File dialog box show failed.");
nfdResult = NFD_ERROR;
}
end:
if ( fileOpenDialog )
fileOpenDialog->Release();
COMUninit(coResult);
return nfdResult;
}
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath,
nfdselcallback_t selCallback )
{
nfdresult_t nfdResult = NFD_ERROR;
HRESULT coResult = COMInit();
if (!COMIsInitialized(coResult))
{
NFDi_SetError("Could not initialize COM.");
return nfdResult;
}
// Create dialog
::IFileSaveDialog *fileSaveDialog(NULL);
HRESULT result = ::CoCreateInstance(::CLSID_FileSaveDialog, NULL,
CLSCTX_ALL, ::IID_IFileSaveDialog,
reinterpret_cast<void**>(&fileSaveDialog) );
if ( !SUCCEEDED(result) )
{
fileSaveDialog = NULL;
NFDi_SetError("Could not create dialog.");
goto end;
}
// Build the filter list
if ( !AddFiltersToDialog( fileSaveDialog, filterList ) )
{
goto end;
}
// Set the default path
if ( !SetDefaultPath( fileSaveDialog, defaultPath ) )
{
goto end;
}
// Show the dialog.
result = fileSaveDialog->Show(NULL);
if ( SUCCEEDED(result) )
{
// Get the file name
::IShellItem *shellItem;
result = fileSaveDialog->GetResult(&shellItem);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get shell item from dialog.");
goto end;
}
wchar_t *filePath(NULL);
result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
if ( !SUCCEEDED(result) )
{
shellItem->Release();
NFDi_SetError("Could not get file path for selected.");
goto end;
}
CopyWCharToNFDChar( filePath, outPath );
CoTaskMemFree(filePath);
if ( !*outPath )
{
/* error is malloc-based, error message would be redundant */
shellItem->Release();
goto end;
}
nfdResult = NFD_OKAY;
shellItem->Release();
}
else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
{
nfdResult = NFD_CANCEL;
}
else
{
NFDi_SetError("File dialog box show failed.");
nfdResult = NFD_ERROR;
}
end:
if ( fileSaveDialog )
fileSaveDialog->Release();
COMUninit(coResult);
return nfdResult;
}
nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
nfdchar_t **outPath)
{
nfdresult_t nfdResult = NFD_ERROR;
DWORD dwOptions = 0;
HRESULT coResult = COMInit();
if (!COMIsInitialized(coResult))
{
NFDi_SetError("CoInitializeEx failed.");
return nfdResult;
}
// Create dialog
::IFileOpenDialog *fileDialog(NULL);
HRESULT result = CoCreateInstance(CLSID_FileOpenDialog,
NULL,
CLSCTX_ALL,
IID_PPV_ARGS(&fileDialog));
if ( !SUCCEEDED(result) )
{
NFDi_SetError("CoCreateInstance for CLSID_FileOpenDialog failed.");
goto end;
}
// Set the default path
if (SetDefaultPath(fileDialog, defaultPath) != NFD_OKAY)
{
NFDi_SetError("SetDefaultPath failed.");
goto end;
}
// Get the dialogs options
if (!SUCCEEDED(fileDialog->GetOptions(&dwOptions)))
{
NFDi_SetError("GetOptions for IFileDialog failed.");
goto end;
}
// Add in FOS_PICKFOLDERS which hides files and only allows selection of folders
if (!SUCCEEDED(fileDialog->SetOptions(dwOptions | FOS_PICKFOLDERS)))
{
NFDi_SetError("SetOptions for IFileDialog failed.");
goto end;
}
// Show the dialog to the user
result = fileDialog->Show(NULL);
if ( SUCCEEDED(result) )
{
// Get the folder name
::IShellItem *shellItem(NULL);
result = fileDialog->GetResult(&shellItem);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get file path for selected.");
shellItem->Release();
goto end;
}
wchar_t *path = NULL;
result = shellItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &path);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("GetDisplayName for IShellItem failed.");
shellItem->Release();
goto end;
}
CopyWCharToNFDChar(path, outPath);
CoTaskMemFree(path);
if ( !*outPath )
{
shellItem->Release();
goto end;
}
nfdResult = NFD_OKAY;
shellItem->Release();
}
else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
{
nfdResult = NFD_CANCEL;
}
else
{
NFDi_SetError("Show for IFileDialog failed.");
nfdResult = NFD_ERROR;
}
end:
if (fileDialog)
fileDialog->Release();
COMUninit(coResult);
return nfdResult;
}

307
extern/nfd-modified/src/nfd_zenity.cpp vendored Normal file
View File

@ -0,0 +1,307 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include "nfd.h"
#include "nfd_common.h"
#define SIMPLE_EXEC_IMPLEMENTATION
#include "simple_exec.h"
const char NO_ZENITY_MSG[] = "zenity not installed";
static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize )
{
size_t len = strlen(filterName);
if( len > 0 )
strncat( filterName, " *.", bufsize - len - 1 );
else
strncat( filterName, "--file-filter=*.", bufsize - len - 1 );
len = strlen(filterName);
strncat( filterName, typebuf, bufsize - len - 1 );
}
static void AddFiltersToCommandArgs(char** commandArgs, int commandArgsLen, const char *filterList )
{
char typebuf[NFD_MAX_STRLEN] = {0};
const char *p_filterList = filterList;
char *p_typebuf = typebuf;
char filterName[NFD_MAX_STRLEN] = {0};
int i;
if ( !filterList || strlen(filterList) == 0 )
return;
while ( 1 )
{
if ( NFDi_IsFilterSegmentChar(*p_filterList) )
{
char typebufWildcard[NFD_MAX_STRLEN];
/* add another type to the filter */
assert( strlen(typebuf) > 0 );
assert( strlen(typebuf) < NFD_MAX_STRLEN-1 );
snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf );
AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN );
p_typebuf = typebuf;
memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN );
}
if ( *p_filterList == ';' || *p_filterList == '\0' )
{
/* end of filter -- add it to the dialog */
for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++);
commandArgs[i] = strdup(filterName);
filterName[0] = '\0';
if ( *p_filterList == '\0' )
break;
}
if ( !NFDi_IsFilterSegmentChar( *p_filterList ) )
{
*p_typebuf = *p_filterList;
p_typebuf++;
}
p_filterList++;
}
/* always append a wildcard option to the end*/
for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++);
commandArgs[i] = strdup("--file-filter=*.*");
}
static nfdresult_t ZenityCommon(char** command, int commandLen, const char* defaultPath, const char* filterList, char** stdOut)
{
if(defaultPath != NULL)
{
char* prefix = "--filename=";
int len = strlen(prefix) + strlen(defaultPath) + 1;
char* tmp = (char*) calloc(len, 1);
strcat(tmp, prefix);
strcat(tmp, defaultPath);
int i;
for(i = 0; command[i] != NULL && i < commandLen; i++);
command[i] = tmp;
}
AddFiltersToCommandArgs(command, commandLen, filterList);
int byteCount = 0;
int exitCode = 0;
int processInvokeError = runCommandArray(stdOut, &byteCount, &exitCode, 0, command);
for(int i = 0; command[i] != NULL && i < commandLen; i++)
free(command[i]);
nfdresult_t result = NFD_OKAY;
if(processInvokeError == COMMAND_NOT_FOUND)
{
NFDi_SetError(NO_ZENITY_MSG);
result = NFD_ERROR;
}
else
{
if(exitCode == 1)
result = NFD_CANCEL;
}
return result;
}
static nfdresult_t AllocPathSet(char* zenityList, nfdpathset_t *pathSet )
{
assert(zenityList);
assert(pathSet);
size_t len = strlen(zenityList) + 1;
pathSet->buf = NFDi_Malloc(len);
int numEntries = 1;
for(size_t i = 0; i < len; i++)
{
char ch = zenityList[i];
if(ch == '|')
{
numEntries++;
ch = '\0';
}
pathSet->buf[i] = ch;
}
pathSet->count = numEntries;
assert( pathSet->count > 0 );
pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count );
int entry = 0;
pathSet->indices[0] = 0;
for(size_t i = 0; i < len; i++)
{
char ch = zenityList[i];
if(ch == '|')
{
entry++;
pathSet->indices[entry] = i + 1;
}
}
return NFD_OKAY;
}
/* public */
nfdresult_t NFD_OpenDialog( const char *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
int commandLen = 100;
char* command[commandLen];
memset(command, 0, commandLen * sizeof(char*));
command[0] = strdup("zenity");
command[1] = strdup("--file-selection");
command[2] = strdup("--title=Open File");
char* stdOut = NULL;
nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut);
if(stdOut != NULL)
{
size_t len = strlen(stdOut);
*outPath = NFDi_Malloc(len);
memcpy(*outPath, stdOut, len);
(*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator
free(stdOut);
}
else
{
*outPath = NULL;
}
return result;
}
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths )
{
int commandLen = 100;
char* command[commandLen];
memset(command, 0, commandLen * sizeof(char*));
command[0] = strdup("zenity");
command[1] = strdup("--file-selection");
command[2] = strdup("--title=Open Files");
command[3] = strdup("--multiple");
char* stdOut = NULL;
nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut);
if(stdOut != NULL)
{
size_t len = strlen(stdOut);
stdOut[len-1] = '\0'; // remove trailing newline
if ( AllocPathSet( stdOut, outPaths ) == NFD_ERROR )
result = NFD_ERROR;
free(stdOut);
}
else
{
result = NFD_ERROR;
}
return result;
}
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
int commandLen = 100;
char* command[commandLen];
memset(command, 0, commandLen * sizeof(char*));
command[0] = strdup("zenity");
command[1] = strdup("--file-selection");
command[2] = strdup("--title=Save File");
command[3] = strdup("--save");
char* stdOut = NULL;
nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut);
if(stdOut != NULL)
{
size_t len = strlen(stdOut);
*outPath = NFDi_Malloc(len);
memcpy(*outPath, stdOut, len);
(*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator
free(stdOut);
}
else
{
*outPath = NULL;
}
return result;
}
nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
nfdchar_t **outPath)
{
int commandLen = 100;
char* command[commandLen];
memset(command, 0, commandLen * sizeof(char*));
command[0] = strdup("zenity");
command[1] = strdup("--file-selection");
command[2] = strdup("--directory");
command[3] = strdup("--title=Select folder");
char* stdOut = NULL;
nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, "", &stdOut);
if(stdOut != NULL)
{
size_t len = strlen(stdOut);
*outPath = NFDi_Malloc(len);
memcpy(*outPath, stdOut, len);
(*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator
free(stdOut);
}
else
{
*outPath = NULL;
}
return result;
}

218
extern/nfd-modified/src/simple_exec.h vendored Normal file
View File

@ -0,0 +1,218 @@
// copied from: https://github.com/wheybags/simple_exec/blob/5a74c507c4ce1b2bb166177ead4cca7cfa23cb35/simple_exec.h
// simple_exec.h, single header library to run external programs + retrieve their status code and output (unix only for now)
//
// do this:
// #define SIMPLE_EXEC_IMPLEMENTATION
// before you include this file in *one* C or C++ file to create the implementation.
// i.e. it should look like this:
// #define SIMPLE_EXEC_IMPLEMENTATION
// #include "simple_exec.h"
#ifndef SIMPLE_EXEC_H
#define SIMPLE_EXEC_H
int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...);
int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs);
#endif // SIMPLE_EXEC_H
#ifdef SIMPLE_EXEC_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <fcntl.h>
#define release_assert(exp) { if (!(exp)) { abort(); } }
enum PIPE_FILE_DESCRIPTORS
{
READ_FD = 0,
WRITE_FD = 1
};
enum RUN_COMMAND_ERROR
{
COMMAND_RAN_OK = 0,
COMMAND_NOT_FOUND = 1
};
int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs)
{
// adapted from: https://stackoverflow.com/a/479103
int bufferSize = 256;
char buffer[bufferSize + 1];
int dataReadFromChildDefaultSize = bufferSize * 5;
int dataReadFromChildSize = dataReadFromChildDefaultSize;
int dataReadFromChildUsed = 0;
char* dataReadFromChild = (char*)malloc(dataReadFromChildSize);
int parentToChild[2];
release_assert(pipe(parentToChild) == 0);
int childToParent[2];
release_assert(pipe(childToParent) == 0);
int errPipe[2];
release_assert(pipe(errPipe) == 0);
pid_t pid;
switch( pid = fork() )
{
case -1:
{
release_assert(0 && "Fork failed");
break;
}
case 0: // child
{
release_assert(dup2(parentToChild[READ_FD ], STDIN_FILENO ) != -1);
release_assert(dup2(childToParent[WRITE_FD], STDOUT_FILENO) != -1);
if(includeStdErr)
{
release_assert(dup2(childToParent[WRITE_FD], STDERR_FILENO) != -1);
}
else
{
int devNull = open("/dev/null", O_WRONLY);
release_assert(dup2(devNull, STDERR_FILENO) != -1);
}
// unused
release_assert(close(parentToChild[WRITE_FD]) == 0);
release_assert(close(childToParent[READ_FD ]) == 0);
release_assert(close(errPipe[READ_FD]) == 0);
const char* command = allArgs[0];
execvp(command, allArgs);
char err = 1;
ssize_t result = write(errPipe[WRITE_FD], &err, 1);
release_assert(result != -1);
close(errPipe[WRITE_FD]);
close(parentToChild[READ_FD]);
close(childToParent[WRITE_FD]);
exit(0);
}
default: // parent
{
// unused
release_assert(close(parentToChild[READ_FD]) == 0);
release_assert(close(childToParent[WRITE_FD]) == 0);
release_assert(close(errPipe[WRITE_FD]) == 0);
while(1)
{
ssize_t bytesRead = 0;
switch(bytesRead = read(childToParent[READ_FD], buffer, bufferSize))
{
case 0: // End-of-File, or non-blocking read.
{
int status = 0;
release_assert(waitpid(pid, &status, 0) == pid);
// done with these now
release_assert(close(parentToChild[WRITE_FD]) == 0);
release_assert(close(childToParent[READ_FD]) == 0);
char errChar = 0;
ssize_t result = read(errPipe[READ_FD], &errChar, 1);
release_assert(result != -1);
close(errPipe[READ_FD]);
if(errChar)
{
free(dataReadFromChild);
return COMMAND_NOT_FOUND;
}
// free any un-needed memory with realloc + add a null terminator for convenience
dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildUsed + 1);
dataReadFromChild[dataReadFromChildUsed] = '\0';
if(stdOut != NULL)
*stdOut = dataReadFromChild;
else
free(dataReadFromChild);
if(stdOutByteCount != NULL)
*stdOutByteCount = dataReadFromChildUsed;
if(returnCode != NULL)
*returnCode = WEXITSTATUS(status);
return COMMAND_RAN_OK;
}
case -1:
{
release_assert(0 && "read() failed");
break;
}
default:
{
if(dataReadFromChildUsed + bytesRead + 1 >= dataReadFromChildSize)
{
dataReadFromChildSize += dataReadFromChildDefaultSize;
dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildSize);
}
memcpy(dataReadFromChild + dataReadFromChildUsed, buffer, bytesRead);
dataReadFromChildUsed += bytesRead;
break;
}
}
}
}
}
}
int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...)
{
va_list vl;
va_start(vl, command);
char* currArg = NULL;
int allArgsInitialSize = 16;
int allArgsSize = allArgsInitialSize;
char** allArgs = (char**)malloc(sizeof(char*) * allArgsSize);
allArgs[0] = command;
int i = 1;
do
{
currArg = va_arg(vl, char*);
allArgs[i] = currArg;
i++;
if(i >= allArgsSize)
{
allArgsSize += allArgsInitialSize;
allArgs = (char**)realloc(allArgs, sizeof(char*) * allArgsSize);
}
} while(currArg != NULL);
va_end(vl);
int retval = runCommandArray(stdOut, stdOutByteCount, returnCode, includeStdErr, allArgs);
free(allArgs);
return retval;
}
#endif //SIMPLE_EXEC_IMPLEMENTATION

View File

@ -0,0 +1,29 @@
#include "nfd.h"
#include <stdio.h>
#include <stdlib.h>
/* this test should compile on all supported platforms */
int main( void )
{
nfdchar_t *outPath = NULL;
nfdresult_t result = NFD_OpenDialog( "png,jpg;pdf", NULL, &outPath );
if ( result == NFD_OKAY )
{
puts("Success!");
puts(outPath);
free(outPath);
}
else if ( result == NFD_CANCEL )
{
puts("User pressed cancel.");
}
else
{
printf("Error: %s\n", NFD_GetError() );
}
return 0;
}

View File

@ -0,0 +1,32 @@
#include "nfd.h"
#include <stdio.h>
#include <stdlib.h>
/* this test should compile on all supported platforms */
int main( void )
{
nfdpathset_t pathSet;
nfdresult_t result = NFD_OpenDialogMultiple( "png,jpg;pdf", NULL, &pathSet );
if ( result == NFD_OKAY )
{
size_t i;
for ( i = 0; i < NFD_PathSet_GetCount(&pathSet); ++i )
{
nfdchar_t *path = NFD_PathSet_GetPath(&pathSet, i);
printf("Path %i: %s\n", (int)i, path );
}
NFD_PathSet_Free(&pathSet);
}
else if ( result == NFD_CANCEL )
{
puts("User pressed cancel.");
}
else
{
printf("Error: %s\n", NFD_GetError() );
}
return 0;
}

View File

@ -0,0 +1,29 @@
#include "nfd.h"
#include <stdio.h>
#include <stdlib.h>
/* this test should compile on all supported platforms */
int main( void )
{
nfdchar_t *outPath = NULL;
nfdresult_t result = NFD_PickFolder( NULL, &outPath );
if ( result == NFD_OKAY )
{
puts("Success!");
puts(outPath);
free(outPath);
}
else if ( result == NFD_CANCEL )
{
puts("User pressed cancel.");
}
else
{
printf("Error: %s\n", NFD_GetError() );
}
return 0;
}

View File

@ -0,0 +1,28 @@
#include "nfd.h"
#include <stdio.h>
#include <stdlib.h>
/* this test should compile on all supported platforms */
int main( void )
{
nfdchar_t *savePath = NULL;
nfdresult_t result = NFD_SaveDialog( "png,jpg;pdf", NULL, &savePath );
if ( result == NFD_OKAY )
{
puts("Success!");
puts(savePath);
free(savePath);
}
else if ( result == NFD_CANCEL )
{
puts("User pressed cancel.");
}
else
{
printf("Error: %s\n", NFD_GetError() );
}
return 0;
}

View File

@ -6,10 +6,13 @@ surely had better graphics than NES, but its sound (fairly weak, 4ch PSG with A-
this console is powered by a derivative of the Texas Instruments SN76489.
the original iteration of the SN76489 used in the TI-99/4A computers was clocked much lower at 447 kHz, being able to play as low as 13.670 Hz (A -1). consequentially, pitch accuracy for higher notes is compromised.
# effects
- `20xy`: set noise mode.
- `x` controls whether to inherit frequency from hannel 3.
- `x` controls whether to inherit frequency from channel 3.
- 0: use one of 3 preset frequencies (C: A-2; C#: A-3; D: A-4).
- 1: use frequency of channel 3.
- `y` controls whether to select noise or thin pulse.

49
papers/newIns.md Normal file
View File

@ -0,0 +1,49 @@
# possible new Furnace instrument format
the main issue with Furnace instrument files is that they are too big, even if the instrument is nothing more than the FM setup...
the aim of this new format is to greatly reduce the size of a resulting instrument.
```
size | description
-----|------------------------------------
6 | "FURINS" format magic
2 | format version
1 | instrument type
??? | feature bits
4 | instrument length (if wave/sample bits are on)
```
the "feature bits" field is a variable length bitfield. bit 7 in a byte indicates "read one more byte".
the feature bits are:
- 0: has wavetables
- 1: has samples
- 2: has name
- 3: FM data
- 4: FM data size (1: 2-op, 0: 4-op)
- 5: FM data includes OPL/OPZ data
- if off, only read an op until ssgEnv.
- if on, read everything else.
- 6: Game Boy data
- 7: (continue in next byte)
- 8: C64 data
- 9: Amiga data
- 10: standard data (macros)
- 11: operator macros
- 12: release points
- 13: op release points
- 14: extended op macros
- 15: (continue in next byte)
- 16: OPL drums mode data
- 17: Amiga sample map data
- 18: Namco 163 data
- 19: extra macros
- 20: FDS data
- 21: OPZ data
- 22: wavetable synth data
- 23: (continue in next byte)
- 24: additional macro modes
- 25: extra C64 data
- 26: MultiPCM data

View File

@ -92,10 +92,12 @@ bool TAMidiOut::closeDevice() {
}
std::vector<String> TAMidiIn::listDevices() {
logW("attempting to list devices of abstract TAMidiIn!");
return std::vector<String>();
}
std::vector<String> TAMidiOut::listDevices() {
logW("attempting to list devices of abstract TAMidiOut!");
return std::vector<String>();
}

View File

@ -21,6 +21,26 @@
#include "../ta-log.h"
#include "taAudio.h"
String sanitizePortName(const String& name) {
#if defined(_WIN32)
// remove port number
size_t namePos=name.rfind(' ');
if (namePos!=String::npos) {
return name.substr(0,namePos);
}
return name;
#elif defined(__linux__)
// remove port location
size_t namePos=name.rfind(' ');
if (namePos!=String::npos) {
return name.substr(0,namePos);
}
return name;
#else
return name;
#endif
}
// --- IN ---
bool TAMidiInRtMidi::gather() {
@ -56,7 +76,7 @@ std::vector<String> TAMidiInRtMidi::listDevices() {
unsigned int count=port->getPortCount();
logD("got port count.");
for (unsigned int i=0; i<count; i++) {
String name=port->getPortName(i);
String name=sanitizePortName(port->getPortName(i));
if (name!="") ret.push_back(name);
}
} catch (RtMidiError& e) {
@ -75,8 +95,12 @@ bool TAMidiInRtMidi::openDevice(String name) {
try {
bool portOpen=false;
unsigned int count=port->getPortCount();
logD("finding port %s...",name);
for (unsigned int i=0; i<count; i++) {
if (port->getPortName(i)==name) {
String portName=sanitizePortName(port->getPortName(i));
logV("- %d: %s",i,portName);
if (portName==name) {
logD("opening port %d...",i);
port->openPort(i);
portOpen=true;
break;
@ -184,8 +208,12 @@ bool TAMidiOutRtMidi::openDevice(String name) {
try {
bool portOpen=false;
unsigned int count=port->getPortCount();
logD("finding port %s...",name);
for (unsigned int i=0; i<count; i++) {
if (port->getPortName(i)==name) {
String portName=sanitizePortName(port->getPortName(i));
logV("- %d: %s",i,portName);
if (portName==name) {
logD("opening port %d...",i);
port->openPort(i);
portOpen=true;
break;
@ -222,7 +250,7 @@ std::vector<String> TAMidiOutRtMidi::listDevices() {
try {
unsigned int count=port->getPortCount();
for (unsigned int i=0; i<count; i++) {
String name=port->getPortName(i);
String name=sanitizePortName(port->getPortName(i));
if (name!="") ret.push_back(name);
}
} catch (RtMidiError& e) {
@ -248,4 +276,4 @@ bool TAMidiOutRtMidi::quit() {
port=NULL;
}
return true;
}
}

View File

@ -1431,7 +1431,10 @@ void DivEngine::stop() {
freelance=false;
playing=false;
extValuePresent=false;
endOfSong=false; // what?
stepPlay=0;
curOrder=prevOrder;
curRow=prevRow;
remainingLoops=-1;
sPreview.sample=-1;
sPreview.wave=-1;
@ -2067,6 +2070,8 @@ int DivEngine::addSample() {
sample->name=fmt::sprintf("Sample %d",sampleCount);
song.sample.push_back(sample);
song.sampleLen=sampleCount+1;
sPreview.sample=-1;
sPreview.pos=0;
saveLock.unlock();
renderSamples();
BUSY_END;
@ -2183,6 +2188,7 @@ int DivEngine::addSampleFromFile(const char* path) {
return -1;
#else
SF_INFO si;
memset(&si,0,sizeof(SF_INFO));
SNDFILE* f=sf_open(path,SFM_READ,&si);
if (f==NULL) {
BUSY_END;
@ -2200,8 +2206,22 @@ int DivEngine::addSampleFromFile(const char* path) {
BUSY_END;
return -1;
}
short* buf=new short[si.channels*si.frames];
if (sf_readf_short(f,buf,si.frames)!=si.frames) {
void* buf=NULL;
sf_count_t sampleLen=sizeof(short);
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
logD("sample is 8-bit unsigned");
buf=new unsigned char[si.channels*si.frames];
sampleLen=sizeof(unsigned char);
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) {
logD("sample is 32-bit float");
buf=new float[si.channels*si.frames];
sampleLen=sizeof(float);
} else {
logD("sample is 16-bit signed");
buf=new short[si.channels*si.frames];
sampleLen=sizeof(short);
}
if (sf_read_raw(f,buf,si.frames*si.channels*sampleLen)!=(si.frames*si.channels*sampleLen)) {
logW("sample read size mismatch!");
}
DivSample* sample=new DivSample;
@ -2215,19 +2235,41 @@ int DivEngine::addSampleFromFile(const char* path) {
sample->depth=16;
}
sample->init(si.frames);
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
int averaged=0;
for (int j=0; j<si.channels; j++) {
averaged+=buf[i+j];
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
int averaged=0;
for (int j=0; j<si.channels; j++) {
averaged+=((int)((unsigned char*)buf)[i+j])-128;
}
averaged/=si.channels;
sample->data8[index++]=averaged;
}
averaged/=si.channels;
if (((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8)) {
sample->data8[index++]=averaged>>8;
} else {
delete[] (unsigned char*)buf;
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) {
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
float averaged=0.0f;
for (int j=0; j<si.channels; j++) {
averaged+=((float*)buf)[i+j];
}
averaged/=si.channels;
averaged*=32767.0;
if (averaged<-32768.0) averaged=-32768.0;
if (averaged>32767.0) averaged=32767.0;
sample->data16[index++]=averaged;
}
delete[] (float*)buf;
} else {
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
int averaged=0;
for (int j=0; j<si.channels; j++) {
averaged+=((short*)buf)[i+j];
}
averaged/=si.channels;
sample->data16[index++]=averaged;
}
delete[] (short*)buf;
}
delete[] buf;
sample->rate=si.samplerate;
if (sample->rate<4000) sample->rate=4000;
if (sample->rate>96000) sample->rate=96000;
@ -2265,6 +2307,8 @@ int DivEngine::addSampleFromFile(const char* path) {
void DivEngine::delSample(int index) {
BUSY_BEGIN;
sPreview.sample=-1;
sPreview.pos=0;
saveLock.lock();
if (index>=0 && index<(int)song.sample.size()) {
delete song.sample[index];
@ -2479,6 +2523,8 @@ bool DivEngine::moveWaveUp(int which) {
bool DivEngine::moveSampleUp(int which) {
if (which<1 || which>=(int)song.sample.size()) return false;
BUSY_BEGIN;
sPreview.sample=-1;
sPreview.pos=0;
DivSample* prev=song.sample[which];
saveLock.lock();
song.sample[which]=song.sample[which-1];
@ -2516,6 +2562,8 @@ bool DivEngine::moveWaveDown(int which) {
bool DivEngine::moveSampleDown(int which) {
if (which<0 || which>=((int)song.sample.size())-1) return false;
BUSY_BEGIN;
sPreview.sample=-1;
sPreview.pos=0;
DivSample* prev=song.sample[which];
saveLock.lock();
song.sample[which]=song.sample[which+1];
@ -2980,6 +3028,8 @@ bool DivEngine::initAudioBackend() {
if (!output->midiIn->openDevice(inName)) {
logW("could not open MIDI input device!");
}
} else {
logV("no MIDI input device selected.");
}
}
if (output->midiOut) {
@ -2990,6 +3040,8 @@ bool DivEngine::initAudioBackend() {
if (!output->midiOut->openDevice(outName)) {
logW("could not open MIDI output device!");
}
} else {
logV("no MIDI output device selected.");
}
}

View File

@ -83,7 +83,7 @@ struct DivChannelState {
int note, oldNote, lastIns, pitch, portaSpeed, portaNote;
int volume, volSpeed, cut, rowDelay, volMax;
int delayOrder, delayRow, retrigSpeed, retrigTick;
int vibratoDepth, vibratoRate, vibratoPos, vibratoDir, vibratoFine;
int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine;
int tremoloDepth, tremoloRate, tremoloPos;
unsigned char arp, arpStage, arpTicks, panL, panR;
bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff;
@ -112,6 +112,7 @@ struct DivChannelState {
vibratoDepth(0),
vibratoRate(0),
vibratoPos(0),
vibratoPosGiant(0),
vibratoDir(0),
vibratoFine(15),
tremoloDepth(0),

View File

@ -22,38 +22,6 @@
#include <string.h>
#include <math.h>
#include "fmshared_OPM.h"
static unsigned short chanOffs[8]={
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
};
static unsigned short opOffs[4]={
0x00, 0x08, 0x10, 0x18
};
static bool isOutput[8][4]={
// 1 3 2 4
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,true ,true},
{false,true ,true ,true},
{false,true ,true ,true},
{true ,true ,true ,true},
};
static unsigned char dtTable[8]={
7,6,5,0,1,2,3,4
};
static int orderedOps[4]={
0,2,1,3
};
#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 NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0)
const char* regCheatSheetOPM[]={
"Test", "00",
"NoteCtl", "08",
@ -198,7 +166,7 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
OPM_Write(&fm,1,w.val);
regPool[w.addr&0xff]=w.val;
//printf("write: %x = %.2x\n",w.addr,w.val);
writes.pop();
writes.pop_front();
} else {
OPM_Write(&fm,0,w.addr);
w.addrOrVal=true;
@ -239,7 +207,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr);
fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val);
regPool[w.addr&0xff]=w.val;
writes.pop();
writes.pop_front();
delay=1;
}
}
@ -938,7 +906,7 @@ void DivPlatformArcade::poke(std::vector<DivRegWrite>& wlist) {
}
void DivPlatformArcade::reset() {
while (!writes.empty()) writes.pop();
while (!writes.empty()) writes.pop_front();
memset(regPool,0,256);
if (useYMFM) {
fm_ymfm->reset();
@ -978,15 +946,20 @@ void DivPlatformArcade::reset() {
}
void DivPlatformArcade::setFlags(unsigned int flags) {
if (flags==2) {
chipClock=4000000.0;
baseFreqOff=-122;
} else if (flags==1) {
chipClock=COLOR_PAL*4.0/5.0;
baseFreqOff=12;
} else {
chipClock=COLOR_NTSC;
baseFreqOff=0;
switch (flags&0xff) {
default:
case 0:
chipClock=COLOR_NTSC;
baseFreqOff=0;
break;
case 1:
chipClock=COLOR_PAL*4.0/5.0;
baseFreqOff=12;
break;
case 2:
chipClock=4000000.0;
baseFreqOff=-122;
break;
}
rate=chipClock/64;
for (int i=0; i<8; i++) {

View File

@ -19,19 +19,23 @@
#ifndef _ARCADE_H
#define _ARCADE_H
#include "../dispatch.h"
#include "fmshared_OPM.h"
#include "../macroInt.h"
#include "../instrument.h"
#include <queue>
#include "../../../extern/opm/opm.h"
#include "sound/ymfm/ymfm_opm.h"
#include "../macroInt.h"
class DivArcadeInterface: public ymfm::ymfm_interface {
};
class DivPlatformArcade: public DivDispatch {
class DivPlatformArcade: public DivPlatformOPM {
protected:
const unsigned short chanOffs[8]={
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
};
struct Channel {
DivInstrumentFM state;
DivMacroInt std;
@ -71,31 +75,18 @@ class DivPlatformArcade: public DivDispatch {
};
Channel chan[8];
DivDispatchOscBuffer* oscBuf[8];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
opm_t fm;
int delay, baseFreqOff;
int baseFreqOff;
int pcmL, pcmR, pcmCycles;
unsigned char lastBusy;
unsigned char amDepth, pmDepth;
ymfm::ym2151* fm_ymfm;
ymfm::ym2151::output_data out_ymfm;
DivArcadeInterface iface;
unsigned char regPool[256];
bool extMode, useYMFM;
bool isMuted[8];
short oldWrites[256];
short pendingWrites[256];
int octave(int freq);
int toFreq(int freq);

View File

@ -27,7 +27,7 @@
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} }
#define CHIP_DIVIDER ((sunsoft||clockSel)?16:8)
#define CHIP_DIVIDER (extMode?extDiv:((sunsoft||clockSel)?16:8))
const char* regCheatSheetAY[]={
"FreqL_A", "0",
@ -489,9 +489,9 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (isMuted[ch]) {
rWrite(0x08+ch,0);
} else if (intellivision && (chan[ch].psgMode&4)) {
} else if (intellivision && (chan[ch].psgMode&4) && chan[ch].active) {
rWrite(0x08+ch,(chan[ch].vol&0xc)<<2);
} else {
} else if (chan[ch].active) {
rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2));
}
}
@ -565,8 +565,6 @@ void DivPlatformAY8910::reset() {
delay=0;
extMode=false;
ioPortA=false;
ioPortB=false;
portAVal=0;
@ -595,50 +593,69 @@ void DivPlatformAY8910::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
}
void DivPlatformAY8910::setFlags(unsigned int flags) {
clockSel=(flags>>7)&1;
switch (flags&15) {
case 1:
chipClock=COLOR_PAL*2.0/5.0;
break;
case 2:
chipClock=1750000;
break;
case 3:
chipClock=2000000;
break;
case 4:
chipClock=1500000;
break;
case 5:
chipClock=1000000;
break;
case 6:
chipClock=COLOR_NTSC/4.0;
break;
case 7:
chipClock=COLOR_PAL*3.0/8.0;
break;
case 8:
chipClock=COLOR_PAL*3.0/16.0;
break;
case 9:
chipClock=COLOR_PAL/4.0;
break;
case 10:
chipClock=2097152;
break;
case 11:
chipClock=COLOR_NTSC;
break;
case 12:
chipClock=3600000;
break;
default:
chipClock=COLOR_NTSC/2.0;
break;
void DivPlatformAY8910::setExtClockDiv(unsigned int eclk, unsigned char ediv) {
if (extMode) {
extClock=eclk;
extDiv=ediv;
}
}
void DivPlatformAY8910::setFlags(unsigned int flags) {
if (extMode) {
chipClock=extClock;
rate=chipClock/extDiv;
} else {
clockSel=(flags>>7)&1;
switch (flags&15) {
default:
case 0:
chipClock=COLOR_NTSC/2.0;
break;
case 1:
chipClock=COLOR_PAL*2.0/5.0;
break;
case 2:
chipClock=1750000;
break;
case 3:
chipClock=2000000;
break;
case 4:
chipClock=1500000;
break;
case 5:
chipClock=1000000;
break;
case 6:
chipClock=COLOR_NTSC/4.0;
break;
case 7:
chipClock=COLOR_PAL*3.0/8.0;
break;
case 8:
chipClock=COLOR_PAL*3.0/16.0;
break;
case 9:
chipClock=COLOR_PAL/4.0;
break;
case 10:
chipClock=2097152;
break;
case 11:
chipClock=COLOR_NTSC;
break;
case 12:
chipClock=3600000;
break;
case 13:
chipClock=20000000/16;
break;
case 14:
chipClock=1536000;
break;
}
rate=chipClock/8;
}
rate=chipClock/8;
for (int i=0; i<3; i++) {
oscBuf[i]->rate=rate;
}

View File

@ -70,6 +70,9 @@ class DivPlatformAY8910: public DivDispatch {
int delay;
bool extMode;
unsigned int extClock;
unsigned char extDiv;
bool stereo, sunsoft, intellivision, clockSel;
bool ioPortA, ioPortB;
unsigned char portAVal, portBVal;
@ -88,6 +91,7 @@ class DivPlatformAY8910: public DivDispatch {
friend void putDispatchChan(void*,int,int);
public:
void setExtClockDiv(unsigned int eclk=COLOR_NTSC, unsigned char ediv=8);
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
@ -111,5 +115,10 @@ class DivPlatformAY8910: public DivDispatch {
const char* getEffectName(unsigned char effect);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
DivPlatformAY8910(bool useExtMode=false, unsigned int eclk=COLOR_NTSC, unsigned char ediv=8):
DivDispatch(),
extMode(useExtMode),
extClock(eclk),
extDiv(ediv) {}
};
#endif

View File

@ -521,7 +521,7 @@ void DivPlatformAY8930::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (isMuted[ch]) {
rWrite(0x08+ch,0);
} else {
} else if (chan[ch].active) {
rWrite(0x08+ch,(chan[ch].outVol&31)|((chan[ch].psgMode&4)<<3));
}
}

View File

@ -306,7 +306,7 @@ void DivPlatformBubSysWSG::reset() {
for (int i=0; i<2; i++) {
chan[i]=DivPlatformBubSysWSG::Channel();
chan[i].std.setEngine(parent);
chan[i].ws.setEngine(parent);
chan[i].ws.setEngine(parent,8);
chan[i].ws.init(NULL,32,15,false);
}
if (dumpWrites) {

View File

@ -17,13 +17,32 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define ADDR_MULT_DT 0x40
#define ADDR_TL 0x60
#define ADDR_RS_AR 0x80
#define ADDR_AM_DR 0xa0
#define ADDR_DT2_D2R 0xc0
#define ADDR_SL_RR 0xe0
#define ADDR_NOTE 0x28
#define ADDR_KF 0x30
#define ADDR_FMS_AMS 0x38
#define ADDR_LR_FB_ALG 0x20
#ifndef _FMSHARED_OPM_H
#define _FMSHARED_OPM_H
#include "fmsharedbase.h"
#define NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0)
class DivPlatformOPM: public DivPlatformFMBase {
protected:
const unsigned short ADDR_MULT_DT=0x40;
const unsigned short ADDR_TL=0x60;
const unsigned short ADDR_RS_AR=0x80;
const unsigned short ADDR_AM_DR=0xa0;
const unsigned short ADDR_DT2_D2R=0xc0;
const unsigned short ADDR_SL_RR=0xe0;
const unsigned short ADDR_NOTE=0x28;
const unsigned short ADDR_KF=0x30;
const unsigned short ADDR_FMS_AMS=0x38;
const unsigned short ADDR_LR_FB_ALG=0x20;
const unsigned short opOffs[4]={
0x00, 0x08, 0x10, 0x18
};
DivPlatformOPM():
DivPlatformFMBase() {}
};
#endif

View File

@ -20,17 +20,7 @@
#ifndef _FMSHARED_OPN_H
#define _FMSHARED_OPN_H
#define ADDR_MULT_DT 0x30
#define ADDR_TL 0x40
#define ADDR_RS_AR 0x50
#define ADDR_AM_DR 0x60
#define ADDR_DT2_D2R 0x70
#define ADDR_SL_RR 0x80
#define ADDR_SSG 0x90
#define ADDR_FREQ 0xa0
#define ADDR_FREQH 0xa4
#define ADDR_FB_ALG 0xb0
#define ADDR_LRAF 0xb4
#include "fmsharedbase.h"
#define PLEASE_HELP_ME(_targetChan) \
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); \
@ -93,4 +83,34 @@
return 2; \
}
#endif
class DivPlatformOPN: public DivPlatformFMBase {
protected:
const unsigned short ADDR_MULT_DT=0x30;
const unsigned short ADDR_TL=0x40;
const unsigned short ADDR_RS_AR=0x50;
const unsigned short ADDR_AM_DR=0x60;
const unsigned short ADDR_DT2_D2R=0x70;
const unsigned short ADDR_SL_RR=0x80;
const unsigned short ADDR_SSG=0x90;
const unsigned short ADDR_FREQ=0xa0;
const unsigned short ADDR_FREQH=0xa4;
const unsigned short ADDR_FB_ALG=0xb0;
const unsigned short ADDR_LRAF=0xb4;
const unsigned short opOffs[4]={
0x00, 0x04, 0x08, 0x0c
};
double fmFreqBase;
unsigned int fmDivBase;
unsigned int ayDiv;
DivPlatformOPN(double f=9440540.0, unsigned int d=72, unsigned int a=32):
DivPlatformFMBase(),
fmFreqBase(f),
fmDivBase(d),
ayDiv(a) {}
};
#endif

View File

@ -0,0 +1,96 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _FMSHARED_BASE_H
#define _FMSHARED_BASE_H
#include "../dispatch.h"
#include <deque>
class DivPlatformFMBase: public DivDispatch {
protected:
const bool isOutput[8][4]={
// 1 3 2 4
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,true ,true},
{false,true ,true ,true},
{false,true ,true ,true},
{true ,true ,true ,true},
};
const unsigned char dtTable[8]={
7,6,5,0,1,2,3,4
};
const int orderedOps[4]={
0,2,1,3
};
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::deque<QueuedWrite> writes;
unsigned char lastBusy;
int delay;
unsigned char regPool[512];
short oldWrites[512];
short pendingWrites[512];
inline void rWrite(unsigned short a, short v) {
if (!skipRegisterWrites) {
pendingWrites[a]=v;
}
}
inline void immWrite(unsigned short a, unsigned char v) {
if (!skipRegisterWrites) {
writes.push_back(QueuedWrite(a,v));
if (dumpWrites) {
addWrite(a,v);
}
}
}
inline void urgentWrite(unsigned short a, unsigned char v) {
if (!skipRegisterWrites) {
if (writes.empty()) {
writes.push_back(QueuedWrite(a,v));
} else if (writes.size()>16 || writes.front().addrOrVal) {
writes.push_back(QueuedWrite(a,v));
} else {
writes.push_front(QueuedWrite(a,v));
}
if (dumpWrites) {
addWrite(a,v);
}
}
}
DivPlatformFMBase():
DivDispatch(),
lastBusy(0),
delay(0) {}
};
#endif

View File

@ -22,17 +22,11 @@
#include <string.h>
#include <math.h>
#include "genesisshared.h"
#define CHIP_FREQBASE fmFreqBase
#define CHIP_DIVIDER fmDivBase
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
static unsigned char konOffs[6]={
0, 1, 2, 4, 5, 6
};
#define CHIP_DIVIDER 72
#define CHIP_FREQBASE 9440540
const char* DivPlatformGenesis::getEffectName(unsigned char effect) {
switch (effect) {
case 0x10:
@ -1247,12 +1241,13 @@ void DivPlatformGenesis::setSoftPCM(bool value) {
}
void DivPlatformGenesis::setFlags(unsigned int flags) {
switch (flags) {
switch (flags&(~0x80000000)) {
default:
case 0: chipClock=COLOR_NTSC*15.0/7.0; break;
case 1: chipClock=COLOR_PAL*12.0/7.0; break;
case 2: chipClock=8000000.0; break;
case 3: chipClock=COLOR_NTSC*12.0/7.0; break;
case 4: chipClock=COLOR_NTSC*9.0/4.0; break;
default: chipClock=COLOR_NTSC*15.0/7.0; break;
}
ladder=flags&0x80000000;
OPN2_SetChipType(ladder?ym3438_mode_ym2612:0);

View File

@ -19,19 +19,26 @@
#ifndef _GENESIS_H
#define _GENESIS_H
#include "../dispatch.h"
#include <deque>
#include "fmshared_OPN.h"
#include "../macroInt.h"
#include "../../../extern/Nuked-OPN2/ym3438.h"
#include "sound/ymfm/ymfm_opn.h"
#include "sms.h"
class DivYM2612Interface: public ymfm::ymfm_interface {
};
class DivPlatformGenesis: public DivDispatch {
class DivPlatformGenesis: public DivPlatformOPN {
protected:
const unsigned short chanOffs[6]={
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
};
const unsigned char konOffs[6]={
0, 1, 2, 4, 5, 6
};
struct Channel {
DivInstrumentFM state;
DivMacroInt std;
@ -92,21 +99,11 @@ class DivPlatformGenesis: public DivDispatch {
Channel chan[10];
DivDispatchOscBuffer* oscBuf[10];
bool isMuted[10];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::deque<QueuedWrite> writes;
ym3438_t fm;
int delay;
unsigned char lastBusy;
ymfm::ym2612* fm_ymfm;
ymfm::ym2612::output_data out_ymfm;
DivYM2612Interface iface;
unsigned char regPool[512];
unsigned char lfoValue;
@ -115,9 +112,6 @@ class DivPlatformGenesis: public DivDispatch {
bool extMode, softPCM, useYMFM;
bool ladder;
short oldWrites[512];
short pendingWrites[512];
unsigned char dacVolTable[128];
friend void putDispatchChan(void*,int,int);
@ -153,6 +147,8 @@ class DivPlatformGenesis: public DivDispatch {
const char* getEffectName(unsigned char effect);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
DivPlatformGenesis():
DivPlatformOPN(9440540.0, 72, 32) {}
~DivPlatformGenesis();
};
#endif

View File

@ -21,10 +21,8 @@
#include "../engine.h"
#include <math.h>
#include "genesisshared.h"
#define CHIP_DIVIDER 72
#define CHIP_FREQBASE 9440540
#define CHIP_FREQBASE fmFreqBase
#define CHIP_DIVIDER fmDivBase
int DivPlatformGenesisExt::dispatch(DivCommand c) {
if (c.chan<2) {

View File

@ -1,60 +0,0 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
static unsigned short chanOffs[6]={
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
};
static unsigned short opOffs[4]={
0x00, 0x04, 0x08, 0x0c
};
static bool isOutput[8][4]={
// 1 3 2 4
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,true ,true},
{false,true ,true ,true},
{false,true ,true ,true},
{true ,true ,true ,true},
};
static unsigned char dtTable[8]={
7,6,5,0,1,2,3,4
};
static int orderedOps[4]={
0,2,1,3
};
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[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.empty()) { \
writes.push_back(QueuedWrite(a,v)); \
} else if (writes.size()>16 || writes.front().addrOrVal) { \
writes.push_back(QueuedWrite(a,v)); \
} else { \
writes.push_front(QueuedWrite(a,v)); \
} \
if (dumpWrites) { \
addWrite(a,v); \
} \
}
#include "fmshared_OPN.h"

View File

@ -380,7 +380,7 @@ void DivPlatformMSM6258::setFlags(unsigned int flags) {
chipClock=4000000;
break;
}
rate=chipClock/128;
rate=chipClock/256;
for (int i=0; i<1; i++) {
oscBuf[i]->rate=rate;
}

View File

@ -26,10 +26,6 @@
class DivPlatformMSM6258: public DivDispatch {
protected:
const unsigned short chanOffs[6]={
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
};
struct Channel {
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
@ -77,12 +73,10 @@ class DivPlatformMSM6258: public DivDispatch {
struct QueuedWrite {
unsigned short addr;
unsigned char val;
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) {}
};
std::queue<QueuedWrite> writes;
okim6258_device* msm;
unsigned char regPool[512];
unsigned char lastBusy;
unsigned char* adpcmMem;
@ -93,11 +87,6 @@ class DivPlatformMSM6258: public DivDispatch {
int delay, updateOsc, sample, samplePos;
bool extMode;
short oldWrites[512];
short pendingWrites[512];
friend void putDispatchChan(void*,int,int);
public:

View File

@ -24,6 +24,7 @@
#include <math.h>
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define rWriteDelay(a,v,d) if (!skipRegisterWrites) {writes.emplace(a,v,d); if (dumpWrites) {addWrite(a,v);} }
const char** DivPlatformMSM6295::getRegisterSheet() {
return NULL;
@ -38,8 +39,11 @@ const char* DivPlatformMSM6295::getEffectName(unsigned char effect) {
return NULL;
}
u8 DivMSM6295Interface::read_byte(u32 address) {
return adpcmMem[address&0xffff];
u8 DivPlatformMSM6295::read_byte(u32 address) {
if (adpcmMem==NULL || address>=getSampleMemCapacity(0)) {
return 0;
}
return adpcmMem[address&0x3ffff];
}
void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t len) {
@ -49,7 +53,7 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t
QueuedWrite& w=writes.front();
switch (w.addr) {
case 0: // command
msm->command_w(w.val);
msm.command_w(w.val);
break;
case 8: // chip clock select (VGM)
case 9:
@ -57,7 +61,7 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t
case 11:
break;
case 12: // rate select
msm->ss_w(!w.val);
msm.ss_w(!w.val);
break;
case 14: // enable bankswitch
break;
@ -70,21 +74,21 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t
break;
}
writes.pop();
delay=32;
delay=w.delay;
}
} else {
delay--;
}
msm->tick();
msm.tick();
bufL[h]=msm->out()<<4;
bufL[h]=msm.out()<<4;
if (++updateOsc>=22) {
updateOsc=0;
// TODO: per-channel osc
for (int i=0; i<4; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=msm->m_voice[i].m_muted?0:(msm->m_voice[i].m_out<<6);
oscBuf[i]->data[oscBuf[i]->needle++]=msm.m_voice[i].m_muted?0:(msm.m_voice[i].m_out<<6);
}
}
}
@ -118,7 +122,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
rWrite(0,(8<<c.chan)); // turn off
rWriteDelay(0,(8<<c.chan),60); // turn off
rWrite(0,0x80|chan[c.chan].sample); // set phrase
rWrite(0,(16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
} else {
@ -133,7 +137,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
}
//DivSample* s=parent->getSample(12*sampleBank+c.value%12);
chan[c.chan].sample=12*sampleBank+c.value%12;
rWrite(0,(8<<c.chan)); // turn off
rWriteDelay(0,(8<<c.chan),60); // turn off
rWrite(0,0x80|chan[c.chan].sample); // set phrase
rWrite(0,(16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
}
@ -143,14 +147,14 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
rWrite(0,(8<<c.chan)); // turn off
rWriteDelay(0,(8<<c.chan),60); // turn off
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
rWrite(0,(8<<c.chan)); // turn off
rWriteDelay(0,(8<<c.chan),60); // turn off
chan[c.chan].std.release();
break;
case DIV_CMD_ENV_RELEASE:
@ -188,7 +192,6 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
break;
@ -212,7 +215,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
void DivPlatformMSM6295::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
msm->m_voice[ch].m_muted=mute;
msm.m_voice[ch].m_muted=mute;
}
void DivPlatformMSM6295::forceIns() {
@ -253,8 +256,8 @@ void DivPlatformMSM6295::poke(std::vector<DivRegWrite>& wlist) {
void DivPlatformMSM6295::reset() {
while (!writes.empty()) writes.pop();
msm->reset();
msm->ss_w(false);
msm.reset();
msm.ss_w(rateSelInit);
if (dumpWrites) {
addWrite(0xffffffff,0);
}
@ -268,7 +271,8 @@ void DivPlatformMSM6295::reset() {
}
sampleBank=0;
rateSel=false;
rateSel=rateSelInit;
rWrite(12,!rateSelInit);
delay=0;
}
@ -343,7 +347,9 @@ void DivPlatformMSM6295::renderSamples() {
}
void DivPlatformMSM6295::setFlags(unsigned int flags) {
switch (flags) {
rateSelInit=(flags>>7)&1;
switch (flags&0x7f) {
default:
case 0:
chipClock=4000000/4;
break;
@ -383,22 +389,27 @@ void DivPlatformMSM6295::setFlags(unsigned int flags) {
case 12:
chipClock=1500000;
break;
default:
chipClock=4000000/4;
case 13:
chipClock=3000000;
break;
case 14:
chipClock=COLOR_NTSC/3.0;
break;
}
rate=chipClock/3;
for (int i=0; i<4; i++) {
oscBuf[i]->rate=rate/22;
}
if (rateSel!=rateSelInit) {
rWrite(12,!rateSelInit);
rateSel=rateSelInit;
}
}
int DivPlatformMSM6295::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
adpcmMem=new unsigned char[getSampleMemCapacity(0)];
adpcmMemLen=0;
iface.adpcmMem=adpcmMem;
iface.sampleBank=0;
dumpWrites=false;
skipRegisterWrites=false;
updateOsc=0;
@ -406,7 +417,6 @@ int DivPlatformMSM6295::init(DivEngine* p, int channels, int sugRate, unsigned i
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
msm=new msm6295_core(iface);
setFlags(flags);
reset();
return 4;
@ -416,7 +426,6 @@ void DivPlatformMSM6295::quit() {
for (int i=0; i<4; i++) {
delete oscBuf[i];
}
delete msm;
delete[] adpcmMem;
}

View File

@ -24,60 +24,31 @@
#include <queue>
#include "sound/oki/msm6295.hpp"
class DivMSM6295Interface: public vgsound_emu_mem_intf {
public:
unsigned char* adpcmMem;
int sampleBank;
u8 read_byte(u32 address);
DivMSM6295Interface(): adpcmMem(NULL), sampleBank(0) {}
};
class DivPlatformMSM6295: public DivDispatch {
class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf {
protected:
const unsigned short chanOffs[6]={
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
};
struct Channel {
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
unsigned char psgMode, autoEnvNum, autoEnvDen;
int note, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
bool active, insChanged, freqChanged, keyOn, keyOff, furnacePCM, hardReset;
int vol, outVol;
int sample;
unsigned char pan;
DivMacroInt std;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
Channel():
freqH(0),
freqL(0),
freq(0),
baseFreq(0),
pitch(0),
pitch2(0),
portaPauseFreq(0),
note(0),
ins(-1),
psgMode(1),
autoEnvNum(0),
autoEnvDen(0),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
portaPause(false),
inPorta(false),
furnacePCM(false),
hardReset(false),
vol(0),
outVol(15),
sample(-1),
pan(3) {}
sample(-1) {}
};
Channel chan[4];
DivDispatchOscBuffer* oscBuf[4];
@ -85,57 +56,59 @@ class DivPlatformMSM6295: public DivDispatch {
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
unsigned short delay;
QueuedWrite(unsigned short a, unsigned char v, unsigned short d=32):
addr(a),
val(v),
delay(d) {}
};
std::queue<QueuedWrite> writes;
msm6295_core* msm;
unsigned char regPool[512];
msm6295_core msm;
unsigned char lastBusy;
unsigned char* adpcmMem;
size_t adpcmMemLen;
DivMSM6295Interface iface;
unsigned char sampleBank;
int delay, updateOsc;
bool extMode;
bool rateSel;
bool rateSel=false, rateSelInit=false;
short oldWrites[512];
short pendingWrites[512];
friend void putDispatchChan(void*,int,int);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool keyOffAffectsArp(int ch);
float getPostAmp();
void notifyInsChange(int ins);
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
void setFlags(unsigned int flags);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
const void* getSampleMem(int index);
size_t getSampleMemCapacity(int index);
size_t getSampleMemUsage(int index);
void renderSamples();
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
virtual u8 read_byte(u32 address) override;
virtual void acquire(short* bufL, short* bufR, size_t start, size_t len) override;
virtual int dispatch(DivCommand c) override;
virtual void* getChanState(int chan) override;
virtual DivMacroInt* getChanMacroInt(int ch) override;
virtual DivDispatchOscBuffer* getOscBuffer(int chan) override;
virtual unsigned char* getRegisterPool() override;
virtual int getRegisterPoolSize() override;
virtual void reset() override;
virtual void forceIns() override;
virtual void tick(bool sysTick=true) override;
virtual void muteChannel(int ch, bool mute) override;
virtual bool keyOffAffectsArp(int ch) override;
virtual float getPostAmp() override;
virtual void notifyInsChange(int ins) override;
virtual void notifyInsDeletion(void* ins) override;
virtual void poke(unsigned int addr, unsigned short val) override;
virtual void poke(std::vector<DivRegWrite>& wlist) override;
virtual void setFlags(unsigned int flags) override;
virtual const char** getRegisterSheet() override;
virtual const char* getEffectName(unsigned char effect) override;
virtual const void* getSampleMem(int index) override;
virtual size_t getSampleMemCapacity(int index) override;
virtual size_t getSampleMemUsage(int index) override;
virtual void renderSamples() override;
virtual int init(DivEngine* parent, int channels, int sugRate, unsigned int flags) override;
virtual void quit() override;
DivPlatformMSM6295():
DivDispatch(),
vgsound_emu_mem_intf(),
msm(*this) {}
~DivPlatformMSM6295();
};
#endif

View File

@ -164,9 +164,6 @@ const char* DivPlatformNamcoWSG::getEffectName(unsigned char effect) {
}
void DivPlatformNamcoWSG::acquire(short* bufL, short* bufR, size_t start, size_t len) {
short* buf[2]={
bufL+start, bufR+start
};
while (!writes.empty()) {
QueuedWrite w=writes.front();
switch (devType) {
@ -186,7 +183,15 @@ void DivPlatformNamcoWSG::acquire(short* bufL, short* bufR, size_t start, size_t
regPool[w.addr&0x3f]=w.val;
writes.pop();
}
namco->sound_stream_update(buf,len);
for (size_t h=start; h<start+len; h++) {
short* buf[2]={
bufL+h, bufR+h
};
namco->sound_stream_update(buf,1);
for (int i=0; i<chans; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=namco->m_channel_list[i].last_out*chans;
}
}
}
void DivPlatformNamcoWSG::updateWave(int ch) {

View File

@ -65,7 +65,7 @@ const char** DivPlatformNES::getRegisterSheet() {
const char* DivPlatformNES::getEffectName(unsigned char effect) {
switch (effect) {
case 0x11:
return "Write to delta modulation counter (0 to 7F)";
return "11xx: Write to delta modulation counter (0 to 7F)";
break;
case 0x12:
return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)";

View File

@ -277,8 +277,13 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
regPool[w.addr&511]=w.val;
writes.pop();
}
OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1];
if (downsample) {
OPL3_GenerateResampled(&fm,o);
} else {
OPL3_Generate(&fm,o);
}
os[0]+=o[0]; os[1]+=o[1];
if (adpcmChan>=0) {
adpcmB->clock();
@ -677,6 +682,9 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) {
fm.channel[outChanMap[ch]].muted=mute;
}
int ops=(slots[3][ch]!=255 && chan[ch].state.ops==4 && oplType==3)?4:2;
if (ch&1 && ch<12) {
if (chan[ch-1].fourOp) return;
}
chan[ch].fourOp=(ops==4);
update4OpMask=true;
for (int i=0; i<ops; i++) {
@ -778,7 +786,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
immWrite(11,(end>>2)&0xff);
immWrite(12,(end>>10)&0xff);
immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat
int freq=(65536.0*(double)s->rate)/(double)rate;
int freq=(65536.0*(double)s->rate)/(double)chipRateBase;
immWrite(16,freq&0xff);
immWrite(17,(freq>>8)&0xff);
}
@ -1508,7 +1516,12 @@ void DivPlatformOPL::reset() {
fm_ymfm->reset();
}
*/
OPL3_Reset(&fm,rate);
if (downsample) {
const unsigned int downsampledRate=(unsigned int)(49716.0*(double(rate)/chipRateBase));
OPL3_Reset(&fm,downsampledRate);
} else {
OPL3_Reset(&fm,rate);
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
@ -1625,6 +1638,7 @@ void DivPlatformOPL::setYMFM(bool use) {
void DivPlatformOPL::setOPLType(int type, bool drums) {
pretendYMU=false;
downsample=false;
adpcmChan=-1;
switch (type) {
case 1: case 2: case 8950:
@ -1654,10 +1668,13 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
if (type==759) {
pretendYMU=true;
adpcmChan=16;
} else if (type==4) {
downsample=true;
}
break;
}
if (type==759) {
chipType=type;
if (type==759 || type==4) {
oplType=3;
} else if (type==8950) {
oplType=1;
@ -1692,17 +1709,73 @@ void DivPlatformOPL::setFlags(unsigned int flags) {
rate=chipClock/36;
}*/
if (oplType==3) {
chipClock=COLOR_NTSC*4.0;
rate=chipClock/288;
} else {
chipClock=COLOR_NTSC;
rate=chipClock/72;
}
if (pretendYMU) {
rate=48000;
chipClock=rate*288;
switch (chipType) {
default:
case 1: case 2: case 8950:
switch (flags&0xff) {
case 0x00:
chipClock=COLOR_NTSC;
break;
case 0x01:
chipClock=COLOR_PAL*4.0/5.0;
break;
case 0x02:
chipClock=4000000.0;
break;
case 0x03:
chipClock=3000000.0;
break;
case 0x04:
chipClock=38400*13*8; // 31948800/8
break;
case 0x05:
chipClock=3500000.0;
break;
}
rate=chipClock/72;
chipRateBase=double(rate);
break;
case 3:
switch (flags&0xff) {
case 0x00:
chipClock=COLOR_NTSC*4.0;
break;
case 0x01:
chipClock=COLOR_PAL*16.0/5.0;
break;
case 0x02:
chipClock=14000000.0;
break;
case 0x03:
chipClock=16000000.0;
break;
case 0x04:
chipClock=15000000.0;
break;
}
rate=chipClock/288;
chipRateBase=double(rate);
break;
case 4:
switch (flags&0xff) {
case 0x02:
chipClock=33868800.0;
break;
case 0x00:
chipClock=COLOR_NTSC*8.0;
break;
case 0x01:
chipClock=COLOR_PAL*32.0/5.0;
break;
}
chipRateBase=double(chipClock)/684.0;
rate=chipClock/768;
break;
case 759:
rate=48000;
chipRateBase=double(rate);
chipClock=rate*288;
break;
}
for (int i=0; i<18; i++) {

View File

@ -95,8 +95,8 @@ class DivPlatformOPL: public DivDispatch {
const unsigned char** slots;
const unsigned short* chanMap;
const unsigned char* outChanMap;
double chipFreqBase;
int delay, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank;
double chipFreqBase, chipRateBase;
int delay, chipType, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank;
unsigned char lastBusy;
unsigned char drumState;
unsigned char drumVol[5];
@ -107,7 +107,7 @@ class DivPlatformOPL: public DivDispatch {
unsigned char lfoValue;
bool useYMFM, update4OpMask, pretendYMU;
bool useYMFM, update4OpMask, pretendYMU, downsample;
short oldWrites[512];
short pendingWrites[512];

View File

@ -21,15 +21,15 @@
#include "../engine.h"
#include <math.h>
#define rWrite(a,v) {regPool[(a)]=(v)&0xff; if((a)==10) {chan.sreg=(v); chan.cnt=2;}}
#define CHIP_DIVIDER 16
#define SAMP_DIVIDER 4
const char* regCheatSheet6522[]={
"T2L", "08",
"T2H", "09",
"SR", "0A",
"ACR", "0B",
"PCR", "0C",
NULL
};
@ -46,26 +46,45 @@ const char* DivPlatformPET::getEffectName(unsigned char effect) {
return NULL;
}
// high-level emulation of 6522 shift register and driver software for now
void DivPlatformPET::rWrite(unsigned int addr, unsigned char val) {
bool hwSROutput=((regPool[11]>>2)&7)==4;
switch (addr) {
case 9:
// simulate phase reset from switching between hw/sw shift registers
if ((regPool[9]==0)^(val==0)) {
chan.sreg=chan.wave;
}
break;
case 10:
chan.sreg=val;
if (hwSROutput) chan.cnt=2;
break;
}
regPool[addr]=val;
}
void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len) {
// high-level emulation of 6522 shift register for now
int t2=regPool[8]*2+4;
if (((regPool[11]>>2)&7)==4) {
bool hwSROutput=((regPool[11]>>2)&7)==4;
if (chan.enable) {
int reload=regPool[8]*2+4;
if (!hwSROutput) {
reload+=regPool[9]*512;
}
for (size_t h=start; h<start+len; h++) {
int cycs=SAMP_DIVIDER;
while (cycs>0) {
int adv=MIN(cycs,chan.cnt);
chan.cnt-=adv;
cycs-=adv;
if (chan.cnt==0) {
chan.out=(chan.sreg&1)*32767;
chan.sreg=(chan.sreg>>1)|((chan.sreg&1)<<7);
chan.cnt=t2;
}
if (SAMP_DIVIDER>chan.cnt) {
chan.out=(chan.sreg&1)*32767;
chan.sreg=(chan.sreg>>1)|((chan.sreg&1)<<7);
chan.cnt+=reload-SAMP_DIVIDER;
} else {
chan.cnt-=SAMP_DIVIDER;
}
bufL[h]=chan.out;
bufR[h]=chan.out;
oscBuf->data[oscBuf->needle++]=chan.out;
}
// emulate driver writes to PCR
if (!hwSROutput) regPool[12]=chan.out?0xe0:0xc0;
} else {
chan.out=0;
for (size_t h=start; h<start+len; h++) {
@ -78,11 +97,10 @@ void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len)
void DivPlatformPET::writeOutVol() {
if (chan.active && !isMuted && chan.outVol>0) {
if (regPool[11]!=16) {
rWrite(11,16);
rWrite(10,chan.wave);
}
chan.enable=true;
rWrite(11,regPool[9]==0?16:0);
} else {
chan.enable=false;
rWrite(11,0);
}
}
@ -118,21 +136,22 @@ void DivPlatformPET::tick(bool sysTick) {
chan.freqChanged=true;
}
if (chan.freqChanged || chan.keyOn || chan.keyOff) {
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER);
if (chan.freq>257) chan.freq=257;
if (chan.freq<2) chan.freq=2;
rWrite(8,chan.freq-2);
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER)-2;
if (chan.freq>65535) chan.freq=65535;
if (chan.freq<0) chan.freq=0;
rWrite(8,chan.freq&0xff);
rWrite(9,chan.freq>>8);
if (chan.keyOn) {
if (!chan.std.vol.will) {
chan.outVol=chan.vol;
writeOutVol();
}
chan.keyOn=false;
}
if (chan.keyOff) {
rWrite(11,0);
chan.keyOff=false;
}
// update mode setting and channel enable
writeOutVol();
chan.freqChanged=false;
}
}

View File

@ -26,7 +26,7 @@
class DivPlatformPET: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2, note, ins;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, enable;
int vol, outVol, wave;
unsigned char sreg;
int cnt;
@ -49,6 +49,7 @@ class DivPlatformPET: public DivDispatch {
keyOn(false),
keyOff(false),
inPorta(false),
enable(false),
vol(1),
outVol(1),
wave(0b00001111),
@ -85,6 +86,7 @@ class DivPlatformPET: public DivDispatch {
~DivPlatformPET();
private:
void writeOutVol();
void rWrite(unsigned int addr, unsigned char val);
};
#endif

View File

@ -332,7 +332,7 @@ void DivPlatformSCC::reset() {
for (int i=0; i<5; i++) {
chan[i]=DivPlatformSCC::Channel();
chan[i].std.setEngine(parent);
chan[i].ws.setEngine(parent);
chan[i].ws.setEngine(parent,128);
chan[i].ws.init(NULL,32,255,false);
chan[i].vol=15;
chan[i].outVol=15;
@ -377,6 +377,27 @@ void DivPlatformSCC::setChipModel(bool isplus) {
isPlus=isplus;
}
void DivPlatformSCC::setFlags(unsigned int flags) {
switch (flags&0x7f) {
case 0x00:
chipClock=COLOR_NTSC/2.0;
break;
case 0x01:
chipClock=COLOR_PAL*2.0/5.0;
break;
case 0x02:
chipClock=3000000.0/2.0;
break;
case 0x03:
chipClock=4000000.0/2.0;
break;
}
rate=chipClock/8;
for (int i=0; i<5; i++) {
oscBuf[i]->rate=rate;
}
}
int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
@ -386,11 +407,7 @@ int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int f
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
chipClock=COLOR_NTSC/2.0;
rate=chipClock/8;
for (int i=0; i<5; i++) {
oscBuf[i]->rate=rate;
}
setFlags(flags);
if (isPlus) {
scc=new k052539_scc_core;
regBase=0xa0;

View File

@ -85,6 +85,7 @@ class DivPlatformSCC: public DivDispatch {
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
void setFlags(unsigned int flags);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void setChipModel(bool isPlus);
void quit();

View File

@ -21,15 +21,21 @@
#include "../engine.h"
#include <math.h>
#define rWrite(v) {if (!skipRegisterWrites) {writes.push(v); if (dumpWrites) {addWrite(0x200,v);}}}
#define rWrite(a,v) {if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(0x200+a,v);}}}
const char* regCheatSheetSN[]={
"DATA", "0",
NULL
};
const char* regCheatSheetGG[]={
"DATA", "0",
"Stereo", "1",
NULL
};
const char** DivPlatformSMS::getRegisterSheet() {
return regCheatSheetSN;
return stereo?regCheatSheetGG:regCheatSheetSN;
}
const char* DivPlatformSMS::getEffectName(unsigned char effect) {
@ -45,8 +51,10 @@ void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_
int o=0;
for (size_t h=start; h<start+len; h++) {
if (!writes.empty()) {
unsigned char w=writes.front();
YMPSG_Write(&sn_nuked,w);
QueuedWrite w=writes.front();
if (w.addr==0) {
YMPSG_Write(&sn_nuked,w.val);
}
writes.pop();
}
YMPSG_Clock(&sn_nuked);
@ -68,7 +76,7 @@ void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_
o=YMPSG_GetOutput(&sn_nuked);
if (o<-32768) o=-32768;
if (o>32767) o=32767;
bufL[h]=o;
bufL[h]=bufR[h]=o;
for (int i=0; i<4; i++) {
if (isMuted[i]) {
oscBuf[i]->data[oscBuf[i]->needle++]=0;
@ -81,12 +89,20 @@ void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_
void DivPlatformSMS::acquire_mame(short* bufL, short* bufR, size_t start, size_t len) {
while (!writes.empty()) {
unsigned char w=writes.front();
sn->write(w);
QueuedWrite w=writes.front();
if (stereo && (w.addr==1))
sn->stereo_w(w.val);
else if (w.addr==0) {
sn->write(w.val);
}
writes.pop();
}
for (size_t h=start; h<start+len; h++) {
sn->sound_stream_update(bufL+h,1);
short* outs[2]={
&bufL[h],
&bufR[h]
};
sn->sound_stream_update(outs,1);
for (int i=0; i<4; i++) {
if (isMuted[i]) {
oscBuf[i]->data[oscBuf[i]->needle++]=0;
@ -105,23 +121,17 @@ void DivPlatformSMS::acquire(short* bufL, short* bufR, size_t start, size_t len)
}
}
int DivPlatformSMS::acquireOne() {
short v;
sn->sound_stream_update(&v,1);
return v;
}
void DivPlatformSMS::tick(bool sysTick) {
for (int i=0; i<4; i++) {
int CHIP_DIVIDER=64;
if (i==3 && isRealSN) CHIP_DIVIDER=60;
double CHIP_DIVIDER=toneDivider;
if (i==3) CHIP_DIVIDER=noiseDivider;
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15));
if (chan[i].outVol<0) chan[i].outVol=0;
// old formula
// ((chan[i].vol&15)*MIN(15,chan[i].std.vol.val))>>4;
rWrite(0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15))));
rWrite(0,0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15))));
}
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
@ -160,6 +170,13 @@ void DivPlatformSMS::tick(bool sysTick) {
}
}
}
if (stereo) {
if (chan[i].std.panL.had) {
lastPan&=~(0x11<<i);
lastPan|=((chan[i].std.panL.val&1)<<i)|(((chan[i].std.panL.val>>1)&1)<<(i+4));
rWrite(1,lastPan);
}
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
@ -172,12 +189,12 @@ void DivPlatformSMS::tick(bool sysTick) {
}
for (int i=0; i<3; i++) {
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,64);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,toneDivider);
if (chan[i].freq>1023) chan[i].freq=1023;
if (chan[i].freq<8) chan[i].freq=1;
//if (chan[i].actualNote>0x5d) chan[i].freq=0x01;
rWrite(0x80|i<<5|(chan[i].freq&15));
rWrite(chan[i].freq>>4);
rWrite(0,0x80|i<<5|(chan[i].freq&15));
rWrite(0,chan[i].freq>>4);
// what?
/*if (i==2 && snNoiseMode&2) {
chan[3].baseFreq=chan[2].baseFreq;
@ -187,24 +204,24 @@ void DivPlatformSMS::tick(bool sysTick) {
}
}
if (chan[3].freqChanged || updateSNMode) {
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,isRealSN?60:64);
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,noiseDivider);
if (chan[3].freq>1023) chan[3].freq=1023;
if (chan[3].actualNote>0x5d) chan[3].freq=0x01;
if (snNoiseMode&2) { // take period from channel 3
if (updateSNMode || resetPhase) {
if (snNoiseMode&1) {
rWrite(0xe7);
rWrite(0,0xe7);
} else {
rWrite(0xe3);
rWrite(0,0xe3);
}
if (updateSNMode) {
rWrite(0xdf);
rWrite(0,0xdf);
}
}
if (chan[3].freqChanged) {
rWrite(0xc0|(chan[3].freq&15));
rWrite(chan[3].freq>>4);
rWrite(0,0xc0|(chan[3].freq&15));
rWrite(0,chan[3].freq>>4);
}
} else { // 3 fixed values
unsigned char value;
@ -221,7 +238,7 @@ void DivPlatformSMS::tick(bool sysTick) {
value=2-value;
if (value!=oldValue || updateSNMode || resetPhase) {
oldValue=value;
rWrite(0xe0|value|((snNoiseMode&1)<<2));
rWrite(0,0xe0|value|((snNoiseMode&1)<<2));
}
}
}
@ -231,8 +248,8 @@ void DivPlatformSMS::tick(bool sysTick) {
}
int DivPlatformSMS::dispatch(DivCommand c) {
int CHIP_DIVIDER=64;
if (c.chan==3 && isRealSN) CHIP_DIVIDER=60;
double CHIP_DIVIDER=toneDivider;
if (c.chan==3) CHIP_DIVIDER=noiseDivider;
switch (c.cmd) {
case DIV_CMD_NOTE_ON:
if (c.value!=DIV_NOTE_NULL) {
@ -242,7 +259,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
chan[c.chan].actualNote=c.value;
}
chan[c.chan].active=true;
rWrite(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
@ -250,7 +267,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
break;
case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false;
rWrite(0x9f|c.chan<<5);
rWrite(0,0x9f|c.chan<<5);
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
@ -267,7 +284,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
}
if (chan[c.chan].active) rWrite(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
if (chan[c.chan].active) rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
}
break;
case DIV_CMD_GET_VOLUME:
@ -307,6 +324,19 @@ int DivPlatformSMS::dispatch(DivCommand c) {
snNoiseMode=(c.value&1)|((c.value&16)>>3);
updateSNMode=true;
break;
case DIV_CMD_PANNING: {
if (stereo) {
if (c.chan>3) c.chan=3;
lastPan&=~(0x11<<c.chan);
int pan=0;
if (c.value>0) pan|=0x10;
if (c.value2>0) pan|=0x01;
if (pan==0) pan=0x11;
lastPan|=pan<<c.chan;
rWrite(1,lastPan);
}
break;
}
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
chan[c.chan].freqChanged=true;
@ -335,7 +365,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
void DivPlatformSMS::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (chan[ch].active) rWrite(0x90|ch<<5|(isMuted[ch]?15:(15-(chan[ch].outVol&15))));
if (chan[ch].active) rWrite(0,0x90|ch<<5|(isMuted[ch]?15:(15-(chan[ch].outVol&15))));
}
void DivPlatformSMS::forceIns() {
@ -370,11 +400,19 @@ void DivPlatformSMS::reset() {
addWrite(0xffffffff,0);
}
sn->device_start();
YMPSG_Init(&sn_nuked,isRealSN);
YMPSG_Init(&sn_nuked,isRealSN,12,isRealSN?13:15,isRealSN?16383:32767);
snNoiseMode=3;
rWrite(0xe7);
rWrite(0,0xe7);
updateSNMode=false;
oldValue=0xff;
lastPan=0xff;
if (stereo) {
rWrite(1,0xff);
}
}
bool DivPlatformSMS::isStereo() {
return stereo;
}
bool DivPlatformSMS::keyOffAffectsArp(int ch) {
@ -396,45 +434,109 @@ void DivPlatformSMS::notifyInsDeletion(void* ins) {
}
void DivPlatformSMS::poke(unsigned int addr, unsigned short val) {
rWrite(val);
rWrite(addr,val);
}
void DivPlatformSMS::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.val);
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
void DivPlatformSMS::setFlags(unsigned int flags) {
if ((flags&3)==3) {
chipClock=COLOR_NTSC/2.0;
} else if ((flags&3)==2) {
chipClock=4000000;
} else if ((flags&3)==1) {
chipClock=COLOR_PAL*4.0/5.0;
} else {
chipClock=COLOR_NTSC;
switch (flags&0xff03) {
default:
case 0x0000:
chipClock=COLOR_NTSC;
break;
case 0x0001:
chipClock=COLOR_PAL*4.0/5.0;
break;
case 0x0002:
chipClock=4000000;
break;
case 0x0003:
chipClock=COLOR_NTSC/2.0;
break;
case 0x0100:
chipClock=3000000;
break;
case 0x0101:
chipClock=2000000;
break;
case 0x0102:
chipClock=COLOR_NTSC/8.0;
break;
}
resetPhase=!(flags&16);
divider=16;
toneDivider=64.0;
noiseDivider=64.0;
if (sn!=NULL) delete sn;
switch ((flags>>2)&3) {
case 1: // TI
sn=new sn76496_base_device(0x4000, 0x4000, 0x01, 0x02, true, 1, false, true);
isRealSN=true;
break;
case 2: // TI+Atari
sn=new sn76496_base_device(0x4000, 0x0f35, 0x01, 0x02, true, 1, false, true);
isRealSN=true;
break;
case 3: // Game Gear (not fully emulated yet!)
sn=new sn76496_base_device(0x8000, 0x8000, 0x01, 0x08, false, 1, false, false);
isRealSN=false;
break;
switch (flags&0xcc) {
default: // Sega
sn=new sn76496_base_device(0x8000, 0x8000, 0x01, 0x08, false, 1, false, false);
case 0x00:
sn=new segapsg_device();
isRealSN=false;
stereo=false;
break;
case 0x04: // TI SN76489
sn=new sn76489_device();
isRealSN=true;
stereo=false;
noiseDivider=60.0; // 64 for match to tone frequency on non-Sega PSG but compatibility
break;
case 0x08: // TI+Atari
sn=new sn76496_base_device(0x4000, 0x0f35, 0x01, 0x02, true, false, 1/*8*/, false, true);
isRealSN=true;
stereo=false;
noiseDivider=60.0;
break;
case 0x0c: // Game Gear (not fully emulated yet!)
sn=new gamegear_device();
isRealSN=false;
stereo=true;
break;
case 0x40: // TI SN76489A
sn=new sn76489a_device();
isRealSN=false; // TODO
stereo=false;
noiseDivider=60.0;
break;
case 0x44: // TI SN76496
sn=new sn76496_device();
isRealSN=false; // TODO
stereo=false;
noiseDivider=60.0;
break;
case 0x48: // NCR 8496
sn=new ncr8496_device();
isRealSN=false;
stereo=false;
noiseDivider=60.0;
break;
case 0x4c: // Tandy PSSJ 3-voice sound
sn=new pssj3_device();
isRealSN=false;
stereo=false;
noiseDivider=60.0;
break;
case 0x80: // TI SN94624
sn=new sn94624_device();
isRealSN=true;
stereo=false;
divider=2;
toneDivider=8.0;
noiseDivider=7.5;
break;
case 0x84: // TI SN76494
sn=new sn76494_device();
isRealSN=false; // TODO
stereo=false;
divider=2;
toneDivider=8.0;
noiseDivider=7.5;
break;
}
rate=chipClock/16;
rate=chipClock/divider;
for (int i=0; i<4; i++) {
oscBuf[i]->rate=rate;
}
@ -450,6 +552,7 @@ int DivPlatformSMS::init(DivEngine* p, int channels, int sugRate, unsigned int f
skipRegisterWrites=false;
resetPhase=false;
oldValue=0xff;
lastPan=0xff;
for (int i=0; i<4; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;

View File

@ -58,21 +58,31 @@ class DivPlatformSMS: public DivDispatch {
Channel chan[4];
DivDispatchOscBuffer* oscBuf[4];
bool isMuted[4];
unsigned char lastPan;
unsigned char oldValue;
unsigned char snNoiseMode;
int divider=16;
double toneDivider=64.0;
double noiseDivider=64.0;
bool updateSNMode;
bool resetPhase;
bool isRealSN;
bool stereo;
bool nuked;
sn76496_base_device* sn;
ympsg_t sn_nuked;
std::queue<unsigned char> writes;
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
friend void putDispatchChan(void*,int,int);
void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len);
void acquire_mame(short* bufL, short* bufR, size_t start, size_t len);
public:
int acquireOne();
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
@ -82,6 +92,7 @@ class DivPlatformSMS: public DivDispatch {
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool isStereo();
bool keyOffAffectsArp(int ch);
bool keyOffAffectsPorta(int ch);
int getPortaFloor(int ch);

View File

@ -172,10 +172,11 @@ void namco_audio_device::build_decoded_waveform(uint8_t *rgnbase)
/* generate sound by oversampling */
uint32_t namco_audio_device::namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq)
uint32_t namco_audio_device::namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq, int16_t& last_out)
{
for (int sampindex = 0; sampindex < size; sampindex++)
{
last_out=wave[WAVEFORM_POSITION(counter)];
buffer[sampindex]+=wave[WAVEFORM_POSITION(counter)];
counter += freq;
}
@ -700,7 +701,7 @@ void namco_audio_device::sound_stream_update(short** outputs, int len)
const int16_t *lw = &m_waveform[lv][voice->waveform_select * 32];
/* generate sound into the buffer */
c = namco_update_one(lmix, len, lw, voice->counter, voice->frequency);
c = namco_update_one(lmix, len, lw, voice->counter, voice->frequency, voice->last_out);
}
/* only update if we have non-zero right volume */
@ -709,7 +710,7 @@ void namco_audio_device::sound_stream_update(short** outputs, int len)
const int16_t *rw = &m_waveform[rv][voice->waveform_select * 32];
/* generate sound into the buffer */
c = namco_update_one(rmix, len, rw, voice->counter, voice->frequency);
c = namco_update_one(rmix, len, rw, voice->counter, voice->frequency, voice->last_out);
}
/* update the counter for this voice */
@ -789,7 +790,7 @@ void namco_audio_device::sound_stream_update(short** outputs, int len)
const int16_t *w = &m_waveform[v][voice->waveform_select * 32];
/* generate sound into buffer and update the counter for this voice */
voice->counter = namco_update_one(buffer, len, w, voice->counter, voice->frequency);
voice->counter = namco_update_one(buffer, len, w, voice->counter, voice->frequency, voice->last_out);
}
}
}

View File

@ -31,6 +31,7 @@ public:
uint32_t noise_counter;
int32_t noise_hold;
int32_t waveform_select;
int16_t last_out;
};
namco_audio_device(uint32_t clock);
@ -43,7 +44,7 @@ public:
void build_decoded_waveform( uint8_t *rgnbase );
void update_namco_waveform(int offset, uint8_t data);
uint32_t namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq);
uint32_t namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq, int16_t& last_out);
/* waveform region */
uint8_t* m_wave_ptr;

View File

@ -128,7 +128,7 @@
10/12/2019: Michael Zapf
* READY line handling by own emu_timer, not depending on sound_stream_update
additional modifications by tildearrow for furnace
additional modifications by tildearrow, cam900 for furnace
TODO: * Implement the TMS9919 - any difference to sn94624?
* Implement the T6W28; has registers in a weird order, needs writes
@ -150,18 +150,20 @@
sn76496_base_device::sn76496_base_device(
int feedbackmask,
int noise_start,
int noise_start,
int noisetap1,
int noisetap2,
bool negate,
bool stereo,
int clockdivider,
bool ncr,
bool sega)
: m_feedback_mask(feedbackmask)
, m_noise_start(noise_start)
, m_noise_start(noise_start)
, m_whitenoise_tap1(noisetap1)
, m_whitenoise_tap2(noisetap2)
, m_negate(negate)
, m_negate(negate)
, m_stereo(stereo)
, m_clock_divider(clockdivider)
, m_ncr_style_psg(ncr)
, m_sega_style_psg(sega)
@ -169,10 +171,54 @@ sn76496_base_device::sn76496_base_device(
}
sn76496_device::sn76496_device()
: sn76496_base_device(0x8000, 0x8000, 0x01, 0x08, false, 1, false, false)
: sn76496_base_device(0x10000, 0x04, 0x08, false, false, 1/*8*/, false, true)
{
}
y2404_device::y2404_device()
: sn76496_base_device(0x10000, 0x04, 0x08, false, false, 1/*8*/, false, true)
{
}
sn76489_device::sn76489_device()
: sn76496_base_device(0x4000, 0x01, 0x02, true, false, 1/*8*/, false, true)
{
}
sn76489a_device::sn76489a_device()
: sn76496_base_device(0x10000, 0x04, 0x08, false, false, 1/*8*/, false, true)
{
}
sn76494_device::sn76494_device()
: sn76496_base_device(0x10000, 0x04, 0x08, false, false, 1, false, true)
{
}
sn94624_device::sn94624_device()
: sn76496_base_device(0x4000, 0x01, 0x02, true, false, 1, false, true)
{
}
ncr8496_device::ncr8496_device()
: sn76496_base_device(0x8000, 0x02, 0x20, true, false, 1/*8*/, true, true)
{
}
pssj3_device::pssj3_device()
: sn76496_base_device(0x8000, 0x02, 0x20, false, false, 1/*8*/, true, true)
{
}
gamegear_device::gamegear_device()
: sn76496_base_device(0x8000, 0x01, 0x08, true, true, 1/*8*/, false, false)
{
}
segapsg_device::segapsg_device()
: sn76496_base_device(0x8000, 0x01, 0x08, true, false, 1/*8*/, false, false)
{
}
void sn76496_base_device::device_start()
{
@ -199,6 +245,7 @@ void sn76496_base_device::device_start()
m_RNG = m_feedback_mask;
m_output[3] = m_RNG & 1;
m_stereo_mask = 0xFF; // all channels enabled
m_current_clock = m_clock_divider-1;
// set gain
@ -225,6 +272,11 @@ void sn76496_base_device::device_start()
m_ready_state = true;
}
void sn76496_base_device::stereo_w(u8 data)
{
if (m_stereo) m_stereo_mask = data;
}
void sn76496_base_device::write(u8 data)
{
int n, r, c;
@ -285,7 +337,7 @@ inline bool sn76496_base_device::in_noise_mode()
return ((m_register[6] & 4)!=0);
}
void sn76496_base_device::sound_stream_update(short* outputs, int outLen)
void sn76496_base_device::sound_stream_update(short** outputs, int outLen)
{
int i;
@ -336,13 +388,30 @@ void sn76496_base_device::sound_stream_update(short* outputs, int outLen)
}
}
if (m_stereo)
{
out = ((((m_stereo_mask & 0x10)!=0) && (m_output[0]!=0))? m_volume[0] : 0)
+ ((((m_stereo_mask & 0x20)!=0) && (m_output[1]!=0))? m_volume[1] : 0)
+ ((((m_stereo_mask & 0x40)!=0) && (m_output[2]!=0))? m_volume[2] : 0)
+ ((((m_stereo_mask & 0x80)!=0) && (m_output[3]!=0))? m_volume[3] : 0);
out2= ((((m_stereo_mask & 0x1)!=0) && (m_output[0]!=0))? m_volume[0] : 0)
+ ((((m_stereo_mask & 0x2)!=0) && (m_output[1]!=0))? m_volume[1] : 0)
+ ((((m_stereo_mask & 0x4)!=0) && (m_output[2]!=0))? m_volume[2] : 0)
+ ((((m_stereo_mask & 0x8)!=0) && (m_output[3]!=0))? m_volume[3] : 0);
}
else
{
out= ((m_output[0]!=0)? m_volume[0]:0)
+((m_output[1]!=0)? m_volume[1]:0)
+((m_output[2]!=0)? m_volume[2]:0)
+((m_output[3]!=0)? m_volume[3]:0);
}
if (m_negate) { out = -out; out2 = -out2; }
outputs[sampindex]=out;
outputs[0][sampindex]=out;
if (m_stereo && (outputs[1] != nullptr))
outputs[1][sampindex]=out2;
}
}

View File

@ -1,7 +1,7 @@
// license:BSD-3-Clause
// copyright-holders:Nicola Salmoria
// additional modifications by tildearrow for furnace
// additional modifications by tildearrow, cam900 for furnace
#ifndef MAME_SOUND_SN76496_H
#define MAME_SOUND_SN76496_H
@ -14,34 +14,57 @@ class sn76496_base_device {
public:
void stereo_w(u8 data);
void write(u8 data);
void device_start();
void sound_stream_update(short* outputs, int outLen);
inline int32_t get_channel_output(int ch) {
return ((m_output[ch]!=0)?m_volume[ch]:0);
}
void device_start();
void sound_stream_update(short** outputs, int outLen);
inline int32_t get_channel_output(int ch) {
return ((m_output[ch]!=0)?m_volume[ch]:0);
}
//DECLARE_READ_LINE_MEMBER( ready_r ) { return m_ready_state ? 1 : 0; }
sn76496_base_device(
int feedbackmask,
int noise_start,
int noise_start,
int noisetap1,
int noisetap2,
bool negate,
bool stereo,
int clockdivider,
bool ncr,
bool sega);
sn76496_base_device(
int feedbackmask,
int noisetap1,
int noisetap2,
bool negate,
bool stereo,
int clockdivider,
bool ncr,
bool sega)
: sn76496_base_device(
feedbackmask,
feedbackmask,
noisetap1,
noisetap2,
negate,
stereo,
clockdivider,
ncr,
sega)
{}
private:
inline bool in_noise_mode();
bool m_ready_state;
const int32_t m_feedback_mask; // mask for feedback
const int32_t m_noise_start; // noise start value
const int32_t m_whitenoise_tap1; // mask for white noise tap 1 (higher one, usually bit 14)
const int32_t m_whitenoise_tap2; // mask for white noise tap 2 (lower one, usually bit 13)
bool m_negate; // output negate flag
const int32_t m_clock_divider; // clock divider
const int32_t m_feedback_mask; // mask for feedback
const int32_t m_noise_start; // noise start value
const int32_t m_whitenoise_tap1; // mask for white noise tap 1 (higher one, usually bit 14)
const int32_t m_whitenoise_tap2; // mask for white noise tap 2 (lower one, usually bit 13)
bool m_negate; // output negate flag
const bool m_stereo; // whether we're dealing with stereo or not
const int32_t m_clock_divider; // clock divider
const bool m_ncr_style_psg; // flag to ignore writes to regs 1,3,5,6,7 with bit 7 low
const bool m_sega_style_psg; // flag to make frequency zero acts as if it is one more than max (0x3ff+1) or if it acts like 0; the initial register is pointing to 0x3 instead of 0x0; the volume reg is preloaded with 0xF instead of 0x0
@ -51,6 +74,7 @@ private:
int32_t m_volume[4]; // db volume of voice 0-2 and noise
uint32_t m_RNG; // noise generator LFSR
int32_t m_current_clock;
int32_t m_stereo_mask; // the stereo output mask
int32_t m_period[4]; // Length of 1/2 of waveform
int32_t m_count[4]; // Position within the waveform
int32_t m_output[4]; // 1-bit output of each channel, pre-volume
@ -63,4 +87,67 @@ public:
sn76496_device();
};
// Y2404 not verified yet. todo: verify; (don't be fooled by the Y, it's a TI chip, not Yamaha)
class y2404_device : public sn76496_base_device
{
public:
y2404_device();
};
// SN76489 not verified yet. todo: verify;
class sn76489_device : public sn76496_base_device
{
public:
sn76489_device();
};
// SN76489A: whitenoise verified, phase verified, periodic verified (by plgdavid)
class sn76489a_device : public sn76496_base_device
{
public:
sn76489a_device();
};
// SN76494 not verified, (according to datasheet: same as sn76489a but without the /8 divider)
class sn76494_device : public sn76496_base_device
{
public:
sn76494_device();
};
// SN94624 whitenoise verified, phase verified, period verified; verified by PlgDavid
class sn94624_device : public sn76496_base_device
{
public:
sn94624_device();
};
// NCR8496 whitenoise verified, phase verified; verified by ValleyBell & NewRisingSun
class ncr8496_device : public sn76496_base_device
{
public:
ncr8496_device();
};
// PSSJ-3 whitenoise verified, phase verified; verified by ValleyBell & NewRisingSun
class pssj3_device : public sn76496_base_device
{
public:
pssj3_device();
};
// Verified by Justin Kerk
class gamegear_device : public sn76496_base_device
{
public:
gamegear_device();
};
// todo: verify; from smspower wiki, assumed to have same invert as gamegear
class segapsg_device : public sn76496_base_device
{
public:
segapsg_device();
};
#endif // MAME_SOUND_SN76496_H

View File

@ -784,7 +784,7 @@ public:
protected:
// simulate the DAC discontinuity
constexpr int32_t dac_discontinuity(int32_t value) const { return (value < 0) ? (value - 2) : (value + 3); }
int32_t dac_discontinuity(int32_t value) const { return (value < 0) ? (value - 2) : (value + 3); }
// internal state
uint16_t m_address; // address register

View File

@ -347,7 +347,7 @@ int DivPlatformSwan::dispatch(DivCommand c) {
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.had) {
if (!chan[c.chan].std.vol.has) {
calcAndWriteOutVol(c.chan,15);
}
}

View File

@ -22,43 +22,11 @@
#include <string.h>
#include <math.h>
#include "fmshared_OPM.h"
// actually 0x40 but the upper bit of data selects address
#define ADDR_WS_FINE 0x100
// actually 0xc0 but bit 5 of data selects address
#define ADDR_EGS_REV 0x120
static unsigned short chanOffs[8]={
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
};
static unsigned short opOffs[4]={
0x00, 0x08, 0x10, 0x18
};
static bool isOutput[8][4]={
// 1 3 2 4
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,true ,true},
{false,true ,true ,true},
{false,true ,true ,true},
{true ,true ,true ,true},
};
static unsigned char dtTable[8]={
7,6,5,0,1,2,3,4
};
static int orderedOps[4]={
0,2,1,3
};
#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 NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0)
const char* regCheatSheetOPZ[]={
"Test", "00",
"NoteCtl", "08",
@ -233,7 +201,7 @@ void DivPlatformTX81Z::acquire(short* bufL, short* bufR, size_t start, size_t le
fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr);
fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val);
regPool[w.addr&0xff]=w.val;
writes.pop();
writes.pop_front();
delay=1;
}
}
@ -1052,7 +1020,7 @@ void DivPlatformTX81Z::poke(std::vector<DivRegWrite>& wlist) {
}
void DivPlatformTX81Z::reset() {
while (!writes.empty()) writes.pop();
while (!writes.empty()) writes.pop_front();
memset(regPool,0,330);
fm_ymfm->reset();
if (dumpWrites) {

View File

@ -19,18 +19,22 @@
#ifndef _TX81Z_H
#define _TX81Z_H
#include "../dispatch.h"
#include "fmshared_OPM.h"
#include "../macroInt.h"
#include "../instrument.h"
#include <queue>
#include "sound/ymfm/ymfm_opz.h"
#include "../macroInt.h"
class DivTXInterface: public ymfm::ymfm_interface {
};
class DivPlatformTX81Z: public DivDispatch {
class DivPlatformTX81Z: public DivPlatformOPM {
protected:
const unsigned short chanOffs[8]={
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
};
struct Channel {
DivInstrumentFM state;
DivMacroInt std;
@ -69,31 +73,18 @@ class DivPlatformTX81Z: public DivDispatch {
};
Channel chan[8];
DivDispatchOscBuffer* oscBuf[8];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
int delay, baseFreqOff;
int baseFreqOff;
int pcmL, pcmR, pcmCycles;
unsigned char lastBusy;
unsigned char amDepth, pmDepth;
ymfm::ym2414* fm_ymfm;
ymfm::ym2414::output_data out_ymfm;
DivTXInterface iface;
unsigned char regPool[330];
bool extMode;
bool isMuted[8];
short oldWrites[330];
short pendingWrites[330];
int octave(int freq);
int toFreq(int freq);

View File

@ -195,7 +195,7 @@ int DivPlatformVIC20::dispatch(DivCommand c) {
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.had) {
if (!chan[c.chan].std.vol.has) {
calcAndWriteOutVol(c.chan,15);
}
}

View File

@ -23,16 +23,8 @@
#include <string.h>
#include <math.h>
#include "sound/ymfm/ymfm_opn.h"
#include "ym2203shared.h"
#include "fmshared_OPN.h"
static unsigned char konOffs[3]={
0, 1, 2
};
#define CHIP_DIVIDER 32
#define CHIP_FREQBASE fmFreqBase
#define CHIP_DIVIDER fmDivBase
const char* regCheatSheetYM2203[]={
// SSG
@ -299,7 +291,7 @@ void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t l
fm->write(0x0,w.addr);
fm->write(0x1,w.val);
regPool[w.addr&0xff]=w.val;
writes.pop();
writes.pop_front();
delay=6;
}
}
@ -959,7 +951,7 @@ void DivPlatformYM2203::poke(std::vector<DivRegWrite>& wlist) {
}
void DivPlatformYM2203::reset() {
while (!writes.empty()) writes.pop();
while (!writes.empty()) writes.pop_front();
memset(regPool,0,256);
if (dumpWrites) {
addWrite(0xffffffff,0);
@ -989,6 +981,10 @@ void DivPlatformYM2203::reset() {
extMode=false;
// set prescaler
immWrite(0x2d,0xff);
immWrite(prescale,0xff);
ay->reset();
ay->getRegisterWrites().clear();
ay->flushWrites();
@ -1021,25 +1017,58 @@ void DivPlatformYM2203::setSkipRegisterWrites(bool value) {
}
void DivPlatformYM2203::setFlags(unsigned int flags) {
unsigned char ayFlags=16;
if (flags==3) {
chipClock=3000000.0;
ayFlags=20;
} else if (flags==2) {
chipClock=4000000.0;
ayFlags=19;
} else if (flags==1) {
chipClock=COLOR_PAL*4.0/5.0;
ayFlags=17;
} else {
chipClock=COLOR_NTSC;
ayFlags=16;
// Clock flags
switch (flags&0x1f) {
default:
case 0x00:
chipClock=COLOR_NTSC;
break;
case 0x01:
chipClock=COLOR_PAL*4.0/5.0;
break;
case 0x02:
chipClock=4000000.0;
break;
case 0x03:
chipClock=3000000.0;
break;
case 0x04:
chipClock=38400*13*8; // 31948800/8
break;
case 0x05:
chipClock=3000000.0/2.0;
break;
}
// Prescaler flags
switch ((flags>>5)&0x3) {
default:
case 0x00: // /6
prescale=0x2d;
fmFreqBase=4720270.0,
fmDivBase=36,
ayDiv=16;
break;
case 0x01: // /3
prescale=0x2e;
fmFreqBase=4720270.0/2.0,
fmDivBase=18,
ayDiv=8;
break;
case 0x02: // /2
prescale=0x2f;
fmFreqBase=4720270.0/3.0,
fmDivBase=12,
ayDiv=4;
break;
}
ay->setFlags(ayFlags);
rate=fm->sample_rate(chipClock);
for (int i=0; i<6; i++) {
oscBuf[i]->rate=rate;
}
immWrite(0x2d,0xff);
immWrite(prescale,0xff);
ay->setExtClockDiv(chipClock,ayDiv);
ay->setFlags(16);
}
int DivPlatformYM2203::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
@ -1053,13 +1082,13 @@ int DivPlatformYM2203::init(DivEngine* p, int channels, int sugRate, unsigned in
fm=new ymfm::ym2203(iface);
fm->set_fidelity(ymfm::OPN_FIDELITY_MIN);
// YM2149, 2MHz
ay=new DivPlatformAY8910;
ay->init(p,3,sugRate,19);
ay=new DivPlatformAY8910(true,chipClock,ayDiv);
ay->init(p,3,sugRate,16);
ay->toggleRegisterDump(true);
setFlags(flags);
reset();
return 16;
return 6;
}
void DivPlatformYM2203::quit() {

View File

@ -19,9 +19,8 @@
#ifndef _YM2203_H
#define _YM2203_H
#include "../dispatch.h"
#include "fmshared_OPN.h"
#include "../macroInt.h"
#include <queue>
#include "sound/ymfm/ymfm_opn.h"
#include "ay.h"
@ -30,12 +29,16 @@ class DivYM2203Interface: public ymfm::ymfm_interface {
};
class DivPlatformYM2203: public DivDispatch {
class DivPlatformYM2203: public DivPlatformOPN {
protected:
const unsigned short chanOffs[3]={
0x00, 0x01, 0x02
};
const unsigned char konOffs[3]={
0, 1, 2
};
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
@ -79,29 +82,16 @@ class DivPlatformYM2203: public DivDispatch {
Channel chan[6];
DivDispatchOscBuffer* oscBuf[6];
bool isMuted[6];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
ymfm::ym2203* fm;
ymfm::ym2203::output_data fmout;
DivYM2203Interface iface;
unsigned char regPool[512];
unsigned char lastBusy;
DivPlatformAY8910* ay;
unsigned char sampleBank;
int delay;
bool extMode;
unsigned char prescale;
short oldWrites[256];
short pendingWrites[256];
friend void putDispatchChan(void*,int,int);
public:
@ -128,6 +118,9 @@ class DivPlatformYM2203: public DivDispatch {
void setFlags(unsigned int flags);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
DivPlatformYM2203():
DivPlatformOPN(4720270.0, 36, 16),
prescale(0x2d) {}
~DivPlatformYM2203();
};
#endif

View File

@ -21,8 +21,8 @@
#include "../engine.h"
#include <math.h>
#include "ym2203shared.h"
#include "fmshared_OPN.h"
#define CHIP_FREQBASE fmFreqBase
#define CHIP_DIVIDER fmDivBase
int DivPlatformYM2203Ext::dispatch(DivCommand c) {
if (c.chan<2) {
@ -516,7 +516,7 @@ int DivPlatformYM2203Ext::init(DivEngine* parent, int channels, int sugRate, uns
}
reset();
return 19;
return 9;
}
void DivPlatformYM2203Ext::quit() {

View File

@ -24,16 +24,8 @@
#include <string.h>
#include <math.h>
#include "sound/ymfm/ymfm_opn.h"
#include "ym2610shared.h"
#include "fmshared_OPN.h"
static unsigned char konOffs[6]={
0, 1, 2, 4, 5, 6
};
#define CHIP_DIVIDER 32
#define CHIP_FREQBASE fmFreqBase
#define CHIP_DIVIDER fmDivBase
const char* regCheatSheetYM2608[]={
// SSG
@ -450,7 +442,7 @@ void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t l
fm->write(0x0+((w.addr>>8)<<1),w.addr);
fm->write(0x1+((w.addr>>8)<<1),w.val);
regPool[w.addr&0x1ff]=w.val;
writes.pop();
writes.pop_front();
delay=4;
}
}
@ -1283,7 +1275,7 @@ void DivPlatformYM2608::poke(std::vector<DivRegWrite>& wlist) {
}
void DivPlatformYM2608::reset() {
while (!writes.empty()) writes.pop();
while (!writes.empty()) writes.pop_front();
memset(regPool,0,512);
if (dumpWrites) {
addWrite(0xffffffff,0);
@ -1333,6 +1325,10 @@ void DivPlatformYM2608::reset() {
// enable 6 channel mode
immWrite(0x29,0x80);
// set prescaler
immWrite(0x2d,0xff);
immWrite(prescale,0xff);
ay->reset();
ay->getRegisterWrites().clear();
ay->flushWrites();
@ -1402,6 +1398,49 @@ void DivPlatformYM2608::renderSamples() {
adpcmBMemLen=memPos+256;
}
void DivPlatformYM2608::setFlags(unsigned int flags) {
// Clock flags
switch (flags&0x1f) {
default:
case 0x00:
chipClock=8000000.0;
break;
case 0x01:
chipClock=38400*13*16; // 31948800/4
break;
}
// Prescaler flags
switch ((flags>>5)&0x3) {
default:
case 0x00: // /6
prescale=0x2d;
fmFreqBase=9440540.0,
fmDivBase=72,
ayDiv=32;
break;
case 0x01: // /3
prescale=0x2e;
fmFreqBase=9440540.0/2.0,
fmDivBase=36,
ayDiv=16;
break;
case 0x02: // /2
prescale=0x2f;
fmFreqBase=9440540.0/3.0,
fmDivBase=24,
ayDiv=8;
break;
}
rate=fm->sample_rate(chipClock);
for (int i=0; i<16; i++) {
oscBuf[i]->rate=rate;
}
immWrite(0x2d,0xff);
immWrite(prescale,0xff);
ay->setExtClockDiv(chipClock,ayDiv);
ay->setFlags(16);
}
int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
adpcmBMem=new unsigned char[getSampleMemCapacity(0)];
@ -1414,17 +1453,13 @@ int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, unsigned in
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
chipClock=8000000;
fm=new ymfm::ym2608(iface);
fm->set_fidelity(ymfm::OPN_FIDELITY_MIN);
rate=fm->sample_rate(chipClock);
for (int i=0; i<16; i++) {
oscBuf[i]->rate=rate;
}
// YM2149, 2MHz
ay=new DivPlatformAY8910;
ay->init(p,3,sugRate,19);
ay=new DivPlatformAY8910(true,chipClock,ayDiv);
ay->init(p,3,sugRate,16);
ay->toggleRegisterDump(true);
setFlags(flags);
reset();
return 16;
}

View File

@ -19,9 +19,8 @@
#ifndef _YM2608_H
#define _YM2608_H
#include "../dispatch.h"
#include "fmshared_OPN.h"
#include "../macroInt.h"
#include <queue>
#include "sound/ymfm/ymfm_opn.h"
#include "ay.h"
@ -35,12 +34,16 @@ class DivYM2608Interface: public ymfm::ymfm_interface {
DivYM2608Interface(): adpcmBMem(NULL), sampleBank(0) {}
};
class DivPlatformYM2608: public DivDispatch {
class DivPlatformYM2608: public DivPlatformOPN {
protected:
const unsigned short chanOffs[6]={
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
};
const unsigned char konOffs[6]={
0, 1, 2, 4, 5, 6
};
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
@ -86,17 +89,8 @@ class DivPlatformYM2608: public DivDispatch {
Channel chan[16];
DivDispatchOscBuffer* oscBuf[16];
bool isMuted[16];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
ymfm::ym2608* fm;
ymfm::ym2608::output_data fmout;
unsigned char regPool[512];
unsigned char lastBusy;
unsigned char* adpcmBMem;
size_t adpcmBMemLen;
@ -106,13 +100,9 @@ class DivPlatformYM2608: public DivDispatch {
unsigned char sampleBank;
unsigned char writeRSSOff, writeRSSOn;
int delay;
bool extMode;
unsigned char prescale;
short oldWrites[512];
short pendingWrites[512];
double NOTE_OPNB(int ch, int note);
double NOTE_ADPCMB(int note);
friend void putDispatchChan(void*,int,int);
@ -142,8 +132,12 @@ class DivPlatformYM2608: public DivDispatch {
size_t getSampleMemCapacity(int index);
size_t getSampleMemUsage(int index);
void renderSamples();
void setFlags(unsigned int flags);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
DivPlatformYM2608():
DivPlatformOPN(9440540.0, 72, 32),
prescale(0x2d) {}
~DivPlatformYM2608();
};
#endif

View File

@ -21,8 +21,8 @@
#include "../engine.h"
#include <math.h>
#include "ym2610shared.h"
#include "fmshared_OPN.h"
#define CHIP_FREQBASE fmFreqBase
#define CHIP_DIVIDER fmDivBase
int DivPlatformYM2608Ext::dispatch(DivCommand c) {
if (c.chan<2) {

View File

@ -24,19 +24,8 @@
#include <string.h>
#include <math.h>
#include "ym2610shared.h"
#include "fmshared_OPN.h"
static unsigned char konOffs[4]={
1, 2, 5, 6
};
static unsigned char bchOffs[4]={
1, 2, 4, 5
};
#define CHIP_DIVIDER 32
#define CHIP_FREQBASE fmFreqBase
#define CHIP_DIVIDER fmDivBase
const char* regCheatSheetYM2610[]={
// SSG
@ -494,7 +483,7 @@ void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t l
fm->write(0x0+((w.addr>>8)<<1),w.addr);
fm->write(0x1+((w.addr>>8)<<1),w.val);
regPool[w.addr&0x1ff]=w.val;
writes.pop();
writes.pop_front();
delay=4;
}
}
@ -1330,7 +1319,7 @@ void DivPlatformYM2610::poke(std::vector<DivRegWrite>& wlist) {
}
void DivPlatformYM2610::reset() {
while (!writes.empty()) writes.pop();
while (!writes.empty()) writes.pop_front();
memset(regPool,0,512);
if (dumpWrites) {
addWrite(0xffffffff,0);
@ -1402,6 +1391,22 @@ void DivPlatformYM2610::setSkipRegisterWrites(bool value) {
ay->setSkipRegisterWrites(value);
}
void DivPlatformYM2610::setFlags(unsigned int flags) {
switch (flags&0xff) {
default:
case 0x00:
chipClock=8000000.0;
break;
case 0x01:
chipClock=24167829/3;
break;
}
rate=chipClock/16;
for (int i=0; i<14; i++) {
oscBuf[i]->rate=rate;
}
}
int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
DivPlatformYM2610Base::init(p, channels, sugRate, flags);
dumpWrites=false;
@ -1410,15 +1415,11 @@ int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned in
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
chipClock=8000000;
rate=chipClock/16;
for (int i=0; i<14; i++) {
oscBuf[i]->rate=rate;
}
fm=new ymfm::ym2610(iface);
setFlags(flags);
// YM2149, 2MHz
ay=new DivPlatformAY8910;
ay->init(p,3,sugRate,19);
ay=new DivPlatformAY8910(true,chipClock,32);
ay->init(p,3,sugRate,16);
ay->toggleRegisterDump(true);
reset();
return 14;

View File

@ -19,9 +19,8 @@
#ifndef _YM2610_H
#define _YM2610_H
#include "../dispatch.h"
#include "fmshared_OPN.h"
#include "../macroInt.h"
#include <queue>
#include "ay.h"
#include "sound/ymfm/ymfm_opn.h"
@ -35,7 +34,7 @@ class DivYM2610Interface: public ymfm::ymfm_interface {
DivYM2610Interface(): adpcmAMem(NULL), adpcmBMem(NULL), sampleBank(0) {}
};
class DivPlatformYM2610Base: public DivDispatch {
class DivPlatformYM2610Base: public DivPlatformOPN {
protected:
unsigned char* adpcmAMem;
size_t adpcmAMemLen;
@ -50,6 +49,8 @@ class DivPlatformYM2610Base: public DivDispatch {
void renderSamples();
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
DivPlatformYM2610Base():
DivPlatformOPN(9440540.0, 72, 32) {}
};
class DivPlatformYM2610: public DivPlatformYM2610Base {
@ -58,6 +59,14 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
0x01, 0x02, 0x101, 0x102
};
const unsigned char konOffs[4]={
1, 2, 5, 6
};
const unsigned char bchOffs[4]={
1, 2, 4, 5
};
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
@ -103,29 +112,15 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
Channel chan[14];
DivDispatchOscBuffer* oscBuf[14];
bool isMuted[14];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
ymfm::ym2610* fm;
ymfm::ym2610::output_data fmout;
DivPlatformAY8910* ay;
unsigned char regPool[512];
unsigned char lastBusy;
unsigned char sampleBank;
int delay;
bool extMode;
short oldWrites[512];
short pendingWrites[512];
double NOTE_OPNB(int ch, int note);
double NOTE_ADPCMB(int note);
friend void putDispatchChan(void*,int,int);
@ -151,6 +146,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
void setFlags(unsigned int flags);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformYM2610();

View File

@ -23,15 +23,8 @@
#include <string.h>
#include <math.h>
#include "ym2610shared.h"
#include "fmshared_OPN.h"
static unsigned char konOffs[6]={
0, 1, 2, 4, 5, 6
};
#define CHIP_DIVIDER 32
#define CHIP_FREQBASE fmFreqBase
#define CHIP_DIVIDER fmDivBase
const char* regCheatSheetYM2610B[]={
// SSG
@ -472,7 +465,7 @@ void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t
fm->write(0x0+((w.addr>>8)<<1),w.addr);
fm->write(0x1+((w.addr>>8)<<1),w.val);
regPool[w.addr&0x1ff]=w.val;
writes.pop();
writes.pop_front();
delay=4;
}
}
@ -1308,7 +1301,7 @@ void DivPlatformYM2610B::poke(std::vector<DivRegWrite>& wlist) {
}
void DivPlatformYM2610B::reset() {
while (!writes.empty()) writes.pop();
while (!writes.empty()) writes.pop_front();
memset(regPool,0,512);
if (dumpWrites) {
addWrite(0xffffffff,0);
@ -1380,6 +1373,22 @@ void DivPlatformYM2610B::setSkipRegisterWrites(bool value) {
ay->setSkipRegisterWrites(value);
}
void DivPlatformYM2610B::setFlags(unsigned int flags) {
switch (flags&0xff) {
default:
case 0x00:
chipClock=8000000.0;
break;
case 0x01:
chipClock=24167829/3;
break;
}
rate=chipClock/16;
for (int i=0; i<16; i++) {
oscBuf[i]->rate=rate;
}
}
int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
DivPlatformYM2610Base::init(p, channels, sugRate, flags);
dumpWrites=false;
@ -1388,15 +1397,11 @@ int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned i
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
chipClock=8000000;
rate=chipClock/16;
for (int i=0; i<16; i++) {
oscBuf[i]->rate=rate;
}
fm=new ymfm::ym2610b(iface);
setFlags(flags);
// YM2149, 2MHz
ay=new DivPlatformAY8910;
ay->init(p,3,sugRate,19);
ay=new DivPlatformAY8910(true,chipClock,32);
ay->init(p,3,sugRate,16);
ay->toggleRegisterDump(true);
reset();
return 16;

View File

@ -19,12 +19,10 @@
#ifndef _YM2610B_H
#define _YM2610B_H
#include "../dispatch.h"
#include "ym2610.h"
#include "../macroInt.h"
#include <queue>
#include "sound/ymfm/ymfm_opn.h"
#include "ym2610.h"
class DivPlatformYM2610B: public DivPlatformYM2610Base {
protected:
@ -32,6 +30,10 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
};
const unsigned char konOffs[6]={
0, 1, 2, 4, 5, 6
};
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
@ -77,27 +79,15 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
Channel chan[16];
DivDispatchOscBuffer* oscBuf[16];
bool isMuted[16];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
ymfm::ym2610b* fm;
ymfm::ym2610b::output_data fmout;
unsigned char regPool[512];
unsigned char lastBusy;
DivPlatformAY8910* ay;
unsigned char sampleBank;
int delay;
bool extMode;
short oldWrites[512];
short pendingWrites[512];
double fmFreqBase=9440540;
unsigned char ayDiv=32;
double NOTE_OPNB(int ch, int note);
double NOTE_ADPCMB(int note);
@ -124,6 +114,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
void setFlags(unsigned int flags);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformYM2610B();

View File

@ -21,8 +21,8 @@
#include "../engine.h"
#include <math.h>
#include "ym2610shared.h"
#include "fmshared_OPN.h"
#define CHIP_FREQBASE fmFreqBase
#define CHIP_DIVIDER fmDivBase
int DivPlatformYM2610BExt::dispatch(DivCommand c) {
if (c.chan<2) {

View File

@ -21,8 +21,8 @@
#include "../engine.h"
#include <math.h>
#include "ym2610shared.h"
#include "fmshared_OPN.h"
#define CHIP_FREQBASE fmFreqBase
#define CHIP_DIVIDER fmDivBase
int DivPlatformYM2610Ext::dispatch(DivCommand c) {
if (c.chan<1) {

View File

@ -1,45 +0,0 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
static unsigned short opOffs[4]={
0x00, 0x04, 0x08, 0x0c
};
static bool isOutput[8][4]={
// 1 3 2 4
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,true ,true},
{false,true ,true ,true},
{false,true ,true ,true},
{true ,true ,true ,true},
};
static unsigned char dtTable[8]={
7,6,5,0,1,2,3,4
};
static int orderedOps[4]={
0,2,1,3
};
#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 CHIP_FREQBASE 9440540

View File

@ -435,6 +435,42 @@ void DivPlatformYMZ280B::setChipModel(int type) {
chipType=type;
}
void DivPlatformYMZ280B::setFlags(unsigned int flags) {
switch (chipType) {
default:
case 280:
switch (flags&0xff) {
case 0x00:
chipClock=16934400;
break;
case 0x01:
chipClock=COLOR_NTSC*4.0;
break;
case 0x02:
chipClock=COLOR_PAL*16.0/5.0;
break;
case 0x03:
chipClock=16000000;
break;
case 0x04:
chipClock=50000000.0/3.0;
break;
case 0x05:
chipClock=14000000;
break;
}
rate=chipClock/384;
break;
case 759:
rate=32000;
chipClock=rate*384;
break;
}
for (int i=0; i<8; i++) {
oscBuf[i]->rate=rate;
}
}
int DivPlatformYMZ280B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
@ -444,18 +480,12 @@ int DivPlatformYMZ280B::init(DivEngine* p, int channels, int sugRate, unsigned i
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
rate=(chipType==759)?32000:44100;
chipClock=rate*384;
sampleMem=new unsigned char[getSampleMemCapacity()];
sampleMemLen=0;
ymz280b.device_start(sampleMem);
setFlags(flags);
reset();
for (int i=0; i<8; i++) {
oscBuf[i]->rate=rate;
}
return 8;
}

View File

@ -96,6 +96,7 @@ class DivPlatformYMZ280B: public DivDispatch {
size_t getSampleMemCapacity(int index = 0);
size_t getSampleMemUsage(int index = 0);
void renderSamples();
void setFlags(unsigned int flags);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
private:

View File

@ -59,6 +59,7 @@ void DivPlatformZXBeeper::acquire(short* bufL, short* bufR, size_t start, size_t
}
o=sampleOut;
bufL[h]=o?16384:0;
oscBuf[0]->data[oscBuf[0]->needle++]=o?16384:-16384;
continue;
}
@ -76,6 +77,7 @@ void DivPlatformZXBeeper::acquire(short* bufL, short* bufR, size_t start, size_t
if (++curChan>=6) curChan=0;
bufL[h]=o?16384:0;
oscBuf[0]->data[oscBuf[0]->needle++]=o?16384:-16384;
}
}
@ -256,7 +258,7 @@ DivMacroInt* DivPlatformZXBeeper::getChanMacroInt(int ch) {
}
DivDispatchOscBuffer* DivPlatformZXBeeper::getOscBuffer(int ch) {
return oscBuf[ch];
return (ch<1)?oscBuf[ch]:NULL;
}
unsigned char* DivPlatformZXBeeper::getRegisterPool() {

View File

@ -988,6 +988,10 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
if (chan[i].vibratoDepth>0) {
chan[i].vibratoPos+=chan[i].vibratoRate;
if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64;
chan[i].vibratoPosGiant+=chan[i].vibratoRate;
if (chan[i].vibratoPos>=512) chan[i].vibratoPos-=512;
switch (chan[i].vibratoDir) {
case 1: // up
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(MAX(0,(chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));

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