wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
AudioBox.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
5
6#include "App/Core/Common.h"
13#include "App/IO/VersionInfo.h"
14#include "App/Wiring/Port.h"
15
16template<>
19 .type = ElementType::AudioBox,
20 .group = ElementGroup::Output,
21 .minInputSize = 1,
22 .maxInputSize = 1,
23 .canChangeAppearance = true,
24 .hasAudioBox = true,
25 .hasLabel = true,
26 .hasVolume = true,
27 };
28 static_assert(validate(constraints));
29
31 {
33 meta.pixmapPath = []{ return QStringLiteral(":/Components/Output/AudioBox/audioboxOff.svg"); };
34 meta.titleText = QT_TRANSLATE_NOOP("AudioBox", "Audio Box");
35 meta.translatedName = QT_TRANSLATE_NOOP("AudioBox", "Audio Box");
36 meta.trContext = "AudioBox";
37 meta.defaultAppearances = QStringList({
38 ":/Components/Output/AudioBox/audioboxOff.svg",
39 ":/Components/Output/AudioBox/audioboxOn.svg",
40 });
41 return meta;
42 }
43
44 static inline const bool registered = []() {
46 ElementFactory::registerCreator(constraints.type, [] { return new AudioBox(); });
47 return true;
48 }();
49};
50
51AudioBox::AudioBox(QGraphicsItem *parent)
53{
55 m_player = new QMediaPlayer(this);
56 m_audioOutput = new QAudioOutput(this);
57 }
58
59 setAudio(QString::fromLatin1(kDefaultAudioPath));
60}
61
62void AudioBox::setAudio(const QString &audioPath)
63{
64 if (audioPath.isEmpty()) {
65 return;
66 }
67
68 // Resolution against contextDir already happened once, at load time (see
69 // ExternalFilePath::forReading(), called from load() below) -- by the time a
70 // path reaches here it's either a resource reference or already directly
71 // usable (a fresh absolute path from a file picker, or an already-resolved
72 // one read back from a saved project). Just validate it's actually there.
73 const QString &path = audioPath;
74 if (!path.startsWith(":/")) {
75 const QFileInfo info(path);
76 if (!info.exists() || !info.isReadable()) {
77 const QString reason = !info.exists()
78 ? tr("File does not exist")
79 : tr("File is not readable");
80 qCDebug(zero) << "Problem loading audio path:" << path;
81 throw PANDACEPTION("Couldn't load audio: %1 (%2)", path, reason);
82 }
83 }
84
85 m_audio.setFile(path);
86
87 // Only set up hardware if device is available
88 if (!m_hasOutputDevice) {
89 return;
90 }
91
92 const QUrl mediaUrl = path.startsWith(":/")
93 ? QUrl("qrc" + path)
94 : QUrl::fromLocalFile(path);
95
96 m_audioOutput->setVolume(m_volume);
97 m_player->setAudioOutput(m_audioOutput);
98 m_player->setSource(mediaUrl);
99 m_player->setLoops(QMediaPlayer::Infinite);
100
101 // If already playing, restart playback with the new audio immediately
102 if (m_isPlaying) {
103 m_player->play();
104 }
105}
106
107QString AudioBox::audio() const
108{
109 return m_audio.filePath();
110}
111
113{
114 if (m_player->source().isEmpty()) {
115 setAudio(QString::fromLatin1(kDefaultAudioPath));
116 }
117 m_player->play();
118}
119
121{
122 m_player->stop();
123}
124
126{
127 m_audioOutput->setVolume(m_volume);
128}
129
131{
132 m_audioOutput->setMuted(m_muted);
133}
134
135QStringList AudioBox::externalFiles() const
136{
137 QStringList result = GraphicElement::externalFiles();
138 const QString audioPath = m_audio.filePath();
139 if (!audioPath.isEmpty() && !audioPath.startsWith(":/")) {
140 result.append(audioPath);
141 }
142 return result;
143}
144
145void AudioBox::save(QDataStream &stream, SerializationOptions options) const
146{
147 GraphicElement::save(stream, options);
148
149 const bool slim = (options.purpose == SerializationPurpose::PortableFile);
150 const QString audioPath = ExternalFilePath::forWriting(m_audio.filePath(), options.purpose);
151
152 QMap<QString, QVariant> map;
153 // PortableFile streams omit resource paths outright — forReading() discards
154 // them on load anyway (the default source is the bundled kDefaultAudioPath
155 // resource, which a fresh element already has), matching the appearance
156 // rule in GraphicElementSerializer::save(). Snapshots write unconditionally.
157 if (!slim || !audioPath.startsWith(":/")) {
158 map.insert("audiobox", audioPath);
159 }
160 if (!slim || m_volume != kDefaultVolume) {
161 map.insert("volume", static_cast<double>(m_volume));
162 }
163
164 stream << map;
165}
166
167void AudioBox::load(QDataStream &stream, SerializationContext &context)
168{
169 GraphicElement::load(stream, context);
170
171 if (!VersionInfo::hasAudio(context.version)) {
172 // Audio was added in v2.4; nothing to read for earlier files
173 return;
174 }
175
176 if (!VersionInfo::hasQMapFormat(context.version)) {
177 // v2.4–4.0 stored the path as a bare QString
178 const QString audio = Serialization::readBoundedString(stream);
179 if (const auto resolved = ExternalFilePath::forReading(audio, context.contextDir, context.purpose)) {
180 setAudio(*resolved);
181 }
182 }
183
184 if (VersionInfo::hasQMapFormat(context.version)) {
185 // v4.1+ uses a key-value map for forward-compatible extensibility
186 QMap<QString, QVariant> map = Serialization::readBoundedMetadata(stream);
187
188 if (map.contains("audiobox")) {
189 if (const auto resolved = ExternalFilePath::forReading(map.value("audiobox").toString(), context.contextDir, context.purpose)) {
190 setAudio(*resolved);
191 }
192 }
193 if (map.contains("volume")) {
194 bool ok = false;
195 const float vol = map.value("volume").toFloat(&ok);
196 if (ok) {
197 setVolume(vol);
198 }
199 }
200 }
201}
Graphic element for the AudioBox (external audio file player) output.
Common logging utilities, the Pandaception error type, and helper macros.
#define PANDACEPTION(msg,...)
Definition Common.h:98
#define qCDebug(category)
Definition Common.h:29
Singleton factory for all circuit element types.
Self-registering element trait template and compile-time constraint validation.
ElementMetadata metadataFromConstraints(const ElementConstraints &c)
Converts ElementConstraints to an ElementMetadata with all constraint-derived fields set.
Definition ElementInfo.h:80
constexpr bool validate(const ElementConstraints &c)
Validates element constraints at compile time.
Definition ElementInfo.h:48
Enums::ElementType ElementType
Definition Enums.h:107
Single, shared implementation of how an element's external-file reference (an appearance image,...
Abstract base class for all graphical circuit elements.
Port classes: Port (base), InputPort, and OutputPort.
Deserialization/serialization context structs passed through load()/save() call chains.
Circuit and waveform file serialization/deserialization utilities.
Named version predicates for file-format compatibility checks.
Audio output element that streams an external audio file when its input is logic-1.
Definition AudioBox.h:24
AudioBox(QGraphicsItem *parent=nullptr)
Constructs an AudioBox element.
Definition AudioBox.cpp:51
void load(QDataStream &stream, SerializationContext &context) override
Definition AudioBox.cpp:167
void stopAudio() override
Stops hardware playback (called from stop() when m_hasOutputDevice is true).
Definition AudioBox.cpp:120
void applyMute() override
Applies the current m_muted state to the hardware backend.
Definition AudioBox.cpp:130
static constexpr const char * kDefaultAudioPath
Definition AudioBox.h:31
void save(QDataStream &stream, SerializationOptions options) const override
Definition AudioBox.cpp:145
void applyVolume() override
Applies the current m_volume to the hardware backend.
Definition AudioBox.cpp:125
void startAudio() override
Starts hardware playback (called from play() when m_hasOutputDevice is true).
Definition AudioBox.cpp:112
void setAudio(const QString &audioPath) override
Sets the audio source to audioPath and reloads the player.
Definition AudioBox.cpp:62
QStringList externalFiles() const override
Definition AudioBox.cpp:135
QString audio() const override
Returns the file path of the currently loaded audio file.
Definition AudioBox.cpp:107
static constexpr float kDefaultVolume
void setVolume(float vol) override
Sets the audio playback volume to vol (0.0–1.0).
AudioOutputElement(ElementType type, QGraphicsItem *parent=nullptr, float initialVolume=kDefaultVolume)
static void registerCreator(ElementType type, std::function< GraphicElement *()> creator)
Registers a creator lambda for type, used by buildElement().
static void registerMetadata(const ElementMetadata &meta)
Registers meta in the global map (called once per element type at startup).
virtual QStringList externalFiles() const
virtual void load(QDataStream &stream, SerializationContext &context)
Loads the graphic element through a binary data stream.
virtual void save(QDataStream &stream, SerializationOptions options) const
static QString readBoundedString(QDataStream &stream)
Reads a QString from stream, refusing to allocate more bytes than remain in the stream (prevents OOM ...
static QMap< QString, QVariant > readBoundedMetadata(QDataStream &stream)
Reads the file-level metadata QMap<QString,QVariant> from stream without calling QList::reserve() wit...
std::optional< QString > forReading(const QString &storedValue, const QString &contextDir, SerializationPurpose purpose)
QString forWriting(const QString &path, SerializationPurpose purpose)
bool hasQMapFormat(const QVersionNumber &v)
V4.1: Format changed from flat binary to keyed QMap; port serial IDs; rotation fix.
Definition VersionInfo.h:60
bool hasAudio(const QVersionNumber &v)
V2.4: Audio element support (Buzzer, AudioBox).
Definition VersionInfo.h:42
Compile-time-validatable subset of ElementMetadata.
Definition ElementInfo.h:21
static ElementMetadata metadata()
Definition AudioBox.cpp:30
static constexpr ElementConstraints constraints
Definition AudioBox.cpp:18
static const bool registered
Definition AudioBox.cpp:44
Self-registering element information trait.
Compile-time-registered properties for one element type.
Bundles all per-deserialization state so that load() overrides receive it through one explicit parame...
QVersionNumber version
File-format version read from the stream header.
SerializationPurpose purpose
What this deserialization is for; see SerializationPurpose.
QString contextDir
Directory of the .panda file (for relative path resolution).
Options passed to GraphicElement::save() (and friends); the save-side counterpart of SerializationCon...
SerializationPurpose purpose
What this serialization is for; see SerializationPurpose. Mandatory, no default.