diff --git a/.gitmodules b/.gitmodules index 0dcbd0c8..f3ef8bbd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,7 @@ [submodule "extern/SDL"] path = extern/SDL url = https://github.com/libsdl-org/SDL.git + branch = release-2.28.x [submodule "extern/libsndfile"] path = extern/libsndfile url = https://github.com/libsndfile/libsndfile.git diff --git a/README.md b/README.md index 4ecbd14c..37371239 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a --- # quick references -- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or the [official Discord server](https://discord.gg/EfrwT2wq7z). +- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, the [official Revolt](https://rvlt.gg/GRPS6tmc) or the [official Discord server](https://discord.gg/EfrwT2wq7z). - **help**: check out the [documentation](doc/README.md). it's incomplete though. ## packages diff --git a/android/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java b/android/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java index 65c5a423..ee5521fd 100644 --- a/android/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java +++ b/android/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java @@ -186,7 +186,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe // 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) { + if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) { try { return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE); } catch (Exception e) { @@ -429,7 +429,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe } }); } - } + } else if (newState == 0) { mIsConnected = false; } diff --git a/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java index cf3c9267..6f7013b2 100644 --- a/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java +++ b/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java @@ -170,7 +170,7 @@ public class HIDDeviceManager { Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol()); Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount()); - // Get endpoint details + // Get endpoint details for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++) { UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi); @@ -251,6 +251,8 @@ public class HIDDeviceManager { 0x20d6, // PowerA 0x24c6, // PowerA 0x2c22, // Qanba + 0x2dc8, // 8BitDo + 0x9886, // ASTRO Gaming }; if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && @@ -271,14 +273,16 @@ public class HIDDeviceManager { final int XB1_IFACE_SUBCLASS = 71; final int XB1_IFACE_PROTOCOL = 208; final int[] SUPPORTED_VENDORS = { + 0x044f, // Thrustmaster 0x045e, // Microsoft 0x0738, // Mad Catz 0x0e6f, // PDP 0x0f0d, // Hori + 0x10f5, // Turtle Beach 0x1532, // Razer Wildcat 0x20d6, // PowerA 0x24c6, // PowerA - 0x2dc8, /* 8BitDo */ + 0x2dc8, // 8BitDo 0x2e24, // Hyperkin }; @@ -353,13 +357,13 @@ public class HIDDeviceManager { private void initializeBluetooth() { Log.d(TAG, "Initializing Bluetooth"); - if (Build.VERSION.SDK_INT <= 30 && + if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ && 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)) { + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) { Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE"); return; } @@ -524,7 +528,7 @@ public class HIDDeviceManager { for (HIDDevice device : mDevicesById.values()) { device.setFrozen(frozen); } - } + } } ////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -573,7 +577,7 @@ public class HIDDeviceManager { try { final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31 int flags; - if (Build.VERSION.SDK_INT >= 31) { + if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) { flags = FLAG_MUTABLE; } else { flags = 0; diff --git a/android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java b/android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java index d20fe80b..bfe0cf95 100644 --- a/android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java +++ b/android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java @@ -52,7 +52,7 @@ class HIDDeviceUSB implements HIDDevice { @Override public String getSerialNumber() { String result = null; - if (Build.VERSION.SDK_INT >= 21) { + if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { try { result = mDevice.getSerialNumber(); } @@ -74,7 +74,7 @@ class HIDDeviceUSB implements HIDDevice { @Override public String getManufacturerName() { String result = null; - if (Build.VERSION.SDK_INT >= 21) { + if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { result = mDevice.getManufacturerName(); } if (result == null) { @@ -86,7 +86,7 @@ class HIDDeviceUSB implements HIDDevice { @Override public String getProductName() { String result = null; - if (Build.VERSION.SDK_INT >= 21) { + if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { result = mDevice.getProductName(); } if (result == null) { diff --git a/android/app/src/main/java/org/libsdl/app/SDL.java b/android/app/src/main/java/org/libsdl/app/SDL.java index dafc0cb8..44c21c1c 100644 --- a/android/app/src/main/java/org/libsdl/app/SDL.java +++ b/android/app/src/main/java/org/libsdl/app/SDL.java @@ -29,6 +29,7 @@ public class SDL { // This function stores the current activity (SDL or not) public static void setContext(Context context) { + SDLAudioManager.setContext(context); mContext = context; } diff --git a/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/android/app/src/main/java/org/libsdl/app/SDLActivity.java index 55b961a4..045e99c7 100644 --- a/android/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/android/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -15,13 +15,9 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Color; -import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -37,11 +33,8 @@ import android.view.Display; import android.view.Gravity; import android.view.InputDevice; import android.view.KeyEvent; -import android.view.MotionEvent; import android.view.PointerIcon; import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -51,6 +44,7 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.Button; +import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; @@ -65,6 +59,9 @@ import java.util.Locale; */ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { private static final String TAG = "SDL"; + private static final int SDL_MAJOR_VERSION = 2; + private static final int SDL_MINOR_VERSION = 28; + private static final int SDL_MICRO_VERSION = 0; /* // Display InputType.SOURCE/CLASS of events and devices // @@ -96,7 +93,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (Build.VERSION.SDK_INT >= 23) { tst = InputDevice.SOURCE_BLUETOOTH_STYLUS; if ((s & tst) == tst) src += " BLUETOOTH_STYLUS"; s2 &= ~tst; @@ -110,7 +107,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh if ((s & tst) == tst) src += " GAMEPAD"; s2 &= ~tst; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (Build.VERSION.SDK_INT >= 21) { tst = InputDevice.SOURCE_HDMI; if ((s & tst) == tst) src += " HDMI"; s2 &= ~tst; @@ -149,7 +146,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh if ((s & tst) == tst) src += " TOUCHSCREEN"; s2 &= ~tst; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + if (Build.VERSION.SDK_INT >= 18) { tst = InputDevice.SOURCE_TOUCH_NAVIGATION; if ((s & tst) == tst) src += " TOUCH_NAVIGATION"; s2 &= ~tst; @@ -173,7 +170,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh */ public static boolean mIsResumedCalled, mHasFocus; - public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24); + public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */); // Cursor types // private static final int SDL_SYSTEM_CURSOR_NONE = -1; @@ -213,7 +210,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // Main components protected static SDLActivity mSingleton; protected static SDLSurface mSurface; - protected static View mTextEdit; + protected static DummyEdit mTextEdit; protected static boolean mScreenKeyboardShown; protected static ViewGroup mLayout; protected static SDLClipboardHandler mClipboardHandler; @@ -227,9 +224,9 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh protected static SDLGenericMotionListener_API12 getMotionListener() { if (mMotionListener == null) { - if (Build.VERSION.SDK_INT >= 26) { + if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) { mMotionListener = new SDLGenericMotionListener_API26(); - } else if (Build.VERSION.SDK_INT >= 24) { + } else if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { mMotionListener = new SDLGenericMotionListener_API24(); } else { mMotionListener = new SDLGenericMotionListener_API12(); @@ -314,6 +311,10 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh mNextNativeState = NativeState.INIT; mCurrentNativeState = NativeState.INIT; } + + protected SDLSurface createSDLSurface(Context context) { + return new SDLSurface(context); + } // Setup @Override @@ -344,8 +345,18 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh errorMsgBrokenLib = e.getMessage(); } - if (mBrokenLibraries) - { + if (!mBrokenLibraries) { + String expected_version = String.valueOf(SDL_MAJOR_VERSION) + "." + + String.valueOf(SDL_MINOR_VERSION) + "." + + String.valueOf(SDL_MICRO_VERSION); + String version = nativeGetVersion(); + if (!version.equals(expected_version)) { + mBrokenLibraries = true; + errorMsgBrokenLib = "SDL C/Java version mismatch (expected " + expected_version + ", got " + version + ")"; + } + } + + if (mBrokenLibraries) { mSingleton = this; AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall." @@ -382,7 +393,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh mHIDDeviceManager = HIDDeviceManager.acquire(this); // Set up the surface - mSurface = new SDLSurface(getApplication()); + mSurface = createSDLSurface(this); mLayout = new RelativeLayout(this); mLayout.addView(mSurface); @@ -393,7 +404,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh SDLActivity.onNativeOrientationChanged(mCurrentOrientation); try { - if (Build.VERSION.SDK_INT < 24) { + if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) { mCurrentLocale = getContext().getResources().getConfiguration().locale; } else { mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0); @@ -577,6 +588,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh mHIDDeviceManager = null; } + SDLAudioManager.release(this); + if (SDLActivity.mBrokenLibraries) { super.onDestroy(); return; @@ -755,7 +768,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh } break; case COMMAND_CHANGE_WINDOW_STYLE: - if (Build.VERSION.SDK_INT >= 19) { + if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { if (context instanceof Activity) { Window window = ((Activity) context).getWindow(); if (window != null) { @@ -830,7 +843,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh msg.obj = data; boolean result = commandHandler.sendMessage(msg); - if (Build.VERSION.SDK_INT >= 19) { + if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { if (command == COMMAND_CHANGE_WINDOW_STYLE) { // Ensure we don't return until the resize has actually happened, // or 500ms have passed. @@ -886,6 +899,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh } // C functions we call + public static native String nativeGetVersion(); public static native int nativeSetupJNI(); public static native int nativeRunMain(String library, String function, Object arguments); public static native void nativeLowMemory(); @@ -957,15 +971,18 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh /* If set, hint "explicitly controls which UI orientations are allowed". */ if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) { orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; - } else if (hint.contains("LandscapeRight")) { - orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; } else if (hint.contains("LandscapeLeft")) { + orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + } else if (hint.contains("LandscapeRight")) { orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; } - if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) { + /* exact match to 'Portrait' to distinguish with PortraitUpsideDown */ + boolean contains_Portrait = hint.contains("Portrait ") || hint.endsWith("Portrait"); + + if (contains_Portrait && hint.contains("PortraitUpsideDown")) { orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; - } else if (hint.contains("Portrait")) { + } else if (contains_Portrait) { orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; } else if (hint.contains("PortraitUpsideDown")) { orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; @@ -1078,7 +1095,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result, // we should stick to relative mode. // - if ((Build.VERSION.SDK_INT < 27) && isDeXMode()) { + if (Build.VERSION.SDK_INT < 27 /* Android 8.1 (O_MR1) */ && isDeXMode()) { return false; } @@ -1168,7 +1185,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh * This method is called by SDL using JNI. */ public static boolean isDeXMode() { - if (Build.VERSION.SDK_INT < 24) { + if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) { return false; } try { @@ -1220,8 +1237,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh } // This method is called by SDLControllerManager's API 26 Generic Motion Handler. - public static View getContentView() - { + public static View getContentView() { return mLayout; } @@ -1292,6 +1308,77 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE; } + public static boolean handleKeyEvent(View v, int keyCode, KeyEvent event, InputConnection ic) { + int deviceId = event.getDeviceId(); + int source = event.getSource(); + + if (source == InputDevice.SOURCE_UNKNOWN) { + InputDevice device = InputDevice.getDevice(deviceId); + if (device != null) { + source = device.getSources(); + } + } + +// if (event.getAction() == KeyEvent.ACTION_DOWN) { +// Log.v("SDL", "key down: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); +// } else if (event.getAction() == KeyEvent.ACTION_UP) { +// Log.v("SDL", "key up: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); +// } + + // Dispatch the different events depending on where they come from + // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD + // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD + // + // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and + // SOURCE_JOYSTICK, while its key events arrive from the keyboard source + // So, retrieve the device itself and check all of its sources + if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) { + // Note that we process events with specific key codes here + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (SDLControllerManager.onNativePadDown(deviceId, keyCode) == 0) { + return true; + } + } else if (event.getAction() == KeyEvent.ACTION_UP) { + if (SDLControllerManager.onNativePadUp(deviceId, keyCode) == 0) { + return true; + } + } + } + + if ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (isTextInputEvent(event)) { + if (ic != null) { + ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); + } else { + SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1); + } + } + onNativeKeyDown(keyCode); + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP) { + onNativeKeyUp(keyCode); + return true; + } + } + + if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { + // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses + // they are ignored here because sending them as mouse input to SDL is messy + if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) { + switch (event.getAction()) { + case KeyEvent.ACTION_DOWN: + case KeyEvent.ACTION_UP: + // mark the event as handled or it will be handled by system + // handling KEYCODE_BACK by system will call onBackPressed() + return true; + } + } + } + + return false; + } + /** * This method is called by SDL using JNI. */ @@ -1535,7 +1622,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh private final Runnable rehideSystemUi = new Runnable() { @Override public void run() { - if (Build.VERSION.SDK_INT >= 19) { + if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | @@ -1588,7 +1675,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888); ++mLastCursorID; - if (Build.VERSION.SDK_INT >= 24) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { try { mCursors.put(mLastCursorID, PointerIcon.create(bitmap, hotSpotX, hotSpotY)); } catch (Exception e) { @@ -1604,7 +1691,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh * This method is called by SDL using JNI. */ public static void destroyCustomCursor(int cursorID) { - if (Build.VERSION.SDK_INT >= 24) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { try { mCursors.remove(cursorID); } catch (Exception e) { @@ -1618,7 +1705,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh */ public static boolean setCustomCursor(int cursorID) { - if (Build.VERSION.SDK_INT >= 24) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { try { mSurface.setPointerIcon(mCursors.get(cursorID)); } catch (Exception e) { @@ -1673,7 +1760,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh cursor_type = 1002; //PointerIcon.TYPE_HAND; break; } - if (Build.VERSION.SDK_INT >= 24) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { try { mSurface.setPointerIcon(PointerIcon.getSystemIcon(SDL.getContext(), cursor_type)); } catch (Exception e) { @@ -1687,7 +1774,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh * This method is called by SDL using JNI. */ public static void requestPermission(String permission, int requestCode) { - if (Build.VERSION.SDK_INT < 23) { + if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) { nativePermissionResult(requestCode, true); return; } @@ -1716,7 +1803,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh i.setData(Uri.parse(url)); int flags = Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_MULTIPLE_TASK; - if (Build.VERSION.SDK_INT >= 21) { + if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { flags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT; } else { flags |= Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET; @@ -1809,455 +1896,6 @@ class SDLMain implements Runnable { } } - -/** - SDLSurface. This is what we draw on, so we need to know when it's created - in order to do anything useful. - - Because of this, that's where we set up the SDL thread -*/ -class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, - View.OnKeyListener, View.OnTouchListener, SensorEventListener { - - // Sensors - protected SensorManager mSensorManager; - protected Display mDisplay; - - // Keep track of the surface size to normalize touch events - protected float mWidth, mHeight; - - // Is SurfaceView ready for rendering - public boolean mIsSurfaceReady; - - // Startup - public SDLSurface(Context context) { - super(context); - getHolder().addCallback(this); - - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); - setOnKeyListener(this); - setOnTouchListener(this); - - mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); - - setOnGenericMotionListener(SDLActivity.getMotionListener()); - - // Some arbitrary defaults to avoid a potential division by zero - mWidth = 1.0f; - mHeight = 1.0f; - - mIsSurfaceReady = false; - } - - public void handlePause() { - enableSensor(Sensor.TYPE_ACCELEROMETER, false); - } - - public void handleResume() { - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); - setOnKeyListener(this); - setOnTouchListener(this); - enableSensor(Sensor.TYPE_ACCELEROMETER, true); - } - - public Surface getNativeSurface() { - return getHolder().getSurface(); - } - - // Called when we have a valid drawing surface - @Override - public void surfaceCreated(SurfaceHolder holder) { - Log.v("SDL", "surfaceCreated()"); - SDLActivity.onNativeSurfaceCreated(); - } - - // Called when we lose the surface - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - Log.v("SDL", "surfaceDestroyed()"); - - // Transition to pause, if needed - SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED; - SDLActivity.handleNativeState(); - - mIsSurfaceReady = false; - SDLActivity.onNativeSurfaceDestroyed(); - } - - // Called when the surface is resized - @Override - public void surfaceChanged(SurfaceHolder holder, - int format, int width, int height) { - Log.v("SDL", "surfaceChanged()"); - - if (SDLActivity.mSingleton == null) { - return; - } - - mWidth = width; - mHeight = height; - int nDeviceWidth = width; - int nDeviceHeight = height; - try - { - if (Build.VERSION.SDK_INT >= 17) { - DisplayMetrics realMetrics = new DisplayMetrics(); - mDisplay.getRealMetrics( realMetrics ); - nDeviceWidth = realMetrics.widthPixels; - nDeviceHeight = realMetrics.heightPixels; - } - } catch(Exception ignored) { - } - - synchronized(SDLActivity.getContext()) { - // In case we're waiting on a size change after going fullscreen, send a notification. - SDLActivity.getContext().notifyAll(); - } - - Log.v("SDL", "Window size: " + width + "x" + height); - Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight); - SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate()); - SDLActivity.onNativeResize(); - - // Prevent a screen distortion glitch, - // for instance when the device is in Landscape and a Portrait App is resumed. - boolean skip = false; - int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation(); - - if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) { - if (mWidth > mHeight) { - skip = true; - } - } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { - if (mWidth < mHeight) { - skip = true; - } - } - - // Special Patch for Square Resolution: Black Berry Passport - if (skip) { - double min = Math.min(mWidth, mHeight); - double max = Math.max(mWidth, mHeight); - - if (max / min < 1.20) { - Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution."); - skip = false; - } - } - - // Don't skip in MultiWindow. - if (skip) { - if (Build.VERSION.SDK_INT >= 24) { - if (SDLActivity.mSingleton.isInMultiWindowMode()) { - Log.v("SDL", "Don't skip in Multi-Window"); - skip = false; - } - } - } - - if (skip) { - Log.v("SDL", "Skip .. Surface is not ready."); - mIsSurfaceReady = false; - return; - } - - /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */ - SDLActivity.onNativeSurfaceChanged(); - - /* Surface is ready */ - mIsSurfaceReady = true; - - SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED; - SDLActivity.handleNativeState(); - } - - // Key events - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - - int deviceId = event.getDeviceId(); - int source = event.getSource(); - - if (source == InputDevice.SOURCE_UNKNOWN) { - InputDevice device = InputDevice.getDevice(deviceId); - if (device != null) { - source = device.getSources(); - } - } - -// if (event.getAction() == KeyEvent.ACTION_DOWN) { -// Log.v("SDL", "key down: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); -// } else if (event.getAction() == KeyEvent.ACTION_UP) { -// Log.v("SDL", "key up: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); -// } - - // Dispatch the different events depending on where they come from - // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD - // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD - // - // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and - // SOURCE_JOYSTICK, while its key events arrive from the keyboard source - // So, retrieve the device itself and check all of its sources - if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) { - // Note that we process events with specific key codes here - if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (SDLControllerManager.onNativePadDown(deviceId, keyCode) == 0) { - return true; - } - } else if (event.getAction() == KeyEvent.ACTION_UP) { - if (SDLControllerManager.onNativePadUp(deviceId, keyCode) == 0) { - return true; - } - } - } - - if ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (SDLActivity.isTextInputEvent(event)) { - SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1); - } - SDLActivity.onNativeKeyDown(keyCode); - return true; - } else if (event.getAction() == KeyEvent.ACTION_UP) { - SDLActivity.onNativeKeyUp(keyCode); - return true; - } - } - - if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { - // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses - // they are ignored here because sending them as mouse input to SDL is messy - if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) { - switch (event.getAction()) { - case KeyEvent.ACTION_DOWN: - case KeyEvent.ACTION_UP: - // mark the event as handled or it will be handled by system - // handling KEYCODE_BACK by system will call onBackPressed() - return true; - } - } - } - - return false; - } - - // Touch events - @Override - public boolean onTouch(View v, MotionEvent event) { - /* Ref: http://developer.android.com/training/gestures/multi.html */ - int touchDevId = event.getDeviceId(); - final int pointerCount = event.getPointerCount(); - int action = event.getActionMasked(); - int pointerFingerId; - int i = -1; - float x,y,p; - - /* - * Prevent id to be -1, since it's used in SDL internal for synthetic events - * Appears when using Android emulator, eg: - * adb shell input mouse tap 100 100 - * adb shell input touchscreen tap 100 100 - */ - if (touchDevId < 0) { - touchDevId -= 1; - } - - // 12290 = Samsung DeX mode desktop mouse - // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN - // 0x2 = SOURCE_CLASS_POINTER - if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) { - int mouseButton = 1; - try { - Object object = event.getClass().getMethod("getButtonState").invoke(event); - if (object != null) { - mouseButton = (Integer) object; - } - } catch(Exception ignored) { - } - - // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values - // if we are. We'll leverage our existing mouse motion listener - SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener(); - x = motionListener.getEventX(event); - y = motionListener.getEventY(event); - - SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode()); - } else { - switch(action) { - case MotionEvent.ACTION_MOVE: - for (i = 0; i < pointerCount; i++) { - pointerFingerId = event.getPointerId(i); - x = event.getX(i) / mWidth; - y = event.getY(i) / mHeight; - p = event.getPressure(i); - if (p > 1.0f) { - // may be larger than 1.0f on some devices - // see the documentation of getPressure(i) - p = 1.0f; - } - SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); - } - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_DOWN: - // Primary pointer up/down, the index is always zero - i = 0; - /* fallthrough */ - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_POINTER_DOWN: - // Non primary pointer up/down - if (i == -1) { - i = event.getActionIndex(); - } - - pointerFingerId = event.getPointerId(i); - x = event.getX(i) / mWidth; - y = event.getY(i) / mHeight; - p = event.getPressure(i); - if (p > 1.0f) { - // may be larger than 1.0f on some devices - // see the documentation of getPressure(i) - p = 1.0f; - } - SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); - break; - - case MotionEvent.ACTION_CANCEL: - for (i = 0; i < pointerCount; i++) { - pointerFingerId = event.getPointerId(i); - x = event.getX(i) / mWidth; - y = event.getY(i) / mHeight; - p = event.getPressure(i); - if (p > 1.0f) { - // may be larger than 1.0f on some devices - // see the documentation of getPressure(i) - p = 1.0f; - } - SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p); - } - break; - - default: - break; - } - } - - return true; - } - - // Sensor events - public void enableSensor(int sensortype, boolean enabled) { - // TODO: This uses getDefaultSensor - what if we have >1 accels? - if (enabled) { - mSensorManager.registerListener(this, - mSensorManager.getDefaultSensor(sensortype), - SensorManager.SENSOR_DELAY_GAME, null); - } else { - mSensorManager.unregisterListener(this, - mSensorManager.getDefaultSensor(sensortype)); - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // TODO - } - - @Override - public void onSensorChanged(SensorEvent event) { - if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { - - // Since we may have an orientation set, we won't receive onConfigurationChanged events. - // We thus should check here. - int newOrientation; - - float x, y; - switch (mDisplay.getRotation()) { - case Surface.ROTATION_90: - x = -event.values[1]; - y = event.values[0]; - newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE; - break; - case Surface.ROTATION_270: - x = event.values[1]; - y = -event.values[0]; - newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED; - break; - case Surface.ROTATION_180: - x = -event.values[0]; - y = -event.values[1]; - newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED; - break; - case Surface.ROTATION_0: - default: - x = event.values[0]; - y = event.values[1]; - newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT; - break; - } - - if (newOrientation != SDLActivity.mCurrentOrientation) { - SDLActivity.mCurrentOrientation = newOrientation; - SDLActivity.onNativeOrientationChanged(newOrientation); - } - - SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, - y / SensorManager.GRAVITY_EARTH, - event.values[2] / SensorManager.GRAVITY_EARTH); - - - } - } - - // Captured pointer events for API 26. - public boolean onCapturedPointerEvent(MotionEvent event) - { - int action = event.getActionMasked(); - - float x, y; - 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: - case MotionEvent.ACTION_MOVE: - x = event.getX(0); - y = event.getY(0); - SDLActivity.onNativeMouse(0, action, x, y, true); - return true; - - case MotionEvent.ACTION_BUTTON_PRESS: - case MotionEvent.ACTION_BUTTON_RELEASE: - - // Change our action value to what SDL's code expects. - if (action == MotionEvent.ACTION_BUTTON_PRESS) { - action = MotionEvent.ACTION_DOWN; - } else { /* MotionEvent.ACTION_BUTTON_RELEASE */ - action = MotionEvent.ACTION_UP; - } - - x = event.getX(0); - y = event.getY(0); - int button = event.getButtonState(); - - SDLActivity.onNativeMouse(button, action, x, y, true); - return true; - } - - return false; - } - -} - /* This is a fake invisible editor view that receives the input and defines the * pan&scan region */ @@ -2278,21 +1916,7 @@ class DummyEdit extends View implements View.OnKeyListener { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - /* - * This handles the hardware keyboard input - */ - if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (SDLActivity.isTextInputEvent(event)) { - ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); - return true; - } - SDLActivity.onNativeKeyDown(keyCode); - return true; - } else if (event.getAction() == KeyEvent.ACTION_UP) { - SDLActivity.onNativeKeyUp(keyCode); - return true; - } - return false; + return SDLActivity.handleKeyEvent(v, keyCode, event, ic); } // @@ -2316,9 +1940,10 @@ class DummyEdit extends View implements View.OnKeyListener { public InputConnection onCreateInputConnection(EditorInfo outAttrs) { ic = new SDLInputConnection(this, true); - outAttrs.inputType = InputType.TYPE_CLASS_TEXT; - outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI - | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */; + outAttrs.inputType = InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_FLAG_MULTI_LINE; + outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI | + EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */; return ic; } @@ -2326,9 +1951,17 @@ class DummyEdit extends View implements View.OnKeyListener { class SDLInputConnection extends BaseInputConnection { + protected EditText mEditText; + protected String mCommittedText = ""; + public SDLInputConnection(View targetView, boolean fullEditor) { super(targetView, fullEditor); + mEditText = new EditText(SDL.getContext()); + } + @Override + public Editable getEditable() { + return mEditText.getEditableText(); } @Override @@ -2351,79 +1984,96 @@ class SDLInputConnection extends BaseInputConnection { } } - return super.sendKeyEvent(event); } @Override public boolean commitText(CharSequence text, int newCursorPosition) { - - /* Generate backspaces for the text we're going to replace */ - final Editable content = getEditable(); - if (content != null) { - int a = getComposingSpanStart(content); - int b = getComposingSpanEnd(content); - if (a == -1 || b == -1) { - a = Selection.getSelectionStart(content); - b = Selection.getSelectionEnd(content); - } - if (a < 0) a = 0; - if (b < 0) b = 0; - if (b < a) { - int tmp = a; - a = b; - b = tmp; - } - int backspaces = (b - a); - - for (int i = 0; i < backspaces; i++) { - nativeGenerateScancodeForUnichar('\b'); - } + if (!super.commitText(text, newCursorPosition)) { + return false; } - - for (int i = 0; i < text.length(); i++) { - char c = text.charAt(i); - if (c == '\n') { - if (SDLActivity.onNativeSoftReturnKey()) { - return true; - } - } - nativeGenerateScancodeForUnichar(c); - } - - SDLInputConnection.nativeCommitText(text.toString(), newCursorPosition); - - return super.commitText(text, newCursorPosition); + updateText(); + return true; } @Override public boolean setComposingText(CharSequence text, int newCursorPosition) { + if (!super.setComposingText(text, newCursorPosition)) { + return false; + } + updateText(); + return true; + } - nativeSetComposingText(text.toString(), newCursorPosition); + @Override + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) { + // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection + // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265 + if (beforeLength > 0 && afterLength == 0) { + // backspace(s) + while (beforeLength-- > 0) { + nativeGenerateScancodeForUnichar('\b'); + } + return true; + } + } - return super.setComposingText(text, newCursorPosition); + if (!super.deleteSurroundingText(beforeLength, afterLength)) { + return false; + } + updateText(); + return true; + } + + protected void updateText() { + final Editable content = getEditable(); + if (content == null) { + return; + } + + String text = content.toString(); + int compareLength = Math.min(text.length(), mCommittedText.length()); + int matchLength, offset; + + /* Backspace over characters that are no longer in the string */ + for (matchLength = 0; matchLength < compareLength; ) { + int codePoint = mCommittedText.codePointAt(matchLength); + if (codePoint != text.codePointAt(matchLength)) { + break; + } + matchLength += Character.charCount(codePoint); + } + /* FIXME: This doesn't handle graphemes, like '🌬️' */ + for (offset = matchLength; offset < mCommittedText.length(); ) { + int codePoint = mCommittedText.codePointAt(offset); + nativeGenerateScancodeForUnichar('\b'); + offset += Character.charCount(codePoint); + } + + if (matchLength < text.length()) { + String pendingText = text.subSequence(matchLength, text.length()).toString(); + for (offset = 0; offset < pendingText.length(); ) { + int codePoint = pendingText.codePointAt(offset); + if (codePoint == '\n') { + if (SDLActivity.onNativeSoftReturnKey()) { + return; + } + } + /* Higher code points don't generate simulated scancodes */ + if (codePoint < 128) { + nativeGenerateScancodeForUnichar((char)codePoint); + } + offset += Character.charCount(codePoint); + } + SDLInputConnection.nativeCommitText(pendingText, 0); + } + mCommittedText = text; } public static native void nativeCommitText(String text, int newCursorPosition); - public native void nativeGenerateScancodeForUnichar(char c); - - public native void nativeSetComposingText(String text, int newCursorPosition); - - @Override - public boolean deleteSurroundingText(int beforeLength, int afterLength) { - // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection - // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265 - if (beforeLength > 0 && afterLength == 0) { - // backspace(s) - while (beforeLength-- > 0) { - nativeGenerateScancodeForUnichar('\b'); - } - return true; - } - - return super.deleteSurroundingText(beforeLength, afterLength); - } + public static native void nativeGenerateScancodeForUnichar(char c); } class SDLClipboardHandler implements diff --git a/android/app/src/main/java/org/libsdl/app/SDLAudioManager.java b/android/app/src/main/java/org/libsdl/app/SDLAudioManager.java index 2bfc7186..7c821a40 100644 --- a/android/app/src/main/java/org/libsdl/app/SDLAudioManager.java +++ b/android/app/src/main/java/org/libsdl/app/SDLAudioManager.java @@ -1,5 +1,8 @@ package org.libsdl.app; +import android.content.Context; +import android.media.AudioDeviceCallback; +import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; @@ -8,34 +11,67 @@ import android.media.MediaRecorder; import android.os.Build; import android.util.Log; -public class SDLAudioManager -{ +import java.util.Arrays; + +public class SDLAudioManager { protected static final String TAG = "SDLAudio"; protected static AudioTrack mAudioTrack; protected static AudioRecord mAudioRecord; + protected static Context mContext; + + private static final int[] NO_DEVICES = {}; + + private static AudioDeviceCallback mAudioDeviceCallback; public static void initialize() { mAudioTrack = null; mAudioRecord = null; + mAudioDeviceCallback = null; + + if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) + { + mAudioDeviceCallback = new AudioDeviceCallback() { + @Override + public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { + Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId())); + } + + @Override + public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { + Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId())); + } + }; + } + } + + public static void setContext(Context context) { + mContext = context; + if (context != null) { + registerAudioDeviceCallback(); + } + } + + public static void release(Context context) { + unregisterAudioDeviceCallback(context); } // 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); + 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) { + protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) { int channelConfig; int sampleSize; int frameSize; @@ -43,14 +79,14 @@ public class SDLAudioManager 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 (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) { if (desiredChannels > 2) { desiredChannels = 2; } } /* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */ - if (Build.VERSION.SDK_INT < 22) { + if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) { if (sampleRate < 8000) { sampleRate = 8000; } else if (sampleRate > 48000) { @@ -59,7 +95,7 @@ public class SDLAudioManager } if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) { - int minSDKVersion = (isCapture ? 23 : 21); + int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */); if (Build.VERSION.SDK_INT < minSDKVersion) { audioFormat = AudioFormat.ENCODING_PCM_16BIT; } @@ -120,7 +156,7 @@ public class SDLAudioManager channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER; break; case 8: - if (Build.VERSION.SDK_INT >= 23) { + if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) { channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; } else { Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround"); @@ -201,6 +237,10 @@ public class SDLAudioManager return null; } + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) { + mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId)); + } + mAudioRecord.startRecording(); } @@ -224,6 +264,10 @@ public class SDLAudioManager return null; } + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) { + mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId)); + } + mAudioTrack.play(); } @@ -238,11 +282,73 @@ public class SDLAudioManager return results; } + private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { + AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) + .filter(deviceInfo -> deviceInfo.getId() == deviceId) + .findFirst() + .orElse(null); + } else { + return null; + } + } + + private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { + AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) + .filter(deviceInfo -> deviceInfo.getId() == deviceId) + .findFirst() + .orElse(null); + } else { + return null; + } + } + + private static void registerAudioDeviceCallback() { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { + AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null); + } + } + + private static void unregisterAudioDeviceCallback(Context context) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback); + } + } + /** * 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); + public static int[] getAudioOutputDevices() { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { + AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray(); + } else { + return NO_DEVICES; + } + } + + /** + * This method is called by SDL using JNI. + */ + public static int[] getAudioInputDevices() { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { + AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray(); + } else { + return NO_DEVICES; + } + } + + /** + * This method is called by SDL using JNI. + */ + public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) { + return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId); } /** @@ -254,6 +360,11 @@ public class SDLAudioManager return; } + if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) { + Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)"); + return; + } + for (int i = 0; i < buffer.length;) { int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING); if (result > 0) { @@ -326,18 +437,22 @@ public class SDLAudioManager /** * 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); + public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) { + return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId); } /** 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); + if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) { + return 0; + } 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 captureReadShortBuffer(short[] buffer, boolean blocking) { - if (Build.VERSION.SDK_INT < 23) { + if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) { return mAudioRecord.read(buffer, 0, buffer.length); } else { return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); @@ -346,7 +461,7 @@ public class SDLAudioManager /** This method is called by SDL using JNI. */ public static int captureReadByteBuffer(byte[] buffer, boolean blocking) { - if (Build.VERSION.SDK_INT < 23) { + if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) { return mAudioRecord.read(buffer, 0, buffer.length); } else { return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); @@ -391,4 +506,9 @@ public class SDLAudioManager } public static native int nativeSetupJNI(); + + public static native void removeAudioDevice(boolean isCapture, int deviceId); + + public static native void addAudioDevice(boolean isCapture, int deviceId); + } diff --git a/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java b/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java index 82373d9d..d6913f15 100644 --- a/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java +++ b/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java @@ -24,7 +24,7 @@ public class SDLControllerManager 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); + int naxes, int axis_mask, 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); @@ -42,7 +42,7 @@ public class SDLControllerManager public static void initialize() { if (mJoystickHandler == null) { - if (Build.VERSION.SDK_INT >= 19) { + if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { mJoystickHandler = new SDLJoystickHandler_API19(); } else { mJoystickHandler = new SDLJoystickHandler_API16(); @@ -50,7 +50,7 @@ public class SDLControllerManager } if (mHapticHandler == null) { - if (Build.VERSION.SDK_INT >= 26) { + if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) { mHapticHandler = new SDLHapticHandler_API26(); } else { mHapticHandler = new SDLHapticHandler(); @@ -168,6 +168,32 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler { arg1Axis = MotionEvent.AXIS_GAS; } + // Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ. + // This is because the usual pairing are: + // - AXIS_X + AXIS_Y (left stick). + // - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers). + // - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers). + // This sorts the axes in the above order, which tends to be correct + // for Xbox-ish game pads that have the right stick on RX/RY and the + // triggers on Z/RZ. + // + // Gamepads that don't have AXIS_Z/AXIS_RZ but use + // AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this. + // + // References: + // - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input + // - https://www.kernel.org/doc/html/latest/input/gamepad.html + if (arg0Axis == MotionEvent.AXIS_Z) { + arg0Axis = MotionEvent.AXIS_RZ - 1; + } else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) { + --arg0Axis; + } + if (arg1Axis == MotionEvent.AXIS_Z) { + arg1Axis = MotionEvent.AXIS_RZ - 1; + } else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) { + --arg1Axis; + } + return arg0Axis - arg1Axis; } } @@ -210,7 +236,7 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler { 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); + getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0); } } } @@ -291,6 +317,9 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler { public int getVendorId(InputDevice joystickDevice) { return 0; } + public int getAxisMask(List ranges) { + return -1; + } public int getButtonMask(InputDevice joystickDevice) { return -1; } @@ -308,6 +337,43 @@ class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 { return joystickDevice.getVendorId(); } + @Override + public int getAxisMask(List ranges) { + // For compatibility, keep computing the axis mask like before, + // only really distinguishing 2, 4 and 6 axes. + int axis_mask = 0; + if (ranges.size() >= 2) { + // ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY)) + axis_mask |= 0x0003; + } + if (ranges.size() >= 4) { + // ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY)) + axis_mask |= 0x000c; + } + if (ranges.size() >= 6) { + // ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) + axis_mask |= 0x0030; + } + // Also add an indicator bit for whether the sorting order has changed. + // This serves to disable outdated gamecontrollerdb.txt mappings. + boolean have_z = false; + boolean have_past_z_before_rz = false; + for (InputDevice.MotionRange range : ranges) { + int axis = range.getAxis(); + if (axis == MotionEvent.AXIS_Z) { + have_z = true; + } else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) { + have_past_z_before_rz = true; + } + } + if (have_z && have_past_z_before_rz) { + // If both these exist, the compare() function changed sorting order. + // Set a bit to indicate this fact. + axis_mask |= 0x8000; + } + return axis_mask; + } + @Override public int getButtonMask(InputDevice joystickDevice) { int button_mask = 0; @@ -743,7 +809,7 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 { @Override public boolean supportsRelativeMouse() { - return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)); + return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */); } @Override @@ -753,7 +819,7 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 { @Override public boolean setRelativeMouseEnabled(boolean enabled) { - if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) { + if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) { if (enabled) { SDLActivity.getContentView().requestPointerCapture(); } else { diff --git a/android/app/src/main/java/org/libsdl/app/SDLSurface.java b/android/app/src/main/java/org/libsdl/app/SDLSurface.java new file mode 100644 index 00000000..0857e4b6 --- /dev/null +++ b/android/app/src/main/java/org/libsdl/app/SDLSurface.java @@ -0,0 +1,405 @@ +package org.libsdl.app; + + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Build; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowManager; + + +/** + SDLSurface. This is what we draw on, so we need to know when it's created + in order to do anything useful. + + Because of this, that's where we set up the SDL thread +*/ +public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, + View.OnKeyListener, View.OnTouchListener, SensorEventListener { + + // Sensors + protected SensorManager mSensorManager; + protected Display mDisplay; + + // Keep track of the surface size to normalize touch events + protected float mWidth, mHeight; + + // Is SurfaceView ready for rendering + public boolean mIsSurfaceReady; + + // Startup + public SDLSurface(Context context) { + super(context); + getHolder().addCallback(this); + + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + setOnKeyListener(this); + setOnTouchListener(this); + + mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); + + setOnGenericMotionListener(SDLActivity.getMotionListener()); + + // Some arbitrary defaults to avoid a potential division by zero + mWidth = 1.0f; + mHeight = 1.0f; + + mIsSurfaceReady = false; + } + + public void handlePause() { + enableSensor(Sensor.TYPE_ACCELEROMETER, false); + } + + public void handleResume() { + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + setOnKeyListener(this); + setOnTouchListener(this); + enableSensor(Sensor.TYPE_ACCELEROMETER, true); + } + + public Surface getNativeSurface() { + return getHolder().getSurface(); + } + + // Called when we have a valid drawing surface + @Override + public void surfaceCreated(SurfaceHolder holder) { + Log.v("SDL", "surfaceCreated()"); + SDLActivity.onNativeSurfaceCreated(); + } + + // Called when we lose the surface + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.v("SDL", "surfaceDestroyed()"); + + // Transition to pause, if needed + SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED; + SDLActivity.handleNativeState(); + + mIsSurfaceReady = false; + SDLActivity.onNativeSurfaceDestroyed(); + } + + // Called when the surface is resized + @Override + public void surfaceChanged(SurfaceHolder holder, + int format, int width, int height) { + Log.v("SDL", "surfaceChanged()"); + + if (SDLActivity.mSingleton == null) { + return; + } + + mWidth = width; + mHeight = height; + int nDeviceWidth = width; + int nDeviceHeight = height; + try + { + if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) { + DisplayMetrics realMetrics = new DisplayMetrics(); + mDisplay.getRealMetrics( realMetrics ); + nDeviceWidth = realMetrics.widthPixels; + nDeviceHeight = realMetrics.heightPixels; + } + } catch(Exception ignored) { + } + + synchronized(SDLActivity.getContext()) { + // In case we're waiting on a size change after going fullscreen, send a notification. + SDLActivity.getContext().notifyAll(); + } + + Log.v("SDL", "Window size: " + width + "x" + height); + Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight); + SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate()); + SDLActivity.onNativeResize(); + + // Prevent a screen distortion glitch, + // for instance when the device is in Landscape and a Portrait App is resumed. + boolean skip = false; + int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation(); + + if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) { + if (mWidth > mHeight) { + skip = true; + } + } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { + if (mWidth < mHeight) { + skip = true; + } + } + + // Special Patch for Square Resolution: Black Berry Passport + if (skip) { + double min = Math.min(mWidth, mHeight); + double max = Math.max(mWidth, mHeight); + + if (max / min < 1.20) { + Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution."); + skip = false; + } + } + + // Don't skip in MultiWindow. + if (skip) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { + if (SDLActivity.mSingleton.isInMultiWindowMode()) { + Log.v("SDL", "Don't skip in Multi-Window"); + skip = false; + } + } + } + + if (skip) { + Log.v("SDL", "Skip .. Surface is not ready."); + mIsSurfaceReady = false; + return; + } + + /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */ + SDLActivity.onNativeSurfaceChanged(); + + /* Surface is ready */ + mIsSurfaceReady = true; + + SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED; + SDLActivity.handleNativeState(); + } + + // Key events + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + return SDLActivity.handleKeyEvent(v, keyCode, event, null); + } + + // Touch events + @Override + public boolean onTouch(View v, MotionEvent event) { + /* Ref: http://developer.android.com/training/gestures/multi.html */ + int touchDevId = event.getDeviceId(); + final int pointerCount = event.getPointerCount(); + int action = event.getActionMasked(); + int pointerFingerId; + int i = -1; + float x,y,p; + + /* + * Prevent id to be -1, since it's used in SDL internal for synthetic events + * Appears when using Android emulator, eg: + * adb shell input mouse tap 100 100 + * adb shell input touchscreen tap 100 100 + */ + if (touchDevId < 0) { + touchDevId -= 1; + } + + // 12290 = Samsung DeX mode desktop mouse + // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN + // 0x2 = SOURCE_CLASS_POINTER + if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) { + int mouseButton = 1; + try { + Object object = event.getClass().getMethod("getButtonState").invoke(event); + if (object != null) { + mouseButton = (Integer) object; + } + } catch(Exception ignored) { + } + + // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values + // if we are. We'll leverage our existing mouse motion listener + SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener(); + x = motionListener.getEventX(event); + y = motionListener.getEventY(event); + + SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode()); + } else { + switch(action) { + case MotionEvent.ACTION_MOVE: + for (i = 0; i < pointerCount; i++) { + pointerFingerId = event.getPointerId(i); + x = event.getX(i) / mWidth; + y = event.getY(i) / mHeight; + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_DOWN: + // Primary pointer up/down, the index is always zero + i = 0; + /* fallthrough */ + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_POINTER_DOWN: + // Non primary pointer up/down + if (i == -1) { + i = event.getActionIndex(); + } + + pointerFingerId = event.getPointerId(i); + x = event.getX(i) / mWidth; + y = event.getY(i) / mHeight; + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); + break; + + case MotionEvent.ACTION_CANCEL: + for (i = 0; i < pointerCount; i++) { + pointerFingerId = event.getPointerId(i); + x = event.getX(i) / mWidth; + y = event.getY(i) / mHeight; + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p); + } + break; + + default: + break; + } + } + + return true; + } + + // Sensor events + public void enableSensor(int sensortype, boolean enabled) { + // TODO: This uses getDefaultSensor - what if we have >1 accels? + if (enabled) { + mSensorManager.registerListener(this, + mSensorManager.getDefaultSensor(sensortype), + SensorManager.SENSOR_DELAY_GAME, null); + } else { + mSensorManager.unregisterListener(this, + mSensorManager.getDefaultSensor(sensortype)); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // TODO + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + + // Since we may have an orientation set, we won't receive onConfigurationChanged events. + // We thus should check here. + int newOrientation; + + float x, y; + switch (mDisplay.getRotation()) { + case Surface.ROTATION_90: + x = -event.values[1]; + y = event.values[0]; + newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE; + break; + case Surface.ROTATION_270: + x = event.values[1]; + y = -event.values[0]; + newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED; + break; + case Surface.ROTATION_180: + x = -event.values[0]; + y = -event.values[1]; + newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED; + break; + case Surface.ROTATION_0: + default: + x = event.values[0]; + y = event.values[1]; + newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT; + break; + } + + if (newOrientation != SDLActivity.mCurrentOrientation) { + SDLActivity.mCurrentOrientation = newOrientation; + SDLActivity.onNativeOrientationChanged(newOrientation); + } + + SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, + y / SensorManager.GRAVITY_EARTH, + event.values[2] / SensorManager.GRAVITY_EARTH); + + + } + } + + // Captured pointer events for API 26. + public boolean onCapturedPointerEvent(MotionEvent event) + { + int action = event.getActionMasked(); + + float x, y; + 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: + case MotionEvent.ACTION_MOVE: + x = event.getX(0); + y = event.getY(0); + SDLActivity.onNativeMouse(0, action, x, y, true); + return true; + + case MotionEvent.ACTION_BUTTON_PRESS: + case MotionEvent.ACTION_BUTTON_RELEASE: + + // Change our action value to what SDL's code expects. + if (action == MotionEvent.ACTION_BUTTON_PRESS) { + action = MotionEvent.ACTION_DOWN; + } else { /* MotionEvent.ACTION_BUTTON_RELEASE */ + action = MotionEvent.ACTION_UP; + } + + x = event.getX(0); + y = event.getY(0); + int button = event.getButtonState(); + + SDLActivity.onNativeMouse(button, action, x, y, true); + return true; + } + + return false; + } +} diff --git a/doc/7-systems/pokey.md b/doc/7-systems/pokey.md index b05c4367..efebfcd3 100644 --- a/doc/7-systems/pokey.md +++ b/doc/7-systems/pokey.md @@ -32,3 +32,6 @@ a sound and input chip developed by Atari for their 8-bit computers (Atari 400, - filtered output on channel 2 (I suggest you to set channel 4 volume to 0). - use for PWM effects (not automatic!). - bit 0: 15KHz mode. +- 12xx: toggle two-tone mode. + - when enabled, channel 2 modulates channel 1. I don't know how, but it does. + - only on ASAP core. diff --git a/extern/SAASound/src/SAADevice.cpp b/extern/SAASound/src/SAADevice.cpp index 78b6e4a4..eb88e9ac 100644 --- a/extern/SAASound/src/SAADevice.cpp +++ b/extern/SAASound/src/SAADevice.cpp @@ -316,27 +316,27 @@ void CSAADevice::_TickAndOutputStereo(unsigned int& left_mixed, unsigned int& ri m_Noise0.Tick(); m_Noise1.Tick(); m_Amp0.TickAndOutputStereo(temp_left, temp_right); - oscBuf[0]->data[oscBuf[0]->needle++]=(temp_left+temp_right)<<3; + oscBuf[0]->data[oscBuf[0]->needle++]=(temp_left+temp_right)<<5; accum_left += temp_left; accum_right += temp_right; m_Amp1.TickAndOutputStereo(temp_left, temp_right); - oscBuf[1]->data[oscBuf[1]->needle++]=(temp_left+temp_right)<<3; + oscBuf[1]->data[oscBuf[1]->needle++]=(temp_left+temp_right)<<5; accum_left += temp_left; accum_right += temp_right; m_Amp2.TickAndOutputStereo(temp_left, temp_right); - oscBuf[2]->data[oscBuf[2]->needle++]=(temp_left+temp_right)<<3; + oscBuf[2]->data[oscBuf[2]->needle++]=(temp_left+temp_right)<<5; accum_left += temp_left; accum_right += temp_right; m_Amp3.TickAndOutputStereo(temp_left, temp_right); - oscBuf[3]->data[oscBuf[3]->needle++]=(temp_left+temp_right)<<3; + oscBuf[3]->data[oscBuf[3]->needle++]=(temp_left+temp_right)<<5; accum_left += temp_left; accum_right += temp_right; m_Amp4.TickAndOutputStereo(temp_left, temp_right); - oscBuf[4]->data[oscBuf[4]->needle++]=(temp_left+temp_right)<<3; + oscBuf[4]->data[oscBuf[4]->needle++]=(temp_left+temp_right)<<5; accum_left += temp_left; accum_right += temp_right; m_Amp5.TickAndOutputStereo(temp_left, temp_right); - oscBuf[5]->data[oscBuf[5]->needle++]=(temp_left+temp_right)<<3; + oscBuf[5]->data[oscBuf[5]->needle++]=(temp_left+temp_right)<<5; accum_left += temp_left; accum_right += temp_right; } diff --git a/extern/SDL b/extern/SDL index 55b03c74..ffa78e6b 160000 --- a/extern/SDL +++ b/extern/SDL @@ -1 +1 @@ -Subproject commit 55b03c7493a7abed33cf803d1380a40fa8af903f +Subproject commit ffa78e6bead23e2ba3adf8ec2367ff2218d4343c diff --git a/extern/igfd/ImGuiFileDialog.cpp b/extern/igfd/ImGuiFileDialog.cpp index a3104839..752f4100 100644 --- a/extern/igfd/ImGuiFileDialog.cpp +++ b/extern/igfd/ImGuiFileDialog.cpp @@ -1137,8 +1137,9 @@ namespace IGFD IGFD::FileManager::FileManager() { puFsRoot = std::string(1u, PATH_SEP); - for (int i=0; i<4; i++) { - puSortingDirection[i]=true; + puSortingDirection[0]=true; + for (int i=1; i<4; i++) { + puSortingDirection[i]=false; } } diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 5a752236..ed0c0ce9 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -5683,7 +5683,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) { SafeWriter* DivEngine::saveDMF(unsigned char version) { // fail if version is not supported - if (version<24 || version>26) { + if (version>26) version=26; + if (version<24) { logE("cannot save in this version!"); lastError="invalid version to save in! this is a bug!"; return NULL; @@ -5817,7 +5818,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeC(curSubSong->timeBase); w->writeC(curSubSong->speeds.val[0]); w->writeC((curSubSong->speeds.len>=2)?curSubSong->speeds.val[1]:curSubSong->speeds.val[0]); - w->writeC((intHz<=53)?1:0); + w->writeC((intHz<=53)?0:1); w->writeC((intHz!=60 && intHz!=50)); char customHz[4]; memset(customHz,0,4); diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 8de0fa3b..0e38bf96 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -76,7 +76,8 @@ void DivPlatformArcade::acquire_nuked(short** buf, size_t len) { } for (int i=0; i<8; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]>>1; + int chOut=(int16_t)fm.ch_out[i]; + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut<<1,-32768,32767); } if (o[0]<-32768) o[0]=-32768; @@ -111,7 +112,8 @@ void DivPlatformArcade::acquire_ymfm(short** buf, size_t len) { fm_ymfm->generate(&out_ymfm); for (int i=0; i<8; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))>>1; + int chOut=fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1); + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut,-32768,32767); } os[0]=out_ymfm.data[0]; diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index d31f0683..c32a4ee2 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -187,9 +187,9 @@ void DivPlatformAY8910::acquire(short** buf, size_t len) { buf[0][i]=ayBuf[0][0]; buf[1][i]=buf[0][i]; - oscBuf[0]->data[oscBuf[0]->needle++]=sunsoftVolTable[31-(ay->lastIndx&31)]<<2; - oscBuf[1]->data[oscBuf[1]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>5)&31)]<<2; - oscBuf[2]->data[oscBuf[2]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>10)&31)]<<2; + oscBuf[0]->data[oscBuf[0]->needle++]=CLAMP(sunsoftVolTable[31-(ay->lastIndx&31)]<<3,-32768,32767); + oscBuf[1]->data[oscBuf[1]->needle++]=CLAMP(sunsoftVolTable[31-((ay->lastIndx>>5)&31)]<<3,-32768,32767); + oscBuf[2]->data[oscBuf[2]->needle++]=CLAMP(sunsoftVolTable[31-((ay->lastIndx>>10)&31)]<<3,-32768,32767); } } else { for (size_t i=0; idata[oscBuf[0]->needle++]=ayBuf[0][0]<<1; - oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<1; - oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<1; + oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<2; + oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<2; + oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<2; } } } diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 51560975..8561548d 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -186,9 +186,9 @@ void DivPlatformAY8930::acquire(short** buf, size_t len) { buf[1][i]=buf[0][i]; } - oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<1; - oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<1; - oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<1; + oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<2; + oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<2; + oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<2; } } diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index bfb1e365..94202de1 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -55,7 +55,7 @@ void DivPlatformBubSysWSG::acquire(short** buf, size_t len) { chanOut=chan[i].waveROM[k005289.addr(i)]*(regPool[2+i]&0xf); out+=chanOut; if (writeOscBuf==0) { - oscBuf[i]->data[oscBuf[i]->needle++]=chanOut<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=chanOut<<7; } } } diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 43cfbdf0..977e3951 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -80,18 +80,18 @@ void DivPlatformC64::acquire(short** buf, size_t len) { sid_fp.clock(4,&buf[0][i]); if (++writeOscBuf>=4) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp.lastChanOut[0]-dcOff)>>6; - oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp.lastChanOut[1]-dcOff)>>6; - oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp.lastChanOut[2]-dcOff)>>6; + oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp.lastChanOut[0]-dcOff)>>5; + oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp.lastChanOut[1]-dcOff)>>5; + oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp.lastChanOut[2]-dcOff)>>5; } } else { sid.clock(); buf[0][i]=sid.output(); if (++writeOscBuf>=16) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>6; - oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>6; - oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>6; + oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>5; + oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>5; + oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>5; } } } diff --git a/src/engine/platform/dummy.cpp b/src/engine/platform/dummy.cpp index 04763898..18fa5ca9 100644 --- a/src/engine/platform/dummy.cpp +++ b/src/engine/platform/dummy.cpp @@ -32,7 +32,7 @@ void DivPlatformDummy::acquire(short** buf, size_t len) { if (chan[j].active) { if (!isMuted[j]) { chanOut=(((signed short)chan[j].pos)*chan[j].amp*chan[j].vol)>>12; - oscBuf[j]->data[oscBuf[j]->needle++]=chanOut>>1; + oscBuf[j]->data[oscBuf[j]->needle++]=chanOut<<1; out+=chanOut; } else { oscBuf[j]->data[oscBuf[j]->needle++]=0; diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index 2674e954..ea903ced 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -64,7 +64,7 @@ void DivPlatformFDS::acquire_puNES(short* buf, size_t len) { buf[i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf->data[oscBuf->needle++]=sample; + oscBuf->data[oscBuf->needle++]=sample*3; } } } @@ -80,7 +80,7 @@ void DivPlatformFDS::acquire_NSFPlay(short* buf, size_t len) { buf[i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf->data[oscBuf->needle++]=sample; + oscBuf->data[oscBuf->needle++]=sample*3; } } } diff --git a/src/engine/platform/ga20.cpp b/src/engine/platform/ga20.cpp index 60764585..7794d61a 100644 --- a/src/engine/platform/ga20.cpp +++ b/src/engine/platform/ga20.cpp @@ -71,8 +71,13 @@ void DivPlatformGA20::acquire(short** buf, size_t len) { delay=w.delay; } } - short *buffer[4] = {&ga20Buf[0][h],&ga20Buf[1][h],&ga20Buf[2][h],&ga20Buf[3][h]}; - ga20.sound_stream_update(buffer, 1); + short *buffer[4]={ + &ga20Buf[0][h], + &ga20Buf[1][h], + &ga20Buf[2][h], + &ga20Buf[3][h] + }; + ga20.sound_stream_update(buffer,1); buf[0][h]=(signed int)(ga20Buf[0][h]+ga20Buf[1][h]+ga20Buf[2][h]+ga20Buf[3][h])>>2; for (int i=0; i<4; i++) { oscBuf[i]->data[oscBuf[i]->needle++]=ga20Buf[i][h]>>1; diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 6a3b92d0..9a5d6d7d 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -74,7 +74,7 @@ void DivPlatformGB::acquire(short** buf, size_t len) { buf[1][i]=gb->apu_output.final_sample.right; for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(gb->apu_output.current_sample[i].left+gb->apu_output.current_sample[i].right)<<5; + oscBuf[i]->data[oscBuf[i]->needle++]=(gb->apu_output.current_sample[i].left+gb->apu_output.current_sample[i].right)<<6; } } } diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 072e8bc0..42d6f79e 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -122,6 +122,8 @@ void DivPlatformGenesis::processDAC(int iRate) { urgentWrite(0x2a,(unsigned char)sample+0x80); chan[5].dacReady=false; } + } else { + urgentWrite(0x2a,0x80); } chan[5].dacPos++; if (!chan[5].dacDirection && (s->isLoopable() && chan[5].dacPos>=(unsigned int)s->loopEnd)) { @@ -187,7 +189,7 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) { oscBuf[5]->data[oscBuf[5]->needle++]=chan[5].dacOutput<<6; oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<6; } else { - oscBuf[i]->data[oscBuf[i]->needle++]=fm.dacdata<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=((fm.dacdata^0x100)-0x100)<<6; oscBuf[6]->data[oscBuf[6]->needle++]=0; } } else { @@ -252,7 +254,7 @@ void DivPlatformGenesis::acquire_ymfm(short** buf, size_t len) { oscBuf[5]->data[oscBuf[5]->needle++]=chan[5].dacOutput<<6; oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<6; } else { - oscBuf[i]->data[oscBuf[i]->needle++]=fm_ymfm->debug_dac_data()<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=((fm_ymfm->debug_dac_data()^0x100)-0x100)<<6; oscBuf[6]->data[oscBuf[6]->needle++]=0; } } else { diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 0edc83b2..e9ac6be9 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -85,9 +85,9 @@ void DivPlatformMMC5::acquire(short** buf, size_t len) { if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:((mmc5->S3.output*10)<<6); - oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:((mmc5->S4.output*10)<<6); - oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:((mmc5->pcm.output*2)<<5); + oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:((mmc5->S3.output)<<11); + oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:((mmc5->S4.output)<<11); + oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:((mmc5->pcm.output)<<7); } } } diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index eee73b99..ead44cc1 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -118,7 +118,7 @@ void DivPlatformN163::acquire(short** buf, size_t len) { buf[0][i]=out; if (n163.voice_cycle()==0x78) for (int i=0; i<8; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=n163.voice_out(i)<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=n163.voice_out(i)<<7; } // command queue diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 07f9c5ae..99131ac3 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -115,11 +115,11 @@ void DivPlatformNES::acquire_puNES(short** buf, size_t len) { buf[0][i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:(nes->S1.output<<10); - oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:(nes->S2.output<<10); - oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:(nes->TR.output<<10); - oscBuf[3]->data[oscBuf[3]->needle++]=isMuted[3]?0:(nes->NS.output<<10); - oscBuf[4]->data[oscBuf[4]->needle++]=isMuted[4]?0:(nes->DMC.output<<7); + oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:(nes->S1.output<<11); + oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:(nes->S2.output<<11); + oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:(nes->TR.output<<11); + oscBuf[3]->data[oscBuf[3]->needle++]=isMuted[3]?0:(nes->NS.output<<11); + oscBuf[4]->data[oscBuf[4]->needle++]=isMuted[4]?0:(nes->DMC.output<<8); } } } @@ -142,11 +142,11 @@ void DivPlatformNES::acquire_NSFPlay(short** buf, size_t len) { buf[0][i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=nes1_NP->out[0]<<10; - oscBuf[1]->data[oscBuf[1]->needle++]=nes1_NP->out[1]<<10; - oscBuf[2]->data[oscBuf[2]->needle++]=nes2_NP->out[0]<<10; - oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<10; - oscBuf[4]->data[oscBuf[4]->needle++]=nes2_NP->out[2]<<7; + oscBuf[0]->data[oscBuf[0]->needle++]=nes1_NP->out[0]<<11; + oscBuf[1]->data[oscBuf[1]->needle++]=nes1_NP->out[1]<<11; + oscBuf[2]->data[oscBuf[2]->needle++]=nes2_NP->out[0]<<11; + oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<12; + oscBuf[4]->data[oscBuf[4]->needle++]=nes2_NP->out[2]<<8; } } } diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index b39fb8f6..d340aa31 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -220,45 +220,45 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) { if (fm.rhy&0x20) { for (int i=0; idata[oscBuf[i]->needle]=0; if (fm.channel[i].out[0]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0]; + chOut+=*fm.channel[ch].out[0]; } if (fm.channel[i].out[1]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1]; + chOut+=*fm.channel[ch].out[1]; } if (fm.channel[i].out[2]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[2]; + chOut+=*fm.channel[ch].out[2]; } if (fm.channel[i].out[3]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[3]; + chOut+=*fm.channel[ch].out[3]; } - oscBuf[i]->needle++; + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut<<(i==melodicChans?1:2),-32768,32767); } // special - oscBuf[melodicChans+1]->data[oscBuf[melodicChans+1]->needle++]=fm.slot[16].out*3; - oscBuf[melodicChans+2]->data[oscBuf[melodicChans+2]->needle++]=fm.slot[14].out*3; - oscBuf[melodicChans+3]->data[oscBuf[melodicChans+3]->needle++]=fm.slot[17].out*3; - oscBuf[melodicChans+4]->data[oscBuf[melodicChans+4]->needle++]=fm.slot[13].out*3; + oscBuf[melodicChans+1]->data[oscBuf[melodicChans+1]->needle++]=fm.slot[16].out*4; + oscBuf[melodicChans+2]->data[oscBuf[melodicChans+2]->needle++]=fm.slot[14].out*4; + oscBuf[melodicChans+3]->data[oscBuf[melodicChans+3]->needle++]=fm.slot[17].out*4; + oscBuf[melodicChans+4]->data[oscBuf[melodicChans+4]->needle++]=fm.slot[13].out*4; } else { for (int i=0; idata[oscBuf[i]->needle]=0; if (fm.channel[i].out[0]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0]; + chOut+=*fm.channel[ch].out[0]; } if (fm.channel[i].out[1]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1]; + chOut+=*fm.channel[ch].out[1]; } if (fm.channel[i].out[2]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[2]; + chOut+=*fm.channel[ch].out[2]; } if (fm.channel[i].out[3]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[3]; + chOut+=*fm.channel[ch].out[3]; } - oscBuf[i]->needle++; + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut<<2,-32768,32767); } } diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index e7486b94..38b892e8 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -68,7 +68,7 @@ void DivPlatformOPLL::acquire_nuked(short** buf, size_t len) { unsigned char nextOut=cycleMapOPLL[fm.cycles]; if ((nextOut>=6 && properDrums) || !isMuted[nextOut]) { os+=(o[0]+o[1]); - if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<5; + if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<6; } else { if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=0; } @@ -76,7 +76,7 @@ void DivPlatformOPLL::acquire_nuked(short** buf, size_t len) { if (!(vrc7 || (fm.rm_enable&0x20))) for (int i=0; i<9; i++) { unsigned char ch=visMapOPLL[i]; if ((i>=6 && properDrums) || !isMuted[ch]) { - oscBuf[ch]->data[oscBuf[ch]->needle++]=(fm.output_ch[i])<<5; + oscBuf[ch]->data[oscBuf[ch]->needle++]=(fm.output_ch[i])<<6; } else { oscBuf[ch]->data[oscBuf[ch]->needle++]=0; } @@ -107,7 +107,6 @@ void DivPlatformOPLL::tick(bool sysTick) { rWrite(0x36,drumVol[0]); rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); rWrite(0x38,drumVol[3]|(drumVol[2]<<4)); - break; } else if (i<6 || !drums) { if (i<9) { rWrite(0x30+i,((15-VOL_SCALE_LOG_BROKEN(chan[i].outVol,15-chan[i].state.op[1].tl,15))&15)|(chan[i].state.opllPreset<<4)); diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index d03fa6de..790346e1 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -35,6 +35,7 @@ const char* regCheatSheetPOKEY[]={ "AUDF4", "6", "AUDC4", "7", "AUDCTL", "8", + "SKCTL", "F", NULL }; @@ -154,6 +155,11 @@ void DivPlatformPOKEY::tick(bool sysTick) { } } + if (skctlChanged) { + skctlChanged=false; + rWrite(15,skctl); + } + for (int i=0; i<4; i++) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,parent->song.linearPitch?chan[i].pitch:0,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,parent->song.linearPitch?chan[i].pitch2:0,chipClock,CHIP_DIVIDER); @@ -320,6 +326,10 @@ int DivPlatformPOKEY::dispatch(DivCommand c) { audctl=c.value&0xff; audctlChanged=true; break; + case DIV_CMD_STD_NOISE_FREQ: + skctl=c.value?0x8b:0x03; + skctlChanged=true; + break; case DIV_CMD_NOTE_PORTA: { int destFreq=NOTE_PERIODIC(c.value2); bool return2=false; @@ -385,6 +395,7 @@ void DivPlatformPOKEY::forceIns() { chan[i].freqChanged=true; } audctlChanged=true; + skctlChanged=true; } void* DivPlatformPOKEY::getChanState(int ch) { @@ -408,7 +419,7 @@ unsigned char* DivPlatformPOKEY::getRegisterPool() { } int DivPlatformPOKEY::getRegisterPoolSize() { - return 9; + return 16; } void DivPlatformPOKEY::reset() { @@ -430,6 +441,8 @@ void DivPlatformPOKEY::reset() { audctl=0; audctlChanged=true; + skctl=3; + skctlChanged=true; } bool DivPlatformPOKEY::keyOffAffectsArp(int ch) { diff --git a/src/engine/platform/pokey.h b/src/engine/platform/pokey.h index f24ea56c..b5087517 100644 --- a/src/engine/platform/pokey.h +++ b/src/engine/platform/pokey.h @@ -48,8 +48,8 @@ class DivPlatformPOKEY: public DivDispatch { QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} }; std::queue writes; - unsigned char audctl; - bool audctlChanged; + unsigned char audctl, skctl; + bool audctlChanged, skctlChanged; unsigned char oscBufDelay; PokeyState pokey; AltASAP::Pokey altASAP; diff --git a/src/engine/platform/pv1000.cpp b/src/engine/platform/pv1000.cpp index 4d32fc5a..bf03df40 100644 --- a/src/engine/platform/pv1000.cpp +++ b/src/engine/platform/pv1000.cpp @@ -42,7 +42,7 @@ void DivPlatformPV1000::acquire(short** buf, size_t len) { short samp=d65010g031_sound_tick(&d65010g031,1); buf[0][h]=samp; for (int i=0; i<3; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(d65010g031.out[i]); + oscBuf[i]->data[oscBuf[i]->needle++]=d65010g031.out[i]<<1; } } } diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index e7ad162f..cd97ff30 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -87,7 +87,7 @@ void DivPlatformSCC::acquire(short** buf, size_t len) { buf[0][h]=out; for (int i=0; i<5; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=scc->voice_out(i)<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=scc->voice_out(i)<<7; } } } diff --git a/src/engine/platform/sm8521.cpp b/src/engine/platform/sm8521.cpp index 1c669a64..e72616bd 100644 --- a/src/engine/platform/sm8521.cpp +++ b/src/engine/platform/sm8521.cpp @@ -58,9 +58,9 @@ void DivPlatformSM8521::acquire(short** buf, size_t len) { sm8521_sound_tick(&sm8521,8); buf[0][h]=sm8521.out<<6; for (int i=0; i<2; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out<<5; + oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out<<7; } - oscBuf[2]->data[oscBuf[2]->needle++]=sm8521.noise.base.out<<5; + oscBuf[2]->data[oscBuf[2]->needle++]=sm8521.noise.base.out<<7; } } diff --git a/src/engine/platform/sound/lynx/Mikey.cpp b/src/engine/platform/sound/lynx/Mikey.cpp index baf376f5..791336c1 100644 --- a/src/engine/platform/sound/lynx/Mikey.cpp +++ b/src/engine/platform/sound/lynx/Mikey.cpp @@ -509,7 +509,7 @@ public: } if (oscb!=NULL) { - oscb[i]->data[oscb[i]->needle++]=oscbWrite>>1; + oscb[i]->data[oscb[i]->needle++]=oscbWrite; } } diff --git a/src/engine/platform/sound/pokey/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp index 9ed2f207..e6675027 100644 --- a/src/engine/platform/sound/pokey/AltASAP.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -39,7 +39,7 @@ static constexpr int MuteInit = 2; static constexpr int MuteSerialInput = 8; //just some magick value to match the audio level of mzpokeysnd static constexpr int16_t MAGICK_VOLUME_BOOSTER = 160; -static constexpr int16_t MAGICK_OSC_VOLUME_BOOSTER = 2; +static constexpr int16_t MAGICK_OSC_VOLUME_BOOSTER = 6; struct PokeyBase { diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 46edfd7f..209e2fc2 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -87,7 +87,7 @@ void DivPlatformSwan::acquire(short** buf, size_t len) { buf[0][h]=samp[0]; buf[1][h]=samp[1]; for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(ws->sample_cache[i][0]+ws->sample_cache[i][1])<<5; + oscBuf[i]->data[oscBuf[i]->needle++]=(ws->sample_cache[i][0]+ws->sample_cache[i][1])<<6; } } } diff --git a/src/engine/platform/t6w28.cpp b/src/engine/platform/t6w28.cpp index 0d7b9223..5d21e1ad 100644 --- a/src/engine/platform/t6w28.cpp +++ b/src/engine/platform/t6w28.cpp @@ -54,7 +54,7 @@ void DivPlatformT6W28::acquire(short** buf, size_t len) { tempL=0; tempR=0; for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(out[i][1].curValue+out[i][2].curValue)<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=(out[i][1].curValue+out[i][2].curValue)<<7; tempL+=out[i][1].curValue<<7; tempR+=out[i][2].curValue<<7; } diff --git a/src/engine/platform/tia.cpp b/src/engine/platform/tia.cpp index cdd11621..1836bee1 100644 --- a/src/engine/platform/tia.cpp +++ b/src/engine/platform/tia.cpp @@ -51,8 +51,8 @@ void DivPlatformTIA::acquire(short** buf, size_t len) { } if (++chanOscCounter>=114) { chanOscCounter=0; - oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0]>>1; - oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1]>>1; + oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0]; + oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1]; } } } diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index c4d921fd..86128340 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -78,7 +78,7 @@ void DivPlatformTX81Z::acquire(short** buf, size_t len) { fm_ymfm->generate(&out_ymfm); for (int i=0; i<8; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1),-32768,32767); } os[0]=out_ymfm.data[0]; diff --git a/src/engine/platform/vb.cpp b/src/engine/platform/vb.cpp index 6732bded..3701b5aa 100644 --- a/src/engine/platform/vb.cpp +++ b/src/engine/platform/vb.cpp @@ -107,7 +107,7 @@ void DivPlatformVB::acquire(short** buf, size_t len) { tempL=0; tempR=0; for (int i=0; i<6; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(vb->last_output[i][0]+vb->last_output[i][1])*4; + oscBuf[i]->data[oscBuf[i]->needle++]=(vb->last_output[i][0]+vb->last_output[i][1])*8; tempL+=vb->last_output[i][0]; tempR+=vb->last_output[i][1]; } diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index cb78e9b4..bd25b528 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -69,7 +69,7 @@ void DivPlatformVIC20::acquire(short** buf, size_t len) { vic_sound_machine_calculate_samples(vic,&samp,1,1,0,SAMP_DIVIDER); buf[0][h]=samp; for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=vic->ch[i].out?(vic->volume<<10):0; + oscBuf[i]->data[oscBuf[i]->needle++]=vic->ch[i].out?(vic->volume<<11):0; } } } diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 7834ecbe..b52bc106 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -87,9 +87,9 @@ void DivPlatformVRC6::acquire(short** buf, size_t len) { if (++writeOscBuf>=32) { writeOscBuf=0; for (int i=0; i<2; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=vrc6.pulse_out(i)<<9; + oscBuf[i]->data[oscBuf[i]->needle++]=vrc6.pulse_out(i)<<11; } - oscBuf[2]->data[oscBuf[2]->needle++]=vrc6.sawtooth_out()<<9; + oscBuf[2]->data[oscBuf[2]->needle++]=vrc6.sawtooth_out()<<10; } // Command part diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 69d1ca8a..135ad140 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -231,11 +231,11 @@ void DivPlatformYM2203::acquire_combo(short** buf, size_t len) { buf[0][h]=os; for (int i=0; i<3; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i]>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i]<<1; } for (int i=3; i<6; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]<<1; } } } @@ -282,11 +282,11 @@ void DivPlatformYM2203::acquire_ymfm(short** buf, size_t len) { for (int i=0; i<3; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))<<1; } for (int i=3; i<6; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]<<1; } } } diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 73280bcb..589d90d3 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -402,12 +402,12 @@ void DivPlatformYM2608::acquire_combo(short** buf, size_t len) { for (int i=0; idata[oscBuf[i]->needle++]=fm_nuked.ch_out[i]>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i]<<1; } ssge->get_last_out(ssgOut); for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]<<1; } for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))<<1; } ssge->get_last_out(ssgOut); for (int i=6; i<9; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]<<1; } for (int i=9; i<15; i++) { diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 303604d6..66c2f411 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -333,12 +333,12 @@ void DivPlatformYM2610::acquire_combo(short** buf, size_t len) { for (int i=0; idata[oscBuf[i]->needle++]=fm_nuked.ch_out[bchOffs[i]]>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[bchOffs[i]]<<1; } ssge->get_last_out(ssgOut); for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]<<1; } for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))<<1; } ssge->get_last_out(ssgOut); for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]<<1; } for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=fm_nuked.ch_out[i]>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i]<<1; } ssge->get_last_out(ssgOut); for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]<<1; } for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))<<1; } ssge->get_last_out(ssgOut); for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]<<1; } for (int i=adpcmAChanOffs; iord[i][curOrder],false); @@ -1314,7 +1319,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { subticks=tickMult; if (stepPlay!=1) { - tempoAccum+=curSubSong->virtualTempoN; + tempoAccum+=(skipping && curSubSong->virtualTempoNvirtualTempoD)?curSubSong->virtualTempoD:curSubSong->virtualTempoN; while (tempoAccum>=curSubSong->virtualTempoD) { tempoAccum-=curSubSong->virtualTempoD; if (--ticks<=0) { @@ -1564,8 +1569,10 @@ void DivEngine::runMidiClock(int totalCycles) { if (speedSum<1.0) speedSum=1.0; if (vD<1) vD=1; double bpm=((24.0*divider)/(timeBase*hl*speedSum))*(double)curSubSong->virtualTempoN/vD; + if (bpm<1.0) bpm=1.0; + int increment=got.rate*pow(2,MASTER_CLOCK_PREC)/(bpm); - midiClockCycles+=got.rate*pow(2,MASTER_CLOCK_PREC)/(bpm); + midiClockCycles+=increment; midiClockDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)(bpm)); if (midiClockDrift>=(bpm)) { midiClockDrift-=(bpm); diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index f9a5abed..2cc3ccf1 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1169,6 +1169,7 @@ void DivEngine::registerSystems() { { {0x10, {DIV_CMD_WAVE, "10xx: Set waveform (0 to 7)"}}, {0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Set AUDCTL"}}, + {0x12, {DIV_CMD_STD_NOISE_FREQ, "12xx: Toggle two-tone mode"}}, } ); diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index c278463e..e6306db2 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -22,6 +22,7 @@ #include "../ta-log.h" #include "imgui.h" #include "imgui_internal.h" +#include "misc/cpp/imgui_stdlib.h" #define FURNACE_FFT_SIZE 4096 #define FURNACE_FFT_RATE 80.0 @@ -284,159 +285,266 @@ void FurnaceGUI::drawChanOsc() { ImGui::ColorPicker4("Color",(float*)&chanOscColor); } + ImGui::Text("Text format:"); + ImGui::SameLine(); + ImGui::InputText("##TextFormat",&chanOscTextFormat); + if (ImGui::IsItemHovered()) { + if (ImGui::BeginTooltip()) { + ImGui::TextUnformatted( + "format guide:\n" + "- %c: channel name\n" + "- %C: channel short name\n" + "- %d: channel number (starting from 0)\n" + "- %D: channel number (starting from 1)\n" + "- %i: instrument name\n" + "- %I: instrument number (decimal)\n" + "- %x: instrument number (hex)\n" + "- %s: chip name\n" + "- %S: chip ID\n" + "- %v: volume (decimal)\n" + "- %V: volume (percentage)\n" + "- %b: volume (hex)\n" + "- %%: percent sign" + ); + ImGui::EndTooltip(); + } + } + + ImGui::ColorEdit4("Text color",(float*)&chanOscTextColor); + if (ImGui::Button("OK")) { chanOscOptions=false; } - } + } else { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f)); + float availY=ImGui::GetContentRegionAvail().y; + if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) { + std::vector oscBufs; + std::vector oscFFTs; + std::vector oscChans; + int chans=e->getTotalChannelCount(); + ImGuiWindow* window=ImGui::GetCurrentWindow(); + ImVec2 waveform[512]; - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f)); - float availY=ImGui::GetContentRegionAvail().y; - if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) { - std::vector oscBufs; - std::vector oscFFTs; - std::vector oscChans; - int chans=e->getTotalChannelCount(); - ImGuiWindow* window=ImGui::GetCurrentWindow(); - ImVec2 waveform[512]; + ImGuiStyle& style=ImGui::GetStyle(); - ImGuiStyle& style=ImGui::GetStyle(); - - for (int i=0; igetOscBuffer(i); - if (buf!=NULL && e->curSubSong->chanShow[i]) { - oscBufs.push_back(buf); - oscFFTs.push_back(&chanOscChan[i]); - oscChans.push_back(i); + for (int i=0; igetOscBuffer(i); + if (buf!=NULL && e->curSubSong->chanShow[i]) { + oscBufs.push_back(buf); + oscFFTs.push_back(&chanOscChan[i]); + oscChans.push_back(i); + } } - } - int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols; + int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols; - for (size_t i=0; ireadNeedle=buf->needle; - } + if (centerSettingReset) { + buf->readNeedle=buf->needle; + } - // check FFT status existence - if (fft->plan==NULL) { - logD("creating FFT plan for channel %d",ch); - fft->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double)); - fft->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex)); - fft->plan=fftw_plan_dft_r2c_1d(FURNACE_FFT_SIZE,fft->inBuf,fft->outBuf,FFTW_ESTIMATE); - } + // check FFT status existence + if (fft->plan==NULL) { + logD("creating FFT plan for channel %d",ch); + fft->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double)); + fft->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex)); + fft->plan=fftw_plan_dft_r2c_1d(FURNACE_FFT_SIZE,fft->inBuf,fft->outBuf,FFTW_ESTIMATE); + } - int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f); + int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f); - ImVec2 minArea=window->DC.CursorPos; - ImVec2 maxArea=ImVec2( - minArea.x+size.x, - minArea.y+size.y - ); - ImRect rect=ImRect(minArea,maxArea); - ImRect inRect=rect; - inRect.Min.x+=dpiScale; - inRect.Min.y+=2.0*dpiScale; - inRect.Max.x-=dpiScale; - inRect.Max.y-=2.0*dpiScale; + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + ImRect inRect=rect; + inRect.Min.x+=dpiScale; + inRect.Min.y+=2.0*dpiScale; + inRect.Max.x-=dpiScale; + inRect.Max.y-=2.0*dpiScale; - int precision=inRect.Max.x-inRect.Min.x; - if (precision<1) precision=1; - if (precision>512) precision=512; + int precision=inRect.Max.x-inRect.Min.x; + if (precision<1) precision=1; + if (precision>512) precision=512; - ImGui::ItemSize(size,style.FramePadding.y); - if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) { - if (!e->isRunning()) { - for (unsigned short i=0; ineedle; - for (int i=0; iinBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0; - } - fftw_execute(fft->plan); - - // find origin frequency - int point=1; - double candAmp=0.0; - for (unsigned short i=1; i<512; i++) { - fftw_complex& f=fft->outBuf[i]; - // AMPLITUDE - double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8); - if (amp>candAmp) { - point=i; - candAmp=amp; + ImGui::ItemSize(size,style.FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) { + if (!e->isRunning()) { + for (unsigned short i=0; ineedle; + for (int i=0; iinBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0; + } + fftw_execute(fft->plan); + + // find origin frequency + int point=1; + double candAmp=0.0; + for (unsigned short i=1; i<512; i++) { + fftw_complex& f=fft->outBuf[i]; + // AMPLITUDE + double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8); + if (amp>candAmp) { + point=i; + candAmp=amp; + } + } + + // PHASE + fftw_complex& candPoint=fft->outBuf[point]; + double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2))); + + if (chanOscWaveCorr) { + needlePos-=phase; + } + chanOscPitch[ch]=(float)point/32.0f; + + /* + String cPhase=fmt::sprintf("%d cphase: %f vol: %f",point,phase,chanOscVol[ch]); + dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); + */ + + needlePos-=displaySize; + for (unsigned short i=0; idata[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f; + if (minLevel>y) minLevel=y; + if (maxLeveldata[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f; + y-=dcOff; + if (y<-0.5f) y=-0.5f; + if (y>0.5f) y=0.5f; + y*=chanOscAmplify; + waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); } } + ImU32 color=ImGui::GetColorU32(chanOscColor); + if (chanOscUseGrad) { + float xVal=computeGradPos(chanOscColorX,ch); + float yVal=computeGradPos(chanOscColorY,ch); - // PHASE - fftw_complex& candPoint=fft->outBuf[point]; - double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2))); + xVal=CLAMP(xVal,0.0f,1.0f); + yVal=CLAMP(yVal,0.0f,1.0f); - if (chanOscWaveCorr) { - needlePos-=phase; + color=chanOscGrad.get(xVal,1.0f-yVal); } - chanOscPitch[ch]=(float)point/32.0f; - - /* - String cPhase=fmt::sprintf("%d cphase: %f vol: %f",point,phase,chanOscVol[ch]); - dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); - */ + ImGui::PushClipRect(inRect.Min,inRect.Max,false); - needlePos-=displaySize; - for (unsigned short i=0; idata[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f; - if (minLevel>y) minLevel=y; - if (maxLeveldata[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f; - y-=dcOff; - if (y<-0.5f) y=-0.5f; - if (y>0.5f) y=0.5f; - waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); + dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale); + + if (!chanOscTextFormat.empty()) { + String text; + bool inFormat=false; + + for (char i: chanOscTextFormat) { + if (inFormat) { + switch (i) { + case 'c': + text+=e->getChannelName(ch); + break; + case 'C': + text+=e->getChannelShortName(ch); + break; + case 'd': + text+=fmt::sprintf("%d",ch); + break; + case 'D': + text+=fmt::sprintf("%d",ch+1); + break; + case 'i': { + DivChannelState* chanState=e->getChanState(ch); + if (chanState==NULL) break; + DivInstrument* ins=e->getIns(chanState->lastIns); + text+=ins->name; + break; + } + case 'I': { + DivChannelState* chanState=e->getChanState(ch); + if (chanState==NULL) break; + text+=fmt::sprintf("%d",chanState->lastIns); + break; + } + case 'x': { + DivChannelState* chanState=e->getChanState(ch); + if (chanState==NULL) break; + if (chanState->lastIns<0) { + text+="??"; + } else { + text+=fmt::sprintf("%.2X",chanState->lastIns); + } + break; + } + case 's': { + text+=e->getSystemName(e->sysOfChan[ch]); + break; + } + case 'S': { + text+=fmt::sprintf("%d",e->dispatchOfChan[ch]); + break; + } + case 'v': + break; + case 'V': + break; + case 'b': + break; + case '%': + text+='%'; + break; + default: + text+='%'; + text+=i; + break; + } + inFormat=false; + } else { + if (i=='%') { + inFormat=true; + } else { + text+=i; + } + } + } + + dl->AddText(ImLerp(inRect.Min,inRect.Max,ImVec2(0.0f,0.0f)),ImGui::GetColorU32(chanOscTextColor),text.c_str()); } + + ImGui::PopClipRect(); } - ImU32 color=ImGui::GetColorU32(chanOscColor); - if (chanOscUseGrad) { - float xVal=computeGradPos(chanOscColorX,ch); - float yVal=computeGradPos(chanOscColorY,ch); - - xVal=CLAMP(xVal,0.0f,1.0f); - yVal=CLAMP(yVal,0.0f,1.0f); - - color=chanOscGrad.get(xVal,1.0f-yVal); - } - ImGui::PushClipRect(inRect.Min,inRect.Max,false); - dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale); - ImGui::PopClipRect(); } } - } - ImGui::EndTable(); + ImGui::EndTable(); - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - chanOscOptions=!chanOscOptions; + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + chanOscOptions=!chanOscOptions; + } } + ImGui::PopStyleVar(); } - ImGui::PopStyleVar(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_CHAN_OSC; ImGui::End(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 8c14f977..a51f9ab8 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5994,13 +5994,22 @@ bool FurnaceGUI::init() { chanOscCols=e->getConfInt("chanOscCols",3); chanOscColorX=e->getConfInt("chanOscColorX",GUI_OSCREF_CENTER); chanOscColorY=e->getConfInt("chanOscColorY",GUI_OSCREF_CENTER); + chanOscTextX=e->getConfFloat("chanOscTextX",0.0f); + chanOscTextY=e->getConfFloat("chanOscTextY",0.0f); + chanOscAmplify=e->getConfFloat("chanOscAmplify",0.95f); chanOscWindowSize=e->getConfFloat("chanOscWindowSize",20.0f); chanOscWaveCorr=e->getConfBool("chanOscWaveCorr",true); chanOscOptions=e->getConfBool("chanOscOptions",false); + chanOscNormalize=e->getConfBool("chanOscNormalize",false); + chanOscTextFormat=e->getConfString("chanOscTextFormat","%c"); chanOscColor.x=e->getConfFloat("chanOscColorR",1.0f); chanOscColor.y=e->getConfFloat("chanOscColorG",1.0f); chanOscColor.z=e->getConfFloat("chanOscColorB",1.0f); chanOscColor.w=e->getConfFloat("chanOscColorA",1.0f); + chanOscTextColor.x=e->getConfFloat("chanOscTextColorR",1.0f); + chanOscTextColor.y=e->getConfFloat("chanOscTextColorG",1.0f); + chanOscTextColor.z=e->getConfFloat("chanOscTextColorB",1.0f); + chanOscTextColor.w=e->getConfFloat("chanOscTextColorA",0.75f); chanOscUseGrad=e->getConfBool("chanOscUseGrad",false); chanOscGrad.fromString(e->getConfString("chanOscGrad","")); chanOscGrad.render(); @@ -6487,13 +6496,22 @@ void FurnaceGUI::commitState() { e->setConf("chanOscCols",chanOscCols); e->setConf("chanOscColorX",chanOscColorX); e->setConf("chanOscColorY",chanOscColorY); + e->setConf("chanOscTextX",chanOscTextX); + e->setConf("chanOscTextY",chanOscTextY); + e->setConf("chanOscAmplify",chanOscAmplify); e->setConf("chanOscWindowSize",chanOscWindowSize); e->setConf("chanOscWaveCorr",chanOscWaveCorr); e->setConf("chanOscOptions",chanOscOptions); + e->setConf("chanOscNormalize",chanOscNormalize); + e->setConf("chanOscTextFormat",chanOscTextFormat); e->setConf("chanOscColorR",chanOscColor.x); e->setConf("chanOscColorG",chanOscColor.y); e->setConf("chanOscColorB",chanOscColor.z); e->setConf("chanOscColorA",chanOscColor.w); + e->setConf("chanOscTextColorR",chanOscTextColor.x); + e->setConf("chanOscTextColorG",chanOscTextColor.y); + e->setConf("chanOscTextColorB",chanOscTextColor.z); + e->setConf("chanOscTextColorA",chanOscTextColor.w); e->setConf("chanOscUseGrad",chanOscUseGrad); e->setConf("chanOscGrad",chanOscGrad.toString()); @@ -6915,11 +6933,17 @@ FurnaceGUI::FurnaceGUI(): chanOscColorX(GUI_OSCREF_CENTER), chanOscColorY(GUI_OSCREF_CENTER), chanOscWindowSize(20.0f), + chanOscTextX(0.0f), + chanOscTextY(0.0f), + chanOscAmplify(0.95f), chanOscWaveCorr(true), chanOscOptions(false), updateChanOscGradTex(true), chanOscUseGrad(false), + chanOscNormalize(false), + chanOscTextFormat("%c"), chanOscColor(1.0f,1.0f,1.0f,1.0f), + chanOscTextColor(1.0f,1.0f,1.0f,0.75f), chanOscGrad(64,64), chanOscGradTex(NULL), followLog(true), diff --git a/src/gui/gui.h b/src/gui/gui.h index fe6faff9..342f249f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1948,9 +1948,10 @@ class FurnaceGUI { // per-channel oscilloscope int chanOscCols, chanOscColorX, chanOscColorY; - float chanOscWindowSize; - bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad; - ImVec4 chanOscColor; + float chanOscWindowSize, chanOscTextX, chanOscTextY, chanOscAmplify; + bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad, chanOscNormalize; + String chanOscTextFormat; + ImVec4 chanOscColor, chanOscTextColor; Gradient2D chanOscGrad; void* chanOscGradTex; float chanOscLP0[DIV_MAX_CHANS];