mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-27 01:31:42 +00:00
Merge pull request #774 from TomHarte/VolumeControl
Adds output volume control.
This commit is contained in:
commit
d3bac57d6a
@ -54,6 +54,12 @@ bool MultiSpeaker::get_is_stereo() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_output_volume(float volume) {
|
||||
for(const auto &speaker: speakers_) {
|
||||
speaker->set_output_volume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
|
||||
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
|
||||
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
|
||||
bool get_is_stereo() override;
|
||||
void set_output_volume(float) override;
|
||||
|
||||
private:
|
||||
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
|
||||
|
@ -204,6 +204,7 @@
|
||||
4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */; };
|
||||
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */; };
|
||||
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */; };
|
||||
4B50AF80242817F40099BBD7 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B50AF7F242817F40099BBD7 /* QuartzCore.framework */; };
|
||||
4B54C0BC1F8D8E790050900F /* KeyboardMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */; };
|
||||
4B54C0BF1F8D8F450050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0BD1F8D8F450050900F /* Keyboard.cpp */; };
|
||||
4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C11F8D91CD0050900F /* Keyboard.cpp */; };
|
||||
@ -1104,6 +1105,7 @@
|
||||
4B4DC8271D2C2470003C5BF8 /* C1540.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = C1540.hpp; sourceTree = "<group>"; };
|
||||
4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerialBus.cpp; sourceTree = "<group>"; };
|
||||
4B4DC82A1D2C27A4003C5BF8 /* SerialBus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SerialBus.hpp; sourceTree = "<group>"; };
|
||||
4B50AF7F242817F40099BBD7 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
|
||||
4B51F70920A521D700AFA2C1 /* Source.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Source.hpp; sourceTree = "<group>"; };
|
||||
4B51F70A20A521D700AFA2C1 /* Observer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Observer.hpp; sourceTree = "<group>"; };
|
||||
4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = KeyboardMachine.cpp; sourceTree = "<group>"; };
|
||||
@ -1793,6 +1795,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4B50AF80242817F40099BBD7 /* QuartzCore.framework in Frameworks */,
|
||||
4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */,
|
||||
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */,
|
||||
);
|
||||
@ -1820,6 +1823,7 @@
|
||||
4B055A761FAE78210060FFFF /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B50AF7F242817F40099BBD7 /* QuartzCore.framework */,
|
||||
4B055AF01FAE9C080060FFFF /* OpenGL.framework */,
|
||||
4B055A771FAE78210060FFFF /* SDL2.framework */,
|
||||
);
|
||||
@ -5137,7 +5141,7 @@
|
||||
GCC_WARN_UNUSED_LABEL = YES;
|
||||
INFOPLIST_FILE = "Clock Signal/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12.2;
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
"-Wreorder",
|
||||
@ -5185,7 +5189,7 @@
|
||||
GCC_WARN_UNUSED_LABEL = YES;
|
||||
INFOPLIST_FILE = "Clock Signal/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12.2;
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
"-Wreorder",
|
||||
|
@ -56,6 +56,10 @@
|
||||
argument = "/Users/thomasharte/Downloads/test-dsk-for-rw-and-50-60-hz/TEST-RW-60Hz.DSK"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--volume=0.001"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--new=amstradcpc"
|
||||
isEnabled = "NO">
|
||||
@ -70,7 +74,7 @@
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--output=CompositeMonochrome"
|
||||
isEnabled = "YES">
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--logical-keyboard"
|
||||
@ -94,11 +98,11 @@
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--help"
|
||||
isEnabled = "YES">
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--model=cpc6128"
|
||||
isEnabled = "YES">
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
</LaunchAction>
|
||||
|
@ -1,14 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15705" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15705"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="openGLView" destination="DEG-fq-cjd" id="Gxs-2u-n7B"/>
|
||||
<outlet property="volumeView" destination="4ap-Gi-2AO" id="v4e-k6-Fqf"/>
|
||||
<outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
@ -19,7 +20,7 @@
|
||||
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="80" y="250" width="600" height="450"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="3840" height="2137"/>
|
||||
<value key="minSize" type="size" width="228" height="171"/>
|
||||
<view key="contentView" id="gIp-Ho-8D9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
|
||||
@ -28,9 +29,57 @@
|
||||
<openGLView hidden="YES" wantsLayer="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
|
||||
</openGLView>
|
||||
<box hidden="YES" boxType="custom" cornerRadius="4" title="Box" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4ap-Gi-2AO">
|
||||
<rect key="frame" x="150" y="20" width="300" height="48"/>
|
||||
<view key="contentView" id="gwO-Ty-LCX">
|
||||
<rect key="frame" x="1" y="1" width="298" height="46"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Hzw-P6-1dH">
|
||||
<rect key="frame" x="258" y="8" width="32" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="32" id="WrK-W9-mPP"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSTouchBarAudioOutputVolumeHighTemplate" id="5dB-4Y-iEl"/>
|
||||
<color key="contentTintColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</imageView>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="p0w-Ti-Tu9">
|
||||
<rect key="frame" x="8" y="8" width="32" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="32" id="dZn-Fc-fRU"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSTouchBarAudioOutputVolumeOffTemplate" id="mUH-aA-T9N"/>
|
||||
<color key="contentTintColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</imageView>
|
||||
<slider verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zaz-lB-Iyt">
|
||||
<rect key="frame" x="46" y="14" width="206" height="19"/>
|
||||
<sliderCell key="cell" continuous="YES" state="on" alignment="left" maxValue="1" doubleValue="1" tickMarkPosition="above" sliderType="linear" id="Zvz-Og-dGA"/>
|
||||
<connections>
|
||||
<action selector="setVolume:" target="-2" id="eEc-5v-I2c"/>
|
||||
</connections>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="p0w-Ti-Tu9" firstAttribute="leading" secondItem="gwO-Ty-LCX" secondAttribute="leading" constant="8" id="1t0-CW-zp9"/>
|
||||
<constraint firstItem="Hzw-P6-1dH" firstAttribute="top" secondItem="gwO-Ty-LCX" secondAttribute="top" constant="8" id="OHP-c3-2Aa"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Hzw-P6-1dH" secondAttribute="trailing" constant="8" id="W24-Wx-bdQ"/>
|
||||
<constraint firstItem="zaz-lB-Iyt" firstAttribute="centerY" secondItem="p0w-Ti-Tu9" secondAttribute="centerY" id="f3C-My-T0S"/>
|
||||
<constraint firstItem="zaz-lB-Iyt" firstAttribute="leading" secondItem="p0w-Ti-Tu9" secondAttribute="trailing" constant="8" id="n0g-y6-wHP"/>
|
||||
<constraint firstItem="p0w-Ti-Tu9" firstAttribute="centerY" secondItem="gwO-Ty-LCX" secondAttribute="centerY" id="uTt-VY-o8c"/>
|
||||
<constraint firstItem="Hzw-P6-1dH" firstAttribute="leading" secondItem="zaz-lB-Iyt" secondAttribute="trailing" constant="8" id="xCS-qF-Gz8"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Hzw-P6-1dH" secondAttribute="bottom" constant="8" id="yaF-k8-W5B"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="300" id="HKL-vy-Mov"/>
|
||||
</constraints>
|
||||
<color key="fillColor" red="0.0" green="0.0" blue="0.0" alpha="0.5" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</box>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="DEG-fq-cjd" firstAttribute="centerX" secondItem="gIp-Ho-8D9" secondAttribute="centerX" id="ES5-nL-N3h"/>
|
||||
<constraint firstItem="4ap-Gi-2AO" firstAttribute="centerX" secondItem="DEG-fq-cjd" secondAttribute="centerX" id="T41-z9-BsM"/>
|
||||
<constraint firstItem="4ap-Gi-2AO" firstAttribute="bottom" secondItem="DEG-fq-cjd" secondAttribute="bottom" constant="-20" id="Tly-Uu-96H"/>
|
||||
<constraint firstItem="DEG-fq-cjd" firstAttribute="height" secondItem="gIp-Ho-8D9" secondAttribute="height" id="YoB-qI-LFX"/>
|
||||
<constraint firstItem="DEG-fq-cjd" firstAttribute="centerY" secondItem="gIp-Ho-8D9" secondAttribute="centerY" id="d5Y-3a-CEI"/>
|
||||
<constraint firstItem="DEG-fq-cjd" firstAttribute="width" secondItem="gIp-Ho-8D9" secondAttribute="width" id="mYS-bH-DST"/>
|
||||
@ -45,6 +94,11 @@
|
||||
<outlet property="delegate" destination="-2" id="0bl-1N-x8E"/>
|
||||
<outlet property="initialFirstResponder" destination="DEG-fq-cjd" id="9RI-Kx-QeN"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="141" y="147"/>
|
||||
</window>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="NSTouchBarAudioOutputVolumeHighTemplate" width="23" height="30"/>
|
||||
<image name="NSTouchBarAudioOutputVolumeOffTemplate" width="23" height="30"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
import AudioToolbox
|
||||
import Cocoa
|
||||
import QuartzCore
|
||||
|
||||
class MachineDocument:
|
||||
NSDocument,
|
||||
@ -62,6 +63,9 @@ class MachineDocument:
|
||||
activityPanel.setIsVisible(true)
|
||||
}
|
||||
|
||||
/// The volume view.
|
||||
@IBOutlet var volumeView: NSView!
|
||||
|
||||
// MARK: - NSDocument Overrides and NSWindowDelegate methods.
|
||||
|
||||
/// Links this class to the MachineDocument NIB.
|
||||
@ -712,4 +716,55 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Volume Control.
|
||||
@IBAction func setVolume(_ sender: NSSlider!) {
|
||||
if let machine = self.machine {
|
||||
machine.setVolume(sender.floatValue);
|
||||
}
|
||||
}
|
||||
|
||||
// This class is pure nonsense to work around Xcode's opaque behaviour.
|
||||
// If I make the main class a sub of CAAnimationDelegate then the compiler
|
||||
// generates a bridging header that doesn't include QuartzCore and therefore
|
||||
// can't find a declaration of the CAAnimationDelegate protocol. Doesn't
|
||||
// seem to matter what I add explicitly to the link stage, which version of
|
||||
// macOS I set as the target, etc.
|
||||
//
|
||||
// So, the workaround: make my CAAnimationDelegate something that doesn't
|
||||
// appear in the bridging header.
|
||||
fileprivate class ViewFader: NSObject, CAAnimationDelegate {
|
||||
var volumeView: NSView
|
||||
|
||||
init(view: NSView) {
|
||||
volumeView = view
|
||||
}
|
||||
|
||||
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
|
||||
volumeView.isHidden = true
|
||||
}
|
||||
}
|
||||
fileprivate var animationFader: ViewFader? = nil
|
||||
|
||||
func openGLViewDidShowOSMouseCursor(_ view: CSOpenGLView) {
|
||||
// The OS mouse cursor became visible, so show the volume controls.
|
||||
animationFader = nil
|
||||
volumeView.layer?.removeAllAnimations()
|
||||
volumeView.isHidden = false
|
||||
volumeView.layer?.opacity = 1.0
|
||||
}
|
||||
|
||||
func openGLViewWillHideOSMouseCursor(_ view: CSOpenGLView) {
|
||||
// The OS mouse cursor will be hidden, so hide the volume controls.
|
||||
if !volumeView.isHidden && volumeView.layer?.animation(forKey: "opacity") == nil {
|
||||
let fadeAnimation = CABasicAnimation(keyPath: "opacity")
|
||||
fadeAnimation.fromValue = 1.0
|
||||
fadeAnimation.toValue = 0.0
|
||||
fadeAnimation.duration = 0.2
|
||||
animationFader = ViewFader(view: volumeView)
|
||||
fadeAnimation.delegate = animationFader
|
||||
volumeView.layer?.add(fadeAnimation, forKey: "opacity")
|
||||
volumeView.layer?.opacity = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,11 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
|
||||
@property (nonatomic, readonly) BOOL canInsertMedia;
|
||||
|
||||
- (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal;
|
||||
- (BOOL)supportsVideoSignal:(CSMachineVideoSignal)videoSignal;
|
||||
|
||||
// Volume contorl.
|
||||
- (void)setVolume:(float)volume;
|
||||
@property (nonatomic, readonly) BOOL hasAudioOutput;
|
||||
|
||||
// Input control.
|
||||
@property (nonatomic, readonly) BOOL hasExclusiveKeyboard;
|
||||
|
@ -613,7 +613,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
}
|
||||
}
|
||||
|
||||
- (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal {
|
||||
- (BOOL)supportsVideoSignal:(CSMachineVideoSignal)videoSignal {
|
||||
Configurable::Device *configurable_device = _machine->configurable_device();
|
||||
if(!configurable_device) return NO;
|
||||
|
||||
@ -708,6 +708,24 @@ struct ActivityObserver: public Activity::Observer {
|
||||
essential_modifiers.find(Inputs::Keyboard::Key::RightMeta) != essential_modifiers.end();
|
||||
}
|
||||
|
||||
#pragma mark - Volume control
|
||||
|
||||
- (void)setVolume:(float)volume {
|
||||
@synchronized(self) {
|
||||
Outputs::Speaker::Speaker *speaker = _machine->crt_machine()->get_speaker();
|
||||
if(speaker) {
|
||||
return speaker->set_output_volume(volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)hasAudioOutput {
|
||||
@synchronized(self) {
|
||||
Outputs::Speaker::Speaker *speaker = _machine->crt_machine()->get_speaker();
|
||||
return speaker ? YES : NO;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Activity observation
|
||||
|
||||
- (void)addLED:(NSString *)led {
|
||||
@ -761,9 +779,10 @@ struct ActivityObserver: public Activity::Observer {
|
||||
|
||||
_timer = [[CSHighPrecisionTimer alloc] initWithTask:^{
|
||||
// Grab the time now and, therefore, the amount of time since the timer last fired
|
||||
// (capped at half a second).
|
||||
// (subject to a cap to avoid potential perpetual regression).
|
||||
const auto timeNow = Time::nanos_now();
|
||||
const auto duration = std::min(timeNow - lastTime, Time::Nanos(10'000'000'000 / TICKS));
|
||||
lastTime = std::max(timeNow - Time::Nanos(10'000'000'000 / TICKS), lastTime);
|
||||
const auto duration = timeNow - lastTime;
|
||||
|
||||
CGSize pixelSize;
|
||||
BOOL splitAndSync = NO;
|
||||
|
@ -50,6 +50,18 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
||||
*/
|
||||
- (void)openGLViewDidReleaseMouse:(nonnull CSOpenGLView *)view;
|
||||
|
||||
/*!
|
||||
Announces that the OS mouse cursor is now being displayed again, after having been invisible.
|
||||
@param view The view making the announcement.
|
||||
*/
|
||||
- (void)openGLViewDidShowOSMouseCursor:(nonnull CSOpenGLView *)view;
|
||||
|
||||
/*!
|
||||
Announces that the OS mouse cursor will now be hidden.
|
||||
@param view The view making the announcement.
|
||||
*/
|
||||
- (void)openGLViewWillHideOSMouseCursor:(nonnull CSOpenGLView *)view;
|
||||
|
||||
@end
|
||||
|
||||
@protocol CSOpenGLViewResponderDelegate <NSObject>
|
||||
|
@ -11,6 +11,8 @@
|
||||
@import CoreVideo;
|
||||
@import GLKit;
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
@interface CSOpenGLView () <NSDraggingDestination, CSApplicationEventDelegate>
|
||||
@end
|
||||
|
||||
@ -23,13 +25,16 @@
|
||||
NSTimer *_mouseHideTimer;
|
||||
BOOL _mouseIsCaptured;
|
||||
|
||||
volatile int32_t _isDrawingFlag;
|
||||
atomic_int _isDrawingFlag;
|
||||
BOOL _isInvalid;
|
||||
}
|
||||
|
||||
- (void)prepareOpenGL {
|
||||
[super prepareOpenGL];
|
||||
|
||||
// Prepare the atomic int.
|
||||
atomic_init(&_isDrawingFlag, 0);
|
||||
|
||||
// Set the clear colour.
|
||||
[self.openGLContext makeCurrentContext];
|
||||
glClearColor(0.0, 0.0, 0.0, 1.0);
|
||||
@ -71,7 +76,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
});
|
||||
|
||||
// Ensure _isDrawingFlag has value 1 when drawing, 0 otherwise.
|
||||
OSAtomicIncrement32(&view->_isDrawingFlag);
|
||||
atomic_store(&view->_isDrawingFlag, 1);
|
||||
|
||||
[view.displayLinkDelegate openGLViewDisplayLinkDidFire:view now:now outputTime:outputTime];
|
||||
/*
|
||||
@ -84,7 +89,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
access the display link itself as part of -drawAtTime:frequency:.
|
||||
*/
|
||||
|
||||
OSAtomicDecrement32(&view->_isDrawingFlag);
|
||||
atomic_store(&view->_isDrawingFlag, 0);
|
||||
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
@ -143,7 +148,10 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
usleep((useconds_t)ceil(duration * 1000000.0));
|
||||
|
||||
// Spin until _isDrawingFlag is 0 (and leave it as 0).
|
||||
while(!OSAtomicCompareAndSwap32(0, 0, &_isDrawingFlag));
|
||||
int expected_value = 0;
|
||||
while(!atomic_compare_exchange_weak(&_isDrawingFlag, &expected_value, 0)) {
|
||||
expected_value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
@ -292,11 +300,13 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
|
||||
_mouseHideTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
|
||||
[NSCursor setHiddenUntilMouseMoves:YES];
|
||||
[self.delegate openGLViewWillHideOSMouseCursor:self];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)event {
|
||||
[self.delegate openGLViewDidShowOSMouseCursor:self];
|
||||
[super mouseEntered:event];
|
||||
[self scheduleMouseHide];
|
||||
}
|
||||
@ -305,6 +315,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
[super mouseExited:event];
|
||||
[_mouseHideTimer invalidate];
|
||||
_mouseHideTimer = nil;
|
||||
[self.delegate openGLViewWillHideOSMouseCursor:self];
|
||||
}
|
||||
|
||||
- (void)releaseMouse {
|
||||
@ -313,6 +324,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
CGAssociateMouseAndMouseCursorPosition(true);
|
||||
[NSCursor unhide];
|
||||
[self.delegate openGLViewDidReleaseMouse:self];
|
||||
[self.delegate openGLViewDidShowOSMouseCursor:self];
|
||||
((CSApplication *)[NSApplication sharedApplication]).eventDelegate = nil;
|
||||
}
|
||||
}
|
||||
@ -324,6 +336,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
// Mouse capture is off, so don't play games with the cursor, just schedule it to
|
||||
// hide in the near future.
|
||||
[self scheduleMouseHide];
|
||||
[self.delegate openGLViewDidShowOSMouseCursor:self];
|
||||
} else {
|
||||
if(_mouseIsCaptured) {
|
||||
// Mouse capture is on, so move the cursor back to the middle of the window, and
|
||||
@ -340,6 +353,8 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
));
|
||||
|
||||
[self.responderDelegate mouseMoved:event];
|
||||
} else {
|
||||
[self.delegate openGLViewDidShowOSMouseCursor:self];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -372,6 +387,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
_mouseIsCaptured = YES;
|
||||
[NSCursor hide];
|
||||
CGAssociateMouseAndMouseCursorPosition(false);
|
||||
[self.delegate openGLViewWillHideOSMouseCursor:self];
|
||||
[self.delegate openGLViewDidCaptureMouse:self];
|
||||
if(self.shouldUsurpCommand) {
|
||||
((CSApplication *)[NSApplication sharedApplication]).eventDelegate = self;
|
||||
|
@ -487,7 +487,7 @@ int main(int argc, char *argv[]) {
|
||||
const ParsedArguments arguments = parse_arguments(argc, argv);
|
||||
|
||||
// This may be printed either as
|
||||
const std::string usage_suffix = " [file or --new={machine}] [OPTIONS] [--rompath={path to ROMs}] [--speed={speed multiplier, e.g. 1.5}] [--logical-keyboard]";
|
||||
const std::string usage_suffix = " [file or --new={machine}] [OPTIONS] [--rompath={path to ROMs}] [--speed={speed multiplier, e.g. 1.5}] [--logical-keyboard] [--volume={0.0 to 1.0}]";
|
||||
|
||||
// Print a help message if requested.
|
||||
if(arguments.selections.find("help") != arguments.selections.end() || arguments.selections.find("h") != arguments.selections.end()) {
|
||||
@ -739,18 +739,39 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// Apply the speed multiplier, if one was requested.
|
||||
const auto speed_argument = arguments.selections.find("speed");
|
||||
if(speed_argument != arguments.selections.end()) {
|
||||
const char *speed_string = speed_argument->second.c_str();
|
||||
char *end;
|
||||
double speed = strtod(speed_string, &end);
|
||||
{
|
||||
const auto speed_argument = arguments.selections.find("speed");
|
||||
if(speed_argument != arguments.selections.end()) {
|
||||
const char *speed_string = speed_argument->second.c_str();
|
||||
char *end;
|
||||
const double speed = strtod(speed_string, &end);
|
||||
|
||||
if(size_t(end - speed_string) != strlen(speed_string)) {
|
||||
std::cerr << "Unable to parse speed: " << speed_string << std::endl;
|
||||
} else if(speed <= 0.0) {
|
||||
std::cerr << "Cannot run at speed " << speed_string << "; speeds must be positive." << std::endl;
|
||||
} else {
|
||||
machine_runner.set_speed_multiplier(speed);
|
||||
if(size_t(end - speed_string) != strlen(speed_string)) {
|
||||
std::cerr << "Unable to parse speed: " << speed_string << std::endl;
|
||||
} else if(speed <= 0.0) {
|
||||
std::cerr << "Cannot run at speed " << speed_string << "; speeds must be positive." << std::endl;
|
||||
} else {
|
||||
machine_runner.set_speed_multiplier(speed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the desired output volume, if requested.
|
||||
{
|
||||
const auto volume_argument = arguments.selections.find("volume");
|
||||
if(volume_argument != arguments.selections.end()) {
|
||||
const char *volume_string = volume_argument->second.c_str();
|
||||
char *end;
|
||||
const double volume = strtod(volume_string, &end);
|
||||
|
||||
if(size_t(end - volume_string) != strlen(volume_string)) {
|
||||
std::cerr << "Unable to parse volume: " << volume_string << std::endl;
|
||||
} else if(volume < 0.0 || volume > 1.0) {
|
||||
std::cerr << "Cannot run with volume " << volume_string << "; volumes must be between 0.0 and 1.0." << std::endl;
|
||||
} else {
|
||||
const auto speaker = machine->crt_machine()->get_speaker();
|
||||
if(speaker) speaker->set_output_volume(volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,9 +31,16 @@ namespace Speaker {
|
||||
template <typename SampleSource> class LowpassSpeaker: public Speaker {
|
||||
public:
|
||||
LowpassSpeaker(SampleSource &sample_source) : sample_source_(sample_source) {
|
||||
// Propagate an initial volume level.
|
||||
sample_source.set_sample_volume_range(32767);
|
||||
}
|
||||
|
||||
void set_output_volume(float volume) final {
|
||||
// Clamp to the acceptable range, and set.
|
||||
volume = std::min(std::max(0.0f, volume), 1.0f);
|
||||
sample_source_.set_sample_volume_range(int16_t(32767.0f * volume));
|
||||
}
|
||||
|
||||
// Implemented as per Speaker.
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum) final {
|
||||
std::lock_guard<std::mutex> lock_guard(filter_parameters_mutex_);
|
||||
|
@ -45,6 +45,9 @@ class Speaker {
|
||||
compute_output_rate();
|
||||
}
|
||||
|
||||
/// Sets the output volume, in the range [0, 1].
|
||||
virtual void set_output_volume(float) = 0;
|
||||
|
||||
/*!
|
||||
Speeds a speed multiplier for this machine, e.g. that it is currently being run at 2.0x its normal rate.
|
||||
This will affect the number of input samples that are combined to produce one output sample.
|
||||
@ -79,6 +82,8 @@ class Speaker {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
|
||||
// This is primarily exposed for MultiSpeaker et al; it's not for general callers.
|
||||
virtual void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) = 0;
|
||||
|
||||
protected:
|
||||
|
Loading…
Reference in New Issue
Block a user