Skip to content

Commit 2b98618

Browse files
committed
I love when coils whine
1 parent 5f47016 commit 2b98618

15 files changed

Lines changed: 228 additions & 1 deletion

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ if (UI_QT)
265265
list(APPEND SOURCES_EM400 src/ui/qt6/panel/binarykeys.h src/ui/qt6/panel/binarykeys.cpp)
266266
list(APPEND SOURCES_EM400 src/ui/qt6/panel/rotary.h src/ui/qt6/panel/rotary.cpp)
267267
list(APPEND SOURCES_EM400 src/ui/qt6/panel/ignition.h src/ui/qt6/panel/ignition.cpp)
268+
list(APPEND SOURCES_EM400 src/ui/qt6/panel/psu.h src/ui/qt6/panel/psu.cpp)
268269
endif()
269270

270271
# --- embedded default config (cfg/em400.ini as a NUL-terminated byte array)

src/ui/qt6/app/configdialog.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,15 @@ QWidget *ConfigDialog::build_sound_page()
447447
gui_volume_row->addWidget(gui_volume, 1);
448448
gui_volume_row->addWidget(gui_volume_val);
449449
gui_form->addRow(tr("Volume:"), gui_volume_row);
450+
451+
QCheckBox *coil_whine = new QCheckBox(tr("I love when coils whine"));
452+
coil_whine->setChecked(QSettings().value("ui/psuSound", false).toBool());
453+
connect(coil_whine, &QCheckBox::toggled, this, [this](bool on) {
454+
QSettings().setValue("ui/psuSound", on);
455+
emit signal_psu_sound_changed(on);
456+
});
457+
gate(coil_whine, "live");
458+
gui_form->addRow(QString(), coil_whine);
450459
outer->addWidget(gui_box);
451460

452461
QGroupBox *buzzer_box = new QGroupBox(tr("CPU speaker"));

src/ui/qt6/app/configdialog.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ public slots:
124124
signals:
125125
void signal_machine_renamed();
126126
void signal_gui_volume_changed(int volume);
127+
void signal_psu_sound_changed(bool enabled);
127128
void signal_mono_font_changed();
128129
void signal_terminal_font_changed();
129130
void signal_panel_theme_changed(bool enabled);

src/ui/qt6/app/mainwindow.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ void MainWindow::restore_layout()
360360
{
361361
QSettings settings;
362362
ui->cp->set_volume(settings.value("ui/guiVolume", 100).toInt());
363+
ui->cp->set_psu_sound(settings.value("ui/psuSound", false).toBool());
363364
// restore the small/large panel choice first; setChecked() fires the toggled
364365
// signal which applies the crop (only when it actually differs from default)
365366
ui->actionSmall_Control_Panel->setChecked(settings.value("layout/smallPanel", false).toBool());
@@ -608,6 +609,7 @@ void MainWindow::open_config()
608609
connect(&cfg_ctl, &ConfigController::active_machine_changed, config_dialog, &ConfigDialog::on_active_machine_changed);
609610
connect(config_dialog, &ConfigDialog::signal_machine_renamed, this, &MainWindow::update_window_title);
610611
connect(config_dialog, &ConfigDialog::signal_gui_volume_changed, ui->cp, &ControlPanel::set_volume);
612+
connect(config_dialog, &ConfigDialog::signal_psu_sound_changed, ui->cp, &ControlPanel::set_psu_sound);
611613
connect(config_dialog, &ConfigDialog::signal_mono_font_changed, this, &MainWindow::refresh_fonts);
612614
connect(config_dialog, &ConfigDialog::signal_terminal_font_changed, this, &MainWindow::refresh_terminal_fonts);
613615
connect(config_dialog, &ConfigDialog::signal_panel_theme_changed, this, &MainWindow::slot_panel_theme_changed);

src/ui/qt6/i18n/em400-qt_pl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,10 @@ Domyślne polecenie korzysta z dołączonego narzędzia emterm.</translation>
289289
<source>Terminal font:</source>
290290
<translation>Czcionka terminala:</translation>
291291
</message>
292+
<message>
293+
<source>I love when coils whine</source>
294+
<translation>Uwielbiam gdy cewki piszczą</translation>
295+
</message>
292296
<message>
293297
<source>MEGA modules:</source>
294298
<translation>Moduły MEGA:</translation>

src/ui/qt6/panel/controlpanel.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ static const QUrl ignition_sounds_l[] = {
114114
QUrl(SND_I "on-lock.wav"), // never happen
115115
};
116116

117+
#define SND_P "qrc:/sounds/psu/"
118+
119+
static const QUrl psu_sound_start = QUrl(SND_P "start.wav");
120+
static const QUrl psu_sound_stop = QUrl(SND_P "stop.wav");
121+
static const QUrl psu_sound_loop = QUrl(SND_P "loop.wav");
122+
117123
// -----------------------------------------------------------------------
118124
ControlPanel::ControlPanel(QWidget *parent):
119125
QWidget(parent)
@@ -149,6 +155,9 @@ ControlPanel::ControlPanel(QWidget *parent):
149155
}
150156
ignition = new Ignition(gfx, ignition_sounds_r, ignition_sounds_l, this);
151157

158+
psu = new Psu(psu_sound_start, psu_sound_stop, psu_sound_loop, this);
159+
connect(ignition, &Ignition::signal_psu, psu, &Psu::slot_set_power);
160+
152161
// Route the control switches out through the panel's own named signals so
153162
// callers talk to the panel's vocabulary instead of reaching into sw[].
154163
connect(sw[SW_START], &Switch::signal_toggled, this, &ControlPanel::signal_start_toggled);
@@ -178,12 +187,19 @@ void ControlPanel::set_volume(int volume_percent)
178187
// rotary and ignition samples are hotter than the switches; trim them to match
179188
rotary->set_volume(linear_volume);
180189
ignition->set_volume(linear_volume);
190+
psu->set_volume(linear_volume);
181191
keys->set_volume(linear_volume);
182192
for (int i=0 ; i<SW_CNT ; i++) {
183193
sw[i]->set_volume(linear_volume);
184194
}
185195
}
186196

197+
// -----------------------------------------------------------------------
198+
void ControlPanel::set_psu_sound(bool on)
199+
{
200+
psu->set_enabled(on);
201+
}
202+
187203
// -----------------------------------------------------------------------
188204
void ControlPanel::change_dimensions(const QRect &rect)
189205
{

src/ui/qt6/panel/controlpanel.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "binarykeys.h"
1010
#include "rotary.h"
1111
#include "ignition.h"
12+
#include "psu.h"
1213

1314
struct sw_desc {
1415
QRect r;
@@ -48,9 +49,11 @@ class ControlPanel : public QWidget
4849
BusWLeds *wleds;
4950
Rotary *rotary;
5051
Ignition *ignition;
52+
Psu *psu;
5153

5254
void dim(bool state);
5355
void set_volume(int volume_percent);
56+
void set_psu_sound(bool on);
5457

5558
public slots:
5659
void slot_state_changed(int state);

src/ui/qt6/panel/ignition.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ void Ignition::force_on()
6060
power_on_timer.stop();
6161
position = 1;
6262
update();
63+
emit signal_psu(true, false);
6364
emit signal_power(true);
6465
}
6566

@@ -72,6 +73,7 @@ void Ignition::snap_off()
7273
power_on_timer.stop();
7374
position = 0;
7475
update();
76+
emit signal_psu(false);
7577
}
7678

7779
// -----------------------------------------------------------------------
@@ -111,9 +113,13 @@ void Ignition::step(int new_pos)
111113
{
112114
if (new_pos > position) snd_r[new_pos].play();
113115
else snd_l[new_pos].play();
114-
if ((position == 0) && (new_pos == 1)) power_on_timer.start();
116+
if ((position == 0) && (new_pos == 1)) {
117+
power_on_timer.start();
118+
emit signal_psu(true);
119+
}
115120
if (new_pos == 0) {
116121
power_on_timer.stop();
122+
emit signal_psu(false);
117123
emit signal_power(false);
118124
}
119125
if (new_pos == 2) emit signal_locked(true);

src/ui/qt6/panel/ignition.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ class Ignition : public QWidget
4444
signals:
4545
void signal_power(bool state);
4646
void signal_locked(bool state);
47+
// PSU tracks the key position directly, not the (delayed, init-gated) power
48+
// lifecycle: it whirs up the instant the key reaches ON and down at OFF.
49+
void signal_psu(bool on, bool audible = true);
4750

4851
private slots:
4952
void power_on();

src/ui/qt6/panel/psu.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright (c) 2026 Jakub Filipowicz <jakubf@gmail.com>
2+
//
3+
// This program is free software; you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License as published by
5+
// the Free Software Foundation; either version 2 of the License, or
6+
// (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with this program; if not, write to the Free Software
15+
// Foundation, Inc.,
16+
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17+
18+
#include "psu.h"
19+
20+
#define FADE_MS 1000
21+
#define FADE_TICK_MS 16
22+
23+
// -----------------------------------------------------------------------
24+
Psu::Psu(const QUrl &snd_start_rs, const QUrl &snd_stop_rs, const QUrl &snd_loop_rs, QObject *parent)
25+
: QObject{parent}
26+
{
27+
snd_start.setSource(snd_start_rs);
28+
snd_stop.setSource(snd_stop_rs);
29+
snd_loop.setSource(snd_loop_rs);
30+
snd_loop.setLoopCount(QSoundEffect::Infinite);
31+
32+
fade_timer.setInterval(FADE_TICK_MS);
33+
connect(&fade_timer, &QTimer::timeout, this, &Psu::fade_step);
34+
connect(&snd_start, &QSoundEffect::playingChanged, this, &Psu::spin_up_finished);
35+
}
36+
37+
// -----------------------------------------------------------------------
38+
void Psu::set_volume(qreal linear_volume)
39+
{
40+
volume = linear_volume;
41+
snd_loop.setVolume(volume);
42+
if (fade_timer.isActive()) return;
43+
snd_start.setVolume(volume);
44+
snd_stop.setVolume(volume);
45+
}
46+
47+
// -----------------------------------------------------------------------
48+
void Psu::set_enabled(bool on)
49+
{
50+
pending_enabled = on;
51+
}
52+
53+
// -----------------------------------------------------------------------
54+
void Psu::crossfade(QSoundEffect *in, QSoundEffect *out)
55+
{
56+
fade_in = in;
57+
fade_out = out;
58+
chaining = false;
59+
fade_in->stop();
60+
fade_in->setVolume(0.0);
61+
fade_in->play();
62+
fade_timer.start();
63+
}
64+
65+
// -----------------------------------------------------------------------
66+
// The spin-up sample ran to its end; hand off to the sustain loop. Guarded so
67+
// our own stop()/restart calls (which also flip isPlaying) never trigger it.
68+
void Psu::spin_up_finished()
69+
{
70+
if (!chaining || snd_start.isPlaying()) return;
71+
chaining = false;
72+
snd_loop.setVolume(volume);
73+
snd_loop.play();
74+
}
75+
76+
// -----------------------------------------------------------------------
77+
void Psu::fade_step()
78+
{
79+
qreal step = volume * ((qreal) FADE_TICK_MS / FADE_MS);
80+
qreal vin = qMin(volume, fade_in->volume() + step);
81+
qreal vout = qMax((qreal) 0.0, fade_out->volume() - step);
82+
fade_in->setVolume(vin);
83+
fade_out->setVolume(vout);
84+
if ((vin >= volume) && (vout <= 0.0)) {
85+
fade_out->stop();
86+
fade_timer.stop();
87+
}
88+
}
89+
90+
// -----------------------------------------------------------------------
91+
void Psu::slot_set_power(bool on, bool audible)
92+
{
93+
if (on == running) return;
94+
running = on;
95+
if (on) enabled = pending_enabled;
96+
if (!audible || !enabled) return;
97+
if (on && snd_stop.isPlaying()) {
98+
// blend the spin-up in over a still-ringing spin-down; every other
99+
// transition is a hard cut
100+
crossfade(&snd_start, &snd_stop);
101+
chaining = true;
102+
} else if (on) {
103+
chaining = false;
104+
snd_stop.stop();
105+
snd_start.stop();
106+
snd_start.setVolume(volume);
107+
snd_start.play();
108+
chaining = true;
109+
} else {
110+
chaining = false;
111+
fade_timer.stop();
112+
snd_start.stop();
113+
snd_loop.stop();
114+
snd_stop.stop();
115+
snd_stop.setVolume(volume);
116+
snd_stop.play();
117+
}
118+
}
119+
120+
// vim: tabstop=4 shiftwidth=4 autoindent

0 commit comments

Comments
 (0)