Skip to content

Commit

Permalink
adds vt sequence DECPS
Browse files Browse the repository at this point in the history
  • Loading branch information
Utkarsh-khambra committed Jun 13, 2022
1 parent b17dcf0 commit e142d4f
Show file tree
Hide file tree
Showing 16 changed files with 262 additions and 4 deletions.
1 change: 1 addition & 0 deletions .github/fedora/contour.spec
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ BuildRequires: ninja-build
BuildRequires: pkgconf
BuildRequires: qt5-qtbase-devel
BuildRequires: qt5-qtbase-gui
BuildRequires: qt5-qtmultimedia-devel

Requires: fontconfig
Requires: freetype
Expand Down
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
- Adds new action `ToggleInputProtection` to protect terminal application against accidental input (#697).
- Adds configuration options `logging.enabled` as well as `logging.file`.
- Adds VT sequences `XTPUSHCOLORS`, `XTPOPCOLORS`, `XTREPORTCOLORS` (#714).
- Adds VT sequence `DECPS` (#237).

### 0.3.1 (2022-05-01)

Expand Down
4 changes: 4 additions & 0 deletions scripts/install-deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ install_deps_ubuntu()
ncurses-bin
pkg-config
qtbase5-dev
qtmultimedia5-dev
"

RELEASE=`grep VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"'`
Expand Down Expand Up @@ -210,6 +211,7 @@ install_deps_FreeBSD()
qt5-network \
qt5-qmake \
qt5-widgets \
qt5-multimedia \
range-v3 \
yaml-cpp
"
Expand All @@ -230,6 +232,7 @@ install_deps_arch()
microsoft-gsl \
ninja \
qt5-base \
qt5-multimedia \
range-v3 \
yaml-cpp
}
Expand All @@ -254,6 +257,7 @@ install_deps_fedora()
pkgconf
qt5-qtbase-devel
qt5-qtbase-gui
qt5-qtmultimedia-devel
range-v3-devel
yaml-cpp-devel
"
Expand Down
81 changes: 81 additions & 0 deletions src/contour/Audio.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include <contour/Audio.h>

#include <crispy/Note.h>
#include <crispy/logstore.h>

#include <qbuffer.h>
#include <qthread.h>

using namespace contour;

Audio::Audio()
{
QAudioFormat f;
f.setSampleRate(44100);
f.setChannelCount(1);
f.setSampleSize(16);
f.setCodec("audio/pcm");
f.setByteOrder(QAudioFormat::LittleEndian);
f.setSampleType(QAudioFormat::SignedInt);
QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
if (!info.isFormatSupported(f))
{
errorlog()("Default output device doesn't support 16 Bit signed integer PCM");
return;
}
audio = std::make_unique<QAudioOutput>(f);
audio->moveToThread(&soundThread_);
connect(audio.get(), &QAudioOutput::stateChanged, this, &Audio::handleStateChanged);
qRegisterMetaType<std::vector<int>>();
connect(this, &Audio::play, this, &Audio::handlePlayback, Qt::ConnectionType::QueuedConnection);
soundThread_.start();
}

Audio::~Audio()
{
soundThread_.quit();
soundThread_.wait();
}

void Audio::fillBuffer(int volume, int duration, crispy::span<int> notes)
{
for (auto i: notes)
{
auto b = crispy::createNote(volume, duration, static_cast<crispy::Note>(i));
byteArray_.append(reinterpret_cast<const char*>(b.data()), static_cast<int>(2 * b.size()));
}
}

void Audio::handlePlayback(int volume, int duration, std::vector<int> notes)
{
if (audio->state() == QAudio::State::ActiveState)
{
fillBuffer(volume, duration, crispy::span(notes.data(), notes.size()));
return;
}
fillBuffer(volume, duration, crispy::span(notes.data(), notes.size()));
audioBuffer_.setBuffer(&byteArray_);
audioBuffer_.open(QIODevice::ReadWrite);
if (audio)
audio->start(&audioBuffer_);
}

void Audio::handleStateChanged(QAudio::State state)
{
switch (state)
{
case QAudio::IdleState:
audio->stop();
audioBuffer_.close();
byteArray_.clear();
break;

case QAudio::StoppedState:
if (audio->error() != QAudio::NoError)
{
errorlog()("Audio playback stopped: {}", audio->error());
}
break;
default: break;
}
}
34 changes: 34 additions & 0 deletions src/contour/Audio.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

#include <crispy/span.h>

#include <memory>

#include <qbuffer.h>
#include <qthread.h>

#include <QtMultimedia/QAudioOutput>
namespace contour
{
class Audio: public QObject
{
Q_OBJECT

public:
Audio();
~Audio() override;
public Q_SLOTS:
signals:
void play(int volume, int duration, std::vector<int> notes);
private slots:
void handleStateChanged(QAudio::State state);
void handlePlayback(int volume, int duration, std::vector<int> notes);

private:
void fillBuffer(int volume, int duration, crispy::span<int> notes);
QByteArray byteArray_;
QBuffer audioBuffer_;
QThread soundThread_;
std::unique_ptr<QAudioOutput> audio;
};
} // namespace contour
6 changes: 3 additions & 3 deletions src/contour/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ if(CONTOUR_BUILD_WITH_QT6)
find_package(Qt6 COMPONENTS Core Gui Network OpenGL OpenGLWidgets Widgets REQUIRED)
set(CONTOUR_BLUR_PLATFORM_KWIN OFF)
else()
find_package(Qt5 COMPONENTS Gui Network Widgets REQUIRED) # apt install qtbase5-dev libqt5gui5
find_package(Qt5 COMPONENTS Gui Network Widgets Multimedia REQUIRED) # apt install qtbase5-dev libqt5gui5
endif()

# OpenGL accelerated TerminalDisplay
Expand Down Expand Up @@ -46,7 +46,7 @@ if(CONTOUR_FRONTEND_GUI)
ScrollableDisplay.cpp ScrollableDisplay.h
TerminalSession.cpp TerminalSession.h
TerminalWindow.cpp TerminalWindow.h
helper.cpp helper.h
helper.cpp helper.h Audio.cpp Audio.h
)
endif()

Expand Down Expand Up @@ -128,7 +128,7 @@ if(CONTOUR_FRONTEND_GUI)
if(CONTOUR_BUILD_WITH_QT6)
target_link_libraries(contour contour_frontend_opengl terminal_renderer Qt6::Core Qt6::Network Qt6::OpenGL Qt6::OpenGLWidgets Qt6::Widgets)
else()
target_link_libraries(contour contour_frontend_opengl terminal_renderer Qt5::Gui Qt5::Network Qt5::Widgets)
target_link_libraries(contour crispy::core contour_frontend_opengl terminal_renderer Qt5::Gui Qt5::Network Qt5::Widgets Qt5::Multimedia)
endif()
if(CONTOUR_BLUR_PLATFORM_KWIN) # TODO: auto-enable on Linux, if KDE dev libs were found.
target_compile_definitions(contour PRIVATE CONTOUR_BLUR_PLATFORM_KWIN)
Expand Down
7 changes: 7 additions & 0 deletions src/contour/TerminalSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,13 @@ void TerminalSession::onHighlightUpdate()
{
terminal_.resetHighlight();
}

void TerminalSession::playSound(terminal::Sequence::Parameters const params_)
{
auto range = params_.range();
emit audio.play(params_.at(0), params_.at(1), { range.begin() + 2, range.end() });
}

// }}}
// {{{ Actions
bool TerminalSession::operator()(actions::CancelSelection)
Expand Down
3 changes: 3 additions & 0 deletions src/contour/TerminalSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/
#pragma once

#include <contour/Audio.h>
#include <contour/Config.h>
#include <contour/TerminalDisplay.h>

Expand Down Expand Up @@ -107,6 +108,7 @@ class TerminalSession: public QObject, public terminal::Terminal::Events
void discardImage(terminal::Image const&) override;
void inputModeChanged(terminal::ViMode mode) override;
void updateHighlights() override;
void playSound(terminal::Sequence::Parameters const params_) override;

// Input Events
using Timestamp = std::chrono::steady_clock::time_point;
Expand Down Expand Up @@ -241,6 +243,7 @@ class TerminalSession: public QObject, public terminal::Terminal::Events
terminal::ScreenType currentScreenType_ = terminal::ScreenType::Primary;
terminal::CellLocation currentMousePosition_ = terminal::CellLocation {};
bool allowKeyMappings_ = true;
Audio audio;
};

} // namespace contour
2 changes: 2 additions & 0 deletions src/crispy/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ set(crispy_SOURCES
escape.h
indexed.h
logstore.h
Note.h
Note.cpp
overloaded.h
reference.h
ring.h
Expand Down
68 changes: 68 additions & 0 deletions src/crispy/Note.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include <crispy/Note.h>
#include <crispy/assert.h>

#include <gsl/gsl_assert>

#include <cmath>
#include <vector>

using namespace crispy;

namespace
{
constexpr double SAMPLE_RATE = 44100;
constexpr double square_wave(double x) noexcept
{
x = std::fmod(x, 2);
return std::isgreater(x, 1) ? -1 : 1;
}

auto createNote(double volume, double duration, double frequency) noexcept
{
duration = duration * 1 / 32.0;
volume /= 7;
std::vector<std::int16_t> buffer;
buffer.reserve(static_cast<size_t>(std::ceil(duration * SAMPLE_RATE)));
for (double i = 0; i < duration * SAMPLE_RATE; ++i)
{
auto sample = static_cast<int16_t>(0x7fff * volume * square_wave(frequency / SAMPLE_RATE * i * 2));
buffer.push_back(sample);
}
return buffer;
}

} // namespace
std::vector<std::int16_t> crispy::createNote(double volume, int duration, Note note_) noexcept
{
Ensures(static_cast<int>(note_) >= 0 && static_cast<int>(note_) < 26);
switch (note_)
{
case Note::A5: return ::createNote(volume, duration, 880.0); break;
case Note::A6: return ::createNote(volume, duration, 1760.0); break;
case Note::A_SHARP_5: return ::createNote(volume, duration, 932.3275); break;
case Note::A_SHARP_6: return ::createNote(volume, duration, 1864.655); break;
case Note::C_SHARP_5: return ::createNote(volume, duration, 554.3653); break;
case Note::C_SHARP_6: return ::createNote(volume, duration, 1108.731); break;
case Note::D_SHARP_5: return ::createNote(volume, duration, 622.2540); break;
case Note::D_SHARP_6: return ::createNote(volume, duration, 1244.508); break;
case Note::F_SHARP_5: return ::createNote(volume, duration, 739.9909); break;
case Note::F_SHARP_6: return ::createNote(volume, duration, 1479.978); break;
case Note::G_SHARP_5: return ::createNote(volume, duration, 830.6094); break;
case Note::G_SHARP_6: return ::createNote(volume, duration, 1661.219); break;
case Note::B5: return ::createNote(volume, duration, 987.7666); break;
case Note::B6: return ::createNote(volume, duration, 1975.533); break;
case Note::C5: return ::createNote(volume, duration, 523.2511); break;
case Note::C6: return ::createNote(volume, duration, 1046.502); break;
case Note::C7: return ::createNote(volume, duration, 2093.005); break;
case Note::D5: return ::createNote(volume, duration, 587.3295); break;
case Note::D6: return ::createNote(volume, duration, 1174.659); break;
case Note::E5: return ::createNote(volume, duration, 659.2551); break;
case Note::E6: return ::createNote(volume, duration, 1318.510); break;
case Note::F5: return ::createNote(volume, duration, 698.4565); break;
case Note::F6: return ::createNote(volume, duration, 1396.913); break;
case Note::G5: return ::createNote(volume, duration, 783.9909); break;
case Note::G6: return ::createNote(volume, duration, 1567.982); break;
case Note::SILENT: return ::createNote(volume, duration, 0); break;
}
crispy::unreachable();
}
39 changes: 39 additions & 0 deletions src/crispy/Note.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once
#include <array>
#include <cstdint>
#include <vector>
namespace crispy
{
enum class Note
{
SILENT = 0,
C5,
C_SHARP_5,
D5,
D_SHARP_5,
E5,
F5,
F_SHARP_5,
G5,
G_SHARP_5,
A5,
A_SHARP_5,
B5,
C6,
C_SHARP_6,
D6,
D_SHARP_6,
E6,
F6,
F_SHARP_6,
G6,
G_SHARP_6,
A6,
A_SHARP_6,
B6,
C7
};
std::array<std::int16_t, 26> intitializeNotes() noexcept;
std::vector<std::int16_t> createNote(double volume, int duration, Note note_) noexcept;

} // namespace crispy
4 changes: 3 additions & 1 deletion src/crispy/span.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ class span
//
constexpr span(iterator _begin, iterator _end) noexcept: begin_ { _begin }, end_ { _end } {}
constexpr span(iterator _begin, size_t _count) noexcept:
begin_ { _begin }, end_ { std::next(_begin, _count) }
begin_ { _begin },
end_ { std::next(_begin,
static_cast<typename std::iterator_traits<iterator>::difference_type>(_count)) }
{
}

Expand Down
2 changes: 2 additions & 0 deletions src/terminal/Functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ constexpr inline auto CAPTURE = detail::CSI('>', 0, 2, std::nullopt, 't', VT

constexpr inline auto DECSSDT = detail::CSI(std::nullopt, 0, 1, '$', '~', VTType::VT320, "DECSSDT", "Select Status Display (Line) Type");
constexpr inline auto DECSASD = detail::CSI(std::nullopt, 0, 1, '$', '}', VTType::VT420, "DECSASD", "Select Active Status Display");
constexpr inline auto DECPS = detail::CSI(std::nullopt, 3, 18, ',', '~', VTType::VT520, "DECPS", "Controls the sound frequency or notes");

// DCS functions
constexpr inline auto STP = detail::DCS(std::nullopt, 0, 0, '$', 'p', VTType::VT525, "STP", "Set Terminal Profile");
Expand Down Expand Up @@ -459,6 +460,7 @@ inline auto const& functions() noexcept
DECIC,
DECMODERESTORE,
DECMODESAVE,
DECPS,
DECRM,
DECRQM,
DECRQM_ANSI,
Expand Down
1 change: 1 addition & 0 deletions src/terminal/Screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3328,6 +3328,7 @@ ApplyResult Screen<Cell>::apply(FunctionDefinition const& function, Sequence con
}
break;

case DECPS: _terminal.playSound(seq.parameters()); break;
// OSC
case SETTITLE:
//(not supported) ChangeIconTitle(seq.intermediateCharacters());
Expand Down
Loading

0 comments on commit e142d4f

Please sign in to comment.