From 31e261f7e5937aadb1d70f747d6a242bf1df816e Mon Sep 17 00:00:00 2001 From: Ryan Carsten Schmidt Date: Wed, 24 Jan 2024 09:00:10 -0600 Subject: [PATCH 01/33] Add missing include of cstring for memcpy. --- Processors/AllRAMProcessor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Processors/AllRAMProcessor.cpp b/Processors/AllRAMProcessor.cpp index 5cb7a21ef..a5c294494 100644 --- a/Processors/AllRAMProcessor.cpp +++ b/Processors/AllRAMProcessor.cpp @@ -8,6 +8,8 @@ #include "AllRAMProcessor.hpp" +#include + using namespace CPU; AllRAMProcessor::AllRAMProcessor(std::size_t memory_size) : From 017674de35bda04073b84d9a630a96613be3de33 Mon Sep 17 00:00:00 2001 From: Ryan Carsten Schmidt Date: Thu, 25 Jan 2024 08:52:06 -0600 Subject: [PATCH 02/33] Rename BUILD.txt to BUILD.md. --- BUILD.txt => BUILD.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename BUILD.txt => BUILD.md (100%) diff --git a/BUILD.txt b/BUILD.md similarity index 100% rename from BUILD.txt rename to BUILD.md From f3a7d82dc125d74dfe29350dd49b58b582b09e30 Mon Sep 17 00:00:00 2001 From: Ryan Carsten Schmidt Date: Thu, 25 Jan 2024 09:00:54 -0600 Subject: [PATCH 03/33] Update build instructions with more specifics. Mention macOS version requirement; see #1179. --- BUILD.md | 101 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 16 deletions(-) diff --git a/BUILD.md b/BUILD.md index cc2bcf60e..8e7255fed 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,30 +1,99 @@ -Linux, BSD -========== +![Clock Signal Application Icon](READMEImages/Icon.png) +# Building Clock Signal -Prerequisites are SDL 2, ZLib and OpenGL (or Mesa), and SCons for the provided build script. OpenGL 3.2 or better is required at runtime. +Clock Signal is available as [a macOS native application using +Metal](#macos-app) or as [a cross-platform command-line-driven SDL executable +using OpenGL](#sdl-app). -Build: +## macOS app + +The macOS native application requires a Metal-capable Mac running macOS 10.13 or +later and has no prerequisites beyond the normal system libraries. It can be +built [using Xcode](#building-the-macos-app-using-xcode) or on the command line +[using `xcodebuild`](#building-the-macos-app-using-xcodebuild). + +Machine ROMs are intended to be built into the application bundle; populate the +dummy folders below ROMImages before building. + +The Xcode project is configured to sign the application using the developer's +certificate, but if you are not the developer then you will get a "No signing +certificate" error. To avoid this, you'll specify that you want to sign the +application to run locally. + +### Building the macOS app using Xcode + +Open the Clock Signal Xcode project in OSBindings/Mac. + +To avoid signing errors, edit the project, select the Signing & Capabilities +tab, and change the Signing Certificate drop-down menu from "Development" to +"Sign to Run Locally". + +To avoid crashes when running Clock Signal via Xcode on older Macs due to +"unrecognized selector sent to instance" errors, edit the scheme, and in the Run +section, scroll down to the Metal heading and uncheck the "API Validation" +checkbox. + +To build, choose "Build" from Xcode's Product menu or press +Command + B. + +To build and run, choose "Run" from the Product menu or press +Command + R. + +To see the folder where the Clock Signal application was built, choose "Show +Build Folder in Finder" from the Product menu. Look in the "Products" folder for +a folder named after the configuration (e.g. "Debug" or "Release"). + +### Building the macOS app using `xcodebuild` + +To build, change to the OSBindings/Mac directory in the Terminal, then run +`xcodebuild`, specifying `-` as the code sign identity to sign the application +to run locally to avoid signing errors: + + cd OSBindings/Mac + xcodebuild CODE_SIGN_IDENTITY=- + +`xcodebuild` will create a "build" folder in this directory which is where you +can find the Clock Signal application after it's compiled, in a directory named +after the configuration (e.g. "Debug" or "Release"). + +## SDL app + +The SDL app can be built on Linux, BSD, macOS, and other Unix-like operating +systems. Prerequisites are SDL 2, ZLib and OpenGL (or Mesa). OpenGL 3.2 or +better is required at runtime. It can be built [using +SCons](#building-the-sdl-app-using-scons). + +### Building the SDL app using SCons + +To build, change to the OSBindings/SDL directory and run `scons`. You can add a +`-j` flag to build in parallel. For example, if you have 8 processor cores: cd OSBindings/SDL - scons + scons -j8 -Optionally: +The `clksignal` executable will be created in this directory. You can run it +from here or install it by copying it where you want it, for example: - cp clksignal /usr/bin + cp clksignal /usr/local/bin -To launch: +To start an emulator with a particular disk image `file`, if you've installed +`clksignal` to a directory in your `PATH`, run: clksignal file -Setting up clksignal as the associated program for supported file types in your favoured filesystem browser is recommended; it has no file navigation abilities of its own. +Or if you're running it from the current directory: -Some emulated systems require the provision of original machine ROMs. These are not included and may be located in either /usr/local/share/CLK/ or /usr/share/CLK/. You will be prompted for them if they are found to be missing. The structure should mirror that under OSBindings in the source archive; see the readme.txt in each folder to determine the proper files and names ahead of time. + ./clksignal file -macOS -===== +Other options are availble. Run `clksignal` or `./clksignal` with no arguments +to learn more. -There are no prerequisites beyond the normal system libraries; the macOS build is a native Cocoa application. +Setting up `clksignal` as the associated program for supported file types in +your favoured filesystem browser is recommended; it has no file navigation +abilities of its own. -Build: open the Xcode project in OSBindings/Mac and press command+b. - -Machine ROMs are intended to be built into the application bundle; populate the dummy folders below ROMImages before building. +Some emulated systems require the provision of original machine ROMs. These are +not included and may be located in either /usr/local/share/CLK/ or +/usr/share/CLK/. You will be prompted for them if they are found to be missing. +The structure should mirror that under OSBindings in the source archive; see the +readme.txt in each folder to determine the proper files and names ahead of time. From cbf8849004cedc52cb47ba1b4a06f95d7baccd01 Mon Sep 17 00:00:00 2001 From: Ryan Carsten Schmidt Date: Tue, 23 Jan 2024 14:07:48 -0600 Subject: [PATCH 04/33] Add CMake build system, initially for SDL version. See #1275 --- CMakeLists.txt | 70 ++++++++++ cmake/CLK_SOURCES.cmake | 259 +++++++++++++++++++++++++++++++++++++ cmake/generate_CLK_SOURCES | 57 ++++++++ 3 files changed, 386 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/CLK_SOURCES.cmake create mode 100755 cmake/generate_CLK_SOURCES diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..975729ae5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 3.19 FATAL_ERROR) + +project(CLK + LANGUAGES CXX + VERSION 24.01.22 +) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(CLK_UIS "SDL") +#list(PREPEND CLK_UIS "QT") +#if(APPLE) +# list(PREPEND CLK_UIS "MAC") +# set(CLK_DEFAULT_UI "MAC") +#else() + set(CLK_DEFAULT_UI "SDL") +#endif() + +set(CLK_UI ${CLK_DEFAULT_UI} CACHE STRING "User interface") +set_property(CACHE CLK_UI PROPERTY STRINGS ${CLK_UIS}) + +if(NOT CLK_UI IN_LIST CLK_UIS) + list(JOIN CLK_UIS ", " CLK_UIS_PRETTY) + message(FATAL_ERROR "Invalid value for 'CLK_UI'; must be one of ${CLK_UIS_PRETTY}") +endif() + +message(STATUS "Configuring for ${CLK_UI} UI") + +list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +include("CLK_SOURCES") + +add_executable(clksignal ${CLK_SOURCES}) + +if(MSVC) + target_compile_options(clksignal PRIVATE /W4) +else() + # TODO: Add -Wpedandic. + target_compile_options(clksignal PRIVATE -Wall -Wextra) +endif() + +find_package(ZLIB REQUIRED) +target_link_libraries(clksignal PRIVATE ZLIB::ZLIB) + +if(CLK_UI STREQUAL "MAC") + enable_language(OBJC OBJCXX SWIFT) + # TODO: Build the Mac version. +else() + find_package(OpenGL REQUIRED) + target_link_libraries(clksignal PRIVATE OpenGL::GL) + if(APPLE) + target_compile_definitions(clksignal PRIVATE "GL_SILENCE_DEPRECATION" "IGNORE_APPLE") + endif() +endif() + +if(CLK_UI STREQUAL "QT") + # TODO: Build the Qt version. +elseif(APPLE) + set(BLA_VENDOR Apple) + find_package(BLAS REQUIRED) + target_link_libraries(clksignal PRIVATE BLAS::BLAS) +endif() + +if(CLK_UI STREQUAL "SDL") + find_package(SDL2 REQUIRED CONFIG REQUIRED COMPONENTS SDL2) + target_link_libraries(clksignal PRIVATE SDL2::SDL2) +endif() + +# TODO: Investigate building on Windows. diff --git a/cmake/CLK_SOURCES.cmake b/cmake/CLK_SOURCES.cmake new file mode 100644 index 000000000..66184bbb4 --- /dev/null +++ b/cmake/CLK_SOURCES.cmake @@ -0,0 +1,259 @@ +# Generated by generate_CLK_SOURCES. + +set(CLK_SOURCES + Analyser/Dynamic/ConfidenceCounter.cpp + Analyser/Dynamic/ConfidenceSummary.cpp + Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.cpp + Analyser/Dynamic/MultiMachine/Implementation/MultiJoystickMachine.cpp + Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.cpp + Analyser/Dynamic/MultiMachine/Implementation/MultiMediaTarget.cpp + Analyser/Dynamic/MultiMachine/Implementation/MultiProducer.cpp + Analyser/Dynamic/MultiMachine/Implementation/MultiSpeaker.cpp + Analyser/Dynamic/MultiMachine/MultiMachine.cpp + Analyser/Static/Acorn/Disk.cpp + Analyser/Static/Acorn/StaticAnalyser.cpp + Analyser/Static/Acorn/Tape.cpp + Analyser/Static/Amiga/StaticAnalyser.cpp + Analyser/Static/AmstradCPC/StaticAnalyser.cpp + Analyser/Static/AppleII/StaticAnalyser.cpp + Analyser/Static/AppleIIgs/StaticAnalyser.cpp + Analyser/Static/Atari2600/StaticAnalyser.cpp + Analyser/Static/AtariST/StaticAnalyser.cpp + Analyser/Static/Coleco/StaticAnalyser.cpp + Analyser/Static/Commodore/Disk.cpp + Analyser/Static/Commodore/File.cpp + Analyser/Static/Commodore/StaticAnalyser.cpp + Analyser/Static/Commodore/Tape.cpp + Analyser/Static/Disassembler/6502.cpp + Analyser/Static/Disassembler/Z80.cpp + Analyser/Static/DiskII/StaticAnalyser.cpp + Analyser/Static/Enterprise/StaticAnalyser.cpp + Analyser/Static/FAT12/StaticAnalyser.cpp + Analyser/Static/MSX/StaticAnalyser.cpp + Analyser/Static/MSX/Tape.cpp + Analyser/Static/Macintosh/StaticAnalyser.cpp + Analyser/Static/Oric/StaticAnalyser.cpp + Analyser/Static/Oric/Tape.cpp + Analyser/Static/PCCompatible/StaticAnalyser.cpp + Analyser/Static/Sega/StaticAnalyser.cpp + Analyser/Static/StaticAnalyser.cpp + Analyser/Static/ZX8081/StaticAnalyser.cpp + Analyser/Static/ZXSpectrum/StaticAnalyser.cpp + + Components/1770/1770.cpp + Components/5380/ncr5380.cpp + Components/6522/Implementation/IRQDelegatePortHandler.cpp + Components/6560/6560.cpp + Components/6850/6850.cpp + Components/68901/MFP68901.cpp + Components/8272/i8272.cpp + Components/8530/z8530.cpp + Components/9918/Implementation/9918.cpp + Components/AY38910/AY38910.cpp + Components/AudioToggle/AudioToggle.cpp + Components/DiskII/DiskII.cpp + Components/DiskII/DiskIIDrive.cpp + Components/DiskII/IWM.cpp + Components/DiskII/MacintoshDoubleDensityDrive.cpp + Components/KonamiSCC/KonamiSCC.cpp + Components/OPx/OPLL.cpp + Components/RP5C01/RP5C01.cpp + Components/SN76489/SN76489.cpp + Components/Serial/Line.cpp + + Inputs/Keyboard.cpp + + InstructionSets/M50740/Decoder.cpp + InstructionSets/M50740/Executor.cpp + InstructionSets/M68k/Decoder.cpp + InstructionSets/M68k/Instruction.cpp + InstructionSets/PowerPC/Decoder.cpp + InstructionSets/x86/Decoder.cpp + InstructionSets/x86/Instruction.cpp + + Machines/Amiga/Amiga.cpp + Machines/Amiga/Audio.cpp + Machines/Amiga/Bitplanes.cpp + Machines/Amiga/Blitter.cpp + Machines/Amiga/Chipset.cpp + Machines/Amiga/Copper.cpp + Machines/Amiga/Disk.cpp + Machines/Amiga/Keyboard.cpp + Machines/Amiga/MouseJoystick.cpp + Machines/Amiga/Sprites.cpp + Machines/AmstradCPC/AmstradCPC.cpp + Machines/AmstradCPC/Keyboard.cpp + Machines/Apple/ADB/Bus.cpp + Machines/Apple/ADB/Keyboard.cpp + Machines/Apple/ADB/Mouse.cpp + Machines/Apple/ADB/ReactiveDevice.cpp + Machines/Apple/AppleII/AppleII.cpp + Machines/Apple/AppleII/DiskIICard.cpp + Machines/Apple/AppleII/Joystick.cpp + Machines/Apple/AppleII/SCSICard.cpp + Machines/Apple/AppleII/Video.cpp + Machines/Apple/AppleIIgs/ADB.cpp + Machines/Apple/AppleIIgs/AppleIIgs.cpp + Machines/Apple/AppleIIgs/MemoryMap.cpp + Machines/Apple/AppleIIgs/Sound.cpp + Machines/Apple/AppleIIgs/Video.cpp + Machines/Apple/Macintosh/Audio.cpp + Machines/Apple/Macintosh/DriveSpeedAccumulator.cpp + Machines/Apple/Macintosh/Keyboard.cpp + Machines/Apple/Macintosh/Macintosh.cpp + Machines/Apple/Macintosh/Video.cpp + Machines/Atari/2600/Atari2600.cpp + Machines/Atari/2600/TIA.cpp + Machines/Atari/2600/TIASound.cpp + Machines/Atari/ST/AtariST.cpp + Machines/Atari/ST/DMAController.cpp + Machines/Atari/ST/IntelligentKeyboard.cpp + Machines/Atari/ST/Video.cpp + Machines/ColecoVision/ColecoVision.cpp + Machines/Commodore/1540/Implementation/C1540.cpp + Machines/Commodore/SerialBus.cpp + Machines/Commodore/Vic-20/Keyboard.cpp + Machines/Commodore/Vic-20/Vic20.cpp + Machines/Electron/Electron.cpp + Machines/Electron/Keyboard.cpp + Machines/Electron/Plus3.cpp + Machines/Electron/SoundGenerator.cpp + Machines/Electron/Tape.cpp + Machines/Electron/Video.cpp + Machines/Enterprise/Dave.cpp + Machines/Enterprise/EXDos.cpp + Machines/Enterprise/Enterprise.cpp + Machines/Enterprise/Keyboard.cpp + Machines/Enterprise/Nick.cpp + Machines/KeyboardMachine.cpp + Machines/MSX/DiskROM.cpp + Machines/MSX/Keyboard.cpp + Machines/MSX/MSX.cpp + Machines/MSX/MemorySlotHandler.cpp + Machines/MasterSystem/MasterSystem.cpp + Machines/Oric/BD500.cpp + Machines/Oric/Jasmin.cpp + Machines/Oric/Keyboard.cpp + Machines/Oric/Microdisc.cpp + Machines/Oric/Oric.cpp + Machines/Oric/Video.cpp + Machines/PCCompatible/PCCompatible.cpp + Machines/Sinclair/Keyboard/Keyboard.cpp + Machines/Sinclair/ZX8081/Video.cpp + Machines/Sinclair/ZX8081/ZX8081.cpp + Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp + Machines/Utility/MachineForTarget.cpp + Machines/Utility/MemoryFuzzer.cpp + Machines/Utility/MemoryPacker.cpp + Machines/Utility/ROMCatalogue.cpp + Machines/Utility/StringSerialiser.cpp + Machines/Utility/Typer.cpp + + Outputs/CRT/CRT.cpp + Outputs/DisplayMetrics.cpp + Outputs/OpenGL/Primitives/Rectangle.cpp + Outputs/OpenGL/Primitives/Shader.cpp + Outputs/OpenGL/Primitives/TextureTarget.cpp + Outputs/OpenGL/ScanTarget.cpp + Outputs/OpenGL/ScanTargetGLSLFragments.cpp + Outputs/ScanTarget.cpp + Outputs/ScanTargets/BufferingScanTarget.cpp + + Processors/6502/AllRAM/6502AllRAM.cpp + Processors/6502/Implementation/6502Storage.cpp + Processors/6502/State/State.cpp + Processors/65816/Implementation/65816Base.cpp + Processors/65816/Implementation/65816Storage.cpp + Processors/AllRAMProcessor.cpp + Processors/Z80/AllRAM/Z80AllRAM.cpp + Processors/Z80/Implementation/PartialMachineCycle.cpp + Processors/Z80/Implementation/Z80Base.cpp + Processors/Z80/Implementation/Z80Storage.cpp + Processors/Z80/State/State.cpp + + Reflection/Struct.cpp + + SignalProcessing/FIRFilter.cpp + + Storage/Cartridge/Cartridge.cpp + Storage/Cartridge/Encodings/CommodoreROM.cpp + Storage/Cartridge/Formats/BinaryDump.cpp + Storage/Cartridge/Formats/PRG.cpp + Storage/Data/Commodore.cpp + Storage/Data/ZX8081.cpp + Storage/Disk/Controller/DiskController.cpp + Storage/Disk/Controller/MFMDiskController.cpp + Storage/Disk/DiskImage/Formats/2MG.cpp + Storage/Disk/DiskImage/Formats/AcornADF.cpp + Storage/Disk/DiskImage/Formats/AmigaADF.cpp + Storage/Disk/DiskImage/Formats/AppleDSK.cpp + Storage/Disk/DiskImage/Formats/CPCDSK.cpp + Storage/Disk/DiskImage/Formats/D64.cpp + Storage/Disk/DiskImage/Formats/DMK.cpp + Storage/Disk/DiskImage/Formats/FAT12.cpp + Storage/Disk/DiskImage/Formats/G64.cpp + Storage/Disk/DiskImage/Formats/HFE.cpp + Storage/Disk/DiskImage/Formats/IMD.cpp + Storage/Disk/DiskImage/Formats/IPF.cpp + Storage/Disk/DiskImage/Formats/MFMSectorDump.cpp + Storage/Disk/DiskImage/Formats/MSA.cpp + Storage/Disk/DiskImage/Formats/MacintoshIMG.cpp + Storage/Disk/DiskImage/Formats/NIB.cpp + Storage/Disk/DiskImage/Formats/OricMFMDSK.cpp + Storage/Disk/DiskImage/Formats/PCBooter.cpp + Storage/Disk/DiskImage/Formats/SSD.cpp + Storage/Disk/DiskImage/Formats/STX.cpp + Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.cpp + Storage/Disk/DiskImage/Formats/WOZ.cpp + Storage/Disk/Drive.cpp + Storage/Disk/Encodings/AppleGCR/Encoder.cpp + Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp + Storage/Disk/Encodings/CommodoreGCR.cpp + Storage/Disk/Encodings/MFM/Encoder.cpp + Storage/Disk/Encodings/MFM/Parser.cpp + Storage/Disk/Encodings/MFM/SegmentParser.cpp + Storage/Disk/Encodings/MFM/Shifter.cpp + Storage/Disk/Parsers/CPM.cpp + Storage/Disk/Parsers/FAT.cpp + Storage/Disk/Track/PCMSegment.cpp + Storage/Disk/Track/PCMTrack.cpp + Storage/Disk/Track/TrackSerialiser.cpp + Storage/Disk/Track/UnformattedTrack.cpp + Storage/FileHolder.cpp + Storage/MassStorage/Encodings/MacintoshVolume.cpp + Storage/MassStorage/Formats/DAT.cpp + Storage/MassStorage/Formats/DSK.cpp + Storage/MassStorage/Formats/HDV.cpp + Storage/MassStorage/Formats/HFV.cpp + Storage/MassStorage/MassStorageDevice.cpp + Storage/MassStorage/SCSI/DirectAccessDevice.cpp + Storage/MassStorage/SCSI/SCSI.cpp + Storage/MassStorage/SCSI/Target.cpp + Storage/State/SNA.cpp + Storage/State/SZX.cpp + Storage/State/Z80.cpp + Storage/Tape/Formats/CAS.cpp + Storage/Tape/Formats/CSW.cpp + Storage/Tape/Formats/CommodoreTAP.cpp + Storage/Tape/Formats/OricTAP.cpp + Storage/Tape/Formats/TZX.cpp + Storage/Tape/Formats/TapePRG.cpp + Storage/Tape/Formats/TapeUEF.cpp + Storage/Tape/Formats/ZX80O81P.cpp + Storage/Tape/Formats/ZXSpectrumTAP.cpp + Storage/Tape/Parsers/Acorn.cpp + Storage/Tape/Parsers/Commodore.cpp + Storage/Tape/Parsers/MSX.cpp + Storage/Tape/Parsers/Oric.cpp + Storage/Tape/Parsers/Spectrum.cpp + Storage/Tape/Parsers/ZX8081.cpp + Storage/Tape/PulseQueuedTape.cpp + Storage/Tape/Tape.cpp + Storage/TimedEventLoop.cpp +) + +if(CLK_UI STREQUAL "SDL") + list(APPEND CLK_SOURCES + OSBindings/SDL/main.cpp + ) +endif() diff --git a/cmake/generate_CLK_SOURCES b/cmake/generate_CLK_SOURCES new file mode 100755 index 000000000..a1f3354b2 --- /dev/null +++ b/cmake/generate_CLK_SOURCES @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +# Run this script to regenerate CLK_SOURCES.cmake after you add or remove any +# source files. + +set -euo pipefail + +script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) +top_dir="$script_dir/.." + +out="$script_dir/CLK_SOURCES.cmake" +trap 'rm -f "$tmp"' EXIT +tmp=$(mktemp "$out.XXXXXXXX") + +awkscript=' +BEGIN { + print ind pre "CLK_SOURCES" +} + +{ + if (NR > 1 && last != $1) + print "" + last = $1 + sub("^", ind "\t") + print +} + +END { + print ind ")" +} +' + +write_sources() { + local ind="$1" + local pre="$2" + shift 2 + find -s "$@" | awk -F/ -vind="$ind" -vpre="$pre" "$awkscript" || exit $? +} + +cd "$top_dir" +printf '# Generated by %s.\n\n' "${BASH_SOURCE[0]##*/}" > "$tmp" +write_sources '' 'set(' [A-Z]* -name OSBindings -prune -o -name '*.cpp' -print >> "$tmp" +# TODO: Add 'Mac/Clock Signal' +# TODO: Add Qt +for dir in SDL; do + ui=$(echo "${dir%%/*}" | tr '[:lower:]' '[:upper:]') + dir="OSBindings/$dir" + printf '\nif(CLK_UI STREQUAL "%s")\n' "$ui" >> "$tmp" + if [[ $ui = "MAC" ]]; then + args=('-name' '*.m' '-o' '-name' '*.metal' '-o' '-name' '*.mm' '-o' '-name' '*.swift') + else + args=('-name' '*.cpp') + fi + write_sources $'\t' 'list(APPEND ' "$dir" "${args[@]}" >> "$tmp" + printf 'endif()\n' >> "$tmp" +done +mv "$tmp" "$out" From 01d9455897290b836a7062a8ef86d5c8bd3461c9 Mon Sep 17 00:00:00 2001 From: Ryan Carsten Schmidt Date: Wed, 24 Jan 2024 22:44:54 -0600 Subject: [PATCH 05/33] Exclude *AllRAM*.cpp from CMake program sources. These files serve as documentation and are used in tests. --- cmake/CLK_SOURCES.cmake | 3 --- cmake/generate_CLK_SOURCES | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cmake/CLK_SOURCES.cmake b/cmake/CLK_SOURCES.cmake index 66184bbb4..903728b12 100644 --- a/cmake/CLK_SOURCES.cmake +++ b/cmake/CLK_SOURCES.cmake @@ -159,13 +159,10 @@ set(CLK_SOURCES Outputs/ScanTarget.cpp Outputs/ScanTargets/BufferingScanTarget.cpp - Processors/6502/AllRAM/6502AllRAM.cpp Processors/6502/Implementation/6502Storage.cpp Processors/6502/State/State.cpp Processors/65816/Implementation/65816Base.cpp Processors/65816/Implementation/65816Storage.cpp - Processors/AllRAMProcessor.cpp - Processors/Z80/AllRAM/Z80AllRAM.cpp Processors/Z80/Implementation/PartialMachineCycle.cpp Processors/Z80/Implementation/Z80Base.cpp Processors/Z80/Implementation/Z80Storage.cpp diff --git a/cmake/generate_CLK_SOURCES b/cmake/generate_CLK_SOURCES index a1f3354b2..fc5e2e344 100755 --- a/cmake/generate_CLK_SOURCES +++ b/cmake/generate_CLK_SOURCES @@ -39,7 +39,8 @@ write_sources() { cd "$top_dir" printf '# Generated by %s.\n\n' "${BASH_SOURCE[0]##*/}" > "$tmp" -write_sources '' 'set(' [A-Z]* -name OSBindings -prune -o -name '*.cpp' -print >> "$tmp" +write_sources '' 'set(' [A-Z]* -name OSBindings -prune -o \ + -name '*AllRAM*.cpp' -prune -o -name '*.cpp' -print >> "$tmp" # TODO: Add 'Mac/Clock Signal' # TODO: Add Qt for dir in SDL; do From ad8abf2e05f32d359e625cb142fd2eef1e6e4fda Mon Sep 17 00:00:00 2001 From: Ryan Carsten Schmidt Date: Thu, 25 Jan 2024 09:50:17 -0600 Subject: [PATCH 06/33] Add CMake SDL builds to CI workflow. --- .github/workflows/build.yml | 44 +++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 891f13c8a..bf8348b60 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,8 @@ name: Build on: [pull_request] jobs: - build-mac: - name: Mac UI on ${{ matrix.os }} + build-mac-xcodebuild: + name: Mac UI / xcodebuild / ${{ matrix.os }} strategy: matrix: os: [macos-latest] @@ -13,8 +13,44 @@ jobs: - name: Make working-directory: OSBindings/Mac run: xcodebuild CODE_SIGN_IDENTITY=- - build-sdl: - name: SDL UI on ${{ matrix.os }} + build-sdl-cmake: + name: SDL UI / cmake / ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest, ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install dependencies + shell: bash + run: | + case $RUNNER_OS in + Linux) + sudo apt-get --allow-releaseinfo-change update + sudo apt-get --fix-missing install cmake gcc-10 libsdl2-dev + ;; + macOS) + brew install cmake sdl2 + ;; + esac + - name: Make + shell: bash + run: | + case $RUNNER_OS in + Linux) + jobs=$(nproc --all) + ;; + macOS) + jobs=$(sysctl -n hw.activecpu) + ;; + *) + jobs=1 + esac + cmake -S. -Bbuild -DCLK_UI=SDL -DCMAKE_BUILD_TYPE=Release + cmake --build build -v -j"$jobs" + build-sdl-scons: + name: SDL UI / scons / ${{ matrix.os }} strategy: matrix: os: [macos-latest, ubuntu-latest] From d811501421ebfc3e6ac4e7cfef68e2c805130f02 Mon Sep 17 00:00:00 2001 From: Ryan Carsten Schmidt Date: Sat, 27 Jan 2024 13:14:17 -0600 Subject: [PATCH 07/33] Compatibility fixes in Markdown files. Improve compatibility with some Markdown readers like MacDown by adding blank lines before lists. Blank lines around headers were added for consistency. One header level was fixed. One code block was fixed. --- InstructionSets/README.md | 9 ++++++++- .../Clock SignalTests/68000 Comparative Tests/readme.md | 4 ++++ OSBindings/Mac/Clock SignalTests/AllSuiteA/readme.md | 1 + .../Mac/Clock SignalTests/Amiga Blitter Tests/README.md | 3 ++- OSBindings/Mac/Clock SignalTests/BCDTest/readme.md | 1 + .../Wolfgang Lorenz 6502 test suite/readme.md | 1 + Packaging/README.md | 5 +++-- README.md | 5 +++++ 8 files changed, 25 insertions(+), 4 deletions(-) diff --git a/InstructionSets/README.md b/InstructionSets/README.md index fc50a84ea..a0c779083 100644 --- a/InstructionSets/README.md +++ b/InstructionSets/README.md @@ -1,8 +1,9 @@ -# Instruction Sets +# Instruction Sets Code in here provides the means to disassemble, and to execute code for certain instruction sets. It **does not seek to emulate specific processors** other than in terms of implementing their instruction sets. So: + * it doesn't involve itself in the actual bus signalling of real processors; and * instruction-level timing (e.g. total cycle counts) may be unimplemented, and is likely to be incomplete. @@ -13,6 +14,7 @@ This part of CLK is intended primarily to provide disassembly services for stati A decoder extracts fully-decoded instructions from a data stream for its associated architecture. The meaning of 'fully-decoded' is flexible but it means that a caller can easily discern at least: + * the operation in use; * its addressing mode; and * relevant registers. @@ -20,6 +22,7 @@ The meaning of 'fully-decoded' is flexible but it means that a caller can easily It may be assumed that callers will have access to the original data stream for immediate values, if it is sensible to do so. In deciding what to expose, what to store ahead of time and what to obtain just-in-time a decoder should have an eye on two principal consumers: + 1. disassemblers; and 2. instruction executors. @@ -50,6 +53,7 @@ A sample interface: std::pair decode(word_type *stream, size_t length) { ... } In this sample the returned pair provides an `int` size that is one of: + * a positive number, indicating a completed decoding that consumed that many `word_type`s; or * a negative number, indicating the [negatived] minimum number of `word_type`s that the caller should try to get hold of before calling `decode` again. @@ -58,6 +62,7 @@ A caller is permitted to react in any way it prefers to negative numbers; they'r ## Parsers A parser sits one level above a decoder; it is handed: + * a start address; * a closing bound; and * a target. @@ -65,6 +70,7 @@ A parser sits one level above a decoder; it is handed: It is responsible for parsing the instruction stream from the start address up to and not beyond the closing bound, and no further than any unconditional branches. It should post to the target: + * any instructions fully decoded; * any conditional branch destinations encountered; * any immediately-knowable accessed addresses; and @@ -75,6 +81,7 @@ So a parser has the same two primary potential recipients as a decoder: diassemb ## Executors An executor is responsible for only one thing: + * mapping from decoded instructions to objects that can perform those instructions. An executor is assumed to bundle all the things that go into instruction set execution: processor state and memory, alongside a parser. diff --git a/OSBindings/Mac/Clock SignalTests/68000 Comparative Tests/readme.md b/OSBindings/Mac/Clock SignalTests/68000 Comparative Tests/readme.md index 75d3e49be..e8155ad0c 100644 --- a/OSBindings/Mac/Clock SignalTests/68000 Comparative Tests/readme.md +++ b/OSBindings/Mac/Clock SignalTests/68000 Comparative Tests/readme.md @@ -3,12 +3,14 @@ Tests contained in this folder are original to Clock Signal. All are JSON. Tests assume a test machine consisting of a vanilla 68000 with 16mb of RAM. For each test either: + 1. start from a reset, e.g. if you have a prefetch queue you need to fill; or 2. just apply the entire initial state, which indicates the proper PC and A7 for itself. Then execute to the end of a single instruction (including any generated exception). Each file contains an array of dictionaries. Each dictionary is a single test and includes: + * a name; * initial memory contents; * initial register state; @@ -43,11 +45,13 @@ So the output is very scattergun approach, with a lot of redundancy. ## Known Issues Errors in generation mean that: + 1. MOVE is mostly untested; MOVEq is well-tested and other MOVEs appear within the test set as per the approximate generation algorithm above but due to an error in the generation of move.json, all of its opcodes are $2000 less than they should be, causing them to hit various instructions other than MOVE; 2. there is sparse coverage of the rotates and shifts: LS[L/R], AS[L/R], RO[L/R] and ROX[L/R]; and 3. there are similarly few tests of MULU. Issues with comparing results between multiple emulators in the case of unusual instructions mean that no tests have been generated for: + 1. MOVE [to or from] SR; 2. TRAP; 3. TRAPV; diff --git a/OSBindings/Mac/Clock SignalTests/AllSuiteA/readme.md b/OSBindings/Mac/Clock SignalTests/AllSuiteA/readme.md index 959121be8..fa93be7e5 100644 --- a/OSBindings/Mac/Clock SignalTests/AllSuiteA/readme.md +++ b/OSBindings/Mac/Clock SignalTests/AllSuiteA/readme.md @@ -1,2 +1,3 @@ # AllSuiteA Test Suite + This file was sourced from hmc-6502 — https://github.com/cminter/hmc-6502 — and was obtained with an unknown licence, believed to be BSD Simplified. \ No newline at end of file diff --git a/OSBindings/Mac/Clock SignalTests/Amiga Blitter Tests/README.md b/OSBindings/Mac/Clock SignalTests/Amiga Blitter Tests/README.md index 7f67d6fd7..1b3a13f5b 100644 --- a/OSBindings/Mac/Clock SignalTests/Amiga Blitter Tests/README.md +++ b/OSBindings/Mac/Clock SignalTests/Amiga Blitter Tests/README.md @@ -1,7 +1,8 @@ -## Amiga Blitter Tests +# Amiga Blitter Tests These tests record register writes and subsequent memory accesses by the Amiga Blitter over a variety of test cases. It is believed that they test all functionality other than stippled lines. They were generated using a slightly-inaccurate public domain model of the chip rather than from the real thing. In particular: + * these tests record the output as though the Blitter weren't pipelined — assuming all channels enabled, it always reads via A, then B, then C, then writes via D. The real Blitter performs two cycles of reads before its first write, and adds a final write with no additional reads; and * the tests do not record which pointer is used for a write target and therefore do not observe that the Blitter will use pointer C as a write destination for the first pixel of a line. diff --git a/OSBindings/Mac/Clock SignalTests/BCDTest/readme.md b/OSBindings/Mac/Clock SignalTests/BCDTest/readme.md index 881548c66..ebaceac92 100644 --- a/OSBindings/Mac/Clock SignalTests/BCDTest/readme.md +++ b/OSBindings/Mac/Clock SignalTests/BCDTest/readme.md @@ -1,2 +1,3 @@ # BCDTest + This test was obtained from http://stardot.org.uk/forums/viewtopic.php?t=8793#p97168 and is the work of David Banks, https://github.com/hoglet67 . It is believed to be public domain. \ No newline at end of file diff --git a/OSBindings/Mac/Clock SignalTests/Wolfgang Lorenz 6502 test suite/readme.md b/OSBindings/Mac/Clock SignalTests/Wolfgang Lorenz 6502 test suite/readme.md index d1e697f5c..ec1d0237a 100644 --- a/OSBindings/Mac/Clock SignalTests/Wolfgang Lorenz 6502 test suite/readme.md +++ b/OSBindings/Mac/Clock SignalTests/Wolfgang Lorenz 6502 test suite/readme.md @@ -1,2 +1,3 @@ # Wolfgang Lorenz’s Test Suite + The files in this folder were authored by Wolfgang Lorenz and were sourced as per http://www.softwolves.com/arkiv/cbm-hackers/7/7114.html ; they are believed to be public domain software. \ No newline at end of file diff --git a/Packaging/README.md b/Packaging/README.md index ee1a0e521..19b4b34c3 100644 --- a/Packaging/README.md +++ b/Packaging/README.md @@ -1,8 +1,9 @@ # RPM packaging for clksignal -This simple Ansible playbook creates and installs an RPM package of the current release of clksignal + +This simple Ansible playbook creates and installs an RPM package of the current release of clksignal. If the version that you build is newer than what you have installed, it will be automatically upgraded. ## Usage -`ansible-playbook main.yml -K + ansible-playbook main.yml -K diff --git a/README.md b/README.md index ec0209fe0..d4a650c5e 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ ![Clock Signal Application Icon](READMEImages/Icon.png) + # Clock Signal + Clock Signal ('CLK') is an emulator that seeks to be invisible. Users directly launch classic software, avoiding the learning curves associated with emulators and with classic machines. macOS and source releases are [hosted on GitHub](https://github.com/TomHarte/CLK/releases). A Qt-based Linux build is available as a [Snap](https://snapcraft.io/clock-signal). This emulator seeks to offer: + * single-click load of any piece of source media for any supported platform; * with a heavy signal processing tilt for accurate reproduction of original outputs; * avoiding latency as much as possible. It currently contains emulations of the: + * Acorn Electron; * Amstrad CPC; * Apple II/II+ and IIe; @@ -26,6 +30,7 @@ It currently contains emulations of the: * Sinclair ZX Spectrum. Also present but very much upcoming are the: + * Commodore Amiga; and * early PC compatible. From 7dc3b5ba06f9158729aa3ccc5f753a1389512b0b Mon Sep 17 00:00:00 2001 From: Ryan Carsten Schmidt Date: Wed, 31 Jan 2024 04:42:24 -0600 Subject: [PATCH 08/33] Update CI to build on macOS 11, 12, 13, and 14. --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 891f13c8a..7de0ff40f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ jobs: name: Mac UI on ${{ matrix.os }} strategy: matrix: - os: [macos-latest] + os: [macos-11, macos-12, macos-13, macos-14] runs-on: ${{ matrix.os }} steps: - name: Checkout @@ -17,7 +17,7 @@ jobs: name: SDL UI on ${{ matrix.os }} strategy: matrix: - os: [macos-latest, ubuntu-latest] + os: [macos-11, macos-12, macos-13, macos-14, ubuntu-latest] runs-on: ${{ matrix.os }} steps: - name: Checkout From ce0d53b277d99957de718c16fb923ad36516e251 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 1 Feb 2024 21:29:00 -0500 Subject: [PATCH 09/33] Clean up SampleSource's getters. --- Components/6560/6560.hpp | 2 +- Components/AY38910/AY38910.hpp | 4 +- Components/KonamiSCC/KonamiSCC.hpp | 2 +- Components/OPx/OPLL.hpp | 2 +- Components/SN76489/SN76489.hpp | 2 +- Machines/Apple/Macintosh/Audio.hpp | 2 +- Machines/Atari/2600/TIASound.hpp | 2 +- Machines/Electron/SoundGenerator.hpp | 2 +- Machines/Enterprise/Dave.hpp | 2 +- .../Speaker/Implementation/CompoundSource.hpp | 125 +++++++++--------- .../Speaker/Implementation/LowpassSpeaker.hpp | 8 +- .../Speaker/Implementation/SampleSource.hpp | 4 +- 12 files changed, 77 insertions(+), 80 deletions(-) diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index f8c570924..5a55317eb 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -28,7 +28,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource { void get_samples(std::size_t number_of_samples, int16_t *target); void skip_samples(std::size_t number_of_samples); void set_sample_volume_range(std::int16_t range); - static constexpr bool get_is_stereo() { return false; } + static constexpr bool is_stereo = false; private: Concurrency::AsyncTaskQueue &audio_queue_; diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index eee96c093..90c5c9b1f 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -66,7 +66,7 @@ enum class Personality { This AY has an attached mono or stereo mixer. */ -template class AY38910: public ::Outputs::Speaker::SampleSource { +template class AY38910: public ::Outputs::Speaker::SampleSource { public: /// Creates a new AY38910. AY38910(Personality, Concurrency::AsyncTaskQueue &); @@ -109,7 +109,7 @@ template class AY38910: public ::Outputs::Speaker::SampleSource void get_samples(std::size_t number_of_samples, int16_t *target); bool is_zero_level() const; void set_sample_volume_range(std::int16_t range); - static constexpr bool get_is_stereo() { return is_stereo; } + static constexpr bool is_stereo = stereo; private: Concurrency::AsyncTaskQueue &task_queue_; diff --git a/Components/KonamiSCC/KonamiSCC.hpp b/Components/KonamiSCC/KonamiSCC.hpp index 9b16e8a1d..a0cb3ca30 100644 --- a/Components/KonamiSCC/KonamiSCC.hpp +++ b/Components/KonamiSCC/KonamiSCC.hpp @@ -31,7 +31,7 @@ class SCC: public ::Outputs::Speaker::SampleSource { /// As per ::SampleSource; provides audio output. void get_samples(std::size_t number_of_samples, std::int16_t *target); void set_sample_volume_range(std::int16_t range); - static constexpr bool get_is_stereo() { return false; } + static constexpr bool is_stereo = false; /// Writes to the SCC. void write(uint16_t address, uint8_t value); diff --git a/Components/OPx/OPLL.hpp b/Components/OPx/OPLL.hpp index 36bb0b51d..284c9ade9 100644 --- a/Components/OPx/OPLL.hpp +++ b/Components/OPx/OPLL.hpp @@ -30,7 +30,7 @@ class OPLL: public OPLBase { // The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in // rhythm mode, but it's correct for melodic output. - double get_average_output_peak() const { return 0.5; } + double average_output_peak() const { return 0.5; } /// Reads from the OPL. uint8_t read(uint16_t address); diff --git a/Components/SN76489/SN76489.hpp b/Components/SN76489/SN76489.hpp index d419692f8..d720ee256 100644 --- a/Components/SN76489/SN76489.hpp +++ b/Components/SN76489/SN76489.hpp @@ -31,7 +31,7 @@ class SN76489: public Outputs::Speaker::SampleSource { void get_samples(std::size_t number_of_samples, std::int16_t *target); bool is_zero_level() const; void set_sample_volume_range(std::int16_t range); - static constexpr bool get_is_stereo() { return false; } + static constexpr bool is_stereo = false; private: int master_divider_ = 0; diff --git a/Machines/Apple/Macintosh/Audio.hpp b/Machines/Apple/Macintosh/Audio.hpp index e132015e0..3eabbc832 100644 --- a/Machines/Apple/Macintosh/Audio.hpp +++ b/Machines/Apple/Macintosh/Audio.hpp @@ -53,7 +53,7 @@ class Audio: public ::Outputs::Speaker::SampleSource { void get_samples(std::size_t number_of_samples, int16_t *target); bool is_zero_level() const; void set_sample_volume_range(std::int16_t range); - constexpr static bool get_is_stereo() { return false; } + constexpr static bool is_stereo = false; private: Concurrency::AsyncTaskQueue &task_queue_; diff --git a/Machines/Atari/2600/TIASound.hpp b/Machines/Atari/2600/TIASound.hpp index 67976be58..097b70f52 100644 --- a/Machines/Atari/2600/TIASound.hpp +++ b/Machines/Atari/2600/TIASound.hpp @@ -28,7 +28,7 @@ class TIASound: public Outputs::Speaker::SampleSource { // To satisfy ::SampleSource. void get_samples(std::size_t number_of_samples, int16_t *target); void set_sample_volume_range(std::int16_t range); - static constexpr bool get_is_stereo() { return false; } + static constexpr bool is_stereo = false; private: Concurrency::AsyncTaskQueue &audio_queue_; diff --git a/Machines/Electron/SoundGenerator.hpp b/Machines/Electron/SoundGenerator.hpp index 32cd04074..2429bb37b 100644 --- a/Machines/Electron/SoundGenerator.hpp +++ b/Machines/Electron/SoundGenerator.hpp @@ -27,7 +27,7 @@ class SoundGenerator: public ::Outputs::Speaker::SampleSource { void get_samples(std::size_t number_of_samples, int16_t *target); void skip_samples(std::size_t number_of_samples); void set_sample_volume_range(std::int16_t range); - static constexpr bool get_is_stereo() { return false; } + static constexpr bool is_stereo = false; private: Concurrency::AsyncTaskQueue &audio_queue_; diff --git a/Machines/Enterprise/Dave.hpp b/Machines/Enterprise/Dave.hpp index 21c08cfe8..7b32f8db1 100644 --- a/Machines/Enterprise/Dave.hpp +++ b/Machines/Enterprise/Dave.hpp @@ -37,7 +37,7 @@ class Audio: public Outputs::Speaker::SampleSource { // MARK: - SampleSource. void set_sample_volume_range(int16_t range); - static constexpr bool get_is_stereo() { return true; } // Dave produces stereo sound. + static constexpr bool is_stereo = true; void get_samples(std::size_t number_of_samples, int16_t *target); private: diff --git a/Outputs/Speaker/Implementation/CompoundSource.hpp b/Outputs/Speaker/Implementation/CompoundSource.hpp index 75f798e55..52a0a3e71 100644 --- a/Outputs/Speaker/Implementation/CompoundSource.hpp +++ b/Outputs/Speaker/Implementation/CompoundSource.hpp @@ -22,62 +22,7 @@ namespace Outputs::Speaker { */ template class CompoundSource: public Outputs::Speaker::SampleSource { - public: - CompoundSource(T &... sources) : source_holder_(sources...) { - // Default: give all sources equal volume. - const auto volume = 1.0 / double(source_holder_.size()); - for(std::size_t c = 0; c < source_holder_.size(); ++c) { - volumes_.push_back(volume); - } - } - - void get_samples(std::size_t number_of_samples, std::int16_t *target) { - source_holder_.template get_samples(number_of_samples, target); - } - - void skip_samples(const std::size_t number_of_samples) { - source_holder_.skip_samples(number_of_samples); - } - - /*! - Sets the total output volume of this CompoundSource. - */ - void set_sample_volume_range(int16_t range) { - volume_range_ = range; - push_volumes(); - } - - /*! - Sets the relative volumes of the various sources underlying this - compound. The caller should ensure that the number of items supplied - matches the number of sources and that the values in it sum to 1.0. - */ - void set_relative_volumes(const std::vector &volumes) { - assert(volumes.size() == source_holder_.size()); - volumes_ = volumes; - push_volumes(); - average_output_peak_ = 1.0 / source_holder_.total_scale(volumes_.data()); - } - - /*! - @returns true if any of the sources owned by this CompoundSource is stereo. - */ - static constexpr bool get_is_stereo() { return CompoundSourceHolder::get_is_stereo(); } - - /*! - @returns the average output peak given the sources owned by this CompoundSource and the - current relative volumes. - */ - double get_average_output_peak() const { - return average_output_peak_; - } - private: - void push_volumes() { - const double scale = source_holder_.total_scale(volumes_.data()); - source_holder_.set_scaled_volume_range(volume_range_, volumes_.data(), scale); - } - template class CompoundSourceHolder: public Outputs::Speaker::SampleSource { public: template void get_samples(std::size_t number_of_samples, std::int16_t *target) { @@ -90,9 +35,7 @@ template class CompoundSource: return 0; } - static constexpr bool get_is_stereo() { - return false; - } + static constexpr bool is_stereo = false; double total_scale(double *) const { return 0.0; @@ -121,7 +64,7 @@ template class CompoundSource: // Merge it in; furthermore if total output is stereo but this source isn't, // map it to stereo. - if constexpr (output_stereo == S::get_is_stereo()) { + if constexpr (output_stereo == S::is_stereo) { while(buffer_size--) { target[buffer_size] += local_samples[buffer_size]; } @@ -144,7 +87,7 @@ template class CompoundSource: } void set_scaled_volume_range(int16_t range, double *volumes, double scale) { - const auto scaled_range = volumes[0] / double(source_.get_average_output_peak()) * double(range) / scale; + const auto scaled_range = volumes[0] / double(source_.average_output_peak()) * double(range) / scale; source_.set_sample_volume_range(int16_t(scaled_range)); next_source_.set_scaled_volume_range(range, &volumes[1], scale); } @@ -153,12 +96,10 @@ template class CompoundSource: return 1 + CompoundSourceHolder::size(); } - static constexpr bool get_is_stereo() { - return S::get_is_stereo() || CompoundSourceHolder::get_is_stereo(); - } + static constexpr bool is_stereo = S::is_stereo || CompoundSourceHolder::is_stereo; double total_scale(double *volumes) const { - return (volumes[0] / source_.get_average_output_peak()) + next_source_.total_scale(&volumes[1]); + return (volumes[0] / source_.average_output_peak()) + next_source_.total_scale(&volumes[1]); } private: @@ -166,6 +107,62 @@ template class CompoundSource: CompoundSourceHolder next_source_; }; + public: + CompoundSource(T &... sources) : source_holder_(sources...) { + // Default: give all sources equal volume. + const auto volume = 1.0 / double(source_holder_.size()); + for(std::size_t c = 0; c < source_holder_.size(); ++c) { + volumes_.push_back(volume); + } + } + + void get_samples(std::size_t number_of_samples, std::int16_t *target) { + source_holder_.template get_samples(number_of_samples, target); + } + + void skip_samples(const std::size_t number_of_samples) { + source_holder_.skip_samples(number_of_samples); + } + + /*! + Sets the total output volume of this CompoundSource. + */ + void set_sample_volume_range(int16_t range) { + volume_range_ = range; + push_volumes(); + } + + /*! + Sets the relative volumes of the various sources underlying this + compound. The caller should ensure that the number of items supplied + matches the number of sources and that the values in it sum to 1.0. + */ + void set_relative_volumes(const std::vector &volumes) { + assert(volumes.size() == source_holder_.size()); + volumes_ = volumes; + push_volumes(); + average_output_peak_ = 1.0 / source_holder_.total_scale(volumes_.data()); + } + + /*! + @c true if any of the sources owned by this CompoundSource is stereo. + */ + static constexpr bool is_stereo = CompoundSourceHolder::is_stereo; + + /*! + @returns the average output peak given the sources owned by this CompoundSource and the + current relative volumes. + */ + double average_output_peak() const { + return average_output_peak_; + } + + private: + void push_volumes() { + const double scale = source_holder_.total_scale(volumes_.data()); + source_holder_.set_scaled_volume_range(volume_range_, volumes_.data(), scale); + } + CompoundSourceHolder source_holder_; std::vector volumes_; int16_t volume_range_ = 0; diff --git a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp index 294e19589..3b64dfcc6 100644 --- a/Outputs/Speaker/Implementation/LowpassSpeaker.hpp +++ b/Outputs/Speaker/Implementation/LowpassSpeaker.hpp @@ -348,7 +348,7 @@ template class PushLowpass: public LowpassBase class PullLowpass: public LowpassBase, SampleSource::get_is_stereo()> { +template class PullLowpass: public LowpassBase, SampleSource::is_stereo> { public: PullLowpass(SampleSource &sample_source) : sample_source_(sample_source) { // Propagate an initial volume level. @@ -362,7 +362,7 @@ template class PullLowpass: public LowpassBase class PullLowpass: public LowpassBase, SampleSource::get_is_stereo()>; + using BaseT = LowpassBase, SampleSource::is_stereo>; friend BaseT; using BaseT::process; @@ -400,7 +400,7 @@ template class PullLowpass: public LowpassBase Date: Thu, 1 Feb 2024 21:32:16 -0500 Subject: [PATCH 10/33] Use `std::fill`; update volume with slider. --- Components/AudioToggle/AudioToggle.cpp | 8 +++++--- Components/AudioToggle/AudioToggle.hpp | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Components/AudioToggle/AudioToggle.cpp b/Components/AudioToggle/AudioToggle.cpp index 7188badd9..ee8e2b417 100644 --- a/Components/AudioToggle/AudioToggle.cpp +++ b/Components/AudioToggle/AudioToggle.cpp @@ -8,19 +8,20 @@ #include "AudioToggle.hpp" +#include + using namespace Audio; Audio::Toggle::Toggle(Concurrency::AsyncTaskQueue &audio_queue) : audio_queue_(audio_queue) {} void Toggle::get_samples(std::size_t number_of_samples, std::int16_t *target) { - for(std::size_t sample = 0; sample < number_of_samples; ++sample) { - target[sample] = level_; - } + std::fill(target, target + number_of_samples, level_); } void Toggle::set_sample_volume_range(std::int16_t range) { volume_ = range; + level_ = level_active_ ? volume_ : 0; } void Toggle::skip_samples(std::size_t) {} @@ -29,6 +30,7 @@ void Toggle::set_output(bool enabled) { if(is_enabled_ == enabled) return; is_enabled_ = enabled; audio_queue_.enqueue([this, enabled] { + level_active_ = enabled; level_ = enabled ? volume_ : 0; }); } diff --git a/Components/AudioToggle/AudioToggle.hpp b/Components/AudioToggle/AudioToggle.hpp index 8209b4653..defd7a976 100644 --- a/Components/AudioToggle/AudioToggle.hpp +++ b/Components/AudioToggle/AudioToggle.hpp @@ -34,6 +34,7 @@ class Toggle: public Outputs::Speaker::SampleSource { // Accessed on the audio thread. int16_t level_ = 0, volume_ = 0; + bool level_active_ = false; }; } From c6c9be0b08be981f4be7fdc1d31f95c8f223d1b9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 1 Feb 2024 21:47:44 -0500 Subject: [PATCH 11/33] Adopt CRTP for SampleSource. --- Components/6560/6560.hpp | 2 +- Components/AY38910/AY38910.hpp | 2 +- Components/AudioToggle/AudioToggle.hpp | 3 ++- Components/KonamiSCC/KonamiSCC.hpp | 2 +- Components/OPx/Implementation/OPLBase.hpp | 2 +- Components/OPx/OPLL.hpp | 1 + Components/SN76489/SN76489.hpp | 2 +- Machines/Apple/AppleIIgs/Sound.hpp | 3 ++- Machines/Apple/Macintosh/Audio.hpp | 2 +- Machines/Atari/2600/TIASound.hpp | 2 +- Machines/Electron/SoundGenerator.hpp | 2 +- Machines/Enterprise/Dave.hpp | 2 +- Outputs/Speaker/Implementation/CompoundSource.hpp | 4 ++-- Outputs/Speaker/Implementation/SampleSource.hpp | 8 +++++++- 14 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index 5a55317eb..5bafeba65 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -17,7 +17,7 @@ namespace MOS::MOS6560 { // audio state -class AudioGenerator: public ::Outputs::Speaker::SampleSource { +class AudioGenerator: public ::Outputs::Speaker::SampleSource { public: AudioGenerator(Concurrency::AsyncTaskQueue &audio_queue); diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 90c5c9b1f..48dcf6277 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -66,7 +66,7 @@ enum class Personality { This AY has an attached mono or stereo mixer. */ -template class AY38910: public ::Outputs::Speaker::SampleSource { +template class AY38910: public ::Outputs::Speaker::SampleSource> { public: /// Creates a new AY38910. AY38910(Personality, Concurrency::AsyncTaskQueue &); diff --git a/Components/AudioToggle/AudioToggle.hpp b/Components/AudioToggle/AudioToggle.hpp index defd7a976..84cebb762 100644 --- a/Components/AudioToggle/AudioToggle.hpp +++ b/Components/AudioToggle/AudioToggle.hpp @@ -16,13 +16,14 @@ namespace Audio { /*! Provides a sample source that can programmatically be set to one of two values. */ -class Toggle: public Outputs::Speaker::SampleSource { +class Toggle: public Outputs::Speaker::SampleSource { public: Toggle(Concurrency::AsyncTaskQueue &audio_queue); void get_samples(std::size_t number_of_samples, std::int16_t *target); void set_sample_volume_range(std::int16_t range); void skip_samples(const std::size_t number_of_samples); + static constexpr bool is_stereo = false; void set_output(bool enabled); bool get_output() const; diff --git a/Components/KonamiSCC/KonamiSCC.hpp b/Components/KonamiSCC/KonamiSCC.hpp index a0cb3ca30..a8c767f79 100644 --- a/Components/KonamiSCC/KonamiSCC.hpp +++ b/Components/KonamiSCC/KonamiSCC.hpp @@ -20,7 +20,7 @@ namespace Konami { and five channels of output. The original SCC uses the same wave for channels four and five, the SCC+ supports different waves for the two channels. */ -class SCC: public ::Outputs::Speaker::SampleSource { +class SCC: public ::Outputs::Speaker::SampleSource { public: /// Creates a new SCC. SCC(Concurrency::AsyncTaskQueue &task_queue); diff --git a/Components/OPx/Implementation/OPLBase.hpp b/Components/OPx/Implementation/OPLBase.hpp index a3067395c..98bee264f 100644 --- a/Components/OPx/Implementation/OPLBase.hpp +++ b/Components/OPx/Implementation/OPLBase.hpp @@ -13,7 +13,7 @@ namespace Yamaha::OPL { -template class OPLBase: public ::Outputs::Speaker::SampleSource { +template class OPLBase: public ::Outputs::Speaker::SampleSource { public: void write(uint16_t address, uint8_t value) { if(address & 1) { diff --git a/Components/OPx/OPLL.hpp b/Components/OPx/OPLL.hpp index 284c9ade9..5323fbeaf 100644 --- a/Components/OPx/OPLL.hpp +++ b/Components/OPx/OPLL.hpp @@ -27,6 +27,7 @@ class OPLL: public OPLBase { /// As per ::SampleSource; provides audio output. void get_samples(std::size_t number_of_samples, std::int16_t *target); void set_sample_volume_range(std::int16_t range); + static constexpr bool is_stereo = false; // The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in // rhythm mode, but it's correct for melodic output. diff --git a/Components/SN76489/SN76489.hpp b/Components/SN76489/SN76489.hpp index d720ee256..820170f3b 100644 --- a/Components/SN76489/SN76489.hpp +++ b/Components/SN76489/SN76489.hpp @@ -13,7 +13,7 @@ namespace TI { -class SN76489: public Outputs::Speaker::SampleSource { +class SN76489: public Outputs::Speaker::SampleSource { public: enum class Personality { SN76489, diff --git a/Machines/Apple/AppleIIgs/Sound.hpp b/Machines/Apple/AppleIIgs/Sound.hpp index 55c4f4c20..2eed23a8f 100644 --- a/Machines/Apple/AppleIIgs/Sound.hpp +++ b/Machines/Apple/AppleIIgs/Sound.hpp @@ -16,7 +16,7 @@ namespace Apple::IIgs::Sound { -class GLU: public Outputs::Speaker::SampleSource { +class GLU: public Outputs::Speaker::SampleSource { public: GLU(Concurrency::AsyncTaskQueue &audio_queue); @@ -37,6 +37,7 @@ class GLU: public Outputs::Speaker::SampleSource { void get_samples(std::size_t number_of_samples, std::int16_t *target); void set_sample_volume_range(std::int16_t range); void skip_samples(const std::size_t number_of_samples); + static constexpr bool is_stereo = false; private: Concurrency::AsyncTaskQueue &audio_queue_; diff --git a/Machines/Apple/Macintosh/Audio.hpp b/Machines/Apple/Macintosh/Audio.hpp index 3eabbc832..e23bcb16b 100644 --- a/Machines/Apple/Macintosh/Audio.hpp +++ b/Machines/Apple/Macintosh/Audio.hpp @@ -23,7 +23,7 @@ namespace Apple::Macintosh { Designed to be clocked at half the rate of the real hardware — i.e. a shade less than 4Mhz. */ -class Audio: public ::Outputs::Speaker::SampleSource { +class Audio: public ::Outputs::Speaker::SampleSource