From d18013c82e6f5f2db964e7544576e8370b0fef07 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Thu, 31 Dec 2020 02:31:32 +0000 Subject: [PATCH 1/6] Add dispatch framework for the DECPS escape sequence. --- src/terminal/adapter/ITermDispatch.hpp | 2 ++ src/terminal/adapter/adaptDispatch.cpp | 11 +++++++++++ src/terminal/adapter/adaptDispatch.hpp | 2 ++ src/terminal/adapter/termDispatch.hpp | 2 ++ src/terminal/parser/OutputStateMachineEngine.cpp | 4 ++++ src/terminal/parser/OutputStateMachineEngine.hpp | 3 ++- src/terminal/parser/telemetry.cpp | 1 + src/terminal/parser/telemetry.hpp | 1 + 8 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index f2dcb1c2a8d..3a8905737eb 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -145,6 +145,8 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch const DispatchTypes::DrcsCharsetSize charsetSize) = 0; // DECDLD virtual StringHandler RequestSetting() = 0; // DECRQSS + + virtual bool PlaySounds(const VTParameters parameters) = 0; // DECPS }; inline Microsoft::Console::VirtualTerminal::ITermDispatch::~ITermDispatch() {} #pragma warning(pop) diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index bce951e5938..b34e20ebcc1 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -2686,3 +2686,14 @@ void AdaptDispatch::_ReportDECSTBMSetting() response.append(L"r\033\\"sv); _api.ReturnResponse({ response.data(), response.size() }); } + +// Routine Description: +// - DECPS - Plays a sequence of musical notes. +// Arguments: +// - params - The volume, duration, and note values to play. +// Return value: +// - True if handled successfully. False otherwise. +bool AdaptDispatch::PlaySounds(const VTParameters /*parameters*/) +{ + return false; +} diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index e132444182c..626434e69de 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -141,6 +141,8 @@ namespace Microsoft::Console::VirtualTerminal StringHandler RequestSetting() override; // DECRQSS + bool PlaySounds(const VTParameters parameters) override; // DECPS + private: enum class ScrollDirection { diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index ad724641160..a0c62883d38 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -138,6 +138,8 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons const DispatchTypes::DrcsCharsetSize /*charsetSize*/) override { return nullptr; } StringHandler RequestSetting() override { return nullptr; }; // DECRQSS + + bool PlaySounds(const VTParameters /*parameters*/) override { return false; }; // DECPS }; #pragma warning(default : 26440) // Restore "can be declared noexcept" warning diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 244e7c64979..bf0d38f779f 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -620,6 +620,10 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete success = _dispatch->AssignColor(parameters.at(0), parameters.at(1).value_or(0), parameters.at(2).value_or(0)); TermTelemetry::Instance().Log(TermTelemetry::Codes::DECAC); break; + case CsiActionCodes::DECPS_PlaySound: + success = _dispatch->PlaySounds(parameters); + TermTelemetry::Instance().Log(TermTelemetry::Codes::DECPS); + break; default: // If no functions to call, overall dispatch was a failure. success = false; diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index 20083b474d8..548770f0989 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -142,7 +142,8 @@ namespace Microsoft::Console::VirtualTerminal XT_PushSgr = VTID("#{"), XT_PopSgr = VTID("#}"), DECSCPP_SetColumnsPerPage = VTID("$|"), - DECAC_AssignColor = VTID(",|") + DECAC_AssignColor = VTID(",|"), + DECPS_PlaySound = VTID(",~") }; enum DcsActionCodes : uint64_t diff --git a/src/terminal/parser/telemetry.cpp b/src/terminal/parser/telemetry.cpp index a40711ef506..c394faaf64b 100644 --- a/src/terminal/parser/telemetry.cpp +++ b/src/terminal/parser/telemetry.cpp @@ -281,6 +281,7 @@ void TermTelemetry::WriteFinalTraceLog() const TraceLoggingUInt32(_uiTimesUsed[XTPUSHSGR], "XTPUSHSGR"), TraceLoggingUInt32(_uiTimesUsed[XTPOPSGR], "XTPOPSGR"), TraceLoggingUInt32(_uiTimesUsed[DECAC], "DECAC"), + TraceLoggingUInt32(_uiTimesUsed[DECPS], "DECPS"), TraceLoggingUInt32Array(_uiTimesFailed, ARRAYSIZE(_uiTimesFailed), "Failed"), TraceLoggingUInt32(_uiTimesFailedOutsideRange, "FailedOutsideRange")); } diff --git a/src/terminal/parser/telemetry.hpp b/src/terminal/parser/telemetry.hpp index 196d13016bd..5e258d62d7b 100644 --- a/src/terminal/parser/telemetry.hpp +++ b/src/terminal/parser/telemetry.hpp @@ -108,6 +108,7 @@ namespace Microsoft::Console::VirtualTerminal XTPUSHSGR, XTPOPSGR, DECAC, + DECPS, // Only use this last enum as a count of the number of codes. NUMBER_OF_CODES }; From 13f7eb5c29cc11a58383dc441b6e0d314e5edd65 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Fri, 29 Jan 2021 00:46:37 +0000 Subject: [PATCH 2/6] Add a shared MIDI library for playing musical notes. --- OpenConsole.sln | 46 +++++++++++ src/audio/midi/MidiAudio.cpp | 114 +++++++++++++++++++++++++++ src/audio/midi/MidiAudio.hpp | 36 +++++++++ src/audio/midi/lib/midi.vcxproj | 26 ++++++ src/audio/midi/precomp.cpp | 4 + src/audio/midi/precomp.h | 30 +++++++ src/terminal/parser/stateMachine.cpp | 38 +++++---- src/terminal/parser/stateMachine.hpp | 41 ++++++---- 8 files changed, 301 insertions(+), 34 deletions(-) create mode 100644 src/audio/midi/MidiAudio.cpp create mode 100644 src/audio/midi/MidiAudio.hpp create mode 100644 src/audio/midi/lib/midi.vcxproj create mode 100644 src/audio/midi/precomp.cpp create mode 100644 src/audio/midi/precomp.h diff --git a/OpenConsole.sln b/OpenConsole.sln index df144adc2de..f13e0cb8b2b 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -410,6 +410,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InteractivityOneCore", "src EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererWddmCon", "src\renderer\wddmcon\lib\wddmcon.vcxproj", "{75C6F576-18E9-4566-978A-F0A301CAC090}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Audio", "Audio", "{40BD8415-DD93-4200-8D82-498DDDC08CC8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MidiAudio", "src\audio\midi\lib\midi.vcxproj", "{3C67784E-1453-49C2-9660-483E2CC7F7AD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AuditMode|Any CPU = AuditMode|Any CPU @@ -3449,6 +3453,46 @@ Global {75C6F576-18E9-4566-978A-F0A301CAC090}.Release|x64.Build.0 = Release|x64 {75C6F576-18E9-4566-978A-F0A301CAC090}.Release|x86.ActiveCfg = Release|Win32 {75C6F576-18E9-4566-978A-F0A301CAC090}.Release|x86.Build.0 = Release|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|x64.ActiveCfg = AuditMode|x64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|x64.Build.0 = AuditMode|x64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|x86.ActiveCfg = AuditMode|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|x86.Build.0 = AuditMode|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|ARM.ActiveCfg = Debug|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|ARM64.Build.0 = Debug|ARM64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|x64.ActiveCfg = Debug|x64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|x64.Build.0 = Debug|x64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|x86.ActiveCfg = Debug|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|x86.Build.0 = Debug|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|DotNet_x64Test.ActiveCfg = Fuzzing|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|DotNet_x86Test.ActiveCfg = Fuzzing|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|x64.Build.0 = Fuzzing|x64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|x86.Build.0 = Fuzzing|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|Any CPU.ActiveCfg = Release|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|ARM.ActiveCfg = Release|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|ARM64.ActiveCfg = Release|ARM64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|ARM64.Build.0 = Release|ARM64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|DotNet_x64Test.ActiveCfg = Release|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|DotNet_x86Test.ActiveCfg = Release|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|x64.ActiveCfg = Release|x64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|x64.Build.0 = Release|x64 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|x86.ActiveCfg = Release|Win32 + {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -3552,6 +3596,8 @@ Global {8222900C-8B6C-452A-91AC-BE95DB04B95F} = {05500DEF-2294-41E3-AF9A-24E580B82836} {06EC74CB-9A12-428C-B551-8537EC964726} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} {75C6F576-18E9-4566-978A-F0A301CAC090} = {05500DEF-2294-41E3-AF9A-24E580B82836} + {40BD8415-DD93-4200-8D82-498DDDC08CC8} = {89CDCC5C-9F53-4054-97A4-639D99F169CD} + {3C67784E-1453-49C2-9660-483E2CC7F7AD} = {40BD8415-DD93-4200-8D82-498DDDC08CC8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271} diff --git a/src/audio/midi/MidiAudio.cpp b/src/audio/midi/MidiAudio.cpp new file mode 100644 index 00000000000..a8af035f288 --- /dev/null +++ b/src/audio/midi/MidiAudio.cpp @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" +#include "MidiAudio.hpp" +#include "../terminal/parser/stateMachine.hpp" + +namespace +{ + class MidiOut + { + public: + static constexpr auto NOTE_OFF = 0x80; + static constexpr auto NOTE_ON = 0x90; + static constexpr auto PROGRAM_CHANGE = 0xC0; + + // We're using a square wave as an approximation of the sound that the + // original VT525 terminals might have produced. This is probably not + // quite right, but it works reasonably well. + static constexpr auto SQUARE_WAVE_SYNTH = 80; + + MidiOut() noexcept + { + midiOutOpen(&handle, MIDI_MAPPER, NULL, NULL, CALLBACK_NULL); + OutputMessage(PROGRAM_CHANGE, SQUARE_WAVE_SYNTH); + } + ~MidiOut() noexcept + { + midiOutClose(handle); + } + void OutputMessage(const int b1, const int b2, const int b3 = 0, const int b4 = 0) noexcept + { + midiOutShortMsg(handle, MAKELONG(MAKEWORD(b1, b2), MAKEWORD(b3, b4))); + } + + MidiOut(const MidiOut&) = delete; + MidiOut(MidiOut&&) = delete; + MidiOut& operator=(const MidiOut&) = delete; + MidiOut& operator=(MidiOut&&) = delete; + + private: + HMIDIOUT handle = nullptr; + }; +} + +using namespace std::chrono_literals; + +MidiAudio::~MidiAudio() noexcept +{ + try + { +#pragma warning(suppress : 26447) + // We acquire the lock here so the class isn't destroyed while in use. + // If this throws, we'll catch it, so the C26447 warning is bogus. + _inUseMutex.lock(); + } + catch (...) + { + // If the lock fails, we'll just have to live with the consequences. + } +} + +void MidiAudio::Initialize() +{ + _shutdownFuture = _shutdownPromise.get_future(); +} + +void MidiAudio::Shutdown() +{ + // Once the shutdown promise is set, any note that is playing will stop + // immediately, and the Unlock call will exit the thread ASAP. + _shutdownPromise.set_value(); +} + +void MidiAudio::Lock() +{ + _inUseMutex.lock(); +} + +void MidiAudio::Unlock() +{ + // We need to check the shutdown status before releasing the mutex, + // because after that the class could be destroyed. + const auto shutdownStatus = _shutdownFuture.wait_for(0s); + _inUseMutex.unlock(); + // If the wait didn't timeout, that means the shutdown promise was set, + // so we need to exit the thread ASAP by throwing an exception. + if (shutdownStatus != std::future_status::timeout) + { + throw Microsoft::Console::VirtualTerminal::StateMachine::ShutdownException{}; + } +} + +void MidiAudio::PlayNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) +{ + // The MidiOut is a local static because we can only have one instance, + // and we only want to construct it when it's actually needed. + static MidiOut midiOut; + + if (velocity) + { + midiOut.OutputMessage(MidiOut::NOTE_ON, noteNumber, velocity); + } + + // By waiting on the shutdown future with the duration of the note, we'll + // either be paused for the appropriate amount of time, or we'll break out + // of the wait early if we've been shutdown. + _shutdownFuture.wait_for(duration); + + if (velocity) + { + midiOut.OutputMessage(MidiOut::NOTE_OFF, noteNumber, velocity); + } +} diff --git a/src/audio/midi/MidiAudio.hpp b/src/audio/midi/MidiAudio.hpp new file mode 100644 index 00000000000..426455e358a --- /dev/null +++ b/src/audio/midi/MidiAudio.hpp @@ -0,0 +1,36 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- MidiAudio.hpp + +Abstract: + This modules provide basic MIDI support with blocking sound output. + */ + +#pragma once + +#include +#include + +class MidiAudio +{ +public: + MidiAudio() = default; + MidiAudio(const MidiAudio&) = delete; + MidiAudio(MidiAudio&&) = delete; + MidiAudio& operator=(const MidiAudio&) = delete; + MidiAudio& operator=(MidiAudio&&) = delete; + ~MidiAudio() noexcept; + void Initialize(); + void Shutdown(); + void Lock(); + void Unlock(); + void PlayNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration); + +private: + std::promise _shutdownPromise; + std::future _shutdownFuture; + std::mutex _inUseMutex; +}; diff --git a/src/audio/midi/lib/midi.vcxproj b/src/audio/midi/lib/midi.vcxproj new file mode 100644 index 00000000000..e6e7318a45b --- /dev/null +++ b/src/audio/midi/lib/midi.vcxproj @@ -0,0 +1,26 @@ + + + + {3c67784e-1453-49c2-9660-483e2cc7f7ad} + Win32Proj + midi + MidiAudio + MidiAudio + StaticLibrary + + + + + + + Create + + + + + + + + + + diff --git a/src/audio/midi/precomp.cpp b/src/audio/midi/precomp.cpp new file mode 100644 index 00000000000..c51e9b31b2f --- /dev/null +++ b/src/audio/midi/precomp.cpp @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" diff --git a/src/audio/midi/precomp.h b/src/audio/midi/precomp.h new file mode 100644 index 00000000000..7e6eb17ddb8 --- /dev/null +++ b/src/audio/midi/precomp.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- precomp.h + +Abstract: +- Contains external headers to include in the precompile phase of console build process. +- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building). +--*/ + +#pragma once + +// clang-format off + +// This includes support libraries from the CRT, STL, WIL, and GSL +#include "LibraryIncludes.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#define NOMCX +#define NOHELP +#define NOCOMM +#endif + +// Windows Header Files: +#include + +// clang-format on diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 3a24b6f7b40..4fe3b58cbf3 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -385,7 +385,7 @@ static constexpr bool _isActionableFromGround(const wchar_t wch) noexcept // - wch - Character to dispatch. // Return Value: // - -void StateMachine::_ActionExecute(const wchar_t wch) noexcept +void StateMachine::_ActionExecute(const wchar_t wch) { _trace.TraceOnExecute(wch); _trace.DispatchSequenceTrace(_SafeExecute([=]() { @@ -401,7 +401,7 @@ void StateMachine::_ActionExecute(const wchar_t wch) noexcept // - wch - Character to dispatch. // Return Value: // - -void StateMachine::_ActionExecuteFromEscape(const wchar_t wch) noexcept +void StateMachine::_ActionExecuteFromEscape(const wchar_t wch) { _trace.TraceOnExecuteFromEscape(wch); _trace.DispatchSequenceTrace(_SafeExecute([=]() { @@ -415,7 +415,7 @@ void StateMachine::_ActionExecuteFromEscape(const wchar_t wch) noexcept // - wch - Character to dispatch. // Return Value: // - -void StateMachine::_ActionPrint(const wchar_t wch) noexcept +void StateMachine::_ActionPrint(const wchar_t wch) { _trace.TraceOnAction(L"Print"); _trace.DispatchSequenceTrace(_SafeExecute([=]() { @@ -444,7 +444,7 @@ void StateMachine::_ActionPrintString(const std::wstring_view string) // - wch - Character to dispatch. // Return Value: // - -void StateMachine::_ActionEscDispatch(const wchar_t wch) noexcept +void StateMachine::_ActionEscDispatch(const wchar_t wch) { _trace.TraceOnAction(L"EscDispatch"); _trace.DispatchSequenceTrace(_SafeExecuteWithLog(wch, [=]() { @@ -459,7 +459,7 @@ void StateMachine::_ActionEscDispatch(const wchar_t wch) noexcept // - wch - Character to dispatch. // Return Value: // - -void StateMachine::_ActionVt52EscDispatch(const wchar_t wch) noexcept +void StateMachine::_ActionVt52EscDispatch(const wchar_t wch) { _trace.TraceOnAction(L"Vt52EscDispatch"); _trace.DispatchSequenceTrace(_SafeExecuteWithLog(wch, [=]() { @@ -474,7 +474,7 @@ void StateMachine::_ActionVt52EscDispatch(const wchar_t wch) noexcept // - wch - Character to dispatch. // Return Value: // - -void StateMachine::_ActionCsiDispatch(const wchar_t wch) noexcept +void StateMachine::_ActionCsiDispatch(const wchar_t wch) { _trace.TraceOnAction(L"CsiDispatch"); _trace.DispatchSequenceTrace(_SafeExecuteWithLog(wch, [=]() { @@ -632,7 +632,7 @@ void StateMachine::_ActionOscPut(const wchar_t wch) // - wch - Character to dispatch. // Return Value: // - -void StateMachine::_ActionOscDispatch(const wchar_t wch) noexcept +void StateMachine::_ActionOscDispatch(const wchar_t wch) { _trace.TraceOnAction(L"OscDispatch"); _trace.DispatchSequenceTrace(_SafeExecuteWithLog(wch, [=]() { @@ -647,7 +647,7 @@ void StateMachine::_ActionOscDispatch(const wchar_t wch) noexcept // - wch - Character to dispatch. // Return Value: // - -void StateMachine::_ActionSs3Dispatch(const wchar_t wch) noexcept +void StateMachine::_ActionSs3Dispatch(const wchar_t wch) { _trace.TraceOnAction(L"Ss3Dispatch"); _trace.DispatchSequenceTrace(_SafeExecuteWithLog(wch, [=]() { @@ -662,7 +662,7 @@ void StateMachine::_ActionSs3Dispatch(const wchar_t wch) noexcept // - wch - Character to dispatch. // Return Value: // - -void StateMachine::_ActionDcsDispatch(const wchar_t wch) noexcept +void StateMachine::_ActionDcsDispatch(const wchar_t wch) { _trace.TraceOnAction(L"DcsDispatch"); @@ -978,7 +978,7 @@ void StateMachine::_EnterSosPmApcString() noexcept // - wch - Character that triggered the event // Return Value: // - -void StateMachine::_EventGround(const wchar_t wch) noexcept +void StateMachine::_EventGround(const wchar_t wch) { _trace.TraceOnEvent(L"Ground"); if (_isC0Code(wch) || _isDelete(wch)) @@ -1093,7 +1093,7 @@ void StateMachine::_EventEscape(const wchar_t wch) // - wch - Character that triggered the event // Return Value: // - -void StateMachine::_EventEscapeIntermediate(const wchar_t wch) noexcept +void StateMachine::_EventEscapeIntermediate(const wchar_t wch) { _trace.TraceOnEvent(L"EscapeIntermediate"); if (_isC0Code(wch)) @@ -1187,7 +1187,7 @@ void StateMachine::_EventCsiEntry(const wchar_t wch) // - wch - Character that triggered the event // Return Value: // - -void StateMachine::_EventCsiIntermediate(const wchar_t wch) noexcept +void StateMachine::_EventCsiIntermediate(const wchar_t wch) { _trace.TraceOnEvent(L"CsiIntermediate"); if (_isC0Code(wch)) @@ -1225,7 +1225,7 @@ void StateMachine::_EventCsiIntermediate(const wchar_t wch) noexcept // - wch - Character that triggered the event // Return Value: // - -void StateMachine::_EventCsiIgnore(const wchar_t wch) noexcept +void StateMachine::_EventCsiIgnore(const wchar_t wch) { _trace.TraceOnEvent(L"CsiIgnore"); if (_isC0Code(wch)) @@ -1574,7 +1574,7 @@ void StateMachine::_EventDcsIgnore() noexcept // - wch - Character that triggered the event // Return Value: // - -void StateMachine::_EventDcsIntermediate(const wchar_t wch) noexcept +void StateMachine::_EventDcsIntermediate(const wchar_t wch) { _trace.TraceOnEvent(L"DcsIntermediate"); if (_isC0Code(wch)) @@ -1786,7 +1786,7 @@ void StateMachine::ProcessCharacter(const wchar_t wch) // - // Return Value: // - true if the engine successfully handled the string. -bool StateMachine::FlushToTerminal() noexcept +bool StateMachine::FlushToTerminal() { auto success{ true }; @@ -2012,11 +2012,15 @@ void StateMachine::_AccumulateTo(const wchar_t wch, VTInt& value) noexcept } template -bool StateMachine::_SafeExecute(TLambda&& lambda) noexcept +bool StateMachine::_SafeExecute(TLambda&& lambda) try { return lambda(); } +catch (const ShutdownException&) +{ + throw; +} catch (...) { LOG_HR(wil::ResultFromCaughtException()); @@ -2024,7 +2028,7 @@ catch (...) } template -bool StateMachine::_SafeExecuteWithLog(const wchar_t wch, TLambda&& lambda) noexcept +bool StateMachine::_SafeExecuteWithLog(const wchar_t wch, TLambda&& lambda) { const bool success = _SafeExecute(std::forward(lambda)); if (!success) diff --git a/src/terminal/parser/stateMachine.hpp b/src/terminal/parser/stateMachine.hpp index e4b2bfccae5..193734e27e2 100644 --- a/src/terminal/parser/stateMachine.hpp +++ b/src/terminal/parser/stateMachine.hpp @@ -61,26 +61,33 @@ namespace Microsoft::Console::VirtualTerminal void ResetState() noexcept; - bool FlushToTerminal() noexcept; + bool FlushToTerminal(); const IStateMachineEngine& Engine() const noexcept; IStateMachineEngine& Engine() noexcept; + class ShutdownException : public wil::ResultException + { + public: + ShutdownException() noexcept : + ResultException(E_ABORT) {} + }; + private: - void _ActionExecute(const wchar_t wch) noexcept; - void _ActionExecuteFromEscape(const wchar_t wch) noexcept; - void _ActionPrint(const wchar_t wch) noexcept; + void _ActionExecute(const wchar_t wch); + void _ActionExecuteFromEscape(const wchar_t wch); + void _ActionPrint(const wchar_t wch); void _ActionPrintString(const std::wstring_view string); - void _ActionEscDispatch(const wchar_t wch) noexcept; - void _ActionVt52EscDispatch(const wchar_t wch) noexcept; + void _ActionEscDispatch(const wchar_t wch); + void _ActionVt52EscDispatch(const wchar_t wch); void _ActionCollect(const wchar_t wch) noexcept; void _ActionParam(const wchar_t wch); - void _ActionCsiDispatch(const wchar_t wch) noexcept; + void _ActionCsiDispatch(const wchar_t wch); void _ActionOscParam(const wchar_t wch) noexcept; void _ActionOscPut(const wchar_t wch); - void _ActionOscDispatch(const wchar_t wch) noexcept; - void _ActionSs3Dispatch(const wchar_t wch) noexcept; - void _ActionDcsDispatch(const wchar_t wch) noexcept; + void _ActionOscDispatch(const wchar_t wch); + void _ActionSs3Dispatch(const wchar_t wch); + void _ActionDcsDispatch(const wchar_t wch); void _ActionClear(); void _ActionIgnore() noexcept; @@ -106,12 +113,12 @@ namespace Microsoft::Console::VirtualTerminal void _EnterDcsPassThrough() noexcept; void _EnterSosPmApcString() noexcept; - void _EventGround(const wchar_t wch) noexcept; + void _EventGround(const wchar_t wch); void _EventEscape(const wchar_t wch); - void _EventEscapeIntermediate(const wchar_t wch) noexcept; + void _EventEscapeIntermediate(const wchar_t wch); void _EventCsiEntry(const wchar_t wch); - void _EventCsiIntermediate(const wchar_t wch) noexcept; - void _EventCsiIgnore(const wchar_t wch) noexcept; + void _EventCsiIntermediate(const wchar_t wch); + void _EventCsiIgnore(const wchar_t wch); void _EventCsiParam(const wchar_t wch); void _EventOscParam(const wchar_t wch) noexcept; void _EventOscString(const wchar_t wch); @@ -121,7 +128,7 @@ namespace Microsoft::Console::VirtualTerminal void _EventVt52Param(const wchar_t wch); void _EventDcsEntry(const wchar_t wch); void _EventDcsIgnore() noexcept; - void _EventDcsIntermediate(const wchar_t wch) noexcept; + void _EventDcsIntermediate(const wchar_t wch); void _EventDcsParam(const wchar_t wch); void _EventDcsPassThrough(const wchar_t wch); void _EventSosPmApcString(const wchar_t wch) noexcept; @@ -129,9 +136,9 @@ namespace Microsoft::Console::VirtualTerminal void _AccumulateTo(const wchar_t wch, VTInt& value) noexcept; template - bool _SafeExecute(TLambda&& lambda) noexcept; + bool _SafeExecute(TLambda&& lambda); template - bool _SafeExecuteWithLog(const wchar_t wch, TLambda&& lambda) noexcept; + bool _SafeExecuteWithLog(const wchar_t wch, TLambda&& lambda); enum class VTStates { From 852401e8120083c39951b911f0dc7acc4ba676b2 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Sat, 30 Jan 2021 19:22:51 +0000 Subject: [PATCH 3/6] Implement the conhost handler for the DECPS sequence. --- src/cascadia/TerminalCore/Terminal.hpp | 1 + src/cascadia/TerminalCore/TerminalApi.cpp | 4 +++ src/host/consoleInformation.cpp | 34 +++++++++++++++++++ src/host/host-common.vcxitems | 5 +++ src/host/output.cpp | 4 ++- src/host/outputStream.cpp | 29 ++++++++++++++++ src/host/outputStream.hpp | 1 + src/host/server.h | 5 +++ src/terminal/adapter/ITerminalApi.hpp | 1 + src/terminal/adapter/adaptDispatch.cpp | 29 ++++++++++++++-- .../adapter/ut_adapter/adapterTest.cpp | 5 +++ 11 files changed, 115 insertions(+), 3 deletions(-) diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 635993d4238..af6a5cd35f0 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -124,6 +124,7 @@ class Microsoft::Terminal::Core::Terminal final : void CopyToClipboard(std::wstring_view content) override; void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override; void SetWorkingDirectory(std::wstring_view uri) override; + void PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) override; void ShowWindow(bool showOrHide) override; void UseAlternateScreenBuffer() override; void UseMainScreenBuffer() override; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index f888cfa67f9..214805f4e9d 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -188,6 +188,10 @@ void Terminal::SetWorkingDirectory(std::wstring_view uri) _workingDirectory = uri; } +void Terminal::PlayMidiNote(const int /*noteNumber*/, const int /*velocity*/, const std::chrono::microseconds /*duration*/) +{ +} + void Terminal::UseAlternateScreenBuffer() { // the new alt buffer is exactly the size of the viewport. diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index ba812189c87..d5399b3e764 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -372,6 +372,40 @@ Microsoft::Console::CursorBlinker& CONSOLE_INFORMATION::GetCursorBlinker() noexc return _blinker; } +// Method Description: +// - Returns the MIDI audio instance, created on demand. +// Arguments: +// - +// Return Value: +// - a reference to the MidiAudio instance. +MidiAudio& CONSOLE_INFORMATION::GetMidiAudio() +{ + if (!_midiAudio) + { + _midiAudio = std::make_unique(); + _midiAudio->Initialize(); + } + return *_midiAudio; +} + +// Method Description: +// - Shuts down the MIDI audio system if previously instantiated. +// Arguments: +// - +// Return Value: +// - +void CONSOLE_INFORMATION::ShutdownMidiAudio() +{ + if (_midiAudio) + { + // We lock the console here to make sure the shutdown promise is + // set before the audio is unlocked in the thread that is playing. + LockConsole(); + _midiAudio->Shutdown(); + UnlockConsole(); + } +} + // Method Description: // - Generates a CHAR_INFO for this output cell, using the TextAttribute // GetLegacyAttributes method to generate the legacy style attributes. diff --git a/src/host/host-common.vcxitems b/src/host/host-common.vcxitems index fdd645c5940..af33f9b31ae 100644 --- a/src/host/host-common.vcxitems +++ b/src/host/host-common.vcxitems @@ -118,6 +118,11 @@ + + + {3c67784e-1453-49c2-9660-483e2cc7f7ad} + + $(IntDir)..\OpenConsoleProxy;%(AdditionalIncludeDirectories) diff --git a/src/host/output.cpp b/src/host/output.cpp index 64716ca3c02..e2138963d5c 100644 --- a/src/host/output.cpp +++ b/src/host/output.cpp @@ -499,7 +499,7 @@ void SetActiveScreenBuffer(SCREEN_INFORMATION& screenInfo) // TODO: MSFT 9450717 This should join the ProcessList class when CtrlEvents become moved into the server. https://osgvsowi/9450717 void CloseConsoleProcessState() { - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); // If there are no connected processes, sending control events is pointless as there's no one do send them to. In // this case we'll just exit conhost. @@ -512,6 +512,8 @@ void CloseConsoleProcessState() HandleCtrlEvent(CTRL_CLOSE_EVENT); + gci.ShutdownMidiAudio(); + // Jiggle the handle: (see MSFT:19419231) // When we call this function, we'll only actually close the console once // we're totally unlocked. If our caller has the console locked, great, diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 82f2d871aa3..e355248d52d 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -7,6 +7,7 @@ #include "_stream.h" #include "getset.h" +#include "handle.h" #include "directio.h" #include "output.h" @@ -355,6 +356,34 @@ void ConhostInternalGetSet::SetWorkingDirectory(const std::wstring_view /*uri*/) { } +// Routine Description: +// - Plays a single MIDI note, blocking for the duration. +// Arguments: +// - noteNumber - The MIDI note number to be played (0 - 127). +// - velocity - The force with which the note should be played (0 - 127). +// - duration - How long the note should be sustained (in milliseconds). +// Return value: +// - true if successful. false otherwise. +void ConhostInternalGetSet::PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) +{ + // We create the audio instance on demand, and lock it for the duration + // of the note output so it can't be destroyed while in use. + auto& midiAudio = ServiceLocator::LocateGlobals().getConsoleInformation().GetMidiAudio(); + midiAudio.Lock(); + + // We then unlock the console, so the UI doesn't hang while we're busy. + UnlockConsole(); + + // This call will block for the duration, unless shutdown early. + midiAudio.PlayNote(noteNumber, velocity, duration); + + // Once complete, we reacquire the console lock and unlock the audio. + // If the console has shutdown in the meantime, the Unlock call + // will throw an exception, forcing the thread to exit ASAP. + LockConsole(); + midiAudio.Unlock(); +} + // Routine Description: // - Resizes the window to the specified dimensions, in characters. // Arguments: diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp index 79316f78ea5..0bb642caeac 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -67,6 +67,7 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal:: void CopyToClipboard(const std::wstring_view content) override; void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override; void SetWorkingDirectory(const std::wstring_view uri) override; + void PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) override; bool IsConsolePty() const override; bool IsVtInputEnabled() const override; diff --git a/src/host/server.h b/src/host/server.h index 1a7ca9ba821..1489caf19d8 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -28,6 +28,7 @@ Revision History: #include "../server/WaitQueue.h" #include "../host/RenderData.hpp" +#include "../audio/midi/MidiAudio.hpp" // clang-format off // Flags flags @@ -138,6 +139,9 @@ class CONSOLE_INFORMATION : friend class CommonState; Microsoft::Console::CursorBlinker& GetCursorBlinker() noexcept; + MidiAudio& GetMidiAudio(); + void ShutdownMidiAudio(); + CHAR_INFO AsCharInfo(const OutputCellView& cell) const noexcept; RenderData renderData; @@ -153,6 +157,7 @@ class CONSOLE_INFORMATION : Microsoft::Console::VirtualTerminal::VtIo _vtIo; Microsoft::Console::CursorBlinker _blinker; + std::unique_ptr _midiAudio; }; #define ConsoleLocked() (ServiceLocator::LocateGlobals()->getConsoleInformation()->ConsoleLock.OwningThread == NtCurrentTeb()->ClientId.UniqueThread) diff --git a/src/terminal/adapter/ITerminalApi.hpp b/src/terminal/adapter/ITerminalApi.hpp index c2f0d7f71e9..c805ec6e128 100644 --- a/src/terminal/adapter/ITerminalApi.hpp +++ b/src/terminal/adapter/ITerminalApi.hpp @@ -70,6 +70,7 @@ namespace Microsoft::Console::VirtualTerminal virtual void CopyToClipboard(const std::wstring_view content) = 0; virtual void SetTaskbarProgress(const DispatchTypes::TaskbarState state, const size_t progress) = 0; virtual void SetWorkingDirectory(const std::wstring_view uri) = 0; + virtual void PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) = 0; virtual bool ResizeWindow(const size_t width, const size_t height) = 0; virtual bool IsConsolePty() const = 0; diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index b34e20ebcc1..4d63b29338a 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -2693,7 +2693,32 @@ void AdaptDispatch::_ReportDECSTBMSetting() // - params - The volume, duration, and note values to play. // Return value: // - True if handled successfully. False otherwise. -bool AdaptDispatch::PlaySounds(const VTParameters /*parameters*/) +bool AdaptDispatch::PlaySounds(const VTParameters parameters) { - return false; + // If we're a conpty, we return false so the command will be passed on + // to the connected terminal. But we need to flush the current frame + // first, otherwise the visual output will lag behind the sound. + if (_api.IsConsolePty()) + { + _renderer.TriggerFlush(false); + return false; + } + + // First parameter is the volume, in the range 0 to 7. We multiply by + // 127 / 7 to obtain an equivalent MIDI velocity in the range 0 to 127. + const auto velocity = std::min(parameters.at(0).value_or(0), 7) * 127 / 7; + // Second parameter is the duration, in the range 0 to 255. Units are + // 1/32 of a second, so we multiply by 1000000us/32 to obtain microseconds. + using namespace std::chrono_literals; + const auto duration = std::min(parameters.at(1).value_or(0), 255) * 1000000us / 32; + // The subsequent parameters are notes, in the range 0 to 25. + return parameters.subspan(2).for_each([=](const auto param) { + // Values 1 to 25 represent the notes C5 to C7, so we add 71 to + // obtain the equivalent MIDI note numbers (72 = C5). + const auto noteNumber = std::min(param.value_or(0), 25) + 71; + // But value 0 is meant to be silent, so if the note number is 71, + // we set the velocity to 0 (i.e. no volume). + _api.PlayMidiNote(noteNumber, noteNumber == 71 ? 0 : velocity, duration); + return true; + }); } diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index ae1a926d54e..9519de3a1a7 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -221,6 +221,11 @@ class TestGetSet final : public ITerminalApi Log::Comment(L"SetWorkingDirectory MOCK called..."); } + void PlayMidiNote(const int /*noteNumber*/, const int /*velocity*/, const std::chrono::microseconds /*duration*/) override + { + Log::Comment(L"PlayMidiNote MOCK called..."); + } + bool IsConsolePty() const override { Log::Comment(L"IsConsolePty MOCK called..."); From 9405fc4e6082ce1fd463707f2b512899465fe11d Mon Sep 17 00:00:00 2001 From: James Holderness Date: Tue, 2 Feb 2021 11:49:21 +0000 Subject: [PATCH 4/6] Implement the Terminal handler for the DECPS sequence. --- src/cascadia/TerminalControl/ControlCore.cpp | 79 ++++++++++++++++++- src/cascadia/TerminalControl/ControlCore.h | 9 +++ .../TerminalControlLib.vcxproj | 4 + src/cascadia/TerminalCore/Terminal.cpp | 18 +++++ src/cascadia/TerminalCore/Terminal.hpp | 3 + src/cascadia/TerminalCore/TerminalApi.cpp | 3 +- 6 files changed, 112 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 5bbe2cc367f..a224ca22c4d 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -105,6 +105,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto pfnShowWindowChanged = std::bind(&ControlCore::_terminalShowWindowChanged, this, std::placeholders::_1); _terminal->SetShowWindowCallback(pfnShowWindowChanged); + auto pfnPlayMidiNote = std::bind(&ControlCore::_terminalPlayMidiNote, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); + _terminal->SetPlayMidiNoteCallback(pfnPlayMidiNote); + // MSFT 33353327: Initialize the renderer in the ctor instead of Initialize(). // We need the renderer to be ready to accept new engines before the SwapChainPanel is ready to go. // If we wait, a screen reader may try to get the AutomationPeer (aka the UIA Engine), and we won't be able to attach @@ -201,6 +204,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _renderer->TriggerTeardown(); } + + _shutdownMidiAudio(); } bool ControlCore::Initialize(const double actualWidth, @@ -1223,6 +1228,66 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } + // Method Description: + // - Plays a single MIDI note, blocking for the duration. + // Arguments: + // - noteNumber - The MIDI note number to be played (0 - 127). + // - velocity - The force with which the note should be played (0 - 127). + // - duration - How long the note should be sustained (in microseconds). + void ControlCore::_terminalPlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) + { + // We create the audio instance on demand, and lock it for the duration + // of the note output so it can't be destroyed while in use. + auto& midiAudio = _getMidiAudio(); + midiAudio.Lock(); + + // We then unlock the terminal, so the UI doesn't hang while we're busy. + auto& terminalLock = _terminal->GetReadWriteLock(); + terminalLock.unlock(); + + // This call will block for the duration, unless shutdown early. + midiAudio.PlayNote(noteNumber, velocity, duration); + + // Once complete, we reacquire the terminal lock and unlock the audio. + // If the terminal has shutdown in the meantime, the Unlock call + // will throw an exception, forcing the thread to exit ASAP. + terminalLock.lock(); + midiAudio.Unlock(); + } + + // Method Description: + // - Returns the MIDI audio instance, created on demand. + // Arguments: + // - + // Return Value: + // - a reference to the MidiAudio instance. + MidiAudio& ControlCore::_getMidiAudio() + { + if (!_midiAudio) + { + _midiAudio = std::make_unique(); + _midiAudio->Initialize(); + } + return *_midiAudio; + } + + // Method Description: + // - Shuts down the MIDI audio system if previously instantiated. + // Arguments: + // - + // Return Value: + // - + void ControlCore::_shutdownMidiAudio() + { + if (_midiAudio) + { + // We lock the terminal here to make sure the shutdown promise is + // set before the audio is unlocked in the thread that is playing. + auto lock = _terminal->LockForWriting(); + _midiAudio->Shutdown(); + } + } + bool ControlCore::HasSelection() const { return _terminal->IsSelectionActive(); @@ -1513,10 +1578,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation } void ControlCore::_connectionOutputHandler(const hstring& hstr) { - _terminal->Write(hstr); + try + { + _terminal->Write(hstr); - // Start the throttled update of where our hyperlinks are. - _updatePatternLocations->Run(); + // Start the throttled update of where our hyperlinks are. + _updatePatternLocations->Run(); + } + catch (...) + { + // We're expecting to receive an exception here if the terminal + // is closed while we're blocked playing a MIDI note. + } } // Method Description: diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 9593ee0c5a2..9cf4355a6ce 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -17,6 +17,7 @@ #include "ControlCore.g.h" #include "ControlSettings.h" +#include "../../audio/midi/MidiAudio.hpp" #include "../../renderer/base/Renderer.hpp" #include "../../cascadia/TerminalCore/Terminal.hpp" #include "../buffer/out/search.h" @@ -273,8 +274,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _terminalCursorPositionChanged(); void _terminalTaskbarProgressChanged(); void _terminalShowWindowChanged(bool showOrHide); + void _terminalPlayMidiNote(const int noteNumber, + const int velocity, + const std::chrono::microseconds duration); #pragma endregion + std::unique_ptr _midiAudio; + + MidiAudio& _getMidiAudio(); + void _shutdownMidiAudio(); + #pragma region RendererCallbacks void _rendererWarning(const HRESULT hr); void _renderEngineSwapChainChanged(); diff --git a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj index 250cb06cbe9..92ab903986c 100644 --- a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj @@ -147,9 +147,13 @@ + + + + diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index b8e32d53cee..fe0fbeab27c 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -963,6 +963,15 @@ WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept #endif } +// Method Description: +// - Get a reference to the the terminal's read/write lock. +// Return Value: +// - a ticket_lock which can be used to manually lock or unlock the terminal. +til::ticket_lock& Terminal::GetReadWriteLock() noexcept +{ + return _readWriteLock; +} + Viewport Terminal::_GetMutableViewport() const noexcept { // GH#3493: if we're in the alt buffer, then it's possible that the mutable @@ -1302,6 +1311,15 @@ void Terminal::SetShowWindowCallback(std::function pfn) noexcept _pfnShowWindowChanged.swap(pfn); } +// Method Description: +// - Allows setting a callback for playing MIDI notes. +// Arguments: +// - pfn: a function callback that takes a note number, a velocity level, and a duration +void Terminal::SetPlayMidiNoteCallback(std::function pfn) noexcept +{ + _pfnPlayMidiNote.swap(pfn); +} + // Method Description: // - Sets the cursor to be currently on. On/Off is tracked independently of // cursor visibility (hidden/visible). On/off is controlled by the cursor diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index af6a5cd35f0..a042ec360f3 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -92,6 +92,7 @@ class Microsoft::Terminal::Core::Terminal final : [[nodiscard]] std::unique_lock LockForReading(); [[nodiscard]] std::unique_lock LockForWriting(); + til::ticket_lock& GetReadWriteLock() noexcept; short GetBufferHeight() const noexcept; @@ -202,6 +203,7 @@ class Microsoft::Terminal::Core::Terminal final : void SetCursorPositionChangedCallback(std::function pfn) noexcept; void TaskbarProgressChangedCallback(std::function pfn) noexcept; void SetShowWindowCallback(std::function pfn) noexcept; + void SetPlayMidiNoteCallback(std::function pfn) noexcept; void SetCursorOn(const bool isOn); bool IsCursorBlinkingAllowed() const noexcept; @@ -271,6 +273,7 @@ class Microsoft::Terminal::Core::Terminal final : std::function _pfnCursorPositionChanged; std::function _pfnTaskbarProgressChanged; std::function _pfnShowWindowChanged; + std::function _pfnPlayMidiNote; RenderSettings _renderSettings; std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 214805f4e9d..6899c7ba4a4 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -188,8 +188,9 @@ void Terminal::SetWorkingDirectory(std::wstring_view uri) _workingDirectory = uri; } -void Terminal::PlayMidiNote(const int /*noteNumber*/, const int /*velocity*/, const std::chrono::microseconds /*duration*/) +void Terminal::PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) { + _pfnPlayMidiNote(noteNumber, velocity, duration); } void Terminal::UseAlternateScreenBuffer() From 01cc6fbedbbf1b04f766abb18ac83150f5062ce8 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Wed, 3 Feb 2021 00:20:35 +0000 Subject: [PATCH 5/6] Add spell-check terms. --- .github/actions/spelling/expect/expect.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 2cc27be650e..877e0f34ed7 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -539,6 +539,7 @@ DECNRCM DECOM deconstructed DECPCTERM +DECPS DECRC DECREQTPARM DECRLM @@ -632,6 +633,7 @@ dllmain DLLVERSIONINFO DLOAD DLOOK +Dls dmp DOCTYPE docx @@ -1006,6 +1008,7 @@ HKLM hlocal hlsl HMENU +HMIDIOUT hmod hmodule hmon @@ -1393,6 +1396,7 @@ MAKELANGID MAKELONG MAKELPARAM MAKELRESULT +MAKEWORD malloc manpage MAPBITMAP From a293def8c940490ba2ff9bdc99d38fc6c064dd45 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Tue, 31 May 2022 15:10:40 +0100 Subject: [PATCH 6/6] Make MidiAudio::PlayNote noexcept. --- src/audio/midi/MidiAudio.cpp | 4 +++- src/audio/midi/MidiAudio.hpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/audio/midi/MidiAudio.cpp b/src/audio/midi/MidiAudio.cpp index a8af035f288..66617a0b63f 100644 --- a/src/audio/midi/MidiAudio.cpp +++ b/src/audio/midi/MidiAudio.cpp @@ -91,7 +91,8 @@ void MidiAudio::Unlock() } } -void MidiAudio::PlayNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) +void MidiAudio::PlayNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) noexcept +try { // The MidiOut is a local static because we can only have one instance, // and we only want to construct it when it's actually needed. @@ -112,3 +113,4 @@ void MidiAudio::PlayNote(const int noteNumber, const int velocity, const std::ch midiOut.OutputMessage(MidiOut::NOTE_OFF, noteNumber, velocity); } } +CATCH_LOG() diff --git a/src/audio/midi/MidiAudio.hpp b/src/audio/midi/MidiAudio.hpp index 426455e358a..435276940ab 100644 --- a/src/audio/midi/MidiAudio.hpp +++ b/src/audio/midi/MidiAudio.hpp @@ -27,7 +27,7 @@ class MidiAudio void Shutdown(); void Lock(); void Unlock(); - void PlayNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration); + void PlayNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) noexcept; private: std::promise _shutdownPromise;