mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-04 20:05:05 +00:00
Merge branch 'master' of https://github.com/tildearrow/furnace into es5506_alt
* 'master' of https://github.com/tildearrow/furnace: dev94 - add a full linear pitch mode, part 1 YM2610(B): use f-num/block baseFreq calculation GUI: remove insLoadAlwaysReplace setting GUI: right click menu for open instrument GUI: add a threshold for macro right click OPZ: remove debug printf GUI: add macro right click menu GUI: prepare for macro right click menu update gitignore add something prepare for something did i fix macOS build? GUI: macro edit improvements
This commit is contained in:
commit
54e78699a7
84 changed files with 6364 additions and 337 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -12,5 +12,8 @@ linuxbuild/
|
||||||
test/songs/
|
test/songs/
|
||||||
test/delta/
|
test/delta/
|
||||||
test/result/
|
test/result/
|
||||||
|
android/.gradle/
|
||||||
|
android/app/build/
|
||||||
|
android/app/.cxx/
|
||||||
.vs/
|
.vs/
|
||||||
CMakeSettings.json
|
CMakeSettings.json
|
||||||
|
|
|
@ -17,18 +17,17 @@ set(CMAKE_PROJECT_VERSION_MAJOR 0)
|
||||||
set(CMAKE_PROJECT_VERSION_MINOR 6)
|
set(CMAKE_PROJECT_VERSION_MINOR 6)
|
||||||
set(CMAKE_PROJECT_VERSION_PATCH 0)
|
set(CMAKE_PROJECT_VERSION_PATCH 0)
|
||||||
|
|
||||||
|
set(BUILD_GUI_DEFAULT ON)
|
||||||
|
set(SYSTEM_SDL2_DEFAULT OFF)
|
||||||
|
|
||||||
if (ANDROID)
|
if (ANDROID)
|
||||||
set(BUILD_GUI_DEFAULT OFF)
|
|
||||||
set(USE_RTMIDI_DEFAULT OFF)
|
set(USE_RTMIDI_DEFAULT OFF)
|
||||||
set(SYSTEM_SDL2_DEFAULT ON)
|
|
||||||
else()
|
else()
|
||||||
set(BUILD_GUI_DEFAULT ON)
|
|
||||||
set(USE_RTMIDI_DEFAULT ON)
|
set(USE_RTMIDI_DEFAULT ON)
|
||||||
set(SYSTEM_SDL2_DEFAULT OFF)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(PkgConfig)
|
find_package(PkgConfig)
|
||||||
if (PKG_CONFIG_FOUND)
|
if (PKG_CONFIG_FOUND AND NOT ANDROID)
|
||||||
pkg_check_modules(JACK jack)
|
pkg_check_modules(JACK jack)
|
||||||
set(WITH_JACK_DEFAULT ${JACK_FOUND})
|
set(WITH_JACK_DEFAULT ${JACK_FOUND})
|
||||||
else()
|
else()
|
||||||
|
@ -168,8 +167,13 @@ if (SYSTEM_SDL2)
|
||||||
endif()
|
endif()
|
||||||
message(STATUS "Using system-installed SDL2")
|
message(STATUS "Using system-installed SDL2")
|
||||||
else()
|
else()
|
||||||
|
if (ANDROID)
|
||||||
|
set(SDL_SHARED ON CACHE BOOL "Force no dynamically-linked SDL" FORCE)
|
||||||
|
set(SDL_STATIC OFF CACHE BOOL "Force statically-linked SDL" FORCE)
|
||||||
|
else()
|
||||||
set(SDL_SHARED OFF CACHE BOOL "Force no dynamically-linked SDL" FORCE)
|
set(SDL_SHARED OFF CACHE BOOL "Force no dynamically-linked SDL" FORCE)
|
||||||
set(SDL_STATIC ON CACHE BOOL "Force statically-linked SDL" FORCE)
|
set(SDL_STATIC ON CACHE BOOL "Force statically-linked SDL" FORCE)
|
||||||
|
endif()
|
||||||
# https://github.com/libsdl-org/SDL/issues/1481
|
# https://github.com/libsdl-org/SDL/issues/1481
|
||||||
# On 2014-06-22 17:15:50 +0000, Sam Lantinga wrote:
|
# On 2014-06-22 17:15:50 +0000, Sam Lantinga wrote:
|
||||||
# If you link SDL statically, you also need to define HAVE_LIBC so it builds with the C runtime that your application uses.
|
# If you link SDL statically, you also need to define HAVE_LIBC so it builds with the C runtime that your application uses.
|
||||||
|
@ -177,7 +181,11 @@ else()
|
||||||
set(SDL_LIBC ON CACHE BOOL "Tell SDL that we want it to use our C runtime (required for proper static linking)" FORCE)
|
set(SDL_LIBC ON CACHE BOOL "Tell SDL that we want it to use our C runtime (required for proper static linking)" FORCE)
|
||||||
add_subdirectory(extern/SDL EXCLUDE_FROM_ALL)
|
add_subdirectory(extern/SDL EXCLUDE_FROM_ALL)
|
||||||
list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/SDL/include)
|
list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/SDL/include)
|
||||||
|
if (ANDROID)
|
||||||
|
list(APPEND DEPENDENCIES_LIBRARIES SDL2)
|
||||||
|
else()
|
||||||
list(APPEND DEPENDENCIES_LIBRARIES SDL2-static)
|
list(APPEND DEPENDENCIES_LIBRARIES SDL2-static)
|
||||||
|
endif()
|
||||||
# Work around add_subdirectory'd SDL not propagating HAVE_LIBC to MSVC furnace build
|
# Work around add_subdirectory'd SDL not propagating HAVE_LIBC to MSVC furnace build
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
list(APPEND DEPENDENCIES_COMPILE_OPTIONS "/DHAVE_LIBC")
|
list(APPEND DEPENDENCIES_COMPILE_OPTIONS "/DHAVE_LIBC")
|
||||||
|
@ -508,6 +516,8 @@ endif()
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
add_executable(furnace WIN32 ${USED_SOURCES})
|
add_executable(furnace WIN32 ${USED_SOURCES})
|
||||||
|
elseif(ANDROID)
|
||||||
|
add_library(furnace SHARED ${USED_SOURCES})
|
||||||
else()
|
else()
|
||||||
add_executable(furnace ${USED_SOURCES})
|
add_executable(furnace ${USED_SOURCES})
|
||||||
endif()
|
endif()
|
||||||
|
@ -529,9 +539,10 @@ if (PKG_CONFIG_FOUND AND (SYSTEM_FMT OR SYSTEM_LIBSNDFILE OR SYSTEM_ZLIB OR SYST
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
install(TARGETS furnace RUNTIME DESTINATION bin)
|
if (NOT ANDROID)
|
||||||
|
install(TARGETS furnace RUNTIME DESTINATION bin)
|
||||||
|
|
||||||
if (NOT WIN32 AND NOT APPLE)
|
if (NOT WIN32 AND NOT APPLE)
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
install(FILES res/furnace.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
install(FILES res/furnace.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||||
install(FILES res/furnace.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
|
install(FILES res/furnace.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
|
||||||
|
@ -539,13 +550,13 @@ if (NOT WIN32 AND NOT APPLE)
|
||||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace)
|
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace)
|
||||||
install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
|
install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
|
||||||
install(FILES res/logo.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/1024x1024/apps)
|
install(FILES res/logo.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/1024x1024/apps)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(CPACK_PACKAGE_NAME "Furnace")
|
set(CPACK_PACKAGE_NAME "Furnace")
|
||||||
set(CPACK_PACKAGE_VENDOR "tildearrow")
|
set(CPACK_PACKAGE_VENDOR "tildearrow")
|
||||||
set(CPACK_PACKAGE_DESCRIPTION "free and open-source chiptune tracker")
|
set(CPACK_PACKAGE_DESCRIPTION "free and open-source chiptune tracker")
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
set(CPACK_GENERATOR Bundle)
|
set(CPACK_GENERATOR Bundle)
|
||||||
set(CPACK_DMG_SLA_DIR ${CMAKE_SOURCE_DIR}/res/macLicense)
|
set(CPACK_DMG_SLA_DIR ${CMAKE_SOURCE_DIR}/res/macLicense)
|
||||||
set(CPACK_DMG_SLA_LANGUAGES en)
|
set(CPACK_DMG_SLA_LANGUAGES en)
|
||||||
|
@ -554,6 +565,7 @@ if (APPLE)
|
||||||
set(CPACK_BUNDLE_PLIST ${CMAKE_SOURCE_DIR}/res/Info.plist)
|
set(CPACK_BUNDLE_PLIST ${CMAKE_SOURCE_DIR}/res/Info.plist)
|
||||||
set(CPACK_BUNDLE_ICON ${CMAKE_SOURCE_DIR}/res/icon.icns)
|
set(CPACK_BUNDLE_ICON ${CMAKE_SOURCE_DIR}/res/icon.icns)
|
||||||
set(CPACK_BUNDLE_STARTUP_COMMAND "furnace")
|
set(CPACK_BUNDLE_STARTUP_COMMAND "furnace")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(CPack)
|
include(CPack)
|
||||||
|
endif()
|
||||||
|
|
1
TODO.md
1
TODO.md
|
@ -23,7 +23,6 @@
|
||||||
- scroll instrument/wave/sample list when selecting item
|
- scroll instrument/wave/sample list when selecting item
|
||||||
- unified data view
|
- unified data view
|
||||||
- volume commands should work on Game Boy
|
- volume commands should work on Game Boy
|
||||||
- macro editor menu
|
|
||||||
- add another FM editor layout
|
- add another FM editor layout
|
||||||
- try to find out why does VSlider not accept keyboard input
|
- try to find out why does VSlider not accept keyboard input
|
||||||
- finish lock layout
|
- finish lock layout
|
||||||
|
|
64
android/app/build.gradle
Normal file
64
android/app/build.gradle
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
def buildAsLibrary = project.hasProperty('BUILD_AS_LIBRARY');
|
||||||
|
def buildAsApplication = !buildAsLibrary
|
||||||
|
if (buildAsApplication) {
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 26
|
||||||
|
defaultConfig {
|
||||||
|
if (buildAsApplication) {
|
||||||
|
applicationId "org.tildearrow.furnace"
|
||||||
|
}
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion 26
|
||||||
|
versionCode 93
|
||||||
|
versionName "0.6pre1"
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static"
|
||||||
|
// abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||||
|
abiFilters 'arm64-v8a'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!project.hasProperty('EXCLUDE_NATIVE_LIBS')) {
|
||||||
|
sourceSets.main {
|
||||||
|
jniLibs.srcDir 'libs'
|
||||||
|
}
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
path 'jni/CMakeLists.txt'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildAsLibrary) {
|
||||||
|
libraryVariants.all { variant ->
|
||||||
|
variant.outputs.each { output ->
|
||||||
|
def outputFile = output.outputFile
|
||||||
|
if (outputFile != null && outputFile.name.endsWith(".aar")) {
|
||||||
|
def fileName = "org.libsdl.app.aar";
|
||||||
|
output.outputFile = new File(outputFile.parent, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
|
}
|
1
android/app/jni
Symbolic link
1
android/app/jni
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../
|
17
android/app/proguard-rules.pro
vendored
Normal file
17
android/app/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in [sdk]/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
96
android/app/src/main/AndroidManifest.xml
Normal file
96
android/app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.tildearrow.furnace"
|
||||||
|
android:versionCode="93"
|
||||||
|
android:versionName="0.6pre1"
|
||||||
|
android:installLocation="auto">
|
||||||
|
|
||||||
|
<!-- OpenGL ES 2.0 -->
|
||||||
|
<uses-feature android:glEsVersion="0x00020000" />
|
||||||
|
|
||||||
|
<!-- Touchscreen support -->
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.touchscreen"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
|
<!-- Game controller support -->
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.bluetooth"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.gamepad"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.usb.host"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
|
<!-- External mouse input events -->
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.type.pc"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
|
<!-- Audio recording support -->
|
||||||
|
<!-- if you want to capture audio, uncomment this. -->
|
||||||
|
<!-- <uses-feature
|
||||||
|
android:name="android.hardware.microphone"
|
||||||
|
android:required="false" /> -->
|
||||||
|
|
||||||
|
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
<!-- Allow access to Bluetooth devices -->
|
||||||
|
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
||||||
|
<!-- <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> -->
|
||||||
|
<!-- <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> -->
|
||||||
|
|
||||||
|
<!-- Allow access to the vibrator -->
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
|
||||||
|
<!-- if you want to capture audio, uncomment this. -->
|
||||||
|
<!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> -->
|
||||||
|
|
||||||
|
<!-- Create a Java class extending SDLActivity and place it in a
|
||||||
|
directory under app/src/main/java matching the package, e.g. app/src/main/java/com/gamemaker/game/MyGame.java
|
||||||
|
|
||||||
|
then replace "SDLActivity" with the name of your class (e.g. "MyGame")
|
||||||
|
in the XML below.
|
||||||
|
|
||||||
|
An example Java class can be found in README-android.md
|
||||||
|
-->
|
||||||
|
<application android:label="@string/app_name"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||||
|
android:hardwareAccelerated="true" >
|
||||||
|
|
||||||
|
<!-- Example of setting SDL hints from AndroidManifest.xml:
|
||||||
|
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<activity android:name="MainActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:alwaysRetainTaskState="true"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
|
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
|
||||||
|
android:exported="true"
|
||||||
|
>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
<!-- Let Android know that we can handle some USB devices and should receive this event -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||||
|
</intent-filter>
|
||||||
|
<!-- Drop file event -->
|
||||||
|
<!--
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="*/*" />
|
||||||
|
</intent-filter>
|
||||||
|
-->
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
22
android/app/src/main/java/org/libsdl/app/HIDDevice.java
Normal file
22
android/app/src/main/java/org/libsdl/app/HIDDevice.java
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import android.hardware.usb.UsbDevice;
|
||||||
|
|
||||||
|
interface HIDDevice
|
||||||
|
{
|
||||||
|
public int getId();
|
||||||
|
public int getVendorId();
|
||||||
|
public int getProductId();
|
||||||
|
public String getSerialNumber();
|
||||||
|
public int getVersion();
|
||||||
|
public String getManufacturerName();
|
||||||
|
public String getProductName();
|
||||||
|
public UsbDevice getDevice();
|
||||||
|
public boolean open();
|
||||||
|
public int sendFeatureReport(byte[] report);
|
||||||
|
public int sendOutputReport(byte[] report);
|
||||||
|
public boolean getFeatureReport(byte[] report);
|
||||||
|
public void setFrozen(boolean frozen);
|
||||||
|
public void close();
|
||||||
|
public void shutdown();
|
||||||
|
}
|
|
@ -0,0 +1,650 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
|
import android.bluetooth.BluetoothGattCallback;
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.bluetooth.BluetoothGattDescriptor;
|
||||||
|
import android.bluetooth.BluetoothManager;
|
||||||
|
import android.bluetooth.BluetoothProfile;
|
||||||
|
import android.bluetooth.BluetoothGattService;
|
||||||
|
import android.hardware.usb.UsbDevice;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.os.*;
|
||||||
|
|
||||||
|
//import com.android.internal.util.HexDump;
|
||||||
|
|
||||||
|
import java.lang.Runnable;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
|
||||||
|
|
||||||
|
private static final String TAG = "hidapi";
|
||||||
|
private HIDDeviceManager mManager;
|
||||||
|
private BluetoothDevice mDevice;
|
||||||
|
private int mDeviceId;
|
||||||
|
private BluetoothGatt mGatt;
|
||||||
|
private boolean mIsRegistered = false;
|
||||||
|
private boolean mIsConnected = false;
|
||||||
|
private boolean mIsChromebook = false;
|
||||||
|
private boolean mIsReconnecting = false;
|
||||||
|
private boolean mFrozen = false;
|
||||||
|
private LinkedList<GattOperation> mOperations;
|
||||||
|
GattOperation mCurrentOperation = null;
|
||||||
|
private Handler mHandler;
|
||||||
|
|
||||||
|
private static final int TRANSPORT_AUTO = 0;
|
||||||
|
private static final int TRANSPORT_BREDR = 1;
|
||||||
|
private static final int TRANSPORT_LE = 2;
|
||||||
|
|
||||||
|
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
|
||||||
|
|
||||||
|
static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
|
||||||
|
static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
|
||||||
|
static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
|
||||||
|
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
|
||||||
|
|
||||||
|
static class GattOperation {
|
||||||
|
private enum Operation {
|
||||||
|
CHR_READ,
|
||||||
|
CHR_WRITE,
|
||||||
|
ENABLE_NOTIFICATION
|
||||||
|
}
|
||||||
|
|
||||||
|
Operation mOp;
|
||||||
|
UUID mUuid;
|
||||||
|
byte[] mValue;
|
||||||
|
BluetoothGatt mGatt;
|
||||||
|
boolean mResult = true;
|
||||||
|
|
||||||
|
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
|
||||||
|
mGatt = gatt;
|
||||||
|
mOp = operation;
|
||||||
|
mUuid = uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
|
||||||
|
mGatt = gatt;
|
||||||
|
mOp = operation;
|
||||||
|
mUuid = uuid;
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
// This is executed in main thread
|
||||||
|
BluetoothGattCharacteristic chr;
|
||||||
|
|
||||||
|
switch (mOp) {
|
||||||
|
case CHR_READ:
|
||||||
|
chr = getCharacteristic(mUuid);
|
||||||
|
//Log.v(TAG, "Reading characteristic " + chr.getUuid());
|
||||||
|
if (!mGatt.readCharacteristic(chr)) {
|
||||||
|
Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
|
||||||
|
mResult = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mResult = true;
|
||||||
|
break;
|
||||||
|
case CHR_WRITE:
|
||||||
|
chr = getCharacteristic(mUuid);
|
||||||
|
//Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
|
||||||
|
chr.setValue(mValue);
|
||||||
|
if (!mGatt.writeCharacteristic(chr)) {
|
||||||
|
Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
|
||||||
|
mResult = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mResult = true;
|
||||||
|
break;
|
||||||
|
case ENABLE_NOTIFICATION:
|
||||||
|
chr = getCharacteristic(mUuid);
|
||||||
|
//Log.v(TAG, "Writing descriptor of " + chr.getUuid());
|
||||||
|
if (chr != null) {
|
||||||
|
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||||
|
if (cccd != null) {
|
||||||
|
int properties = chr.getProperties();
|
||||||
|
byte[] value;
|
||||||
|
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
|
||||||
|
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
|
||||||
|
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
|
||||||
|
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Unable to start notifications on input characteristic");
|
||||||
|
mResult = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mGatt.setCharacteristicNotification(chr, true);
|
||||||
|
cccd.setValue(value);
|
||||||
|
if (!mGatt.writeDescriptor(cccd)) {
|
||||||
|
Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
|
||||||
|
mResult = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mResult = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean finish() {
|
||||||
|
return mResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
|
||||||
|
BluetoothGattService valveService = mGatt.getService(steamControllerService);
|
||||||
|
if (valveService == null)
|
||||||
|
return null;
|
||||||
|
return valveService.getCharacteristic(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {
|
||||||
|
return new GattOperation(gatt, Operation.CHR_READ, uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {
|
||||||
|
return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
|
||||||
|
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
|
||||||
|
mManager = manager;
|
||||||
|
mDevice = device;
|
||||||
|
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
|
||||||
|
mIsRegistered = false;
|
||||||
|
mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||||
|
mOperations = new LinkedList<GattOperation>();
|
||||||
|
mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
mGatt = connectGatt();
|
||||||
|
// final HIDDeviceBLESteamController finalThis = this;
|
||||||
|
// mHandler.postDelayed(new Runnable() {
|
||||||
|
// @Override
|
||||||
|
// public void run() {
|
||||||
|
// finalThis.checkConnectionForChromebookIssue();
|
||||||
|
// }
|
||||||
|
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIdentifier() {
|
||||||
|
return String.format("SteamController.%s", mDevice.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
public BluetoothGatt getGatt() {
|
||||||
|
return mGatt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
|
||||||
|
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
|
||||||
|
private BluetoothGatt connectGatt(boolean managed) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
|
try {
|
||||||
|
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BluetoothGatt connectGatt() {
|
||||||
|
return connectGatt(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getConnectionState() {
|
||||||
|
|
||||||
|
Context context = mManager.getContext();
|
||||||
|
if (context == null) {
|
||||||
|
// We are lacking any context to get our Bluetooth information. We'll just assume disconnected.
|
||||||
|
return BluetoothProfile.STATE_DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||||
|
if (btManager == null) {
|
||||||
|
// This device doesn't support Bluetooth. We should never be here, because how did
|
||||||
|
// we instantiate a device to start with?
|
||||||
|
return BluetoothProfile.STATE_DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reconnect() {
|
||||||
|
|
||||||
|
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
|
||||||
|
mGatt.disconnect();
|
||||||
|
mGatt = connectGatt();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkConnectionForChromebookIssue() {
|
||||||
|
if (!mIsChromebook) {
|
||||||
|
// We only do this on Chromebooks, because otherwise it's really annoying to just attempt
|
||||||
|
// over and over.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int connectionState = getConnectionState();
|
||||||
|
|
||||||
|
switch (connectionState) {
|
||||||
|
case BluetoothProfile.STATE_CONNECTED:
|
||||||
|
if (!mIsConnected) {
|
||||||
|
// We are in the Bad Chromebook Place. We can force a disconnect
|
||||||
|
// to try to recover.
|
||||||
|
Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect.");
|
||||||
|
mIsReconnecting = true;
|
||||||
|
mGatt.disconnect();
|
||||||
|
mGatt = connectGatt(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (!isRegistered()) {
|
||||||
|
if (mGatt.getServices().size() > 0) {
|
||||||
|
Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover.");
|
||||||
|
probeService(this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover.");
|
||||||
|
mIsReconnecting = true;
|
||||||
|
mGatt.disconnect();
|
||||||
|
mGatt = connectGatt(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BluetoothProfile.STATE_DISCONNECTED:
|
||||||
|
Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover.");
|
||||||
|
|
||||||
|
mIsReconnecting = true;
|
||||||
|
mGatt.disconnect();
|
||||||
|
mGatt = connectGatt(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BluetoothProfile.STATE_CONNECTING:
|
||||||
|
Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
final HIDDeviceBLESteamController finalThis = this;
|
||||||
|
mHandler.postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
finalThis.checkConnectionForChromebookIssue();
|
||||||
|
}
|
||||||
|
}, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRegistered() {
|
||||||
|
return mIsRegistered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRegistered() {
|
||||||
|
mIsRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean probeService(HIDDeviceBLESteamController controller) {
|
||||||
|
|
||||||
|
if (isRegistered()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mIsConnected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.v(TAG, "probeService controller=" + controller);
|
||||||
|
|
||||||
|
for (BluetoothGattService service : mGatt.getServices()) {
|
||||||
|
if (service.getUuid().equals(steamControllerService)) {
|
||||||
|
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
|
||||||
|
|
||||||
|
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
|
||||||
|
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||||
|
Log.v(TAG, "Found input characteristic");
|
||||||
|
// Start notifications
|
||||||
|
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||||
|
if (cccd != null) {
|
||||||
|
enableNotification(chr.getUuid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {
|
||||||
|
Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
|
||||||
|
mIsConnected = false;
|
||||||
|
mIsReconnecting = true;
|
||||||
|
mGatt.disconnect();
|
||||||
|
mGatt = connectGatt(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private void finishCurrentGattOperation() {
|
||||||
|
GattOperation op = null;
|
||||||
|
synchronized (mOperations) {
|
||||||
|
if (mCurrentOperation != null) {
|
||||||
|
op = mCurrentOperation;
|
||||||
|
mCurrentOperation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (op != null) {
|
||||||
|
boolean result = op.finish(); // TODO: Maybe in main thread as well?
|
||||||
|
|
||||||
|
// Our operation failed, let's add it back to the beginning of our queue.
|
||||||
|
if (!result) {
|
||||||
|
mOperations.addFirst(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
executeNextGattOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeNextGattOperation() {
|
||||||
|
synchronized (mOperations) {
|
||||||
|
if (mCurrentOperation != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (mOperations.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
mCurrentOperation = mOperations.removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run in main thread
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
synchronized (mOperations) {
|
||||||
|
if (mCurrentOperation == null) {
|
||||||
|
Log.e(TAG, "Current operation null in executor?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mCurrentOperation.run();
|
||||||
|
// now wait for the GATT callback and when it comes, finish this operation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void queueGattOperation(GattOperation op) {
|
||||||
|
synchronized (mOperations) {
|
||||||
|
mOperations.add(op);
|
||||||
|
}
|
||||||
|
executeNextGattOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableNotification(UUID chrUuid) {
|
||||||
|
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
|
||||||
|
queueGattOperation(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeCharacteristic(UUID uuid, byte[] value) {
|
||||||
|
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
|
||||||
|
queueGattOperation(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readCharacteristic(UUID uuid) {
|
||||||
|
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
|
||||||
|
queueGattOperation(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////////// BluetoothGattCallback overridden methods
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
|
||||||
|
//Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
|
||||||
|
mIsReconnecting = false;
|
||||||
|
if (newState == 2) {
|
||||||
|
mIsConnected = true;
|
||||||
|
// Run directly, without GattOperation
|
||||||
|
if (!isRegistered()) {
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mGatt.discoverServices();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (newState == 0) {
|
||||||
|
mIsConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
||||||
|
//Log.v(TAG, "onServicesDiscovered status=" + status);
|
||||||
|
if (status == 0) {
|
||||||
|
if (gatt.getServices().size() == 0) {
|
||||||
|
Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
|
||||||
|
mIsReconnecting = true;
|
||||||
|
mIsConnected = false;
|
||||||
|
gatt.disconnect();
|
||||||
|
mGatt = connectGatt(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
probeService(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||||
|
//Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid());
|
||||||
|
|
||||||
|
if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {
|
||||||
|
mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
finishCurrentGattOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||||
|
//Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid());
|
||||||
|
|
||||||
|
if (characteristic.getUuid().equals(reportCharacteristic)) {
|
||||||
|
// Only register controller with the native side once it has been fully configured
|
||||||
|
if (!isRegistered()) {
|
||||||
|
Log.v(TAG, "Registering Steam Controller with ID: " + getId());
|
||||||
|
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);
|
||||||
|
setRegistered();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finishCurrentGattOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||||
|
// Enable this for verbose logging of controller input reports
|
||||||
|
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
|
||||||
|
|
||||||
|
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
|
||||||
|
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||||
|
//Log.v(TAG, "onDescriptorRead status=" + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||||
|
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
|
||||||
|
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
|
||||||
|
|
||||||
|
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||||
|
boolean hasWrittenInputDescriptor = true;
|
||||||
|
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
|
||||||
|
if (reportChr != null) {
|
||||||
|
Log.v(TAG, "Writing report characteristic to enter valve mode");
|
||||||
|
reportChr.setValue(enterValveMode);
|
||||||
|
gatt.writeCharacteristic(reportChr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finishCurrentGattOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
|
||||||
|
//Log.v(TAG, "onReliableWriteCompleted status=" + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
||||||
|
//Log.v(TAG, "onReadRemoteRssi status=" + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||||
|
//Log.v(TAG, "onMtuChanged status=" + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//////// Public API
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return mDeviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVendorId() {
|
||||||
|
// Valve Corporation
|
||||||
|
final int VALVE_USB_VID = 0x28DE;
|
||||||
|
return VALVE_USB_VID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getProductId() {
|
||||||
|
// We don't have an easy way to query from the Bluetooth device, but we know what it is
|
||||||
|
final int D0G_BLE2_PID = 0x1106;
|
||||||
|
return D0G_BLE2_PID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSerialNumber() {
|
||||||
|
// This will be read later via feature report by Steam
|
||||||
|
return "12345";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVersion() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getManufacturerName() {
|
||||||
|
return "Valve Corporation";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProductName() {
|
||||||
|
return "Steam Controller";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UsbDevice getDevice() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean open() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int sendFeatureReport(byte[] report) {
|
||||||
|
if (!isRegistered()) {
|
||||||
|
Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
|
||||||
|
if (mIsConnected) {
|
||||||
|
probeService(this);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to skip the first byte, as that doesn't go over the air
|
||||||
|
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
|
||||||
|
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
|
||||||
|
writeCharacteristic(reportCharacteristic, actual_report);
|
||||||
|
return report.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int sendOutputReport(byte[] report) {
|
||||||
|
if (!isRegistered()) {
|
||||||
|
Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
|
||||||
|
if (mIsConnected) {
|
||||||
|
probeService(this);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
|
||||||
|
writeCharacteristic(reportCharacteristic, report);
|
||||||
|
return report.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getFeatureReport(byte[] report) {
|
||||||
|
if (!isRegistered()) {
|
||||||
|
Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
|
||||||
|
if (mIsConnected) {
|
||||||
|
probeService(this);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Log.v(TAG, "getFeatureReport");
|
||||||
|
readCharacteristic(reportCharacteristic);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFrozen(boolean frozen) {
|
||||||
|
mFrozen = frozen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
close();
|
||||||
|
|
||||||
|
BluetoothGatt g = mGatt;
|
||||||
|
if (g != null) {
|
||||||
|
g.disconnect();
|
||||||
|
g.close();
|
||||||
|
mGatt = null;
|
||||||
|
}
|
||||||
|
mManager = null;
|
||||||
|
mIsRegistered = false;
|
||||||
|
mIsConnected = false;
|
||||||
|
mOperations.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
675
android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
Normal file
675
android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
Normal file
|
@ -0,0 +1,675 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.BluetoothManager;
|
||||||
|
import android.bluetooth.BluetoothProfile;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.hardware.usb.*;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class HIDDeviceManager {
|
||||||
|
private static final String TAG = "hidapi";
|
||||||
|
private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
|
||||||
|
|
||||||
|
private static HIDDeviceManager sManager;
|
||||||
|
private static int sManagerRefCount = 0;
|
||||||
|
|
||||||
|
public static HIDDeviceManager acquire(Context context) {
|
||||||
|
if (sManagerRefCount == 0) {
|
||||||
|
sManager = new HIDDeviceManager(context);
|
||||||
|
}
|
||||||
|
++sManagerRefCount;
|
||||||
|
return sManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void release(HIDDeviceManager manager) {
|
||||||
|
if (manager == sManager) {
|
||||||
|
--sManagerRefCount;
|
||||||
|
if (sManagerRefCount == 0) {
|
||||||
|
sManager.close();
|
||||||
|
sManager = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
|
||||||
|
private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
|
||||||
|
private int mNextDeviceId = 0;
|
||||||
|
private SharedPreferences mSharedPreferences = null;
|
||||||
|
private boolean mIsChromebook = false;
|
||||||
|
private UsbManager mUsbManager;
|
||||||
|
private Handler mHandler;
|
||||||
|
private BluetoothManager mBluetoothManager;
|
||||||
|
private List<BluetoothDevice> mLastBluetoothDevices;
|
||||||
|
|
||||||
|
private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
|
||||||
|
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||||
|
handleUsbDeviceAttached(usbDevice);
|
||||||
|
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
|
||||||
|
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||||
|
handleUsbDeviceDetached(usbDevice);
|
||||||
|
} else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
|
||||||
|
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||||
|
handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
// Bluetooth device was connected. If it was a Steam Controller, handle it
|
||||||
|
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
|
||||||
|
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||||
|
Log.d(TAG, "Bluetooth device connected: " + device);
|
||||||
|
|
||||||
|
if (isSteamController(device)) {
|
||||||
|
connectBluetoothDevice(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bluetooth device was disconnected, remove from controller manager (if any)
|
||||||
|
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
|
||||||
|
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||||
|
Log.d(TAG, "Bluetooth device disconnected: " + device);
|
||||||
|
|
||||||
|
disconnectBluetoothDevice(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private HIDDeviceManager(final Context context) {
|
||||||
|
mContext = context;
|
||||||
|
|
||||||
|
HIDDeviceRegisterCallback();
|
||||||
|
|
||||||
|
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
|
||||||
|
mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||||
|
|
||||||
|
// if (shouldClear) {
|
||||||
|
// SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||||
|
// spedit.clear();
|
||||||
|
// spedit.commit();
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
{
|
||||||
|
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context getContext() {
|
||||||
|
return mContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDeviceIDForIdentifier(String identifier) {
|
||||||
|
SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||||
|
|
||||||
|
int result = mSharedPreferences.getInt(identifier, 0);
|
||||||
|
if (result == 0) {
|
||||||
|
result = mNextDeviceId++;
|
||||||
|
spedit.putInt("next_device_id", mNextDeviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
spedit.putInt(identifier, result);
|
||||||
|
spedit.commit();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeUSB() {
|
||||||
|
mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
|
||||||
|
if (mUsbManager == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Logging
|
||||||
|
for (UsbDevice device : mUsbManager.getDeviceList().values()) {
|
||||||
|
Log.i(TAG,"Path: " + device.getDeviceName());
|
||||||
|
Log.i(TAG,"Manufacturer: " + device.getManufacturerName());
|
||||||
|
Log.i(TAG,"Product: " + device.getProductName());
|
||||||
|
Log.i(TAG,"ID: " + device.getDeviceId());
|
||||||
|
Log.i(TAG,"Class: " + device.getDeviceClass());
|
||||||
|
Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
|
||||||
|
Log.i(TAG,"Vendor ID " + device.getVendorId());
|
||||||
|
Log.i(TAG,"Product ID: " + device.getProductId());
|
||||||
|
Log.i(TAG,"Interface count: " + device.getInterfaceCount());
|
||||||
|
Log.i(TAG,"---------------------------------------");
|
||||||
|
|
||||||
|
// Get interface details
|
||||||
|
for (int index = 0; index < device.getInterfaceCount(); index++) {
|
||||||
|
UsbInterface mUsbInterface = device.getInterface(index);
|
||||||
|
Log.i(TAG," ***** *****");
|
||||||
|
Log.i(TAG," Interface index: " + index);
|
||||||
|
Log.i(TAG," Interface ID: " + mUsbInterface.getId());
|
||||||
|
Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass());
|
||||||
|
Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass());
|
||||||
|
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
|
||||||
|
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
|
||||||
|
|
||||||
|
// Get endpoint details
|
||||||
|
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
|
||||||
|
{
|
||||||
|
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
|
||||||
|
Log.i(TAG," ++++ ++++ ++++");
|
||||||
|
Log.i(TAG," Endpoint index: " + epi);
|
||||||
|
Log.i(TAG," Attributes: " + mEndpoint.getAttributes());
|
||||||
|
Log.i(TAG," Direction: " + mEndpoint.getDirection());
|
||||||
|
Log.i(TAG," Number: " + mEndpoint.getEndpointNumber());
|
||||||
|
Log.i(TAG," Interval: " + mEndpoint.getInterval());
|
||||||
|
Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize());
|
||||||
|
Log.i(TAG," Type: " + mEndpoint.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.i(TAG," No more devices connected.");
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Register for USB broadcasts and permission completions
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||||
|
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
|
||||||
|
filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
|
||||||
|
mContext.registerReceiver(mUsbBroadcast, filter);
|
||||||
|
|
||||||
|
for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
|
||||||
|
handleUsbDeviceAttached(usbDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UsbManager getUSBManager() {
|
||||||
|
return mUsbManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdownUSB() {
|
||||||
|
try {
|
||||||
|
mContext.unregisterReceiver(mUsbBroadcast);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// We may not have registered, that's okay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||||
|
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||||
|
final int XB360_IFACE_SUBCLASS = 93;
|
||||||
|
final int XB360_IFACE_PROTOCOL = 1; // Wired
|
||||||
|
final int XB360W_IFACE_PROTOCOL = 129; // Wireless
|
||||||
|
final int[] SUPPORTED_VENDORS = {
|
||||||
|
0x0079, // GPD Win 2
|
||||||
|
0x044f, // Thrustmaster
|
||||||
|
0x045e, // Microsoft
|
||||||
|
0x046d, // Logitech
|
||||||
|
0x056e, // Elecom
|
||||||
|
0x06a3, // Saitek
|
||||||
|
0x0738, // Mad Catz
|
||||||
|
0x07ff, // Mad Catz
|
||||||
|
0x0e6f, // PDP
|
||||||
|
0x0f0d, // Hori
|
||||||
|
0x1038, // SteelSeries
|
||||||
|
0x11c9, // Nacon
|
||||||
|
0x12ab, // Unknown
|
||||||
|
0x1430, // RedOctane
|
||||||
|
0x146b, // BigBen
|
||||||
|
0x1532, // Razer Sabertooth
|
||||||
|
0x15e4, // Numark
|
||||||
|
0x162e, // Joytech
|
||||||
|
0x1689, // Razer Onza
|
||||||
|
0x1949, // Lab126, Inc.
|
||||||
|
0x1bad, // Harmonix
|
||||||
|
0x24c6, // PowerA
|
||||||
|
};
|
||||||
|
|
||||||
|
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||||
|
usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
|
||||||
|
(usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||
|
||||||
|
usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {
|
||||||
|
int vendor_id = usbDevice.getVendorId();
|
||||||
|
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||||
|
if (vendor_id == supportedVid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||||
|
final int XB1_IFACE_SUBCLASS = 71;
|
||||||
|
final int XB1_IFACE_PROTOCOL = 208;
|
||||||
|
final int[] SUPPORTED_VENDORS = {
|
||||||
|
0x045e, // Microsoft
|
||||||
|
0x0738, // Mad Catz
|
||||||
|
0x0e6f, // PDP
|
||||||
|
0x0f0d, // Hori
|
||||||
|
0x1532, // Razer Wildcat
|
||||||
|
0x24c6, // PowerA
|
||||||
|
0x2e24, // Hyperkin
|
||||||
|
};
|
||||||
|
|
||||||
|
if (usbInterface.getId() == 0 &&
|
||||||
|
usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||||
|
usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
||||||
|
usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
|
||||||
|
int vendor_id = usbDevice.getVendorId();
|
||||||
|
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||||
|
if (vendor_id == supportedVid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUsbDeviceAttached(UsbDevice usbDevice) {
|
||||||
|
connectHIDDeviceUSB(usbDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUsbDeviceDetached(UsbDevice usbDevice) {
|
||||||
|
List<Integer> devices = new ArrayList<Integer>();
|
||||||
|
for (HIDDevice device : mDevicesById.values()) {
|
||||||
|
if (usbDevice.equals(device.getDevice())) {
|
||||||
|
devices.add(device.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int id : devices) {
|
||||||
|
HIDDevice device = mDevicesById.get(id);
|
||||||
|
mDevicesById.remove(id);
|
||||||
|
device.shutdown();
|
||||||
|
HIDDeviceDisconnected(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
|
||||||
|
for (HIDDevice device : mDevicesById.values()) {
|
||||||
|
if (usbDevice.equals(device.getDevice())) {
|
||||||
|
boolean opened = false;
|
||||||
|
if (permission_granted) {
|
||||||
|
opened = device.open();
|
||||||
|
}
|
||||||
|
HIDDeviceOpenResult(device.getId(), opened);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectHIDDeviceUSB(UsbDevice usbDevice) {
|
||||||
|
synchronized (this) {
|
||||||
|
int interface_mask = 0;
|
||||||
|
for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {
|
||||||
|
UsbInterface usbInterface = usbDevice.getInterface(interface_index);
|
||||||
|
if (isHIDDeviceInterface(usbDevice, usbInterface)) {
|
||||||
|
// Check to see if we've already added this interface
|
||||||
|
// This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive
|
||||||
|
int interface_id = usbInterface.getId();
|
||||||
|
if ((interface_mask & (1 << interface_id)) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
interface_mask |= (1 << interface_id);
|
||||||
|
|
||||||
|
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);
|
||||||
|
int id = device.getId();
|
||||||
|
mDevicesById.put(id, device);
|
||||||
|
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeBluetooth() {
|
||||||
|
Log.d(TAG, "Initializing Bluetooth");
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT <= 30 &&
|
||||||
|
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18)) {
|
||||||
|
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find bonded bluetooth controllers and create SteamControllers for them
|
||||||
|
mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||||
|
if (mBluetoothManager == null) {
|
||||||
|
// This device doesn't support Bluetooth.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();
|
||||||
|
if (btAdapter == null) {
|
||||||
|
// This device has Bluetooth support in the codebase, but has no available adapters.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get our bonded devices.
|
||||||
|
for (BluetoothDevice device : btAdapter.getBondedDevices()) {
|
||||||
|
|
||||||
|
Log.d(TAG, "Bluetooth device available: " + device);
|
||||||
|
if (isSteamController(device)) {
|
||||||
|
connectBluetoothDevice(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: These don't work on Chromebooks, to my undying dismay.
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||||
|
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||||
|
mContext.registerReceiver(mBluetoothBroadcast, filter);
|
||||||
|
|
||||||
|
if (mIsChromebook) {
|
||||||
|
mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mLastBluetoothDevices = new ArrayList<BluetoothDevice>();
|
||||||
|
|
||||||
|
// final HIDDeviceManager finalThis = this;
|
||||||
|
// mHandler.postDelayed(new Runnable() {
|
||||||
|
// @Override
|
||||||
|
// public void run() {
|
||||||
|
// finalThis.chromebookConnectionHandler();
|
||||||
|
// }
|
||||||
|
// }, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdownBluetooth() {
|
||||||
|
try {
|
||||||
|
mContext.unregisterReceiver(mBluetoothBroadcast);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// We may not have registered, that's okay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
|
||||||
|
// This function provides a sort of dummy version of that, watching for changes in the
|
||||||
|
// connected devices and attempting to add controllers as things change.
|
||||||
|
public void chromebookConnectionHandler() {
|
||||||
|
if (!mIsChromebook) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
|
||||||
|
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
|
||||||
|
|
||||||
|
List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
|
||||||
|
|
||||||
|
for (BluetoothDevice bluetoothDevice : currentConnected) {
|
||||||
|
if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
|
||||||
|
connected.add(bluetoothDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
|
||||||
|
if (!currentConnected.contains(bluetoothDevice)) {
|
||||||
|
disconnected.add(bluetoothDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mLastBluetoothDevices = currentConnected;
|
||||||
|
|
||||||
|
for (BluetoothDevice bluetoothDevice : disconnected) {
|
||||||
|
disconnectBluetoothDevice(bluetoothDevice);
|
||||||
|
}
|
||||||
|
for (BluetoothDevice bluetoothDevice : connected) {
|
||||||
|
connectBluetoothDevice(bluetoothDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
final HIDDeviceManager finalThis = this;
|
||||||
|
mHandler.postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
finalThis.chromebookConnectionHandler();
|
||||||
|
}
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||||
|
Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
|
||||||
|
synchronized (this) {
|
||||||
|
if (mBluetoothDevices.containsKey(bluetoothDevice)) {
|
||||||
|
Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect");
|
||||||
|
|
||||||
|
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||||
|
device.reconnect();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);
|
||||||
|
int id = device.getId();
|
||||||
|
mBluetoothDevices.put(bluetoothDevice, device);
|
||||||
|
mDevicesById.put(id, device);
|
||||||
|
|
||||||
|
// The Steam Controller will mark itself connected once initialization is complete
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||||
|
synchronized (this) {
|
||||||
|
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||||
|
if (device == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int id = device.getId();
|
||||||
|
mBluetoothDevices.remove(bluetoothDevice);
|
||||||
|
mDevicesById.remove(id);
|
||||||
|
device.shutdown();
|
||||||
|
HIDDeviceDisconnected(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSteamController(BluetoothDevice bluetoothDevice) {
|
||||||
|
// Sanity check. If you pass in a null device, by definition it is never a Steam Controller.
|
||||||
|
if (bluetoothDevice == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the device has no local name, we really don't want to try an equality check against it.
|
||||||
|
if (bluetoothDevice.getName() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close() {
|
||||||
|
shutdownUSB();
|
||||||
|
shutdownBluetooth();
|
||||||
|
synchronized (this) {
|
||||||
|
for (HIDDevice device : mDevicesById.values()) {
|
||||||
|
device.shutdown();
|
||||||
|
}
|
||||||
|
mDevicesById.clear();
|
||||||
|
mBluetoothDevices.clear();
|
||||||
|
HIDDeviceReleaseCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFrozen(boolean frozen) {
|
||||||
|
synchronized (this) {
|
||||||
|
for (HIDDevice device : mDevicesById.values()) {
|
||||||
|
device.setFrozen(frozen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private HIDDevice getDevice(int id) {
|
||||||
|
synchronized (this) {
|
||||||
|
HIDDevice result = mDevicesById.get(id);
|
||||||
|
if (result == null) {
|
||||||
|
Log.v(TAG, "No device for id: " + id);
|
||||||
|
Log.v(TAG, "Available devices: " + mDevicesById.keySet());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////// JNI interface functions
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public boolean initialize(boolean usb, boolean bluetooth) {
|
||||||
|
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
|
||||||
|
|
||||||
|
if (usb) {
|
||||||
|
initializeUSB();
|
||||||
|
}
|
||||||
|
if (bluetooth) {
|
||||||
|
initializeBluetooth();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean openDevice(int deviceID) {
|
||||||
|
Log.v(TAG, "openDevice deviceID=" + deviceID);
|
||||||
|
HIDDevice device = getDevice(deviceID);
|
||||||
|
if (device == null) {
|
||||||
|
HIDDeviceDisconnected(deviceID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look to see if this is a USB device and we have permission to access it
|
||||||
|
UsbDevice usbDevice = device.getDevice();
|
||||||
|
if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {
|
||||||
|
HIDDeviceOpenPending(deviceID);
|
||||||
|
try {
|
||||||
|
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
|
||||||
|
int flags;
|
||||||
|
if (Build.VERSION.SDK_INT >= 31) {
|
||||||
|
flags = FLAG_MUTABLE;
|
||||||
|
} else {
|
||||||
|
flags = 0;
|
||||||
|
}
|
||||||
|
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
|
||||||
|
HIDDeviceOpenResult(deviceID, false);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return device.open();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int sendOutputReport(int deviceID, byte[] report) {
|
||||||
|
try {
|
||||||
|
//Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
|
||||||
|
HIDDevice device;
|
||||||
|
device = getDevice(deviceID);
|
||||||
|
if (device == null) {
|
||||||
|
HIDDeviceDisconnected(deviceID);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return device.sendOutputReport(report);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int sendFeatureReport(int deviceID, byte[] report) {
|
||||||
|
try {
|
||||||
|
//Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
|
||||||
|
HIDDevice device;
|
||||||
|
device = getDevice(deviceID);
|
||||||
|
if (device == null) {
|
||||||
|
HIDDeviceDisconnected(deviceID);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return device.sendFeatureReport(report);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getFeatureReport(int deviceID, byte[] report) {
|
||||||
|
try {
|
||||||
|
//Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
|
||||||
|
HIDDevice device;
|
||||||
|
device = getDevice(deviceID);
|
||||||
|
if (device == null) {
|
||||||
|
HIDDeviceDisconnected(deviceID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return device.getFeatureReport(report);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeDevice(int deviceID) {
|
||||||
|
try {
|
||||||
|
Log.v(TAG, "closeDevice deviceID=" + deviceID);
|
||||||
|
HIDDevice device;
|
||||||
|
device = getDevice(deviceID);
|
||||||
|
if (device == null) {
|
||||||
|
HIDDeviceDisconnected(deviceID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
device.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/////////////// Native methods
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private native void HIDDeviceRegisterCallback();
|
||||||
|
private native void HIDDeviceReleaseCallback();
|
||||||
|
|
||||||
|
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
|
||||||
|
native void HIDDeviceOpenPending(int deviceID);
|
||||||
|
native void HIDDeviceOpenResult(int deviceID, boolean opened);
|
||||||
|
native void HIDDeviceDisconnected(int deviceID);
|
||||||
|
|
||||||
|
native void HIDDeviceInputReport(int deviceID, byte[] report);
|
||||||
|
native void HIDDeviceFeatureReport(int deviceID, byte[] report);
|
||||||
|
}
|
309
android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java
Normal file
309
android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java
Normal file
|
@ -0,0 +1,309 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import android.hardware.usb.*;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
class HIDDeviceUSB implements HIDDevice {
|
||||||
|
|
||||||
|
private static final String TAG = "hidapi";
|
||||||
|
|
||||||
|
protected HIDDeviceManager mManager;
|
||||||
|
protected UsbDevice mDevice;
|
||||||
|
protected int mInterfaceIndex;
|
||||||
|
protected int mInterface;
|
||||||
|
protected int mDeviceId;
|
||||||
|
protected UsbDeviceConnection mConnection;
|
||||||
|
protected UsbEndpoint mInputEndpoint;
|
||||||
|
protected UsbEndpoint mOutputEndpoint;
|
||||||
|
protected InputThread mInputThread;
|
||||||
|
protected boolean mRunning;
|
||||||
|
protected boolean mFrozen;
|
||||||
|
|
||||||
|
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
|
||||||
|
mManager = manager;
|
||||||
|
mDevice = usbDevice;
|
||||||
|
mInterfaceIndex = interface_index;
|
||||||
|
mInterface = mDevice.getInterface(mInterfaceIndex).getId();
|
||||||
|
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
|
||||||
|
mRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIdentifier() {
|
||||||
|
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return mDeviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVendorId() {
|
||||||
|
return mDevice.getVendorId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getProductId() {
|
||||||
|
return mDevice.getProductId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSerialNumber() {
|
||||||
|
String result = null;
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
try {
|
||||||
|
result = mDevice.getSerialNumber();
|
||||||
|
}
|
||||||
|
catch (SecurityException exception) {
|
||||||
|
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = "";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVersion() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getManufacturerName() {
|
||||||
|
String result = null;
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
result = mDevice.getManufacturerName();
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = String.format("%x", getVendorId());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProductName() {
|
||||||
|
String result = null;
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
result = mDevice.getProductName();
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = String.format("%x", getProductId());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UsbDevice getDevice() {
|
||||||
|
return mDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeviceName() {
|
||||||
|
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean open() {
|
||||||
|
mConnection = mManager.getUSBManager().openDevice(mDevice);
|
||||||
|
if (mConnection == null) {
|
||||||
|
Log.w(TAG, "Unable to open USB device " + getDeviceName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force claim our interface
|
||||||
|
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||||
|
if (!mConnection.claimInterface(iface, true)) {
|
||||||
|
Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the endpoints
|
||||||
|
for (int j = 0; j < iface.getEndpointCount(); j++) {
|
||||||
|
UsbEndpoint endpt = iface.getEndpoint(j);
|
||||||
|
switch (endpt.getDirection()) {
|
||||||
|
case UsbConstants.USB_DIR_IN:
|
||||||
|
if (mInputEndpoint == null) {
|
||||||
|
mInputEndpoint = endpt;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UsbConstants.USB_DIR_OUT:
|
||||||
|
if (mOutputEndpoint == null) {
|
||||||
|
mOutputEndpoint = endpt;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the required endpoints were present
|
||||||
|
if (mInputEndpoint == null || mOutputEndpoint == null) {
|
||||||
|
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening for input
|
||||||
|
mRunning = true;
|
||||||
|
mInputThread = new InputThread();
|
||||||
|
mInputThread.start();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int sendFeatureReport(byte[] report) {
|
||||||
|
int res = -1;
|
||||||
|
int offset = 0;
|
||||||
|
int length = report.length;
|
||||||
|
boolean skipped_report_id = false;
|
||||||
|
byte report_number = report[0];
|
||||||
|
|
||||||
|
if (report_number == 0x0) {
|
||||||
|
++offset;
|
||||||
|
--length;
|
||||||
|
skipped_report_id = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = mConnection.controlTransfer(
|
||||||
|
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
|
||||||
|
0x09/*HID set_report*/,
|
||||||
|
(3/*HID feature*/ << 8) | report_number,
|
||||||
|
mInterface,
|
||||||
|
report, offset, length,
|
||||||
|
1000/*timeout millis*/);
|
||||||
|
|
||||||
|
if (res < 0) {
|
||||||
|
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipped_report_id) {
|
||||||
|
++length;
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int sendOutputReport(byte[] report) {
|
||||||
|
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
|
||||||
|
if (r != report.length) {
|
||||||
|
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getFeatureReport(byte[] report) {
|
||||||
|
int res = -1;
|
||||||
|
int offset = 0;
|
||||||
|
int length = report.length;
|
||||||
|
boolean skipped_report_id = false;
|
||||||
|
byte report_number = report[0];
|
||||||
|
|
||||||
|
if (report_number == 0x0) {
|
||||||
|
/* Offset the return buffer by 1, so that the report ID
|
||||||
|
will remain in byte 0. */
|
||||||
|
++offset;
|
||||||
|
--length;
|
||||||
|
skipped_report_id = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = mConnection.controlTransfer(
|
||||||
|
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
|
||||||
|
0x01/*HID get_report*/,
|
||||||
|
(3/*HID feature*/ << 8) | report_number,
|
||||||
|
mInterface,
|
||||||
|
report, offset, length,
|
||||||
|
1000/*timeout millis*/);
|
||||||
|
|
||||||
|
if (res < 0) {
|
||||||
|
Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipped_report_id) {
|
||||||
|
++res;
|
||||||
|
++length;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] data;
|
||||||
|
if (res == length) {
|
||||||
|
data = report;
|
||||||
|
} else {
|
||||||
|
data = Arrays.copyOfRange(report, 0, res);
|
||||||
|
}
|
||||||
|
mManager.HIDDeviceFeatureReport(mDeviceId, data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
mRunning = false;
|
||||||
|
if (mInputThread != null) {
|
||||||
|
while (mInputThread.isAlive()) {
|
||||||
|
mInputThread.interrupt();
|
||||||
|
try {
|
||||||
|
mInputThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Keep trying until we're done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mInputThread = null;
|
||||||
|
}
|
||||||
|
if (mConnection != null) {
|
||||||
|
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||||
|
mConnection.releaseInterface(iface);
|
||||||
|
mConnection.close();
|
||||||
|
mConnection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
close();
|
||||||
|
mManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFrozen(boolean frozen) {
|
||||||
|
mFrozen = frozen;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class InputThread extends Thread {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
int packetSize = mInputEndpoint.getMaxPacketSize();
|
||||||
|
byte[] packet = new byte[packetSize];
|
||||||
|
while (mRunning) {
|
||||||
|
int r;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (r < 0) {
|
||||||
|
// Could be a timeout or an I/O error
|
||||||
|
}
|
||||||
|
if (r > 0) {
|
||||||
|
byte[] data;
|
||||||
|
if (r == packetSize) {
|
||||||
|
data = packet;
|
||||||
|
} else {
|
||||||
|
data = Arrays.copyOfRange(packet, 0, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mFrozen) {
|
||||||
|
mManager.HIDDeviceInputReport(mDeviceId, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
android/app/src/main/java/org/libsdl/app/SDL.java
Normal file
85
android/app/src/main/java/org/libsdl/app/SDL.java
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.lang.Class;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
SDL library initialization
|
||||||
|
*/
|
||||||
|
public class SDL {
|
||||||
|
|
||||||
|
// This function should be called first and sets up the native code
|
||||||
|
// so it can call into the Java classes
|
||||||
|
public static void setupJNI() {
|
||||||
|
SDLActivity.nativeSetupJNI();
|
||||||
|
SDLAudioManager.nativeSetupJNI();
|
||||||
|
SDLControllerManager.nativeSetupJNI();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function should be called each time the activity is started
|
||||||
|
public static void initialize() {
|
||||||
|
setContext(null);
|
||||||
|
|
||||||
|
SDLActivity.initialize();
|
||||||
|
SDLAudioManager.initialize();
|
||||||
|
SDLControllerManager.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function stores the current activity (SDL or not)
|
||||||
|
public static void setContext(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Context getContext() {
|
||||||
|
return mContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||||
|
|
||||||
|
if (libraryName == null) {
|
||||||
|
throw new NullPointerException("No library name provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Let's see if we have ReLinker available in the project. This is necessary for
|
||||||
|
// some projects that have huge numbers of local libraries bundled, and thus may
|
||||||
|
// trip a bug in Android's native library loader which ReLinker works around. (If
|
||||||
|
// loadLibrary works properly, ReLinker will simply use the normal Android method
|
||||||
|
// internally.)
|
||||||
|
//
|
||||||
|
// To use ReLinker, just add it as a dependency. For more information, see
|
||||||
|
// https://github.com/KeepSafe/ReLinker for ReLinker's repository.
|
||||||
|
//
|
||||||
|
Class<?> relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
|
||||||
|
Class<?> relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
|
||||||
|
Class<?> contextClass = mContext.getClassLoader().loadClass("android.content.Context");
|
||||||
|
Class<?> stringClass = mContext.getClassLoader().loadClass("java.lang.String");
|
||||||
|
|
||||||
|
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
|
||||||
|
// they've changed during updates.
|
||||||
|
Method forceMethod = relinkClass.getDeclaredMethod("force");
|
||||||
|
Object relinkInstance = forceMethod.invoke(null);
|
||||||
|
Class<?> relinkInstanceClass = relinkInstance.getClass();
|
||||||
|
|
||||||
|
// Actually load the library!
|
||||||
|
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
|
||||||
|
loadMethod.invoke(relinkInstance, mContext, libraryName, null, null);
|
||||||
|
}
|
||||||
|
catch (final Throwable e) {
|
||||||
|
// Fall back
|
||||||
|
try {
|
||||||
|
System.loadLibrary(libraryName);
|
||||||
|
}
|
||||||
|
catch (final UnsatisfiedLinkError ule) {
|
||||||
|
throw ule;
|
||||||
|
}
|
||||||
|
catch (final SecurityException se) {
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Context mContext;
|
||||||
|
}
|
2328
android/app/src/main/java/org/libsdl/app/SDLActivity.java
Normal file
2328
android/app/src/main/java/org/libsdl/app/SDLActivity.java
Normal file
File diff suppressed because it is too large
Load diff
394
android/app/src/main/java/org/libsdl/app/SDLAudioManager.java
Normal file
394
android/app/src/main/java/org/libsdl/app/SDLAudioManager.java
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import android.media.AudioFormat;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.media.AudioRecord;
|
||||||
|
import android.media.AudioTrack;
|
||||||
|
import android.media.MediaRecorder;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class SDLAudioManager
|
||||||
|
{
|
||||||
|
protected static final String TAG = "SDLAudio";
|
||||||
|
|
||||||
|
protected static AudioTrack mAudioTrack;
|
||||||
|
protected static AudioRecord mAudioRecord;
|
||||||
|
|
||||||
|
public static void initialize() {
|
||||||
|
mAudioTrack = null;
|
||||||
|
mAudioRecord = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
|
||||||
|
protected static String getAudioFormatString(int audioFormat) {
|
||||||
|
switch (audioFormat) {
|
||||||
|
case AudioFormat.ENCODING_PCM_8BIT:
|
||||||
|
return "8-bit";
|
||||||
|
case AudioFormat.ENCODING_PCM_16BIT:
|
||||||
|
return "16-bit";
|
||||||
|
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||||
|
return "float";
|
||||||
|
default:
|
||||||
|
return Integer.toString(audioFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
|
||||||
|
int channelConfig;
|
||||||
|
int sampleSize;
|
||||||
|
int frameSize;
|
||||||
|
|
||||||
|
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
|
||||||
|
|
||||||
|
/* On older devices let's use known good settings */
|
||||||
|
if (Build.VERSION.SDK_INT < 21) {
|
||||||
|
if (desiredChannels > 2) {
|
||||||
|
desiredChannels = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
|
||||||
|
if (Build.VERSION.SDK_INT < 22) {
|
||||||
|
if (sampleRate < 8000) {
|
||||||
|
sampleRate = 8000;
|
||||||
|
} else if (sampleRate > 48000) {
|
||||||
|
sampleRate = 48000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||||
|
int minSDKVersion = (isCapture ? 23 : 21);
|
||||||
|
if (Build.VERSION.SDK_INT < minSDKVersion) {
|
||||||
|
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (audioFormat)
|
||||||
|
{
|
||||||
|
case AudioFormat.ENCODING_PCM_8BIT:
|
||||||
|
sampleSize = 1;
|
||||||
|
break;
|
||||||
|
case AudioFormat.ENCODING_PCM_16BIT:
|
||||||
|
sampleSize = 2;
|
||||||
|
break;
|
||||||
|
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||||
|
sampleSize = 4;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
|
||||||
|
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||||
|
sampleSize = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCapture) {
|
||||||
|
switch (desiredChannels) {
|
||||||
|
case 1:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_IN_MONO;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||||
|
desiredChannels = 2;
|
||||||
|
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (desiredChannels) {
|
||||||
|
case 1:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
||||||
|
} else {
|
||||||
|
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
|
||||||
|
desiredChannels = 6;
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||||
|
desiredChannels = 2;
|
||||||
|
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Log.v(TAG, "Speaker configuration (and order of channels):");
|
||||||
|
|
||||||
|
if ((channelConfig & 0x00000004) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000008) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000010) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000020) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000040) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_BACK_LEFT");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000080) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000100) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000200) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000400) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_BACK_CENTER");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00000800) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT");
|
||||||
|
}
|
||||||
|
if ((channelConfig & 0x00001000) != 0) {
|
||||||
|
Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
frameSize = (sampleSize * desiredChannels);
|
||||||
|
|
||||||
|
// Let the user pick a larger buffer if they really want -- but ye
|
||||||
|
// gods they probably shouldn't, the minimums are horrifyingly high
|
||||||
|
// latency already
|
||||||
|
int minBufferSize;
|
||||||
|
if (isCapture) {
|
||||||
|
minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||||
|
} else {
|
||||||
|
minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||||
|
}
|
||||||
|
desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
|
||||||
|
|
||||||
|
int[] results = new int[4];
|
||||||
|
|
||||||
|
if (isCapture) {
|
||||||
|
if (mAudioRecord == null) {
|
||||||
|
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
|
||||||
|
channelConfig, audioFormat, desiredFrames * frameSize);
|
||||||
|
|
||||||
|
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
|
||||||
|
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||||
|
Log.e(TAG, "Failed during initialization of AudioRecord");
|
||||||
|
mAudioRecord.release();
|
||||||
|
mAudioRecord = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mAudioRecord.startRecording();
|
||||||
|
}
|
||||||
|
|
||||||
|
results[0] = mAudioRecord.getSampleRate();
|
||||||
|
results[1] = mAudioRecord.getAudioFormat();
|
||||||
|
results[2] = mAudioRecord.getChannelCount();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (mAudioTrack == null) {
|
||||||
|
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
|
||||||
|
|
||||||
|
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
|
||||||
|
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
|
||||||
|
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
|
||||||
|
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
|
||||||
|
/* Try again, with safer values */
|
||||||
|
|
||||||
|
Log.e(TAG, "Failed during initialization of Audio Track");
|
||||||
|
mAudioTrack.release();
|
||||||
|
mAudioTrack = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mAudioTrack.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
results[0] = mAudioTrack.getSampleRate();
|
||||||
|
results[1] = mAudioTrack.getAudioFormat();
|
||||||
|
results[2] = mAudioTrack.getChannelCount();
|
||||||
|
}
|
||||||
|
results[3] = desiredFrames;
|
||||||
|
|
||||||
|
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
|
||||||
|
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void audioWriteFloatBuffer(float[] buffer) {
|
||||||
|
if (mAudioTrack == null) {
|
||||||
|
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < buffer.length;) {
|
||||||
|
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
|
||||||
|
if (result > 0) {
|
||||||
|
i += result;
|
||||||
|
} else if (result == 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1);
|
||||||
|
} catch(InterruptedException e) {
|
||||||
|
// Nom nom
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "SDL audio: error return from write(float)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void audioWriteShortBuffer(short[] buffer) {
|
||||||
|
if (mAudioTrack == null) {
|
||||||
|
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < buffer.length;) {
|
||||||
|
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||||
|
if (result > 0) {
|
||||||
|
i += result;
|
||||||
|
} else if (result == 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1);
|
||||||
|
} catch(InterruptedException e) {
|
||||||
|
// Nom nom
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "SDL audio: error return from write(short)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void audioWriteByteBuffer(byte[] buffer) {
|
||||||
|
if (mAudioTrack == null) {
|
||||||
|
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < buffer.length; ) {
|
||||||
|
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||||
|
if (result > 0) {
|
||||||
|
i += result;
|
||||||
|
} else if (result == 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1);
|
||||||
|
} catch(InterruptedException e) {
|
||||||
|
// Nom nom
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "SDL audio: error return from write(byte)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
|
||||||
|
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method is called by SDL using JNI. */
|
||||||
|
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
|
||||||
|
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method is called by SDL using JNI. */
|
||||||
|
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
|
||||||
|
if (Build.VERSION.SDK_INT < 23) {
|
||||||
|
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||||
|
} else {
|
||||||
|
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method is called by SDL using JNI. */
|
||||||
|
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
|
||||||
|
if (Build.VERSION.SDK_INT < 23) {
|
||||||
|
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||||
|
} else {
|
||||||
|
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method is called by SDL using JNI. */
|
||||||
|
public static void audioClose() {
|
||||||
|
if (mAudioTrack != null) {
|
||||||
|
mAudioTrack.stop();
|
||||||
|
mAudioTrack.release();
|
||||||
|
mAudioTrack = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method is called by SDL using JNI. */
|
||||||
|
public static void captureClose() {
|
||||||
|
if (mAudioRecord != null) {
|
||||||
|
mAudioRecord.stop();
|
||||||
|
mAudioRecord.release();
|
||||||
|
mAudioRecord = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method is called by SDL using JNI. */
|
||||||
|
public static void audioSetThreadPriority(boolean iscapture, int device_id) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
/* Set thread name */
|
||||||
|
if (iscapture) {
|
||||||
|
Thread.currentThread().setName("SDLAudioC" + device_id);
|
||||||
|
} else {
|
||||||
|
Thread.currentThread().setName("SDLAudioP" + device_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set thread priority */
|
||||||
|
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.v(TAG, "modify thread properties failed " + e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static native int nativeSetupJNI();
|
||||||
|
}
|
|
@ -0,0 +1,794 @@
|
||||||
|
package org.libsdl.app;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.InputDevice;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
|
||||||
|
public class SDLControllerManager
|
||||||
|
{
|
||||||
|
|
||||||
|
public static native int nativeSetupJNI();
|
||||||
|
|
||||||
|
public static native int nativeAddJoystick(int device_id, String name, String desc,
|
||||||
|
int vendor_id, int product_id,
|
||||||
|
boolean is_accelerometer, int button_mask,
|
||||||
|
int naxes, int nhats, int nballs);
|
||||||
|
public static native int nativeRemoveJoystick(int device_id);
|
||||||
|
public static native int nativeAddHaptic(int device_id, String name);
|
||||||
|
public static native int nativeRemoveHaptic(int device_id);
|
||||||
|
public static native int onNativePadDown(int device_id, int keycode);
|
||||||
|
public static native int onNativePadUp(int device_id, int keycode);
|
||||||
|
public static native void onNativeJoy(int device_id, int axis,
|
||||||
|
float value);
|
||||||
|
public static native void onNativeHat(int device_id, int hat_id,
|
||||||
|
int x, int y);
|
||||||
|
|
||||||
|
protected static SDLJoystickHandler mJoystickHandler;
|
||||||
|
protected static SDLHapticHandler mHapticHandler;
|
||||||
|
|
||||||
|
private static final String TAG = "SDLControllerManager";
|
||||||
|
|
||||||
|
public static void initialize() {
|
||||||
|
if (mJoystickHandler == null) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 19) {
|
||||||
|
mJoystickHandler = new SDLJoystickHandler_API19();
|
||||||
|
} else {
|
||||||
|
mJoystickHandler = new SDLJoystickHandler_API16();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mHapticHandler == null) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 26) {
|
||||||
|
mHapticHandler = new SDLHapticHandler_API26();
|
||||||
|
} else {
|
||||||
|
mHapticHandler = new SDLHapticHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
|
||||||
|
public static boolean handleJoystickMotionEvent(MotionEvent event) {
|
||||||
|
return mJoystickHandler.handleMotionEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void pollInputDevices() {
|
||||||
|
mJoystickHandler.pollInputDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void pollHapticDevices() {
|
||||||
|
mHapticHandler.pollHapticDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void hapticRun(int device_id, float intensity, int length) {
|
||||||
|
mHapticHandler.run(device_id, intensity, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by SDL using JNI.
|
||||||
|
*/
|
||||||
|
public static void hapticStop(int device_id)
|
||||||
|
{
|
||||||
|
mHapticHandler.stop(device_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a given device is considered a possible SDL joystick
|
||||||
|
public static boolean isDeviceSDLJoystick(int deviceId) {
|
||||||
|
InputDevice device = InputDevice.getDevice(deviceId);
|
||||||
|
// We cannot use InputDevice.isVirtual before API 16, so let's accept
|
||||||
|
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
|
||||||
|
if ((device == null) || (deviceId < 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int sources = device.getSources();
|
||||||
|
|
||||||
|
/* This is called for every button press, so let's not spam the logs */
|
||||||
|
/*
|
||||||
|
if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||||
|
Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
|
||||||
|
}
|
||||||
|
if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
|
||||||
|
Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
|
||||||
|
}
|
||||||
|
if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
|
||||||
|
Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
|
||||||
|
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
|
||||||
|
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLJoystickHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles given MotionEvent.
|
||||||
|
* @param event the event to be handled.
|
||||||
|
* @return if given event was processed.
|
||||||
|
*/
|
||||||
|
public boolean handleMotionEvent(MotionEvent event) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles adding and removing of input devices.
|
||||||
|
*/
|
||||||
|
public void pollInputDevices() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actual joystick functionality available for API >= 12 devices */
|
||||||
|
class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||||
|
|
||||||
|
static class SDLJoystick {
|
||||||
|
public int device_id;
|
||||||
|
public String name;
|
||||||
|
public String desc;
|
||||||
|
public ArrayList<InputDevice.MotionRange> axes;
|
||||||
|
public ArrayList<InputDevice.MotionRange> hats;
|
||||||
|
}
|
||||||
|
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
|
||||||
|
@Override
|
||||||
|
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
|
||||||
|
// Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL
|
||||||
|
int arg0Axis = arg0.getAxis();
|
||||||
|
int arg1Axis = arg1.getAxis();
|
||||||
|
if (arg0Axis == MotionEvent.AXIS_GAS) {
|
||||||
|
arg0Axis = MotionEvent.AXIS_BRAKE;
|
||||||
|
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
|
||||||
|
arg0Axis = MotionEvent.AXIS_GAS;
|
||||||
|
}
|
||||||
|
if (arg1Axis == MotionEvent.AXIS_GAS) {
|
||||||
|
arg1Axis = MotionEvent.AXIS_BRAKE;
|
||||||
|
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
|
||||||
|
arg1Axis = MotionEvent.AXIS_GAS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return arg0Axis - arg1Axis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ArrayList<SDLJoystick> mJoysticks;
|
||||||
|
|
||||||
|
public SDLJoystickHandler_API16() {
|
||||||
|
|
||||||
|
mJoysticks = new ArrayList<SDLJoystick>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pollInputDevices() {
|
||||||
|
int[] deviceIds = InputDevice.getDeviceIds();
|
||||||
|
|
||||||
|
for (int device_id : deviceIds) {
|
||||||
|
if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
|
||||||
|
SDLJoystick joystick = getJoystick(device_id);
|
||||||
|
if (joystick == null) {
|
||||||
|
InputDevice joystickDevice = InputDevice.getDevice(device_id);
|
||||||
|
joystick = new SDLJoystick();
|
||||||
|
joystick.device_id = device_id;
|
||||||
|
joystick.name = joystickDevice.getName();
|
||||||
|
joystick.desc = getJoystickDescriptor(joystickDevice);
|
||||||
|
joystick.axes = new ArrayList<InputDevice.MotionRange>();
|
||||||
|
joystick.hats = new ArrayList<InputDevice.MotionRange>();
|
||||||
|
|
||||||
|
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
|
||||||
|
Collections.sort(ranges, new RangeComparator());
|
||||||
|
for (InputDevice.MotionRange range : ranges) {
|
||||||
|
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||||
|
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
|
||||||
|
joystick.hats.add(range);
|
||||||
|
} else {
|
||||||
|
joystick.axes.add(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mJoysticks.add(joystick);
|
||||||
|
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
|
||||||
|
getVendorId(joystickDevice), getProductId(joystickDevice), false,
|
||||||
|
getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check removed devices */
|
||||||
|
ArrayList<Integer> removedDevices = null;
|
||||||
|
for (SDLJoystick joystick : mJoysticks) {
|
||||||
|
int device_id = joystick.device_id;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < deviceIds.length; i++) {
|
||||||
|
if (device_id == deviceIds[i]) break;
|
||||||
|
}
|
||||||
|
if (i == deviceIds.length) {
|
||||||
|
if (removedDevices == null) {
|
||||||
|
removedDevices = new ArrayList<Integer>();
|
||||||
|
}
|
||||||
|
removedDevices.add(device_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedDevices != null) {
|
||||||
|
for (int device_id : removedDevices) {
|
||||||
|
SDLControllerManager.nativeRemoveJoystick(device_id);
|
||||||
|
for (int i = 0; i < mJoysticks.size(); i++) {
|
||||||
|
if (mJoysticks.get(i).device_id == device_id) {
|
||||||
|
mJoysticks.remove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SDLJoystick getJoystick(int device_id) {
|
||||||
|
for (SDLJoystick joystick : mJoysticks) {
|
||||||
|
if (joystick.device_id == device_id) {
|
||||||
|
return joystick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleMotionEvent(MotionEvent event) {
|
||||||
|
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
|
||||||
|
int actionPointerIndex = event.getActionIndex();
|
||||||
|
int action = event.getActionMasked();
|
||||||
|
if (action == MotionEvent.ACTION_MOVE) {
|
||||||
|
SDLJoystick joystick = getJoystick(event.getDeviceId());
|
||||||
|
if (joystick != null) {
|
||||||
|
for (int i = 0; i < joystick.axes.size(); i++) {
|
||||||
|
InputDevice.MotionRange range = joystick.axes.get(i);
|
||||||
|
/* Normalize the value to -1...1 */
|
||||||
|
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
||||||
|
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < joystick.hats.size() / 2; i++) {
|
||||||
|
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
|
||||||
|
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
|
||||||
|
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJoystickDescriptor(InputDevice joystickDevice) {
|
||||||
|
String desc = joystickDevice.getDescriptor();
|
||||||
|
|
||||||
|
if (desc != null && !desc.isEmpty()) {
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return joystickDevice.getName();
|
||||||
|
}
|
||||||
|
public int getProductId(InputDevice joystickDevice) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
public int getVendorId(InputDevice joystickDevice) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
public int getButtonMask(InputDevice joystickDevice) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getProductId(InputDevice joystickDevice) {
|
||||||
|
return joystickDevice.getProductId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVendorId(InputDevice joystickDevice) {
|
||||||
|
return joystickDevice.getVendorId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getButtonMask(InputDevice joystickDevice) {
|
||||||
|
int button_mask = 0;
|
||||||
|
int[] keys = new int[] {
|
||||||
|
KeyEvent.KEYCODE_BUTTON_A,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_B,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_X,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Y,
|
||||||
|
KeyEvent.KEYCODE_BACK,
|
||||||
|
KeyEvent.KEYCODE_MENU,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_MODE,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_START,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBL,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBR,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L1,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R1,
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP,
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_SELECT,
|
||||||
|
KeyEvent.KEYCODE_DPAD_CENTER,
|
||||||
|
|
||||||
|
// These don't map into any SDL controller buttons directly
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L2,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R2,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_C,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Z,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_1,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_2,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_3,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_4,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_5,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_6,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_7,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_8,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_9,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_10,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_11,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_12,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_13,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_14,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_15,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_16,
|
||||||
|
};
|
||||||
|
int[] masks = new int[] {
|
||||||
|
(1 << 0), // A -> A
|
||||||
|
(1 << 1), // B -> B
|
||||||
|
(1 << 2), // X -> X
|
||||||
|
(1 << 3), // Y -> Y
|
||||||
|
(1 << 4), // BACK -> BACK
|
||||||
|
(1 << 6), // MENU -> START
|
||||||
|
(1 << 5), // MODE -> GUIDE
|
||||||
|
(1 << 6), // START -> START
|
||||||
|
(1 << 7), // THUMBL -> LEFTSTICK
|
||||||
|
(1 << 8), // THUMBR -> RIGHTSTICK
|
||||||
|
(1 << 9), // L1 -> LEFTSHOULDER
|
||||||
|
(1 << 10), // R1 -> RIGHTSHOULDER
|
||||||
|
(1 << 11), // DPAD_UP -> DPAD_UP
|
||||||
|
(1 << 12), // DPAD_DOWN -> DPAD_DOWN
|
||||||
|
(1 << 13), // DPAD_LEFT -> DPAD_LEFT
|
||||||
|
(1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
|
||||||
|
(1 << 4), // SELECT -> BACK
|
||||||
|
(1 << 0), // DPAD_CENTER -> A
|
||||||
|
(1 << 15), // L2 -> ??
|
||||||
|
(1 << 16), // R2 -> ??
|
||||||
|
(1 << 17), // C -> ??
|
||||||
|
(1 << 18), // Z -> ??
|
||||||
|
(1 << 20), // 1 -> ??
|
||||||
|
(1 << 21), // 2 -> ??
|
||||||
|
(1 << 22), // 3 -> ??
|
||||||
|
(1 << 23), // 4 -> ??
|
||||||
|
(1 << 24), // 5 -> ??
|
||||||
|
(1 << 25), // 6 -> ??
|
||||||
|
(1 << 26), // 7 -> ??
|
||||||
|
(1 << 27), // 8 -> ??
|
||||||
|
(1 << 28), // 9 -> ??
|
||||||
|
(1 << 29), // 10 -> ??
|
||||||
|
(1 << 30), // 11 -> ??
|
||||||
|
(1 << 31), // 12 -> ??
|
||||||
|
// We're out of room...
|
||||||
|
0xFFFFFFFF, // 13 -> ??
|
||||||
|
0xFFFFFFFF, // 14 -> ??
|
||||||
|
0xFFFFFFFF, // 15 -> ??
|
||||||
|
0xFFFFFFFF, // 16 -> ??
|
||||||
|
};
|
||||||
|
boolean[] has_keys = joystickDevice.hasKeys(keys);
|
||||||
|
for (int i = 0; i < keys.length; ++i) {
|
||||||
|
if (has_keys[i]) {
|
||||||
|
button_mask |= masks[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return button_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLHapticHandler_API26 extends SDLHapticHandler {
|
||||||
|
@Override
|
||||||
|
public void run(int device_id, float intensity, int length) {
|
||||||
|
SDLHaptic haptic = getHaptic(device_id);
|
||||||
|
if (haptic != null) {
|
||||||
|
Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
|
||||||
|
if (intensity == 0.0f) {
|
||||||
|
stop(device_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vibeValue = Math.round(intensity * 255);
|
||||||
|
|
||||||
|
if (vibeValue > 255) {
|
||||||
|
vibeValue = 255;
|
||||||
|
}
|
||||||
|
if (vibeValue < 1) {
|
||||||
|
stop(device_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
// Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
|
||||||
|
// something went horribly wrong with the Android 8.0 APIs.
|
||||||
|
haptic.vib.vibrate(length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLHapticHandler {
|
||||||
|
|
||||||
|
static class SDLHaptic {
|
||||||
|
public int device_id;
|
||||||
|
public String name;
|
||||||
|
public Vibrator vib;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ArrayList<SDLHaptic> mHaptics;
|
||||||
|
|
||||||
|
public SDLHapticHandler() {
|
||||||
|
mHaptics = new ArrayList<SDLHaptic>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(int device_id, float intensity, int length) {
|
||||||
|
SDLHaptic haptic = getHaptic(device_id);
|
||||||
|
if (haptic != null) {
|
||||||
|
haptic.vib.vibrate(length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop(int device_id) {
|
||||||
|
SDLHaptic haptic = getHaptic(device_id);
|
||||||
|
if (haptic != null) {
|
||||||
|
haptic.vib.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pollHapticDevices() {
|
||||||
|
|
||||||
|
final int deviceId_VIBRATOR_SERVICE = 999999;
|
||||||
|
boolean hasVibratorService = false;
|
||||||
|
|
||||||
|
int[] deviceIds = InputDevice.getDeviceIds();
|
||||||
|
// It helps processing the device ids in reverse order
|
||||||
|
// For example, in the case of the XBox 360 wireless dongle,
|
||||||
|
// so the first controller seen by SDL matches what the receiver
|
||||||
|
// considers to be the first controller
|
||||||
|
|
||||||
|
for (int i = deviceIds.length - 1; i > -1; i--) {
|
||||||
|
SDLHaptic haptic = getHaptic(deviceIds[i]);
|
||||||
|
if (haptic == null) {
|
||||||
|
InputDevice device = InputDevice.getDevice(deviceIds[i]);
|
||||||
|
Vibrator vib = device.getVibrator();
|
||||||
|
if (vib.hasVibrator()) {
|
||||||
|
haptic = new SDLHaptic();
|
||||||
|
haptic.device_id = deviceIds[i];
|
||||||
|
haptic.name = device.getName();
|
||||||
|
haptic.vib = vib;
|
||||||
|
mHaptics.add(haptic);
|
||||||
|
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check VIBRATOR_SERVICE */
|
||||||
|
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
|
if (vib != null) {
|
||||||
|
hasVibratorService = vib.hasVibrator();
|
||||||
|
|
||||||
|
if (hasVibratorService) {
|
||||||
|
SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
|
||||||
|
if (haptic == null) {
|
||||||
|
haptic = new SDLHaptic();
|
||||||
|
haptic.device_id = deviceId_VIBRATOR_SERVICE;
|
||||||
|
haptic.name = "VIBRATOR_SERVICE";
|
||||||
|
haptic.vib = vib;
|
||||||
|
mHaptics.add(haptic);
|
||||||
|
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check removed devices */
|
||||||
|
ArrayList<Integer> removedDevices = null;
|
||||||
|
for (SDLHaptic haptic : mHaptics) {
|
||||||
|
int device_id = haptic.device_id;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < deviceIds.length; i++) {
|
||||||
|
if (device_id == deviceIds[i]) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
|
||||||
|
if (i == deviceIds.length) {
|
||||||
|
if (removedDevices == null) {
|
||||||
|
removedDevices = new ArrayList<Integer>();
|
||||||
|
}
|
||||||
|
removedDevices.add(device_id);
|
||||||
|
}
|
||||||
|
} // else: don't remove the vibrator if it is still present
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedDevices != null) {
|
||||||
|
for (int device_id : removedDevices) {
|
||||||
|
SDLControllerManager.nativeRemoveHaptic(device_id);
|
||||||
|
for (int i = 0; i < mHaptics.size(); i++) {
|
||||||
|
if (mHaptics.get(i).device_id == device_id) {
|
||||||
|
mHaptics.remove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SDLHaptic getHaptic(int device_id) {
|
||||||
|
for (SDLHaptic haptic : mHaptics) {
|
||||||
|
if (haptic.device_id == device_id) {
|
||||||
|
return haptic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
|
||||||
|
// Generic Motion (mouse hover, joystick...) events go here
|
||||||
|
@Override
|
||||||
|
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||||
|
float x, y;
|
||||||
|
int action;
|
||||||
|
|
||||||
|
switch ( event.getSource() ) {
|
||||||
|
case InputDevice.SOURCE_JOYSTICK:
|
||||||
|
case InputDevice.SOURCE_GAMEPAD:
|
||||||
|
case InputDevice.SOURCE_DPAD:
|
||||||
|
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||||
|
|
||||||
|
case InputDevice.SOURCE_MOUSE:
|
||||||
|
action = event.getActionMasked();
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_SCROLL:
|
||||||
|
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||||
|
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_HOVER_MOVE:
|
||||||
|
x = event.getX(0);
|
||||||
|
y = event.getY(0);
|
||||||
|
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event was not managed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supportsRelativeMouse() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean inRelativeMode() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reclaimRelativeMouseModeIfNeeded()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getEventX(MotionEvent event) {
|
||||||
|
return event.getX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getEventY(MotionEvent event) {
|
||||||
|
return event.getY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
|
||||||
|
// Generic Motion (mouse hover, joystick...) events go here
|
||||||
|
|
||||||
|
private boolean mRelativeModeEnabled;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||||
|
|
||||||
|
// Handle relative mouse mode
|
||||||
|
if (mRelativeModeEnabled) {
|
||||||
|
if (event.getSource() == InputDevice.SOURCE_MOUSE) {
|
||||||
|
int action = event.getActionMasked();
|
||||||
|
if (action == MotionEvent.ACTION_HOVER_MOVE) {
|
||||||
|
float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||||
|
float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event was not managed, call SDLGenericMotionListener_API12 method
|
||||||
|
return super.onGenericMotion(v, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsRelativeMouse() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inRelativeMode() {
|
||||||
|
return mRelativeModeEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||||
|
mRelativeModeEnabled = enabled;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getEventX(MotionEvent event) {
|
||||||
|
if (mRelativeModeEnabled) {
|
||||||
|
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||||
|
} else {
|
||||||
|
return event.getX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getEventY(MotionEvent event) {
|
||||||
|
if (mRelativeModeEnabled) {
|
||||||
|
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||||
|
} else {
|
||||||
|
return event.getY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
|
||||||
|
// Generic Motion (mouse hover, joystick...) events go here
|
||||||
|
private boolean mRelativeModeEnabled;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||||
|
float x, y;
|
||||||
|
int action;
|
||||||
|
|
||||||
|
switch ( event.getSource() ) {
|
||||||
|
case InputDevice.SOURCE_JOYSTICK:
|
||||||
|
case InputDevice.SOURCE_GAMEPAD:
|
||||||
|
case InputDevice.SOURCE_DPAD:
|
||||||
|
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||||
|
|
||||||
|
case InputDevice.SOURCE_MOUSE:
|
||||||
|
// DeX desktop mouse cursor is a separate non-standard input type.
|
||||||
|
case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
|
||||||
|
action = event.getActionMasked();
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_SCROLL:
|
||||||
|
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||||
|
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_HOVER_MOVE:
|
||||||
|
x = event.getX(0);
|
||||||
|
y = event.getY(0);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InputDevice.SOURCE_MOUSE_RELATIVE:
|
||||||
|
action = event.getActionMasked();
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_SCROLL:
|
||||||
|
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||||
|
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_HOVER_MOVE:
|
||||||
|
x = event.getX(0);
|
||||||
|
y = event.getY(0);
|
||||||
|
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event was not managed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsRelativeMouse() {
|
||||||
|
return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inRelativeMode() {
|
||||||
|
return mRelativeModeEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||||
|
if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) {
|
||||||
|
if (enabled) {
|
||||||
|
SDLActivity.getContentView().requestPointerCapture();
|
||||||
|
} else {
|
||||||
|
SDLActivity.getContentView().releasePointerCapture();
|
||||||
|
}
|
||||||
|
mRelativeModeEnabled = enabled;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reclaimRelativeMouseModeIfNeeded()
|
||||||
|
{
|
||||||
|
if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
|
||||||
|
SDLActivity.getContentView().requestPointerCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getEventX(MotionEvent event) {
|
||||||
|
// Relative mouse in capture mode will only have relative for X/Y
|
||||||
|
return event.getX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getEventY(MotionEvent event) {
|
||||||
|
// Relative mouse in capture mode will only have relative for X/Y
|
||||||
|
return event.getY(0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.tildearrow.furnace;
|
||||||
|
|
||||||
|
import org.libsdl.app.SDLActivity;
|
||||||
|
|
||||||
|
public class MainActivity extends SDLActivity
|
||||||
|
{
|
||||||
|
}
|
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
6
android/app/src/main/res/values/colors.xml
Normal file
6
android/app/src/main/res/values/colors.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#3F51B5</color>
|
||||||
|
<color name="colorPrimaryDark">#303F9F</color>
|
||||||
|
<color name="colorAccent">#FF4081</color>
|
||||||
|
</resources>
|
3
android/app/src/main/res/values/strings.xml
Normal file
3
android/app/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Furnace</string>
|
||||||
|
</resources>
|
8
android/app/src/main/res/values/styles.xml
Normal file
8
android/app/src/main/res/values/styles.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
25
android/build.gradle
Normal file
25
android/build.gradle
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:7.0.3'
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
17
android/gradle.properties
Normal file
17
android/gradle.properties
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Project-wide Gradle settings.
|
||||||
|
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
|
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#Thu Nov 11 18:20:34 PST 2021
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
160
android/gradlew
vendored
Executable file
160
android/gradlew
vendored
Executable file
|
@ -0,0 +1,160 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn ( ) {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die ( ) {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||||
|
function splitJvmOpts() {
|
||||||
|
JVM_OPTS=("$@")
|
||||||
|
}
|
||||||
|
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||||
|
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
android/gradlew.bat
vendored
Normal file
90
android/gradlew.bat
vendored
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windowz variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
goto execute
|
||||||
|
|
||||||
|
:4NT_args
|
||||||
|
@rem Get arguments from the 4NT Shell from JP Software
|
||||||
|
set CMD_LINE_ARGS=%$
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
1
android/settings.gradle
Normal file
1
android/settings.gradle
Normal file
|
@ -0,0 +1 @@
|
||||||
|
include ':app'
|
|
@ -232,6 +232,9 @@ size | description
|
||||||
4f | A-4 tuning
|
4f | A-4 tuning
|
||||||
1 | limit slides (>=36) or reserved
|
1 | limit slides (>=36) or reserved
|
||||||
1 | linear pitch (>=36) or reserved
|
1 | linear pitch (>=36) or reserved
|
||||||
|
| - 0: non-linaer
|
||||||
|
| - 1: only pitch change (04xy/E5xx) linear
|
||||||
|
| - 2: full linear (>=94)
|
||||||
1 | loop modality (>=36) or reserved
|
1 | loop modality (>=36) or reserved
|
||||||
1 | proper noise layout (>=42) or reserved
|
1 | proper noise layout (>=42) or reserved
|
||||||
1 | wave duty is volume (>=42) or reserved
|
1 | wave duty is volume (>=42) or reserved
|
||||||
|
@ -286,7 +289,8 @@ size | description
|
||||||
1 | weird f-num/block-based chip pitch slides (>=85) or reserved
|
1 | weird f-num/block-based chip pitch slides (>=85) or reserved
|
||||||
1 | SN duty macro always resets phase (>=86) or reserved
|
1 | SN duty macro always resets phase (>=86) or reserved
|
||||||
1 | pitch macro is linear (>=90) or reserved
|
1 | pitch macro is linear (>=90) or reserved
|
||||||
19 | reserved
|
1 | pitch slide speed in full linear pitch mode (>=94) or reserved
|
||||||
|
18 | reserved
|
||||||
```
|
```
|
||||||
|
|
||||||
# instrument
|
# instrument
|
||||||
|
|
|
@ -94,7 +94,7 @@ struct TAMidiMessage {
|
||||||
double time;
|
double time;
|
||||||
unsigned char type;
|
unsigned char type;
|
||||||
unsigned char data[7];
|
unsigned char data[7];
|
||||||
std::shared_ptr<unsigned char[]> sysExData;
|
std::shared_ptr<unsigned char> sysExData;
|
||||||
size_t sysExLen;
|
size_t sysExLen;
|
||||||
|
|
||||||
void submitSysEx(std::vector<unsigned char> data);
|
void submitSysEx(std::vector<unsigned char> data);
|
||||||
|
|
|
@ -987,6 +987,9 @@ int DivEngine::calcBaseFreq(double clock, double divider, int note, bool period)
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
double DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) {
|
double DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) {
|
||||||
|
if (song.linearPitch==2) { // full linear
|
||||||
|
return (note<<7);
|
||||||
|
}
|
||||||
double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0);
|
double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0);
|
||||||
return period?
|
return period?
|
||||||
(clock/base)/divider:
|
(clock/base)/divider:
|
||||||
|
@ -994,19 +997,27 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) {
|
unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) {
|
||||||
|
if (song.linearPitch==2) { // full linear
|
||||||
|
return (note<<7);
|
||||||
|
}
|
||||||
|
double tuning=song.tuning;
|
||||||
|
if (tuning<400.0) tuning=400.0;
|
||||||
|
if (tuning>500.0) tuning=500.0;
|
||||||
int bf=calcBaseFreq(clock,divider,note,false);
|
int bf=calcBaseFreq(clock,divider,note,false);
|
||||||
|
int boundaryBottom=tuning*pow(2.0,0.25)*(divider/clock);
|
||||||
|
int boundaryTop=2.0*tuning*pow(2.0,0.25)*(divider/clock);
|
||||||
int block=note/12;
|
int block=note/12;
|
||||||
if (block<0) block=0;
|
if (block<0) block=0;
|
||||||
if (block>7) block=7;
|
if (block>7) block=7;
|
||||||
bf>>=block;
|
bf>>=block;
|
||||||
if (bf<0) bf=0;
|
if (bf<0) bf=0;
|
||||||
// octave boundaries
|
// octave boundaries
|
||||||
while (bf>0 && bf<644 && block>0) {
|
while (bf>0 && bf<boundaryBottom && block>0) {
|
||||||
bf<<=1;
|
bf<<=1;
|
||||||
block--;
|
block--;
|
||||||
}
|
}
|
||||||
if (bf>1288) {
|
if (bf>boundaryTop) {
|
||||||
while (block<7) {
|
while (block<7 && bf>boundaryTop) {
|
||||||
bf>>=1;
|
bf>>=1;
|
||||||
block++;
|
block++;
|
||||||
}
|
}
|
||||||
|
@ -1014,11 +1025,19 @@ unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, in
|
||||||
bf=(1<<bits)-1;
|
bf=(1<<bits)-1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//logV("f-num: %d block: %d",bf,block);
|
||||||
return bf|(block<<bits);
|
return bf|(block<<bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
int DivEngine::calcFreq(int base, int pitch, bool period, int octave, int pitch2) {
|
int DivEngine::calcFreq(int base, int pitch, bool period, int octave, int pitch2, double clock, double divider) {
|
||||||
if (song.linearPitch) {
|
if (song.linearPitch==2) {
|
||||||
|
// do frequency calculation here
|
||||||
|
double fbase=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(base+pitch+384)/(128.0*12.0));
|
||||||
|
return period?
|
||||||
|
(clock/fbase)/divider:
|
||||||
|
fbase*(divider/clock);
|
||||||
|
}
|
||||||
|
if (song.linearPitch==1) {
|
||||||
// global pitch multiplier
|
// global pitch multiplier
|
||||||
int whatTheFuck=(1024+(globalPitch<<6)-(globalPitch<0?globalPitch-6:0));
|
int whatTheFuck=(1024+(globalPitch<<6)-(globalPitch<0?globalPitch-6:0));
|
||||||
if (whatTheFuck<1) whatTheFuck=1; // avoids division by zero but please kill me
|
if (whatTheFuck<1) whatTheFuck=1; // avoids division by zero but please kill me
|
||||||
|
@ -1213,7 +1232,7 @@ void DivEngine::reset() {
|
||||||
chan[i]=DivChannelState();
|
chan[i]=DivChannelState();
|
||||||
if (i<chans) chan[i].volMax=(disCont[dispatchOfChan[i]].dispatch->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff;
|
if (i<chans) chan[i].volMax=(disCont[dispatchOfChan[i]].dispatch->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff;
|
||||||
chan[i].volume=chan[i].volMax;
|
chan[i].volume=chan[i].volMax;
|
||||||
if (!song.linearPitch) chan[i].vibratoFine=4;
|
if (song.linearPitch==0) chan[i].vibratoFine=4;
|
||||||
}
|
}
|
||||||
extValue=0;
|
extValue=0;
|
||||||
extValuePresent=0;
|
extValuePresent=0;
|
||||||
|
@ -2662,6 +2681,8 @@ bool DivEngine::init() {
|
||||||
// init config
|
// init config
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
configPath=getWinConfigPath();
|
configPath=getWinConfigPath();
|
||||||
|
#elif defined(ANDROID)
|
||||||
|
configPath=SDL_GetPrefPath("tildearrow","furnace");
|
||||||
#else
|
#else
|
||||||
struct stat st;
|
struct stat st;
|
||||||
char* home=getenv("HOME");
|
char* home=getenv("HOME");
|
||||||
|
|
|
@ -45,8 +45,8 @@
|
||||||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||||
|
|
||||||
#define DIV_VERSION "dev93"
|
#define DIV_VERSION "dev94"
|
||||||
#define DIV_ENGINE_VERSION 93
|
#define DIV_ENGINE_VERSION 94
|
||||||
|
|
||||||
// for imports
|
// for imports
|
||||||
#define DIV_VERSION_MOD 0xff01
|
#define DIV_VERSION_MOD 0xff01
|
||||||
|
@ -486,7 +486,7 @@ class DivEngine {
|
||||||
unsigned short calcBaseFreqFNumBlock(double clock, double divider, int note, int bits);
|
unsigned short calcBaseFreqFNumBlock(double clock, double divider, int note, int bits);
|
||||||
|
|
||||||
// calculate frequency/period
|
// calculate frequency/period
|
||||||
int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0);
|
int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0, double clock=1.0, double divider=1.0);
|
||||||
|
|
||||||
// convert panning formats
|
// convert panning formats
|
||||||
int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range);
|
int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range);
|
||||||
|
|
|
@ -139,7 +139,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
||||||
|
|
||||||
// compatibility flags
|
// compatibility flags
|
||||||
ds.limitSlides=true;
|
ds.limitSlides=true;
|
||||||
ds.linearPitch=true;
|
ds.linearPitch=1;
|
||||||
ds.loopModality=0;
|
ds.loopModality=0;
|
||||||
ds.properNoiseLayout=false;
|
ds.properNoiseLayout=false;
|
||||||
ds.waveDutyIsVol=false;
|
ds.waveDutyIsVol=false;
|
||||||
|
@ -173,12 +173,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
||||||
ds.legacyVolumeSlides=false;
|
ds.legacyVolumeSlides=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neo Geo detune
|
// Neo Geo detune is caused by Defle running Neo Geo at the wrong clock.
|
||||||
|
/*
|
||||||
if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT
|
if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT
|
||||||
|| ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT
|
|| ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT
|
||||||
|| ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) {
|
|| ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) {
|
||||||
ds.tuning=443.23;
|
ds.tuning=443.23;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
logI("reading module data...");
|
logI("reading module data...");
|
||||||
if (ds.version>0x0c) {
|
if (ds.version>0x0c) {
|
||||||
|
@ -948,7 +950,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
||||||
|
|
||||||
if (ds.version<37) { // compat flags not stored back then
|
if (ds.version<37) { // compat flags not stored back then
|
||||||
ds.limitSlides=true;
|
ds.limitSlides=true;
|
||||||
ds.linearPitch=true;
|
ds.linearPitch=1;
|
||||||
ds.loopModality=0;
|
ds.loopModality=0;
|
||||||
}
|
}
|
||||||
if (ds.version<43) {
|
if (ds.version<43) {
|
||||||
|
@ -1390,7 +1392,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
||||||
} else {
|
} else {
|
||||||
reader.readC();
|
reader.readC();
|
||||||
}
|
}
|
||||||
for (int i=0; i<19; i++) {
|
if (ds.version>=94) {
|
||||||
|
ds.pitchSlideSpeed=reader.readC();
|
||||||
|
} else {
|
||||||
|
reader.readC();
|
||||||
|
}
|
||||||
|
for (int i=0; i<18; i++) {
|
||||||
reader.readC();
|
reader.readC();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1654,7 +1661,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
||||||
DivSong ds;
|
DivSong ds;
|
||||||
ds.tuning=436.0;
|
ds.tuning=436.0;
|
||||||
ds.version=DIV_VERSION_MOD;
|
ds.version=DIV_VERSION_MOD;
|
||||||
ds.linearPitch=false;
|
ds.linearPitch=0;
|
||||||
ds.noSlidesOnFirstTick=true;
|
ds.noSlidesOnFirstTick=true;
|
||||||
ds.rowResetsArpPos=true;
|
ds.rowResetsArpPos=true;
|
||||||
ds.ignoreJumpAtEnd=false;
|
ds.ignoreJumpAtEnd=false;
|
||||||
|
@ -2713,7 +2720,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
||||||
w->writeC(song.fbPortaPause);
|
w->writeC(song.fbPortaPause);
|
||||||
w->writeC(song.snDutyReset);
|
w->writeC(song.snDutyReset);
|
||||||
w->writeC(song.pitchMacroIsLinear);
|
w->writeC(song.pitchMacroIsLinear);
|
||||||
for (int i=0; i<19; i++) {
|
w->writeC(song.pitchSlideSpeed);
|
||||||
|
for (int i=0; i<18; i++) {
|
||||||
w->writeC(0);
|
w->writeC(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -220,7 +220,7 @@ void DivPlatformAmiga::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
|
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||||
if (chan[i].freq<0) chan[i].freq=0;
|
if (chan[i].freq<0) chan[i].freq=0;
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
|
|
|
@ -251,7 +251,7 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
||||||
if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1;
|
if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1;
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
||||||
|
|
|
@ -280,7 +280,7 @@ void DivPlatformAY8930::tick(bool sysTick) {
|
||||||
immWrite(0x1a,ayNoiseOr);
|
immWrite(0x1a,ayNoiseOr);
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
if (chan[i].insChanged) {
|
if (chan[i].insChanged) {
|
||||||
|
|
|
@ -137,7 +137,7 @@ void DivPlatformBubSysWSG::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SCC);
|
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SCC);
|
||||||
chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)+chan[i].pitch2;
|
chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||||
if (chan[i].freq<0) chan[i].freq=0;
|
if (chan[i].freq<0) chan[i].freq=0;
|
||||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||||
k005289->load(i,chan[i].freq);
|
k005289->load(i,chan[i].freq);
|
||||||
|
|
|
@ -217,7 +217,7 @@ void DivPlatformC64::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||||
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
|
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay));
|
rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay));
|
||||||
|
|
|
@ -61,7 +61,7 @@ void DivPlatformDummy::tick(bool sysTick) {
|
||||||
|
|
||||||
if (chan[i].freqChanged) {
|
if (chan[i].freqChanged) {
|
||||||
chan[i].freqChanged=false;
|
chan[i].freqChanged=false;
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,0,chipClock,CHIP_FREQBASE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,7 +220,7 @@ void DivPlatformFDS::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||||
if (chan[i].freq<0) chan[i].freq=0;
|
if (chan[i].freq<0) chan[i].freq=0;
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
|
|
|
@ -237,7 +237,7 @@ void DivPlatformGB::tick(bool sysTick) {
|
||||||
if (ntPos>255) ntPos=255;
|
if (ntPos>255) ntPos=255;
|
||||||
chan[i].freq=noiseTable[ntPos];
|
chan[i].freq=noiseTable[ntPos];
|
||||||
} else {
|
} else {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||||
if (chan[i].freq>2047) chan[i].freq=2047;
|
if (chan[i].freq>2047) chan[i].freq=2047;
|
||||||
if (chan[i].freq<0) chan[i].freq=0;
|
if (chan[i].freq<0) chan[i].freq=0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -727,6 +727,8 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
|
||||||
|
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
|
||||||
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||||
int newFreq;
|
int newFreq;
|
||||||
bool return2=false;
|
bool return2=false;
|
||||||
|
@ -749,13 +751,13 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
||||||
// check for octave boundary
|
// check for octave boundary
|
||||||
// what the heck!
|
// what the heck!
|
||||||
if (!chan[c.chan].portaPause) {
|
if (!chan[c.chan].portaPause) {
|
||||||
if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) {
|
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||||
chan[c.chan].portaPauseFreq=(644)|((newFreq+0x800)&0xf800);
|
chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||||
chan[c.chan].portaPause=true;
|
chan[c.chan].portaPause=true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) {
|
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||||
chan[c.chan].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800);
|
chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||||
chan[c.chan].portaPause=true;
|
chan[c.chan].portaPause=true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,6 +127,8 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_NOTE_PORTA: {
|
case DIV_CMD_NOTE_PORTA: {
|
||||||
|
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
|
||||||
|
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
|
||||||
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||||
int newFreq;
|
int newFreq;
|
||||||
bool return2=false;
|
bool return2=false;
|
||||||
|
@ -148,18 +150,18 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
||||||
}
|
}
|
||||||
// what the heck!
|
// what the heck!
|
||||||
if (!opChan[ch].portaPause) {
|
if (!opChan[ch].portaPause) {
|
||||||
if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) {
|
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||||
if (parent->song.fbPortaPause) {
|
if (parent->song.fbPortaPause) {
|
||||||
opChan[ch].portaPauseFreq=(644)|((newFreq+0x800)&0xf800);
|
opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||||
opChan[ch].portaPause=true;
|
opChan[ch].portaPause=true;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
|
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) {
|
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||||
if (parent->song.fbPortaPause) {
|
if (parent->song.fbPortaPause) {
|
||||||
opChan[ch].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800);
|
opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||||
opChan[ch].portaPause=true;
|
opChan[ch].portaPause=true;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -201,7 +201,7 @@ void DivPlatformLynx::tick(bool sysTick) {
|
||||||
WRITE_OTHER(i, ((chan[i].lfsr&0xf00)>>4));
|
WRITE_OTHER(i, ((chan[i].lfsr&0xf00)>>4));
|
||||||
chan[i].lfsr=-1;
|
chan[i].lfsr=-1;
|
||||||
}
|
}
|
||||||
chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||||
if (chan[i].std.duty.had) {
|
if (chan[i].std.duty.had) {
|
||||||
chan[i].duty=chan[i].std.duty.val;
|
chan[i].duty=chan[i].std.duty.val;
|
||||||
WRITE_FEEDBACK(i, chan[i].duty.feedback);
|
WRITE_FEEDBACK(i, chan[i].duty.feedback);
|
||||||
|
|
|
@ -147,7 +147,7 @@ void DivPlatformMMC5::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
|
||||||
if (chan[i].freq>2047) chan[i].freq=2047;
|
if (chan[i].freq>2047) chan[i].freq=2047;
|
||||||
if (chan[i].freq<0) chan[i].freq=0;
|
if (chan[i].freq<0) chan[i].freq=0;
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
|
|
|
@ -357,7 +357,7 @@ void DivPlatformN163::tick(bool sysTick) {
|
||||||
chan[i].waveUpdated=false;
|
chan[i].waveUpdated=false;
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||||
if (chan[i].freq<0) chan[i].freq=0;
|
if (chan[i].freq<0) chan[i].freq=0;
|
||||||
if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff;
|
if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff;
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
|
|
|
@ -309,7 +309,7 @@ void DivPlatformNES::tick(bool sysTick) {
|
||||||
if (ntPos>252) ntPos=252;
|
if (ntPos>252) ntPos=252;
|
||||||
chan[i].freq=(parent->song.properNoiseLayout)?(15-(chan[i].baseFreq&15)):(noiseTable[ntPos]);
|
chan[i].freq=(parent->song.properNoiseLayout)?(15-(chan[i].baseFreq&15)):(noiseTable[ntPos]);
|
||||||
} else {
|
} else {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
|
||||||
if (chan[i].freq>2047) chan[i].freq=2047;
|
if (chan[i].freq>2047) chan[i].freq=2047;
|
||||||
if (chan[i].freq<0) chan[i].freq=0;
|
if (chan[i].freq<0) chan[i].freq=0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -511,7 +511,7 @@ void DivPlatformOPL::tick(bool sysTick) {
|
||||||
bool updateDrums=false;
|
bool updateDrums=false;
|
||||||
for (int i=0; i<totalChans; i++) {
|
for (int i=0; i<totalChans; i++) {
|
||||||
if (chan[i].freqChanged) {
|
if (chan[i].freqChanged) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||||
if (chan[i].freq>131071) chan[i].freq=131071;
|
if (chan[i].freq>131071) chan[i].freq=131071;
|
||||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
||||||
chan[i].freqH=freqt>>8;
|
chan[i].freqH=freqt>>8;
|
||||||
|
|
|
@ -300,7 +300,7 @@ void DivPlatformOPLL::tick(bool sysTick) {
|
||||||
|
|
||||||
for (int i=0; i<11; i++) {
|
for (int i=0; i<11; i++) {
|
||||||
if (chan[i].freqChanged) {
|
if (chan[i].freqChanged) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||||
if (chan[i].freq>262143) chan[i].freq=262143;
|
if (chan[i].freq>262143) chan[i].freq=262143;
|
||||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
||||||
chan[i].freqL=freqt&0xff;
|
chan[i].freqL=freqt&0xff;
|
||||||
|
|
|
@ -227,7 +227,7 @@ void DivPlatformPCE::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE);
|
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE);
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||||
if (chan[i].furnaceDac) {
|
if (chan[i].furnaceDac) {
|
||||||
double off=1.0;
|
double off=1.0;
|
||||||
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
|
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
|
||||||
|
|
|
@ -223,7 +223,7 @@ void DivPlatformPCSpeaker::tick(bool sysTick) {
|
||||||
chan[i].freqChanged=true;
|
chan[i].freqChanged=true;
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
|
||||||
if (chan[i].freq<0) chan[i].freq=0;
|
if (chan[i].freq<0) chan[i].freq=0;
|
||||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
|
|
|
@ -118,7 +118,7 @@ void DivPlatformPET::tick(bool sysTick) {
|
||||||
chan.freqChanged=true;
|
chan.freqChanged=true;
|
||||||
}
|
}
|
||||||
if (chan.freqChanged || chan.keyOn || chan.keyOff) {
|
if (chan.freqChanged || chan.keyOn || chan.keyOff) {
|
||||||
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2);
|
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>257) chan.freq=257;
|
||||||
if (chan.freq<2) chan.freq=2;
|
if (chan.freq<2) chan.freq=2;
|
||||||
rWrite(8,chan.freq-2);
|
rWrite(8,chan.freq-2);
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#define CHIP_DIVIDER (1248*2)
|
#define CHIP_DIVIDER (1248*2)
|
||||||
#define QS_NOTE_FREQUENCY(x) parent->calcBaseFreq(440,0x1000,(x)-3,false)
|
#define QS_NOTE_FREQUENCY(x) parent->calcBaseFreq(440,4096,(x)-3,false)
|
||||||
|
|
||||||
#define rWrite(a,v) {if(!skipRegisterWrites) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v); }}
|
#define rWrite(a,v) {if(!skipRegisterWrites) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v); }}
|
||||||
#define immWrite(a,v) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v);}
|
#define immWrite(a,v) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v);}
|
||||||
|
@ -296,14 +296,8 @@ void DivPlatformQSound::tick(bool sysTick) {
|
||||||
uint16_t qsound_addr = 0;
|
uint16_t qsound_addr = 0;
|
||||||
uint16_t qsound_loop = 0;
|
uint16_t qsound_loop = 0;
|
||||||
uint16_t qsound_end = 0;
|
uint16_t qsound_end = 0;
|
||||||
double off=1.0;
|
|
||||||
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||||
DivSample* s=parent->getSample(chan[i].sample);
|
DivSample* s=parent->getSample(chan[i].sample);
|
||||||
if (s->centerRate<1) {
|
|
||||||
off=1.0;
|
|
||||||
} else {
|
|
||||||
off=(double)s->centerRate/24038.0/16.0;
|
|
||||||
}
|
|
||||||
qsound_bank = 0x8000 | (s->offQSound >> 16);
|
qsound_bank = 0x8000 | (s->offQSound >> 16);
|
||||||
qsound_addr = s->offQSound & 0xffff;
|
qsound_addr = s->offQSound & 0xffff;
|
||||||
|
|
||||||
|
@ -322,15 +316,15 @@ void DivPlatformQSound::tick(bool sysTick) {
|
||||||
if (chan[i].std.arp.had) {
|
if (chan[i].std.arp.had) {
|
||||||
if (!chan[i].inPorta) {
|
if (!chan[i].inPorta) {
|
||||||
if (chan[i].std.arp.mode) {
|
if (chan[i].std.arp.mode) {
|
||||||
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].std.arp.val);
|
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||||
} else {
|
} else {
|
||||||
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chan[i].freqChanged=true;
|
chan[i].freqChanged=true;
|
||||||
} else {
|
} else {
|
||||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||||
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note);
|
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note);
|
||||||
chan[i].freqChanged=true;
|
chan[i].freqChanged=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -354,7 +348,16 @@ void DivPlatformQSound::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
|
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
|
double off=1.0;
|
||||||
|
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||||
|
DivSample* s=parent->getSample(chan[i].sample);
|
||||||
|
if (s->centerRate<1) {
|
||||||
|
off=1.0;
|
||||||
|
} else {
|
||||||
|
off=(double)s->centerRate/24038.0/16.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,440.0,4096.0);
|
||||||
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
|
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank);
|
rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank);
|
||||||
|
@ -388,17 +391,8 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
||||||
case DIV_CMD_NOTE_ON: {
|
case DIV_CMD_NOTE_ON: {
|
||||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||||
chan[c.chan].sample=ins->amiga.initSample;
|
chan[c.chan].sample=ins->amiga.initSample;
|
||||||
double off=1.0;
|
|
||||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
|
||||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
|
||||||
if (s->centerRate<1) {
|
|
||||||
off=1.0;
|
|
||||||
} else {
|
|
||||||
off=(double)s->centerRate/24038.0/16.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (c.value!=DIV_NOTE_NULL) {
|
if (c.value!=DIV_NOTE_NULL) {
|
||||||
chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value);
|
chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value);
|
||||||
}
|
}
|
||||||
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
|
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
|
||||||
chan[c.chan].sample=-1;
|
chan[c.chan].sample=-1;
|
||||||
|
@ -467,16 +461,7 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
||||||
chan[c.chan].freqChanged=true;
|
chan[c.chan].freqChanged=true;
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_NOTE_PORTA: {
|
case DIV_CMD_NOTE_PORTA: {
|
||||||
double off=1.0;
|
int destFreq=QS_NOTE_FREQUENCY(c.value2);
|
||||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
|
||||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
|
||||||
if (s->centerRate<1) {
|
|
||||||
off=1.0;
|
|
||||||
} else {
|
|
||||||
off=(double)s->centerRate/24038.0/16.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int destFreq=off*QS_NOTE_FREQUENCY(c.value2);
|
|
||||||
bool return2=false;
|
bool return2=false;
|
||||||
if (destFreq>chan[c.chan].baseFreq) {
|
if (destFreq>chan[c.chan].baseFreq) {
|
||||||
chan[c.chan].baseFreq+=c.value;
|
chan[c.chan].baseFreq+=c.value;
|
||||||
|
@ -499,16 +484,7 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_LEGATO: {
|
case DIV_CMD_LEGATO: {
|
||||||
double off=1.0;
|
chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
|
||||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
|
||||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
|
||||||
if (s->centerRate<1) {
|
|
||||||
off=1.0;
|
|
||||||
} else {
|
|
||||||
off=(double)s->centerRate/24038.0/16.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
|
|
||||||
chan[c.chan].freqChanged=true;
|
chan[c.chan].freqChanged=true;
|
||||||
chan[c.chan].note=c.value;
|
chan[c.chan].note=c.value;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -200,7 +200,7 @@ void DivPlatformSAA1099::tick(bool sysTick) {
|
||||||
rWrite(0x18+(i/3),saaEnv[i/3]);
|
rWrite(0x18+(i/3),saaEnv[i/3]);
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||||
if (chan[i].freq>=32768) {
|
if (chan[i].freq>=32768) {
|
||||||
chan[i].freqH=7;
|
chan[i].freqH=7;
|
||||||
|
|
|
@ -121,7 +121,7 @@ void DivPlatformSMS::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
for (int i=0; i<3; i++) {
|
for (int i=0; i<3; i++) {
|
||||||
if (chan[i].freqChanged) {
|
if (chan[i].freqChanged) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,64);
|
||||||
if (chan[i].freq>1023) chan[i].freq=1023;
|
if (chan[i].freq>1023) chan[i].freq=1023;
|
||||||
if (chan[i].freq<8) chan[i].freq=1;
|
if (chan[i].freq<8) chan[i].freq=1;
|
||||||
//if (chan[i].actualNote>0x5d) chan[i].freq=0x01;
|
//if (chan[i].actualNote>0x5d) chan[i].freq=0x01;
|
||||||
|
@ -136,7 +136,7 @@ void DivPlatformSMS::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (chan[3].freqChanged || updateSNMode) {
|
if (chan[3].freqChanged || updateSNMode) {
|
||||||
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2);
|
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,isRealSN?60:64);
|
||||||
if (chan[3].freq>1023) chan[3].freq=1023;
|
if (chan[3].freq>1023) chan[3].freq=1023;
|
||||||
if (chan[3].actualNote>0x5d) chan[3].freq=0x01;
|
if (chan[3].actualNote>0x5d) chan[3].freq=0x01;
|
||||||
if (snNoiseMode&2) { // take period from channel 3
|
if (snNoiseMode&2) { // take period from channel 3
|
||||||
|
|
|
@ -183,7 +183,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
|
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||||
if (chan[i].pcm) {
|
if (chan[i].pcm) {
|
||||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
|
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
|
||||||
DivSample* sample=parent->getSample(ins->amiga.initSample);
|
DivSample* sample=parent->getSample(ins->amiga.initSample);
|
||||||
|
|
|
@ -203,7 +203,7 @@ void DivPlatformSwan::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||||
if (i==1 && pcm && furnaceDac) {
|
if (i==1 && pcm && furnaceDac) {
|
||||||
double off=1.0;
|
double off=1.0;
|
||||||
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
|
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
|
||||||
|
|
|
@ -919,7 +919,6 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
|
||||||
}
|
}
|
||||||
case DIV_CMD_FM_FIXFREQ: {
|
case DIV_CMD_FM_FIXFREQ: {
|
||||||
if (c.value<0 || c.value>3) break;
|
if (c.value<0 || c.value>3) break;
|
||||||
printf("fixfreq %x\n",c.value2);
|
|
||||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||||
op.egt=(c.value2>0);
|
op.egt=(c.value2>0);
|
||||||
|
|
|
@ -147,6 +147,7 @@ void DivPlatformVERA::reset() {
|
||||||
chan[16].pan=3;
|
chan[16].pan=3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: linear pitch stuff
|
||||||
int DivPlatformVERA::calcNoteFreq(int ch, int note) {
|
int DivPlatformVERA::calcNoteFreq(int ch, int note) {
|
||||||
if (ch<16) {
|
if (ch<16) {
|
||||||
return parent->calcBaseFreq(chipClock,2097152,note,false);
|
return parent->calcBaseFreq(chipClock,2097152,note,false);
|
||||||
|
@ -208,7 +209,7 @@ void DivPlatformVERA::tick(bool sysTick) {
|
||||||
chan[i].freqChanged=true;
|
chan[i].freqChanged=true;
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged) {
|
if (chan[i].freqChanged) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2,chipClock,2097152);
|
||||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||||
rWrite(i,0,chan[i].freq&0xff);
|
rWrite(i,0,chan[i].freq&0xff);
|
||||||
rWrite(i,1,(chan[i].freq>>8)&0xff);
|
rWrite(i,1,(chan[i].freq>>8)&0xff);
|
||||||
|
|
|
@ -132,7 +132,7 @@ void DivPlatformVIC20::tick(bool sysTick) {
|
||||||
chan[i].freqChanged=true;
|
chan[i].freqChanged=true;
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||||
if (i<3) {
|
if (i<3) {
|
||||||
chan[i].freq>>=(2-i);
|
chan[i].freq>>=(2-i);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -197,9 +197,9 @@ void DivPlatformVRC6::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
if (i==2) { // sawtooth
|
if (i==2) { // sawtooth
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,14)-1;
|
||||||
} else { // pulse
|
} else { // pulse
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,16)-1;
|
||||||
if (chan[i].furnaceDac) {
|
if (chan[i].furnaceDac) {
|
||||||
double off=1.0;
|
double off=1.0;
|
||||||
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
|
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
|
||||||
|
|
|
@ -260,6 +260,7 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: linear pitch stuff
|
||||||
double DivPlatformX1_010::NoteX1_010(int ch, int note) {
|
double DivPlatformX1_010::NoteX1_010(int ch, int note) {
|
||||||
if (chan[ch].pcm) { // PCM note
|
if (chan[ch].pcm) { // PCM note
|
||||||
double off=1.0;
|
double off=1.0;
|
||||||
|
@ -487,7 +488,7 @@ void DivPlatformX1_010::tick(bool sysTick) {
|
||||||
chan[i].envChanged=false;
|
chan[i].envChanged=false;
|
||||||
}
|
}
|
||||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||||
if (chan[i].pcm) {
|
if (chan[i].pcm) {
|
||||||
if (chan[i].freq<1) chan[i].freq=1;
|
if (chan[i].freq<1) chan[i].freq=1;
|
||||||
if (chan[i].freq>255) chan[i].freq=255;
|
if (chan[i].freq>255) chan[i].freq=255;
|
||||||
|
|
|
@ -456,7 +456,7 @@ double DivPlatformYM2610::NOTE_OPNB(int ch, int note) {
|
||||||
return NOTE_PERIODIC(note);
|
return NOTE_PERIODIC(note);
|
||||||
}
|
}
|
||||||
// FM
|
// FM
|
||||||
return NOTE_FREQUENCY(note);
|
return NOTE_FNUM_BLOCK(note,11);
|
||||||
}
|
}
|
||||||
|
|
||||||
double DivPlatformYM2610::NOTE_ADPCMB(int note) {
|
double DivPlatformYM2610::NOTE_ADPCMB(int note) {
|
||||||
|
@ -559,15 +559,15 @@ void DivPlatformYM2610::tick(bool sysTick) {
|
||||||
if (chan[i].std.arp.had) {
|
if (chan[i].std.arp.had) {
|
||||||
if (!chan[i].inPorta) {
|
if (!chan[i].inPorta) {
|
||||||
if (chan[i].std.arp.mode) {
|
if (chan[i].std.arp.mode) {
|
||||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
|
||||||
} else {
|
} else {
|
||||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
|
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chan[i].freqChanged=true;
|
chan[i].freqChanged=true;
|
||||||
} else {
|
} else {
|
||||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
|
||||||
chan[i].freqChanged=true;
|
chan[i].freqChanged=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -742,11 +742,20 @@ void DivPlatformYM2610::tick(bool sysTick) {
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
if (i==1 && extMode) continue;
|
if (i==1 && extMode) continue;
|
||||||
if (chan[i].freqChanged) {
|
if (chan[i].freqChanged) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
|
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2);
|
||||||
if (chan[i].freq>262143) chan[i].freq=262143;
|
int block=(chan[i].baseFreq&0xf800)>>11;
|
||||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
if (fNum<0) fNum=0;
|
||||||
immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8);
|
if (fNum>2047) {
|
||||||
immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff);
|
while (block<7) {
|
||||||
|
fNum>>=1;
|
||||||
|
block++;
|
||||||
|
}
|
||||||
|
if (fNum>2047) fNum=2047;
|
||||||
|
}
|
||||||
|
chan[i].freq=(block<<11)|fNum;
|
||||||
|
if (chan[i].freq>0x3fff) chan[i].freq=0x3fff;
|
||||||
|
immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8);
|
||||||
|
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
||||||
chan[i].freqChanged=false;
|
chan[i].freqChanged=false;
|
||||||
}
|
}
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
|
@ -756,47 +765,6 @@ void DivPlatformYM2610::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int DivPlatformYM2610::octave(int freq) {
|
|
||||||
if (freq>=622.0f*128) {
|
|
||||||
return 128;
|
|
||||||
} else if (freq>=622.0f*64) {
|
|
||||||
return 64;
|
|
||||||
} else if (freq>=622.0f*32) {
|
|
||||||
return 32;
|
|
||||||
} else if (freq>=622.0f*16) {
|
|
||||||
return 16;
|
|
||||||
} else if (freq>=622.0f*8) {
|
|
||||||
return 8;
|
|
||||||
} else if (freq>=622.0f*4) {
|
|
||||||
return 4;
|
|
||||||
} else if (freq>=622.0f*2) {
|
|
||||||
return 2;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int DivPlatformYM2610::toFreq(int freq) {
|
|
||||||
if (freq>=622.0f*128) {
|
|
||||||
return 0x3800|((freq>>7)&0x7ff);
|
|
||||||
} else if (freq>=622.0f*64) {
|
|
||||||
return 0x3000|((freq>>6)&0x7ff);
|
|
||||||
} else if (freq>=622.0f*32) {
|
|
||||||
return 0x2800|((freq>>5)&0x7ff);
|
|
||||||
} else if (freq>=622.0f*16) {
|
|
||||||
return 0x2000|((freq>>4)&0x7ff);
|
|
||||||
} else if (freq>=622.0f*8) {
|
|
||||||
return 0x1800|((freq>>3)&0x7ff);
|
|
||||||
} else if (freq>=622.0f*4) {
|
|
||||||
return 0x1000|((freq>>2)&0x7ff);
|
|
||||||
} else if (freq>=622.0f*2) {
|
|
||||||
return 0x800|((freq>>1)&0x7ff);
|
|
||||||
} else {
|
|
||||||
return freq&0x7ff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int DivPlatformYM2610::dispatch(DivCommand c) {
|
int DivPlatformYM2610::dispatch(DivCommand c) {
|
||||||
if (c.chan>3 && c.chan<7) {
|
if (c.chan>3 && c.chan<7) {
|
||||||
c.chan-=4;
|
c.chan-=4;
|
||||||
|
@ -928,7 +896,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
||||||
chan[c.chan].insChanged=false;
|
chan[c.chan].insChanged=false;
|
||||||
|
|
||||||
if (c.value!=DIV_NOTE_NULL) {
|
if (c.value!=DIV_NOTE_NULL) {
|
||||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||||
chan[c.chan].portaPause=false;
|
chan[c.chan].portaPause=false;
|
||||||
chan[c.chan].freqChanged=true;
|
chan[c.chan].freqChanged=true;
|
||||||
chan[c.chan].note=c.value;
|
chan[c.chan].note=c.value;
|
||||||
|
@ -1048,33 +1016,48 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
|
||||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
|
||||||
|
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||||
int newFreq;
|
int newFreq;
|
||||||
bool return2=false;
|
bool return2=false;
|
||||||
|
if (chan[c.chan].portaPause) {
|
||||||
|
chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq;
|
||||||
|
}
|
||||||
if (destFreq>chan[c.chan].baseFreq) {
|
if (destFreq>chan[c.chan].baseFreq) {
|
||||||
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
|
newFreq=chan[c.chan].baseFreq+c.value;
|
||||||
if (newFreq>=destFreq) {
|
if (newFreq>=destFreq) {
|
||||||
newFreq=destFreq;
|
newFreq=destFreq;
|
||||||
return2=true;
|
return2=true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
|
newFreq=chan[c.chan].baseFreq-c.value;
|
||||||
if (newFreq<=destFreq) {
|
if (newFreq<=destFreq) {
|
||||||
newFreq=destFreq;
|
newFreq=destFreq;
|
||||||
return2=true;
|
return2=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// check for octave boundary
|
||||||
|
// what the heck!
|
||||||
if (!chan[c.chan].portaPause) {
|
if (!chan[c.chan].portaPause) {
|
||||||
if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) {
|
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||||
|
chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||||
|
chan[c.chan].portaPause=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||||
|
chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||||
chan[c.chan].portaPause=true;
|
chan[c.chan].portaPause=true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chan[c.chan].baseFreq=newFreq;
|
|
||||||
chan[c.chan].portaPause=false;
|
chan[c.chan].portaPause=false;
|
||||||
chan[c.chan].freqChanged=true;
|
chan[c.chan].freqChanged=true;
|
||||||
if (return2) return 2;
|
chan[c.chan].baseFreq=newFreq;
|
||||||
|
if (return2) {
|
||||||
|
chan[c.chan].inPorta=false;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_SAMPLE_BANK:
|
case DIV_CMD_SAMPLE_BANK:
|
||||||
|
|
|
@ -61,7 +61,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
|
||||||
struct Channel {
|
struct Channel {
|
||||||
DivInstrumentFM state;
|
DivInstrumentFM state;
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
|
||||||
|
@ -80,6 +80,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
|
||||||
baseFreq(0),
|
baseFreq(0),
|
||||||
pitch(0),
|
pitch(0),
|
||||||
pitch2(0),
|
pitch2(0),
|
||||||
|
portaPauseFreq(0),
|
||||||
note(0),
|
note(0),
|
||||||
ins(-1),
|
ins(-1),
|
||||||
psgMode(1),
|
psgMode(1),
|
||||||
|
@ -125,8 +126,6 @@ class DivPlatformYM2610: public DivPlatformYM2610Base {
|
||||||
short oldWrites[512];
|
short oldWrites[512];
|
||||||
short pendingWrites[512];
|
short pendingWrites[512];
|
||||||
|
|
||||||
int octave(int freq);
|
|
||||||
int toFreq(int freq);
|
|
||||||
double NOTE_OPNB(int ch, int note);
|
double NOTE_OPNB(int ch, int note);
|
||||||
double NOTE_ADPCMB(int note);
|
double NOTE_ADPCMB(int note);
|
||||||
friend void putDispatchChan(void*,int,int);
|
friend void putDispatchChan(void*,int,int);
|
||||||
|
|
|
@ -436,7 +436,7 @@ double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) {
|
||||||
return NOTE_PERIODIC(note);
|
return NOTE_PERIODIC(note);
|
||||||
}
|
}
|
||||||
// FM
|
// FM
|
||||||
return NOTE_FREQUENCY(note);
|
return NOTE_FNUM_BLOCK(note,11);
|
||||||
}
|
}
|
||||||
|
|
||||||
double DivPlatformYM2610B::NOTE_ADPCMB(int note) {
|
double DivPlatformYM2610B::NOTE_ADPCMB(int note) {
|
||||||
|
@ -538,15 +538,15 @@ void DivPlatformYM2610B::tick(bool sysTick) {
|
||||||
if (chan[i].std.arp.had) {
|
if (chan[i].std.arp.had) {
|
||||||
if (!chan[i].inPorta) {
|
if (!chan[i].inPorta) {
|
||||||
if (chan[i].std.arp.mode) {
|
if (chan[i].std.arp.mode) {
|
||||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
|
||||||
} else {
|
} else {
|
||||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
|
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chan[i].freqChanged=true;
|
chan[i].freqChanged=true;
|
||||||
} else {
|
} else {
|
||||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
|
||||||
chan[i].freqChanged=true;
|
chan[i].freqChanged=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -720,11 +720,20 @@ void DivPlatformYM2610B::tick(bool sysTick) {
|
||||||
for (int i=0; i<6; i++) {
|
for (int i=0; i<6; i++) {
|
||||||
if (i==2 && extMode) continue;
|
if (i==2 && extMode) continue;
|
||||||
if (chan[i].freqChanged) {
|
if (chan[i].freqChanged) {
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
|
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2);
|
||||||
if (chan[i].freq>262143) chan[i].freq=262143;
|
int block=(chan[i].baseFreq&0xf800)>>11;
|
||||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
if (fNum<0) fNum=0;
|
||||||
immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8);
|
if (fNum>2047) {
|
||||||
immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff);
|
while (block<7) {
|
||||||
|
fNum>>=1;
|
||||||
|
block++;
|
||||||
|
}
|
||||||
|
if (fNum>2047) fNum=2047;
|
||||||
|
}
|
||||||
|
chan[i].freq=(block<<11)|fNum;
|
||||||
|
if (chan[i].freq>0x3fff) chan[i].freq=0x3fff;
|
||||||
|
immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8);
|
||||||
|
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
||||||
chan[i].freqChanged=false;
|
chan[i].freqChanged=false;
|
||||||
}
|
}
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn) {
|
||||||
|
@ -734,47 +743,6 @@ void DivPlatformYM2610B::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int DivPlatformYM2610B::octave(int freq) {
|
|
||||||
if (freq>=622.0f*128) {
|
|
||||||
return 128;
|
|
||||||
} else if (freq>=622.0f*64) {
|
|
||||||
return 64;
|
|
||||||
} else if (freq>=622.0f*32) {
|
|
||||||
return 32;
|
|
||||||
} else if (freq>=622.0f*16) {
|
|
||||||
return 16;
|
|
||||||
} else if (freq>=622.0f*8) {
|
|
||||||
return 8;
|
|
||||||
} else if (freq>=622.0f*4) {
|
|
||||||
return 4;
|
|
||||||
} else if (freq>=622.0f*2) {
|
|
||||||
return 2;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int DivPlatformYM2610B::toFreq(int freq) {
|
|
||||||
if (freq>=622.0f*128) {
|
|
||||||
return 0x3800|((freq>>7)&0x7ff);
|
|
||||||
} else if (freq>=622.0f*64) {
|
|
||||||
return 0x3000|((freq>>6)&0x7ff);
|
|
||||||
} else if (freq>=622.0f*32) {
|
|
||||||
return 0x2800|((freq>>5)&0x7ff);
|
|
||||||
} else if (freq>=622.0f*16) {
|
|
||||||
return 0x2000|((freq>>4)&0x7ff);
|
|
||||||
} else if (freq>=622.0f*8) {
|
|
||||||
return 0x1800|((freq>>3)&0x7ff);
|
|
||||||
} else if (freq>=622.0f*4) {
|
|
||||||
return 0x1000|((freq>>2)&0x7ff);
|
|
||||||
} else if (freq>=622.0f*2) {
|
|
||||||
return 0x800|((freq>>1)&0x7ff);
|
|
||||||
} else {
|
|
||||||
return freq&0x7ff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int DivPlatformYM2610B::dispatch(DivCommand c) {
|
int DivPlatformYM2610B::dispatch(DivCommand c) {
|
||||||
if (c.chan>5 && c.chan<9) {
|
if (c.chan>5 && c.chan<9) {
|
||||||
c.chan-=6;
|
c.chan-=6;
|
||||||
|
@ -906,7 +874,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
||||||
chan[c.chan].insChanged=false;
|
chan[c.chan].insChanged=false;
|
||||||
|
|
||||||
if (c.value!=DIV_NOTE_NULL) {
|
if (c.value!=DIV_NOTE_NULL) {
|
||||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||||
chan[c.chan].portaPause=false;
|
chan[c.chan].portaPause=false;
|
||||||
chan[c.chan].freqChanged=true;
|
chan[c.chan].freqChanged=true;
|
||||||
chan[c.chan].note=c.value;
|
chan[c.chan].note=c.value;
|
||||||
|
@ -1026,33 +994,48 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
|
||||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
|
||||||
|
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||||
int newFreq;
|
int newFreq;
|
||||||
bool return2=false;
|
bool return2=false;
|
||||||
|
if (chan[c.chan].portaPause) {
|
||||||
|
chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq;
|
||||||
|
}
|
||||||
if (destFreq>chan[c.chan].baseFreq) {
|
if (destFreq>chan[c.chan].baseFreq) {
|
||||||
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
|
newFreq=chan[c.chan].baseFreq+c.value;
|
||||||
if (newFreq>=destFreq) {
|
if (newFreq>=destFreq) {
|
||||||
newFreq=destFreq;
|
newFreq=destFreq;
|
||||||
return2=true;
|
return2=true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
|
newFreq=chan[c.chan].baseFreq-c.value;
|
||||||
if (newFreq<=destFreq) {
|
if (newFreq<=destFreq) {
|
||||||
newFreq=destFreq;
|
newFreq=destFreq;
|
||||||
return2=true;
|
return2=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// check for octave boundary
|
||||||
|
// what the heck!
|
||||||
if (!chan[c.chan].portaPause) {
|
if (!chan[c.chan].portaPause) {
|
||||||
if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) {
|
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||||
|
chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||||
|
chan[c.chan].portaPause=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||||
|
chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||||
chan[c.chan].portaPause=true;
|
chan[c.chan].portaPause=true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chan[c.chan].baseFreq=newFreq;
|
|
||||||
chan[c.chan].portaPause=false;
|
chan[c.chan].portaPause=false;
|
||||||
chan[c.chan].freqChanged=true;
|
chan[c.chan].freqChanged=true;
|
||||||
if (return2) return 2;
|
chan[c.chan].baseFreq=newFreq;
|
||||||
|
if (return2) {
|
||||||
|
chan[c.chan].inPorta=false;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_SAMPLE_BANK:
|
case DIV_CMD_SAMPLE_BANK:
|
||||||
|
|
|
@ -35,7 +35,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
|
||||||
struct Channel {
|
struct Channel {
|
||||||
DivInstrumentFM state;
|
DivInstrumentFM state;
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
|
||||||
|
@ -54,6 +54,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
|
||||||
baseFreq(0),
|
baseFreq(0),
|
||||||
pitch(0),
|
pitch(0),
|
||||||
pitch2(0),
|
pitch2(0),
|
||||||
|
portaPauseFreq(0),
|
||||||
note(0),
|
note(0),
|
||||||
ins(-1),
|
ins(-1),
|
||||||
psgMode(1),
|
psgMode(1),
|
||||||
|
@ -98,8 +99,6 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base {
|
||||||
short oldWrites[512];
|
short oldWrites[512];
|
||||||
short pendingWrites[512];
|
short pendingWrites[512];
|
||||||
|
|
||||||
int octave(int freq);
|
|
||||||
int toFreq(int freq);
|
|
||||||
double NOTE_OPNB(int ch, int note);
|
double NOTE_OPNB(int ch, int note);
|
||||||
double NOTE_ADPCMB(int note);
|
double NOTE_ADPCMB(int note);
|
||||||
friend void putDispatchChan(void*,int,int);
|
friend void putDispatchChan(void*,int,int);
|
||||||
|
|
|
@ -120,31 +120,51 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_NOTE_PORTA: {
|
case DIV_CMD_NOTE_PORTA: {
|
||||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
|
||||||
|
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
|
||||||
|
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||||
int newFreq;
|
int newFreq;
|
||||||
bool return2=false;
|
bool return2=false;
|
||||||
|
if (opChan[ch].portaPause) {
|
||||||
|
opChan[ch].baseFreq=opChan[ch].portaPauseFreq;
|
||||||
|
}
|
||||||
if (destFreq>opChan[ch].baseFreq) {
|
if (destFreq>opChan[ch].baseFreq) {
|
||||||
newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq);
|
newFreq=opChan[ch].baseFreq+c.value;
|
||||||
if (newFreq>=destFreq) {
|
if (newFreq>=destFreq) {
|
||||||
newFreq=destFreq;
|
newFreq=destFreq;
|
||||||
return2=true;
|
return2=true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq);
|
newFreq=opChan[ch].baseFreq-c.value;
|
||||||
if (newFreq<=destFreq) {
|
if (newFreq<=destFreq) {
|
||||||
newFreq=destFreq;
|
newFreq=destFreq;
|
||||||
return2=true;
|
return2=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// what the heck!
|
||||||
if (!opChan[ch].portaPause) {
|
if (!opChan[ch].portaPause) {
|
||||||
if (octave(opChan[ch].baseFreq)!=octave(newFreq)) {
|
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||||
|
if (parent->song.fbPortaPause) {
|
||||||
|
opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||||
opChan[ch].portaPause=true;
|
opChan[ch].portaPause=true;
|
||||||
break;
|
break;
|
||||||
|
} else {
|
||||||
|
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||||
|
if (parent->song.fbPortaPause) {
|
||||||
|
opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||||
|
opChan[ch].portaPause=true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
opChan[ch].baseFreq=newFreq;
|
|
||||||
opChan[ch].portaPause=false;
|
opChan[ch].portaPause=false;
|
||||||
opChan[ch].freqChanged=true;
|
opChan[ch].freqChanged=true;
|
||||||
|
opChan[ch].baseFreq=newFreq;
|
||||||
if (return2) return 2;
|
if (return2) return 2;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -364,14 +384,20 @@ void DivPlatformYM2610BExt::tick(bool sysTick) {
|
||||||
unsigned char writeMask=2;
|
unsigned char writeMask=2;
|
||||||
if (extMode) for (int i=0; i<4; i++) {
|
if (extMode) for (int i=0; i<4; i++) {
|
||||||
if (opChan[i].freqChanged) {
|
if (opChan[i].freqChanged) {
|
||||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,octave(opChan[i].baseFreq),opChan[i].pitch2);
|
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
|
||||||
if (opChan[i].freq>262143) opChan[i].freq=262143;
|
int block=(opChan[i].baseFreq&0xf800)>>11;
|
||||||
int freqt=toFreq(opChan[i].freq);
|
if (fNum<0) fNum=0;
|
||||||
opChan[i].freqH=freqt>>8;
|
if (fNum>2047) {
|
||||||
opChan[i].freqL=freqt&0xff;
|
while (block<7) {
|
||||||
immWrite(opChanOffsH[i],opChan[i].freqH);
|
fNum>>=1;
|
||||||
immWrite(opChanOffsL[i],opChan[i].freqL);
|
block++;
|
||||||
opChan[i].freqChanged=false;
|
}
|
||||||
|
if (fNum>2047) fNum=2047;
|
||||||
|
}
|
||||||
|
opChan[i].freq=(block<<11)|fNum;
|
||||||
|
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
|
||||||
|
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||||
|
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||||
}
|
}
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=opChan[i].active<<(4+i);
|
||||||
if (opChan[i].keyOn) {
|
if (opChan[i].keyOn) {
|
||||||
|
|
|
@ -25,12 +25,12 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B {
|
||||||
struct OpChannel {
|
struct OpChannel {
|
||||||
DivMacroInt std;
|
DivMacroInt std;
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
|
||||||
int vol;
|
int vol;
|
||||||
unsigned char pan;
|
unsigned char pan;
|
||||||
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
|
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
|
||||||
};
|
};
|
||||||
OpChannel opChan[4];
|
OpChannel opChan[4];
|
||||||
bool isOpMuted[4];
|
bool isOpMuted[4];
|
||||||
|
|
|
@ -63,7 +63,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
|
||||||
opChan[ch].insChanged=false;
|
opChan[ch].insChanged=false;
|
||||||
|
|
||||||
if (c.value!=DIV_NOTE_NULL) {
|
if (c.value!=DIV_NOTE_NULL) {
|
||||||
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
|
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||||
opChan[ch].portaPause=false;
|
opChan[ch].portaPause=false;
|
||||||
opChan[ch].freqChanged=true;
|
opChan[ch].freqChanged=true;
|
||||||
}
|
}
|
||||||
|
@ -120,36 +120,56 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_NOTE_PORTA: {
|
case DIV_CMD_NOTE_PORTA: {
|
||||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
|
||||||
|
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
|
||||||
|
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||||
int newFreq;
|
int newFreq;
|
||||||
bool return2=false;
|
bool return2=false;
|
||||||
|
if (opChan[ch].portaPause) {
|
||||||
|
opChan[ch].baseFreq=opChan[ch].portaPauseFreq;
|
||||||
|
}
|
||||||
if (destFreq>opChan[ch].baseFreq) {
|
if (destFreq>opChan[ch].baseFreq) {
|
||||||
newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq);
|
newFreq=opChan[ch].baseFreq+c.value;
|
||||||
if (newFreq>=destFreq) {
|
if (newFreq>=destFreq) {
|
||||||
newFreq=destFreq;
|
newFreq=destFreq;
|
||||||
return2=true;
|
return2=true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq);
|
newFreq=opChan[ch].baseFreq-c.value;
|
||||||
if (newFreq<=destFreq) {
|
if (newFreq<=destFreq) {
|
||||||
newFreq=destFreq;
|
newFreq=destFreq;
|
||||||
return2=true;
|
return2=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// what the heck!
|
||||||
if (!opChan[ch].portaPause) {
|
if (!opChan[ch].portaPause) {
|
||||||
if (octave(opChan[ch].baseFreq)!=octave(newFreq)) {
|
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||||
|
if (parent->song.fbPortaPause) {
|
||||||
|
opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||||
opChan[ch].portaPause=true;
|
opChan[ch].portaPause=true;
|
||||||
break;
|
break;
|
||||||
|
} else {
|
||||||
|
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||||
|
if (parent->song.fbPortaPause) {
|
||||||
|
opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||||
|
opChan[ch].portaPause=true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
opChan[ch].baseFreq=newFreq;
|
|
||||||
opChan[ch].portaPause=false;
|
opChan[ch].portaPause=false;
|
||||||
opChan[ch].freqChanged=true;
|
opChan[ch].freqChanged=true;
|
||||||
|
opChan[ch].baseFreq=newFreq;
|
||||||
if (return2) return 2;
|
if (return2) return 2;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_LEGATO: {
|
case DIV_CMD_LEGATO: {
|
||||||
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
|
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||||
opChan[ch].freqChanged=true;
|
opChan[ch].freqChanged=true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -364,14 +384,20 @@ void DivPlatformYM2610Ext::tick(bool sysTick) {
|
||||||
unsigned char writeMask=2;
|
unsigned char writeMask=2;
|
||||||
if (extMode) for (int i=0; i<4; i++) {
|
if (extMode) for (int i=0; i<4; i++) {
|
||||||
if (opChan[i].freqChanged) {
|
if (opChan[i].freqChanged) {
|
||||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,octave(opChan[i].baseFreq),opChan[i].pitch2);
|
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
|
||||||
if (opChan[i].freq>262143) opChan[i].freq=262143;
|
int block=(opChan[i].baseFreq&0xf800)>>11;
|
||||||
int freqt=toFreq(opChan[i].freq);
|
if (fNum<0) fNum=0;
|
||||||
opChan[i].freqH=freqt>>8;
|
if (fNum>2047) {
|
||||||
opChan[i].freqL=freqt&0xff;
|
while (block<7) {
|
||||||
immWrite(opChanOffsH[i],opChan[i].freqH);
|
fNum>>=1;
|
||||||
immWrite(opChanOffsL[i],opChan[i].freqL);
|
block++;
|
||||||
opChan[i].freqChanged=false;
|
}
|
||||||
|
if (fNum>2047) fNum=2047;
|
||||||
|
}
|
||||||
|
opChan[i].freq=(block<<11)|fNum;
|
||||||
|
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
|
||||||
|
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||||
|
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||||
}
|
}
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=opChan[i].active<<(4+i);
|
||||||
if (opChan[i].keyOn) {
|
if (opChan[i].keyOn) {
|
||||||
|
|
|
@ -25,12 +25,12 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 {
|
||||||
struct OpChannel {
|
struct OpChannel {
|
||||||
DivMacroInt std;
|
DivMacroInt std;
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
|
||||||
int vol;
|
int vol;
|
||||||
unsigned char pan;
|
unsigned char pan;
|
||||||
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
|
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
|
||||||
};
|
};
|
||||||
OpChannel opChan[4];
|
OpChannel opChan[4];
|
||||||
bool isOpMuted[4];
|
bool isOpMuted[4];
|
||||||
|
|
|
@ -642,7 +642,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
chan[i].scheduledSlideReset=false;
|
chan[i].scheduledSlideReset=false;
|
||||||
chan[i].inPorta=false;
|
chan[i].inPorta=false;
|
||||||
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0));
|
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0));
|
||||||
dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote));
|
dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote));
|
||||||
chan[i].portaNote=-1;
|
chan[i].portaNote=-1;
|
||||||
chan[i].portaSpeed=-1;
|
chan[i].portaSpeed=-1;
|
||||||
chan[i].inPorta=false;
|
chan[i].inPorta=false;
|
||||||
|
@ -966,7 +966,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
||||||
}
|
}
|
||||||
if (!song.noSlidesOnFirstTick || !firstTick) {
|
if (!song.noSlidesOnFirstTick || !firstTick) {
|
||||||
if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) {
|
if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) {
|
||||||
if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) {
|
if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) {
|
||||||
chan[i].portaSpeed=0;
|
chan[i].portaSpeed=0;
|
||||||
chan[i].oldNote=chan[i].note;
|
chan[i].oldNote=chan[i].note;
|
||||||
chan[i].note=chan[i].portaNote;
|
chan[i].note=chan[i].portaNote;
|
||||||
|
|
|
@ -302,7 +302,12 @@ struct DivSong {
|
||||||
|
|
||||||
// compatibility flags
|
// compatibility flags
|
||||||
bool limitSlides;
|
bool limitSlides;
|
||||||
bool linearPitch;
|
// linear pitch
|
||||||
|
// 0: not linear
|
||||||
|
// 1: only pitch changes (04xy/E5xx) linear
|
||||||
|
// 2: full linear
|
||||||
|
unsigned char linearPitch;
|
||||||
|
unsigned char pitchSlideSpeed;
|
||||||
// loop behavior
|
// loop behavior
|
||||||
// 0: reset on loop
|
// 0: reset on loop
|
||||||
// 1: fake reset on loop
|
// 1: fake reset on loop
|
||||||
|
@ -413,7 +418,8 @@ struct DivSong {
|
||||||
masterVol(1.0f),
|
masterVol(1.0f),
|
||||||
tuning(440.0f),
|
tuning(440.0f),
|
||||||
limitSlides(false),
|
limitSlides(false),
|
||||||
linearPitch(true),
|
linearPitch(1),
|
||||||
|
pitchSlideSpeed(4),
|
||||||
loopModality(0),
|
loopModality(0),
|
||||||
properNoiseLayout(false),
|
properNoiseLayout(false),
|
||||||
waveDutyIsVol(false),
|
waveDutyIsVol(false),
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "gui.h"
|
#include "gui.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "intConst.h"
|
||||||
|
|
||||||
void FurnaceGUI::drawCompatFlags() {
|
void FurnaceGUI::drawCompatFlags() {
|
||||||
if (nextWindow==GUI_WINDOW_COMPAT_FLAGS) {
|
if (nextWindow==GUI_WINDOW_COMPAT_FLAGS) {
|
||||||
|
@ -32,10 +34,6 @@ void FurnaceGUI::drawCompatFlags() {
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves.");
|
ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves.");
|
||||||
}
|
}
|
||||||
ImGui::Checkbox("Linear pitch control",&e->song.linearPitch);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work in tonality space\nnon-linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work on frequency/period space");
|
|
||||||
}
|
|
||||||
ImGui::Checkbox("Proper noise layout on NES and PC Engine",&e->song.properNoiseLayout);
|
ImGui::Checkbox("Proper noise layout on NES and PC Engine",&e->song.properNoiseLayout);
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("use a proper noise channel note mapping (0-15) instead of a rather unusual compatible one.\nunlocks all noise frequencies on PC Engine.");
|
ImGui::SetTooltip("use a proper noise channel note mapping (0-15) instead of a rather unusual compatible one.\nunlocks all noise frequencies on PC Engine.");
|
||||||
|
@ -126,6 +124,35 @@ void FurnaceGUI::drawCompatFlags() {
|
||||||
ImGui::SetTooltip("when enabled, the pitch macro of an instrument is in linear space.");
|
ImGui::SetTooltip("when enabled, the pitch macro of an instrument is in linear space.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::Text("Pitch linearity:");
|
||||||
|
if (ImGui::RadioButton("None",e->song.linearPitch==0)) {
|
||||||
|
e->song.linearPitch=0;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("like ProTracker/FamiTracker");
|
||||||
|
}
|
||||||
|
if (ImGui::RadioButton("Partial (only 04xy/E5xx)",e->song.linearPitch==1)) {
|
||||||
|
e->song.linearPitch=1;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("like DefleMask");
|
||||||
|
}
|
||||||
|
if (ImGui::RadioButton("Full",e->song.linearPitch==2)) {
|
||||||
|
e->song.linearPitch=2;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("like Impulse Tracker");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e->song.linearPitch==2) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(120.0f*dpiScale);
|
||||||
|
if (ImGui::InputScalar("Pitch slide speed multiplier",ImGuiDataType_U8,&e->song.pitchSlideSpeed,&_ONE,&_ONE)) {
|
||||||
|
if (e->song.pitchSlideSpeed<1) e->song.pitchSlideSpeed=1;
|
||||||
|
if (e->song.pitchSlideSpeed>64) e->song.pitchSlideSpeed=64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Text("Loop modality:");
|
ImGui::Text("Loop modality:");
|
||||||
if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) {
|
if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) {
|
||||||
e->song.loopModality=0;
|
e->song.loopModality=0;
|
||||||
|
|
|
@ -47,7 +47,20 @@ void FurnaceGUI::drawInsList() {
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) {
|
if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) {
|
||||||
doAction((settings.insLoadAlwaysReplace && curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN);
|
doAction(GUI_ACTION_INS_LIST_OPEN);
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupContextItem("InsOpenOpt")) {
|
||||||
|
if (ImGui::MenuItem("replace...")) {
|
||||||
|
doAction((curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN);
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("load from TX81Z")) {
|
||||||
|
doAction(GUI_ACTION_TX81Z_REQUEST);
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("Open (insert; right-click to replace)");
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) {
|
if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) {
|
||||||
|
|
|
@ -25,6 +25,11 @@
|
||||||
#include "actionUtil.h"
|
#include "actionUtil.h"
|
||||||
#include "sampleUtil.h"
|
#include "sampleUtil.h"
|
||||||
|
|
||||||
|
const unsigned char avRequest[15]={
|
||||||
|
0xf0, 0x43, 0x20, 0x7e, 0x4c, 0x4d, 0x20, 0x20, 0x38, 0x39, 0x37, 0x36, 0x41, 0x45, 0xf7
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
void FurnaceGUI::doAction(int what) {
|
void FurnaceGUI::doAction(int what) {
|
||||||
switch (what) {
|
switch (what) {
|
||||||
case GUI_ACTION_OPEN:
|
case GUI_ACTION_OPEN:
|
||||||
|
@ -141,6 +146,17 @@ void FurnaceGUI::doAction(int what) {
|
||||||
fullScreen=!fullScreen;
|
fullScreen=!fullScreen;
|
||||||
SDL_SetWindowFullscreen(sdlWin,fullScreen?(SDL_WINDOW_FULLSCREEN|SDL_WINDOW_FULLSCREEN_DESKTOP):0);
|
SDL_SetWindowFullscreen(sdlWin,fullScreen?(SDL_WINDOW_FULLSCREEN|SDL_WINDOW_FULLSCREEN_DESKTOP):0);
|
||||||
break;
|
break;
|
||||||
|
case GUI_ACTION_TX81Z_REQUEST: {
|
||||||
|
TAMidiMessage msg;
|
||||||
|
msg.type=TA_MIDI_SYSEX;
|
||||||
|
msg.sysExData.reset(new unsigned char[15]);
|
||||||
|
msg.sysExLen=15;
|
||||||
|
memcpy(msg.sysExData.get(),avRequest,15);
|
||||||
|
if (!e->sendMidiMessage(msg)) {
|
||||||
|
showError("Error while sending request (MIDI output not configured?)");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case GUI_ACTION_PANIC:
|
case GUI_ACTION_PANIC:
|
||||||
e->syncReset();
|
e->syncReset();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -59,6 +59,12 @@ extern "C" {
|
||||||
#define BACKUP_FUR "/backup.fur"
|
#define BACKUP_FUR "/backup.fur"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
#define MOBILE_UI_DEFAULT true
|
||||||
|
#else
|
||||||
|
#define MOBILE_UI_DEFAULT false
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "actionUtil.h"
|
#include "actionUtil.h"
|
||||||
|
|
||||||
bool Particle::update(float frameTime) {
|
bool Particle::update(float frameTime) {
|
||||||
|
@ -1716,8 +1722,15 @@ void FurnaceGUI::showError(String what) {
|
||||||
if (macroDragLineMode) { \
|
if (macroDragLineMode) { \
|
||||||
if (!macroDragInitialValueSet) { \
|
if (!macroDragInitialValueSet) { \
|
||||||
macroDragLineInitial=ImVec2(x,y); \
|
macroDragLineInitial=ImVec2(x,y); \
|
||||||
|
macroDragLineInitialV=ImVec2(dragX,dragY); \
|
||||||
macroDragInitialValueSet=true; \
|
macroDragInitialValueSet=true; \
|
||||||
|
macroDragMouseMoved=false; \
|
||||||
|
} else if (!macroDragMouseMoved) { \
|
||||||
|
if ((pow(dragX-macroDragLineInitialV.x,2.0)+pow(dragY-macroDragLineInitialV.y,2.0))>=16.0f) { \
|
||||||
|
macroDragMouseMoved=true; \
|
||||||
} \
|
} \
|
||||||
|
} \
|
||||||
|
if (macroDragMouseMoved) { \
|
||||||
if ((int)round(x-macroDragLineInitial.x)==0) { \
|
if ((int)round(x-macroDragLineInitial.x)==0) { \
|
||||||
t[x]=macroDragLineInitial.y; \
|
t[x]=macroDragLineInitial.y; \
|
||||||
} else { \
|
} else { \
|
||||||
|
@ -1735,6 +1748,7 @@ void FurnaceGUI::showError(String what) {
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
|
} \
|
||||||
} else { \
|
} else { \
|
||||||
t[x]=y; \
|
t[x]=y; \
|
||||||
} \
|
} \
|
||||||
|
@ -2342,6 +2356,9 @@ bool FurnaceGUI::loop() {
|
||||||
if (macroDragActive || macroLoopDragActive || waveDragActive || (sampleDragActive && sampleDragMode)) {
|
if (macroDragActive || macroLoopDragActive || waveDragActive || (sampleDragActive && sampleDragMode)) {
|
||||||
MARK_MODIFIED;
|
MARK_MODIFIED;
|
||||||
}
|
}
|
||||||
|
if (macroDragActive && macroDragLineMode && !macroDragMouseMoved) {
|
||||||
|
displayMacroMenu=true;
|
||||||
|
}
|
||||||
macroDragActive=false;
|
macroDragActive=false;
|
||||||
macroDragBitMode=false;
|
macroDragBitMode=false;
|
||||||
macroDragInitialValue=false;
|
macroDragInitialValue=false;
|
||||||
|
@ -3641,6 +3658,7 @@ bool FurnaceGUI::init() {
|
||||||
waveHex=e->getConfBool("waveHex",false);
|
waveHex=e->getConfBool("waveHex",false);
|
||||||
lockLayout=e->getConfBool("lockLayout",false);
|
lockLayout=e->getConfBool("lockLayout",false);
|
||||||
fullScreen=e->getConfBool("fullScreen",false);
|
fullScreen=e->getConfBool("fullScreen",false);
|
||||||
|
mobileUI=e->getConfBool("mobileUI",MOBILE_UI_DEFAULT);
|
||||||
|
|
||||||
syncSettings();
|
syncSettings();
|
||||||
|
|
||||||
|
@ -3822,6 +3840,7 @@ bool FurnaceGUI::finish() {
|
||||||
e->setConf("waveHex",waveHex);
|
e->setConf("waveHex",waveHex);
|
||||||
e->setConf("lockLayout",lockLayout);
|
e->setConf("lockLayout",lockLayout);
|
||||||
e->setConf("fullScreen",fullScreen);
|
e->setConf("fullScreen",fullScreen);
|
||||||
|
e->setConf("mobileUI",mobileUI);
|
||||||
|
|
||||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||||
delete oldPat[i];
|
delete oldPat[i];
|
||||||
|
@ -3851,6 +3870,7 @@ FurnaceGUI::FurnaceGUI():
|
||||||
displayExporting(false),
|
displayExporting(false),
|
||||||
vgmExportLoop(true),
|
vgmExportLoop(true),
|
||||||
wantCaptureKeyboard(false),
|
wantCaptureKeyboard(false),
|
||||||
|
displayMacroMenu(false),
|
||||||
displayNew(false),
|
displayNew(false),
|
||||||
fullScreen(false),
|
fullScreen(false),
|
||||||
preserveChanPos(false),
|
preserveChanPos(false),
|
||||||
|
@ -3960,6 +3980,7 @@ FurnaceGUI::FurnaceGUI():
|
||||||
followOrders(true),
|
followOrders(true),
|
||||||
followPattern(true),
|
followPattern(true),
|
||||||
changeAllOrders(false),
|
changeAllOrders(false),
|
||||||
|
mobileUI(MOBILE_UI_DEFAULT),
|
||||||
collapseWindow(false),
|
collapseWindow(false),
|
||||||
demandScrollX(false),
|
demandScrollX(false),
|
||||||
fancyPattern(false),
|
fancyPattern(false),
|
||||||
|
@ -4004,8 +4025,15 @@ FurnaceGUI::FurnaceGUI():
|
||||||
macroDragInitialValue(false),
|
macroDragInitialValue(false),
|
||||||
macroDragChar(false),
|
macroDragChar(false),
|
||||||
macroDragLineMode(false),
|
macroDragLineMode(false),
|
||||||
|
macroDragMouseMoved(false),
|
||||||
macroDragLineInitial(0,0),
|
macroDragLineInitial(0,0),
|
||||||
|
macroDragLineInitialV(0,0),
|
||||||
macroDragActive(false),
|
macroDragActive(false),
|
||||||
|
lastMacroDesc(NULL,NULL,0,0,0.0f),
|
||||||
|
macroOffX(0),
|
||||||
|
macroOffY(0),
|
||||||
|
macroScaleX(100.0f),
|
||||||
|
macroScaleY(100.0f),
|
||||||
macroLoopDragStart(0,0),
|
macroLoopDragStart(0,0),
|
||||||
macroLoopDragAreaSize(0,0),
|
macroLoopDragAreaSize(0,0),
|
||||||
macroLoopDragTarget(NULL),
|
macroLoopDragTarget(NULL),
|
||||||
|
|
|
@ -306,6 +306,7 @@ enum FurnaceGUIActions {
|
||||||
GUI_ACTION_FOLLOW_ORDERS,
|
GUI_ACTION_FOLLOW_ORDERS,
|
||||||
GUI_ACTION_FOLLOW_PATTERN,
|
GUI_ACTION_FOLLOW_PATTERN,
|
||||||
GUI_ACTION_FULLSCREEN,
|
GUI_ACTION_FULLSCREEN,
|
||||||
|
GUI_ACTION_TX81Z_REQUEST,
|
||||||
GUI_ACTION_PANIC,
|
GUI_ACTION_PANIC,
|
||||||
|
|
||||||
GUI_ACTION_WINDOW_EDIT_CONTROLS,
|
GUI_ACTION_WINDOW_EDIT_CONTROLS,
|
||||||
|
@ -770,7 +771,7 @@ class FurnaceGUI {
|
||||||
String mmlString[17];
|
String mmlString[17];
|
||||||
String mmlStringW;
|
String mmlStringW;
|
||||||
|
|
||||||
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard;
|
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, displayMacroMenu;
|
||||||
bool displayNew, fullScreen, preserveChanPos;
|
bool displayNew, fullScreen, preserveChanPos;
|
||||||
bool willExport[32];
|
bool willExport[32];
|
||||||
int vgmExportVersion;
|
int vgmExportVersion;
|
||||||
|
@ -889,7 +890,6 @@ class FurnaceGUI {
|
||||||
int eventDelay;
|
int eventDelay;
|
||||||
int moveWindowTitle;
|
int moveWindowTitle;
|
||||||
int hiddenSystems;
|
int hiddenSystems;
|
||||||
int insLoadAlwaysReplace;
|
|
||||||
int horizontalDataView;
|
int horizontalDataView;
|
||||||
int noMultiSystem;
|
int noMultiSystem;
|
||||||
unsigned int maxUndoSteps;
|
unsigned int maxUndoSteps;
|
||||||
|
@ -974,7 +974,6 @@ class FurnaceGUI {
|
||||||
eventDelay(0),
|
eventDelay(0),
|
||||||
moveWindowTitle(0),
|
moveWindowTitle(0),
|
||||||
hiddenSystems(0),
|
hiddenSystems(0),
|
||||||
insLoadAlwaysReplace(1),
|
|
||||||
horizontalDataView(0),
|
horizontalDataView(0),
|
||||||
noMultiSystem(0),
|
noMultiSystem(0),
|
||||||
maxUndoSteps(100),
|
maxUndoSteps(100),
|
||||||
|
@ -1006,7 +1005,7 @@ class FurnaceGUI {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
SelectionPoint selStart, selEnd, cursor;
|
SelectionPoint selStart, selEnd, cursor;
|
||||||
bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders;
|
bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI;
|
||||||
bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, lockLayout, editOptsVisible, latchNibble, nonLatchNibble;
|
bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, lockLayout, editOptsVisible, latchNibble, nonLatchNibble;
|
||||||
FurnaceGUIWindows curWindow, nextWindow, curWindowLast;
|
FurnaceGUIWindows curWindow, nextWindow, curWindowLast;
|
||||||
float peak[2];
|
float peak[2];
|
||||||
|
@ -1082,8 +1081,13 @@ class FurnaceGUI {
|
||||||
bool macroDragInitialValue;
|
bool macroDragInitialValue;
|
||||||
bool macroDragChar;
|
bool macroDragChar;
|
||||||
bool macroDragLineMode;
|
bool macroDragLineMode;
|
||||||
|
bool macroDragMouseMoved;
|
||||||
ImVec2 macroDragLineInitial;
|
ImVec2 macroDragLineInitial;
|
||||||
|
ImVec2 macroDragLineInitialV;
|
||||||
bool macroDragActive;
|
bool macroDragActive;
|
||||||
|
FurnaceGUIMacroDesc lastMacroDesc;
|
||||||
|
int macroOffX, macroOffY;
|
||||||
|
float macroScaleX, macroScaleY;
|
||||||
|
|
||||||
ImVec2 macroLoopDragStart;
|
ImVec2 macroLoopDragStart;
|
||||||
ImVec2 macroLoopDragAreaSize;
|
ImVec2 macroLoopDragAreaSize;
|
||||||
|
|
|
@ -466,6 +466,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
|
||||||
D("FOLLOW_ORDERS", "Follow orders", 0),
|
D("FOLLOW_ORDERS", "Follow orders", 0),
|
||||||
D("FOLLOW_PATTERN", "Follow pattern", 0),
|
D("FOLLOW_PATTERN", "Follow pattern", 0),
|
||||||
D("FULLSCREEN", "Toggle full-screen", SDLK_F11),
|
D("FULLSCREEN", "Toggle full-screen", SDLK_F11),
|
||||||
|
D("TX81Z_REQUEST", "Request voice from TX81Z", 0),
|
||||||
D("PANIC", "Panic", SDLK_F12),
|
D("PANIC", "Panic", SDLK_F12),
|
||||||
|
|
||||||
D("WINDOW_EDIT_CONTROLS", "Edit Controls", 0),
|
D("WINDOW_EDIT_CONTROLS", "Edit Controls", 0),
|
||||||
|
|
|
@ -27,10 +27,6 @@
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include "plot_nolerp.h"
|
#include "plot_nolerp.h"
|
||||||
|
|
||||||
const unsigned char avRequest[15]={
|
|
||||||
0xf0, 0x43, 0x20, 0x7e, 0x4c, 0x4d, 0x20, 0x20, 0x38, 0x39, 0x37, 0x36, 0x41, 0x45, 0xf7
|
|
||||||
};
|
|
||||||
|
|
||||||
const char* ssgEnvTypes[8]={
|
const char* ssgEnvTypes[8]={
|
||||||
"Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN"
|
"Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN"
|
||||||
};
|
};
|
||||||
|
@ -1214,6 +1210,9 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
|
||||||
if (i.macro->name=="arp") {
|
if (i.macro->name=="arp") {
|
||||||
i.macro->vZoom=24;
|
i.macro->vZoom=24;
|
||||||
i.macro->vScroll=120-12;
|
i.macro->vScroll=120-12;
|
||||||
|
} else if (i.macro->name=="pitch") {
|
||||||
|
i.macro->vZoom=64;
|
||||||
|
i.macro->vScroll=1024-32;
|
||||||
} else {
|
} else {
|
||||||
i.macro->vZoom=i.max-i.min;
|
i.macro->vZoom=i.max-i.min;
|
||||||
i.macro->vScroll=0;
|
i.macro->vScroll=0;
|
||||||
|
@ -1248,6 +1247,7 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
|
||||||
macroDragChar=false;
|
macroDragChar=false;
|
||||||
macroDragLineMode=(i.isBitfield)?false:ImGui::IsItemClicked(ImGuiMouseButton_Right);
|
macroDragLineMode=(i.isBitfield)?false:ImGui::IsItemClicked(ImGuiMouseButton_Right);
|
||||||
macroDragLineInitial=ImVec2(0,0);
|
macroDragLineInitial=ImVec2(0,0);
|
||||||
|
lastMacroDesc=i;
|
||||||
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
|
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
|
||||||
}
|
}
|
||||||
if (i.macro->open) {
|
if (i.macro->open) {
|
||||||
|
@ -1419,7 +1419,6 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
|
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
// TODO: load replace
|
|
||||||
if (ImGui::Button(ICON_FA_FOLDER_OPEN "##IELoad")) {
|
if (ImGui::Button(ICON_FA_FOLDER_OPEN "##IELoad")) {
|
||||||
doAction(GUI_ACTION_INS_LIST_OPEN_REPLACE);
|
doAction(GUI_ACTION_INS_LIST_OPEN_REPLACE);
|
||||||
}
|
}
|
||||||
|
@ -1444,6 +1443,48 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
for (DivInstrumentType i: e->getPossibleInsTypes()) {
|
for (DivInstrumentType i: e->getPossibleInsTypes()) {
|
||||||
if (ImGui::Selectable(insTypes[i],insType==i)) {
|
if (ImGui::Selectable(insTypes[i],insType==i)) {
|
||||||
ins->type=i;
|
ins->type=i;
|
||||||
|
|
||||||
|
// reset macro zoom
|
||||||
|
ins->std.volMacro.vZoom=-1;
|
||||||
|
ins->std.dutyMacro.vZoom=-1;
|
||||||
|
ins->std.waveMacro.vZoom=-1;
|
||||||
|
ins->std.ex1Macro.vZoom=-1;
|
||||||
|
ins->std.ex2Macro.vZoom=-1;
|
||||||
|
ins->std.ex3Macro.vZoom=-1;
|
||||||
|
ins->std.ex4Macro.vZoom=-1;
|
||||||
|
ins->std.ex5Macro.vZoom=-1;
|
||||||
|
ins->std.ex6Macro.vZoom=-1;
|
||||||
|
ins->std.ex7Macro.vZoom=-1;
|
||||||
|
ins->std.ex8Macro.vZoom=-1;
|
||||||
|
ins->std.panLMacro.vZoom=-1;
|
||||||
|
ins->std.panRMacro.vZoom=-1;
|
||||||
|
ins->std.phaseResetMacro.vZoom=-1;
|
||||||
|
ins->std.algMacro.vZoom=-1;
|
||||||
|
ins->std.fbMacro.vZoom=-1;
|
||||||
|
ins->std.fmsMacro.vZoom=-1;
|
||||||
|
ins->std.amsMacro.vZoom=-1;
|
||||||
|
for (int j=0; j<4; j++) {
|
||||||
|
ins->std.opMacros[j].amMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].arMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].drMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].multMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].rrMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].slMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].tlMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].dt2Macro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].rsMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].dtMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].d2rMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].ssgMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].damMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].dvbMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].egtMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].kslMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].susMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].vibMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].wsMacro.vZoom=-1;
|
||||||
|
ins->std.opMacros[j].ksrMacro.vZoom=-1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndCombo();
|
ImGui::EndCombo();
|
||||||
|
@ -1490,14 +1531,7 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
|
drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale));
|
||||||
if (ImGui::Button("Request from TX81Z")) {
|
if (ImGui::Button("Request from TX81Z")) {
|
||||||
TAMidiMessage msg;
|
doAction(GUI_ACTION_TX81Z_REQUEST);
|
||||||
msg.type=TA_MIDI_SYSEX;
|
|
||||||
msg.sysExData.reset(new unsigned char[15]);
|
|
||||||
msg.sysExLen=15;
|
|
||||||
memcpy(msg.sysExData.get(),avRequest,15);
|
|
||||||
if (!e->sendMidiMessage(msg)) {
|
|
||||||
showError("Error while sending request (MIDI output not configured?)");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Send to TX81Z")) {
|
if (ImGui::Button("Send to TX81Z")) {
|
||||||
|
@ -3387,6 +3421,110 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
popAccentColors();
|
popAccentColors();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (displayMacroMenu) {
|
||||||
|
displayMacroMenu=false;
|
||||||
|
if (lastMacroDesc.macro!=NULL) {
|
||||||
|
ImGui::OpenPopup("macroMenu");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopup("macroMenu",ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) {
|
||||||
|
if (ImGui::MenuItem("copy")) {
|
||||||
|
String mmlStr;
|
||||||
|
encodeMMLStr(mmlStr,lastMacroDesc.macro->val,lastMacroDesc.macro->len,lastMacroDesc.macro->loop,lastMacroDesc.macro->rel);
|
||||||
|
SDL_SetClipboardText(mmlStr.c_str());
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("paste")) {
|
||||||
|
String mmlStr;
|
||||||
|
char* clipText=SDL_GetClipboardText();
|
||||||
|
if (clipText!=NULL) {
|
||||||
|
if (clipText[0]) {
|
||||||
|
mmlStr=clipText;
|
||||||
|
}
|
||||||
|
SDL_free(clipText);
|
||||||
|
}
|
||||||
|
if (!mmlStr.empty()) {
|
||||||
|
decodeMMLStr(mmlStr,lastMacroDesc.macro->val,lastMacroDesc.macro->len,lastMacroDesc.macro->loop,lastMacroDesc.min,(lastMacroDesc.isBitfield)?((1<<(lastMacroDesc.isBitfield?lastMacroDesc.max:0))-1):lastMacroDesc.max,lastMacroDesc.macro->rel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("clear")) {
|
||||||
|
lastMacroDesc.macro->len=0;
|
||||||
|
lastMacroDesc.macro->loop=-1;
|
||||||
|
lastMacroDesc.macro->rel=-1;
|
||||||
|
for (int i=0; i<256; i++) {
|
||||||
|
lastMacroDesc.macro->val[i]=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("clear contents")) {
|
||||||
|
for (int i=0; i<256; i++) {
|
||||||
|
lastMacroDesc.macro->val[i]=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::BeginMenu("offset...")) {
|
||||||
|
ImGui::InputInt("X",¯oOffX,1,10);
|
||||||
|
ImGui::InputInt("Y",¯oOffY,1,10);
|
||||||
|
if (ImGui::Button("offset")) {
|
||||||
|
int oldData[256];
|
||||||
|
memset(oldData,0,256*sizeof(int));
|
||||||
|
memcpy(oldData,lastMacroDesc.macro->val,lastMacroDesc.macro->len*sizeof(int));
|
||||||
|
|
||||||
|
for (int i=0; i<lastMacroDesc.macro->len; i++) {
|
||||||
|
int val=0;
|
||||||
|
if ((i-macroOffX)>=0 && (i-macroOffX)<lastMacroDesc.macro->len) {
|
||||||
|
val=oldData[i-macroOffX]+macroOffY;
|
||||||
|
if (val<lastMacroDesc.min) val=lastMacroDesc.min;
|
||||||
|
if (val>lastMacroDesc.max) val=lastMacroDesc.max;
|
||||||
|
}
|
||||||
|
lastMacroDesc.macro->val[i]=val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastMacroDesc.macro->loop>=0 && lastMacroDesc.macro->loop<lastMacroDesc.macro->len) {
|
||||||
|
lastMacroDesc.macro->loop+=macroOffX;
|
||||||
|
} else {
|
||||||
|
lastMacroDesc.macro->loop=-1;
|
||||||
|
}
|
||||||
|
if ((lastMacroDesc.macro->rel+macroOffX)>=0 && (lastMacroDesc.macro->rel+macroOffX)<lastMacroDesc.macro->len) {
|
||||||
|
lastMacroDesc.macro->rel+=macroOffX;
|
||||||
|
} else {
|
||||||
|
lastMacroDesc.macro->rel=-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
if (ImGui::BeginMenu("scale...")) {
|
||||||
|
if (ImGui::InputFloat("X",¯oScaleX,1.0f,10.0f,"%.2f%%")) {
|
||||||
|
if (macroScaleX<0.1) macroScaleX=0.1;
|
||||||
|
if (macroScaleX>12800.0) macroScaleX=12800.0;
|
||||||
|
}
|
||||||
|
ImGui::InputFloat("Y",¯oScaleY,1.0f,10.0f,"%.2f%%");
|
||||||
|
if (ImGui::Button("scale")) {
|
||||||
|
int oldData[256];
|
||||||
|
memset(oldData,0,256*sizeof(int));
|
||||||
|
memcpy(oldData,lastMacroDesc.macro->val,lastMacroDesc.macro->len*sizeof(int));
|
||||||
|
|
||||||
|
lastMacroDesc.macro->len=MIN(128,((double)lastMacroDesc.macro->len*(macroScaleX/100.0)));
|
||||||
|
|
||||||
|
for (int i=0; i<lastMacroDesc.macro->len; i++) {
|
||||||
|
int val=0;
|
||||||
|
double posX=round((double)i*(100.0/macroScaleX)-0.01);
|
||||||
|
if (posX>=0 && posX<lastMacroDesc.macro->len) {
|
||||||
|
val=round((double)oldData[(int)posX]*(macroScaleY/100.0));
|
||||||
|
if (val<lastMacroDesc.min) val=lastMacroDesc.min;
|
||||||
|
if (val>lastMacroDesc.max) val=lastMacroDesc.max;
|
||||||
|
}
|
||||||
|
lastMacroDesc.macro->val[i]=val;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_EDIT;
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_EDIT;
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
|
@ -419,11 +419,6 @@ void FurnaceGUI::drawSettings() {
|
||||||
settings.restartOnFlagChange=restartOnFlagChangeB;
|
settings.restartOnFlagChange=restartOnFlagChangeB;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool insLoadAlwaysReplaceB=settings.insLoadAlwaysReplace;
|
|
||||||
if (ImGui::Checkbox("Always replace currently selected instrument when loading from instrument list",&insLoadAlwaysReplaceB)) {
|
|
||||||
settings.insLoadAlwaysReplace=insLoadAlwaysReplaceB;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sysFileDialogB=settings.sysFileDialog;
|
bool sysFileDialogB=settings.sysFileDialog;
|
||||||
if (ImGui::Checkbox("Use system file picker",&sysFileDialogB)) {
|
if (ImGui::Checkbox("Use system file picker",&sysFileDialogB)) {
|
||||||
settings.sysFileDialog=sysFileDialogB;
|
settings.sysFileDialog=sysFileDialogB;
|
||||||
|
@ -1852,7 +1847,6 @@ void FurnaceGUI::syncSettings() {
|
||||||
settings.eventDelay=e->getConfInt("eventDelay",0);
|
settings.eventDelay=e->getConfInt("eventDelay",0);
|
||||||
settings.moveWindowTitle=e->getConfInt("moveWindowTitle",0);
|
settings.moveWindowTitle=e->getConfInt("moveWindowTitle",0);
|
||||||
settings.hiddenSystems=e->getConfInt("hiddenSystems",0);
|
settings.hiddenSystems=e->getConfInt("hiddenSystems",0);
|
||||||
settings.insLoadAlwaysReplace=e->getConfInt("insLoadAlwaysReplace",1);
|
|
||||||
settings.horizontalDataView=e->getConfInt("horizontalDataView",0);
|
settings.horizontalDataView=e->getConfInt("horizontalDataView",0);
|
||||||
settings.noMultiSystem=e->getConfInt("noMultiSystem",0);
|
settings.noMultiSystem=e->getConfInt("noMultiSystem",0);
|
||||||
|
|
||||||
|
@ -1925,7 +1919,6 @@ void FurnaceGUI::syncSettings() {
|
||||||
clampSetting(settings.eventDelay,0,1);
|
clampSetting(settings.eventDelay,0,1);
|
||||||
clampSetting(settings.moveWindowTitle,0,1);
|
clampSetting(settings.moveWindowTitle,0,1);
|
||||||
clampSetting(settings.hiddenSystems,0,1);
|
clampSetting(settings.hiddenSystems,0,1);
|
||||||
clampSetting(settings.insLoadAlwaysReplace,0,1);
|
|
||||||
clampSetting(settings.horizontalDataView,0,1);
|
clampSetting(settings.horizontalDataView,0,1);
|
||||||
clampSetting(settings.noMultiSystem,0,1)
|
clampSetting(settings.noMultiSystem,0,1)
|
||||||
|
|
||||||
|
@ -2039,7 +2032,6 @@ void FurnaceGUI::commitSettings() {
|
||||||
e->setConf("moveWindowTitle",settings.moveWindowTitle);
|
e->setConf("moveWindowTitle",settings.moveWindowTitle);
|
||||||
e->setConf("hiddenSystems",settings.hiddenSystems);
|
e->setConf("hiddenSystems",settings.hiddenSystems);
|
||||||
e->setConf("initialSys",e->encodeSysDesc(settings.initialSys));
|
e->setConf("initialSys",e->encodeSysDesc(settings.initialSys));
|
||||||
e->setConf("insLoadAlwaysReplace",settings.insLoadAlwaysReplace);
|
|
||||||
e->setConf("horizontalDataView",settings.horizontalDataView);
|
e->setConf("horizontalDataView",settings.horizontalDataView);
|
||||||
e->setConf("noMultiSystem",settings.noMultiSystem);
|
e->setConf("noMultiSystem",settings.noMultiSystem);
|
||||||
|
|
||||||
|
|
|
@ -243,7 +243,7 @@ void initParams() {
|
||||||
// TODO: add crash log
|
// TODO: add crash log
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
initLog();
|
initLog();
|
||||||
#if !(defined(__APPLE__) || defined(_WIN32))
|
#if !(defined(__APPLE__) || defined(_WIN32) || defined(ANDROID))
|
||||||
// workaround for Wayland HiDPI issue
|
// workaround for Wayland HiDPI issue
|
||||||
if (getenv("SDL_VIDEODRIVER")==NULL) {
|
if (getenv("SDL_VIDEODRIVER")==NULL) {
|
||||||
setenv("SDL_VIDEODRIVER","x11",1);
|
setenv("SDL_VIDEODRIVER","x11",1);
|
||||||
|
|
Loading…
Reference in a new issue