Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions resources/styles/main.qss
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,17 @@ QLineEdit[placeholderText] {
color: #6b7280;
}

/* inline editors inside item views must fit their row: the input box model
above (padding + min-height) otherwise overflows compact rows */
QTreeView QLineEdit,
QTableView QLineEdit,
QListView QLineEdit {
border: none;
border-radius: 0;
padding: 0 4px;
min-height: 0;
}

QComboBox::drop-down {
border: none;
width: 24px;
Expand Down
14 changes: 14 additions & 0 deletions src/core/Actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ QString Actor::matchedRole(const QString& text) const {
return {};
}

QString Actor::secondaryName(const QString& matchedRole) const {
QString secondary;
if (m_useRoleName && !m_roles.isEmpty())
secondary = m_name;
else
secondary = matchedRole.isEmpty() ? primaryRole() : matchedRole;
if (secondary.compare(displayName(), Qt::CaseInsensitive) == 0)
return {};
return secondary;
}

QJsonObject Actor::toJson() const {
QJsonObject json;
json["id"] = m_id;
Expand All @@ -33,6 +44,8 @@ QJsonObject Actor::toJson() const {
json["channel"] = m_channel;
json["order"] = m_order;
json["active"] = m_active;
if (m_useRoleName)
json["useRoleName"] = true;

if (!m_profiles.isEmpty()) {
QJsonObject profilesObj;
Expand Down Expand Up @@ -68,6 +81,7 @@ Actor Actor::fromJson(const QJsonObject& json) {
actor.m_channel = json["channel"].toInt();
actor.m_order = json["order"].toInt();
actor.m_active = json["active"].toBool(true);
actor.m_useRoleName = json["useRoleName"].toBool(false);

const QJsonObject profilesObj = json["profiles"].toObject();
for (auto it = profilesObj.constBegin(); it != profilesObj.constEnd(); ++it)
Expand Down
10 changes: 10 additions & 0 deletions src/core/Actor.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ class Actor {
// the stored role case-insensitively equal to text, or empty when none
[[nodiscard]] QString matchedRole(const QString& text) const;

[[nodiscard]] bool useRoleName() const noexcept { return m_useRoleName; }
void setUseRoleName(bool use) { m_useRoleName = use; }

[[nodiscard]] QString displayName() const {
return (m_useRoleName && !m_roles.isEmpty()) ? m_roles.first() : m_name;
}
// the parenthesised label paired with displayName(); empty when redundant
[[nodiscard]] QString secondaryName(const QString& matchedRole = QString()) const;

[[nodiscard]] int channel() const noexcept { return m_channel; }
void setChannel(int channel) { m_channel = channel; }

Expand All @@ -57,6 +66,7 @@ class Actor {
int m_channel = 0;
int m_order = 0;
bool m_active = true;
bool m_useRoleName = false;
QMap<QString, ActorProfile> m_profiles; // slot -> profile
};

Expand Down
4 changes: 2 additions & 2 deletions src/core/ActorProfile.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ namespace OpenMix {
// scales them to the console's wire format.
struct EqBand {
int band = 1; // 1-based band index
bool on = true; //
bool on = true;
int type = 0; // console EQ type enum (PEQ / shelf / ...), driver-mapped
double freq = 1000.0; // Hz
double gain = 0.0; // dB
double q = 2.0; //
double q = 2.0;

QJsonObject toJson() const;
[[nodiscard]] static EqBand fromJson(const QJsonObject& json);
Expand Down
8 changes: 8 additions & 0 deletions src/core/PlaybackEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ void PlaybackEngine::setCueList(CueList* cueList) {
stop();
if (m_cueList) {
connect(m_cueList, &CueList::cueRemoved, this, &PlaybackEngine::onCueRemoved);
connect(m_cueList, &CueList::listCleared, this, &PlaybackEngine::onCueListReset);
connect(m_cueList, &CueList::listLoaded, this, &PlaybackEngine::onCueListReset);
if (!m_cueList->isEmpty())
setStandbyIndex(0);
}
Expand Down Expand Up @@ -54,6 +56,12 @@ void PlaybackEngine::onCueRemoved(int index) {
}
}

void PlaybackEngine::onCueListReset() {
m_fadeEngine.cancelAll();
m_appliedChannelLevels.clear();
stop();
}

void PlaybackEngine::setMixer(MixerProtocol* mixer) { m_mixer = mixer; }

void PlaybackEngine::setDCAMapping(DCAMapping* mapping) { m_dcaMapping = mapping; }
Expand Down
1 change: 1 addition & 0 deletions src/core/PlaybackEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class PlaybackEngine : public QObject {
private slots:
void onAutoFollowTimerTimeout();
void onCueRemoved(int index);
void onCueListReset();

private:
void setState(PlaybackState state);
Expand Down
2 changes: 1 addition & 1 deletion src/core/ScribbleController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ void ScribbleController::refreshNames() {
if (ch == m_cueChannel)
continue; // reserved for the cue number
if (const Actor* actor = m_library->actorForChannel(ch))
m_mixer->setChannelName(ch, actor->name());
m_mixer->setChannelName(ch, actor->displayName());
}
}

Expand Down
1 change: 0 additions & 1 deletion src/core/ShortcutManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ void ShortcutManager::registerAction(const QString& id, QAction* action,

m_shortcuts[id] = info;

// apply the default shortcut to the action
action->setShortcut(defaultShortcut);
}

Expand Down
93 changes: 74 additions & 19 deletions src/io/TmixImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include <QHash>
#include <QRegularExpression>
#include <QSet>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
Expand Down Expand Up @@ -66,6 +67,35 @@ void clearShow(Show* show) {
show->ensembleLibrary()->removeEnsemble(e.id());
}

// name the actor on `channel`: an existing named actor wins (the actors
// table is authoritative), an unnamed one is filled in, else a new one is cast
void applyChannelName(Show* show, int channel, const QString& name,
TmixImportSummary* summary) {
if (channel <= 0 || name.isEmpty())
return;
ActorProfileLibrary* lib = show->actorProfileLibrary();
const QList<Actor> actors = lib->actors(); // copy: updateActor mutates the list
for (const Actor& a : actors) {
if (a.channel() != channel)
continue;
if (!a.name().isEmpty())
return;
Actor copy = a;
copy.setName(name);
lib->updateActor(a.id(), copy);
if (summary)
++summary->channelNames;
return;
}
Actor a(name, channel);
a.setOrder(lib->actorCount());
lib->addActor(a);
if (summary) {
++summary->actors;
++summary->channelNames;
}
}

} // namespace

bool TmixImporter::import(const QString& path, Show* show, QString* error,
Expand Down Expand Up @@ -107,25 +137,8 @@ bool TmixImporter::import(const QString& path, Show* show, QString* error,
}
}

// channel voice presets become profile slots; map file id to slot name
QHash<int, QString> profileSlotMap;
if (q.exec("SELECT * FROM profiles")) {
while (q.next()) {
const QSqlRecord r = q.record();
QString label = r.value("label").toString();
if (label.isEmpty())
label = r.value("name").toString();
if (!label.isEmpty()) {
show->actorProfileLibrary()->addSlot(label);
if (summary)
summary->profileSlots.append(label);
}
if (r.indexOf("id") >= 0)
profileSlotMap.insert(r.value("id").toInt(), label);
}
}

// actors
// actors: authoritative cast when present; usually empty, with the
// cast carried by the profiles table instead
if (q.exec("SELECT * FROM actors")) {
while (q.next()) {
const QSqlRecord r = q.record();
Expand All @@ -138,6 +151,48 @@ bool TmixImporter::import(const QString& path, Show* show, QString* error,
}
}

// profiles: a channel's default row carries its Show Setup name and
// fills the actor name; additional presets become voice slots, and
// flat files without a channel column treat every label as a slot
QHash<int, QString> profileSlotMap;
{
const QSqlRecord layout = db.record("profiles");
const bool perChannel = layout.indexOf("channel") >= 0;
const bool hasDefaultFlag = layout.indexOf("default") >= 0;
const bool hasLabel = layout.indexOf("label") >= 0;
QSet<int> defaultSeen;
if (q.exec("SELECT * FROM profiles")) {
while (q.next()) {
const QSqlRecord r = q.record();
QString label = hasLabel ? r.value("label").toString() : QString();
if (label.isEmpty())
label = r.value("name").toString();
const int id = r.indexOf("id") >= 0 ? r.value("id").toInt() : -1;

if (perChannel) {
const int channel = r.value("channel").toInt();
const bool isDefault = hasDefaultFlag
? r.value("default").toInt() != 0
: !defaultSeen.contains(channel);
if (isDefault) {
defaultSeen.insert(channel);
applyChannelName(show, channel, label.trimmed(), summary);
if (id >= 0)
profileSlotMap.insert(id, ActorProfileLibrary::DEFAULT_SLOT);
continue;
}
}
if (!label.isEmpty()) {
show->actorProfileLibrary()->addSlot(label);
if (summary && !summary->profileSlots.contains(label))
summary->profileSlots.append(label);
}
if (id >= 0)
profileSlotMap.insert(id, label);
}
}
}

// ensembles
if (q.exec("SELECT * FROM ensembles")) {
while (q.next()) {
Expand Down
8 changes: 6 additions & 2 deletions src/io/TmixImporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ struct TmixImportSummary {
int positions = 0;
int ensembles = 0;
int rolesInferred = 0; // actor roles guessed from cue DCA labels
int channelNames = 0; // actor names taken from default profile rows
QStringList profileSlots;
};

// Imports a .tmix show file (a SQLite database) into a Show.
// The show is cleared first, then populated from the file's config, actors,
// profiles, positions, ensembles and cues tables. Best-effort: missing tables
// or columns are skipped rather than treated as errors.
// profiles, positions, ensembles and cues tables. These files store each
// channel's Show Setup name as that channel's default profile row, so default
// profiles become actor names and only additional profiles become voice
// slots. Best-effort: missing tables or columns are skipped rather than
// treated as errors.
class TmixImporter {
public:
// Populate `show` from the .tmix at `path`. Returns false and sets *error
Expand Down
2 changes: 0 additions & 2 deletions src/protocol/allenheath/AllenHeathTcpProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ QByteArray AllenHeathTcpProtocol::buildDCAFaderMessage(int dca, float level) {
return msg;
}

// buildDCAMuteMessage
QByteArray AllenHeathTcpProtocol::buildDCAMuteMessage(int dca, bool muted) {
if (dca < 1)
return {};
Expand All @@ -177,7 +176,6 @@ QByteArray AllenHeathTcpProtocol::buildDCAMuteMessage(int dca, bool muted) {
return msg;
}

// buildSceneRecallMessage
QByteArray AllenHeathTcpProtocol::buildSceneRecallMessage(int sceneNumber) {
QByteArray msg;

Expand Down
4 changes: 0 additions & 4 deletions src/protocol/behringer/WingProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,16 @@ WingProtocol::WingProtocol(const MixerCapabilities& caps, QObject* parent)
QObject::connect(&m_transport, &OscTransport::messageReceived, this,
&WingProtocol::onMessageReceived);

// keep-alive timer
QObject::connect(&m_keepAliveTimer, &QTimer::timeout, this, &WingProtocol::onKeepAliveTimeout);

// connection timeout timer
m_connectionTimer.setSingleShot(true);
QObject::connect(&m_connectionTimer, &QTimer::timeout, this,
&WingProtocol::onConnectionTimeout);

// request timeout check timer
m_requestTimeoutTimer.setInterval(500);
QObject::connect(&m_requestTimeoutTimer, &QTimer::timeout, this,
&WingProtocol::onRequestTimeoutCheck);

// reconnection timer
m_reconnectTimer.setSingleShot(true);
QObject::connect(&m_reconnectTimer, &QTimer::timeout, this, &WingProtocol::onReconnectAttempt);
}
Expand Down
Loading
Loading