From 9087bb9b08ced2179252e306f8e801ffa7da8b5d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 21 Mar 2020 22:00:47 -0400 Subject: [PATCH 1/9] Allows audio volume to be set. --- .../Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp | 6 ++++++ .../Dynamic/MultiMachine/Implementation/MultiSpeaker.hpp | 1 + Outputs/Speaker/Implementation/LowpassSpeaker.hpp | 7 +++++++ Outputs/Speaker/Speaker.hpp | 5 +++++ 4 files changed, 19 insertions(+) diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp index 1465771e0..eb833a0c6 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp @@ -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; } diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.hpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.hpp index 3c75654db..f48bc639c 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.hpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.hpp @@ -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 &buffer) final; diff --git a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp index 94d3a4b12..89ab38268 100644 --- a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp +++ b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp @@ -31,9 +31,16 @@ namespace Speaker { template 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 lock_guard(filter_parameters_mutex_); diff --git a/Outputs/Speaker/Speaker.hpp b/Outputs/Speaker/Speaker.hpp index 2f5da46ca..13a67e674 100644 --- a/Outputs/Speaker/Speaker.hpp +++ b/Outputs/Speaker/Speaker.hpp @@ -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: From 15d54dfb4c5fe971d7f707de46ff672d2ae920ee Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 21 Mar 2020 22:24:31 -0400 Subject: [PATCH 2/9] Adds 'volume' command-line parameter for kiosk mode. --- .../xcschemes/Clock Signal Kiosk.xcscheme | 10 +++-- OSBindings/SDL/main.cpp | 45 ++++++++++++++----- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme index 66ee95aed..9a32a7574 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme @@ -56,6 +56,10 @@ argument = "/Users/thomasharte/Downloads/test-dsk-for-rw-and-50-60-hz/TEST-RW-60Hz.DSK" isEnabled = "NO"> + + @@ -70,7 +74,7 @@ + isEnabled = "NO"> + isEnabled = "NO"> + isEnabled = "NO"> diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index 451f3c8d9..c42d55284 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -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); + } } } From 7398cb44e2f56cc1c228ec6ce8bc76bd487649b3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 22 Mar 2020 13:24:23 -0400 Subject: [PATCH 3/9] Adds a functioning volume control for macOS, it just doesn't know how to hide yet. --- .../xcschemes/Clock Signal.xcscheme | 2 +- .../Base.lproj/MachineDocument.xib | 60 ++++++++++++++++++- .../Documents/MachineDocument.swift | 10 ++++ .../Mac/Clock Signal/Machine/CSMachine.h | 6 +- .../Mac/Clock Signal/Machine/CSMachine.mm | 20 ++++++- 5 files changed, 92 insertions(+), 6 deletions(-) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 7f370216e..f049730f4 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -67,7 +67,7 @@ - + - + + @@ -19,7 +20,7 @@ - + @@ -28,9 +29,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -45,6 +94,11 @@ + + + + + diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index f60789319..37ed10732 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -62,6 +62,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 +715,11 @@ class MachineDocument: } } } + + // MARK: - Volume Control. + @IBAction func setVolume(_ sender: NSSlider!) { + if let machine = self.machine { + machine.setVolume(sender.floatValue); + } + } } diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h index a437e5f4d..d8595e06c 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h @@ -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; diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index a3f3f27ae..691249e7d 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -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 { From 442ce403f93aead64138d3acf029fcfdd583946e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 22 Mar 2020 16:25:07 -0400 Subject: [PATCH 4/9] It's a bit jarring, but ensures volume control shows and hides according to mouse cursor. --- .../xcshareddata/xcschemes/Clock Signal.xcscheme | 2 +- .../Mac/Clock Signal/Base.lproj/MachineDocument.xib | 2 +- .../Mac/Clock Signal/Documents/MachineDocument.swift | 10 ++++++++++ OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h | 12 ++++++++++++ OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m | 2 ++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index f049730f4..7f370216e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -67,7 +67,7 @@