Defer engine start until window is open, control master volume, transition audio in on start.

main
Ben Niemann 1 year ago
parent 62c28181a8
commit d132c430c8

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z" /></svg>

After

Width:  |  Height:  |  Size: 510 B

@ -154,6 +154,7 @@ async def uiProject(mockApp, modelProject):
context=ui_base.CommonContext(app=mockApp))
try:
p.setup()
p.restartEngine()
yield p
finally:
p.cleanup()

@ -86,6 +86,7 @@ public:
return ERROR_STATUS("%s: Failed to init swr context: %s", port_name(), av_make_error_string(buf, sizeof(buf), rc));
}
_out_sample_rate = out_sample_rate;
_out_buffer_size = 2 * av_rescale_rnd(in_block_size, out_sample_rate, in_sample_rate, AV_ROUND_UP);
_out_buffer = new float[_out_buffer_size];
_out_offset = 0;
@ -132,7 +133,7 @@ public:
return Status::Ok();
}
void finish_block() {
void finish_block(float master_volume) {
if (_out_offset > 0 && _jack_offset < _jack_buffer_size) {
size_t num_samples = std::min(_out_offset, _jack_buffer_size - _jack_offset);
//printf("%s: consume %lu samples\n", port_name(), num_samples);
@ -152,13 +153,22 @@ public:
}
}
// Clip output signal to [-1.0, 1.0].
// Clip output signal to [-1.0, 1.0] and apply master volume.
// Transition audio in over 1sec to lessen the shock.
// TODO: signal to UI that clipping happened.
float* s = _jack_buffer;
uint32_t transition_end = _out_sample_rate;
uint32_t smpl = (uint32_t)std::min(_samples_written, (uint64_t)transition_end);
for (unsigned int i = 0 ; i < _jack_buffer_size ; ++i) {
float v = std::max(-1.0f, std::min(*s, 1.0f));
v *= master_volume * smpl / transition_end;
*s++ = v;
if (smpl < transition_end) {
++smpl;
}
}
_samples_written += _jack_buffer_size;
}
jack_port_t* port() const { return _port; }
@ -169,6 +179,8 @@ private:
jack_client_t* _client;
jack_port_t* _port;
SwrContext* _swr_ctxt = nullptr;
uint64_t _samples_written = 0;
uint32_t _out_sample_rate;
size_t _out_buffer_size;
float* _out_buffer = nullptr;
size_t _out_offset = 0;
@ -672,7 +684,7 @@ int BackendJack::_process_frame(jack_nframes_t nframes) {
for (const auto& hit : io_ports->io_ports()) {
for (const auto& io_port : hit.second) {
io_port->finish_block();
io_port->finish_block(_engine->master_volume());
}
}

@ -69,6 +69,9 @@ public:
uint32_t sample_rate() const { return 44100; }
void set_sample_rate(uint32_t sample_rate) override {}
float master_volume() const { return 1.0; }
void set_master_volume(float voume) override {}
Status add_node(uint64_t id, const NodeDescription* description) override { return Status::Ok(); }
Status remove_node(uint64_t id) override { return Status::Ok(); }
@ -423,8 +426,8 @@ TEST_F(BackendJackTest, Clipping) {
ASSERT_SUCCESS(be_->connect_io_buffer(0x1234, 0, "backend_jack_test", "playback_1"));
ASSERT_SUCCESS(be_->connect_io_buffer(0x1234, 1, "backend_jack_test", "playback_2"));
// Run for a few blocks...
process_blocks(10);
// Run for a few blocks (at least one sec)...
process_blocks(engine_->sample_rate() / engine_->block_size() + 10);
// The engine should have seen the IO buffers by now.
ASSERT_TRUE(engine_->last_io_buffers.count(0x1234) == 1);

@ -67,6 +67,9 @@ public:
virtual uint32_t sample_rate() const = 0;
virtual void set_sample_rate(uint32_t sample_rate) = 0;
virtual float master_volume() const = 0;
virtual void set_master_volume(float volume) = 0;
virtual Status add_node(uint64_t id, const NodeDescription* description) = 0;
virtual Status remove_node(uint64_t id) = 0;

@ -61,6 +61,9 @@ cdef extern from "noisicaa/engine/engine.h" namespace "noisicaa" nogil:
uint32_t sample_rate() const
void set_sample_rate(uint32_t sample_rate)
float master_volume() const
void set_master_volume(float volume)
Status add_node(uint64_t id, const NodeDescription* description)
Status remove_node(uint64_t id)

@ -87,6 +87,14 @@ cdef class PyEngine:
def block_size(self, v):
self.__engine.set_block_size(v)
@property
def master_volume(self):
return self.__engine.master_volume()
@master_volume.setter
def master_volume(self, v):
self.__engine.set_master_volume(v)
def add_node_message_listener(self, node_id, cb):
cdef uint64_t c_node_id = node_id
cdef PyObject* c_cb = <PyObject*>cb

@ -88,6 +88,7 @@ EngineImpl::EngineImpl()
_old_out_messages(nullptr),
_logger_registry(LoggerRegistry::get_registry()),
_rt_log_sink(new QueueLogSink()),
_master_volume(1.0),
_block_size(1024),
_sample_rate(48000),
_next_program(nullptr),
@ -359,6 +360,14 @@ Status EngineImpl::set_backend(const BackendSettings* settings) {
return Status::Ok();
}
float EngineImpl::master_volume() const { return _master_volume; }
void EngineImpl::set_master_volume(float volume) {
assert(volume >= 0.0);
assert(volume <= 1.0);
_master_volume = volume;
}
uint32_t EngineImpl::block_size() const { return _block_size; }
void EngineImpl::set_block_size(uint32_t block_size) {

@ -82,6 +82,9 @@ public:
uint32_t sample_rate() const override;
void set_sample_rate(uint32_t sample_rate) override;
float master_volume() const override;
void set_master_volume(float volume) override;
Status add_node(uint64_t id, const NodeDescription* description) override;
Status remove_node(uint64_t id) override;
@ -137,6 +140,7 @@ private:
std::unique_ptr<QueueLogSink> _rt_log_sink;
std::unique_ptr<Backend> _backend;
float _master_volume;
uint32_t _block_size;
uint32_t _sample_rate;
uint32_t _sample_pos;

@ -82,6 +82,7 @@ class App(ui_base.PropertyContainer, QtCore.QObject):
displayMaterialTheme, displayMaterialThemeChanged = ui_base.Property('displayMaterialTheme', str)
displayMaterialVariant, displayMaterialVariantChanged = ui_base.Property('displayMaterialVariant', str)
engineProgram, engineProgramChanged = ui_base.Property('engineProgram', str)
engineMasterVolume, engineMasterVolumeChanged = ui_base.Property('engineMasterVolume', float)
def __init__(self) -> None:
super().__init__()
@ -102,6 +103,8 @@ class App(ui_base.PropertyContainer, QtCore.QObject):
self._projectWindowComponent = None # type: QtQml.QQmlComponent
self._window = None # type: QtQuick.QQuickItem
self.engineMasterVolumeChanged.connect(self.__engineMasterVolumeChanged)
@ui_base.ConstProperty(str)
def appVersion(self) -> str:
return noisicaa.__version__
@ -194,6 +197,7 @@ class App(ui_base.PropertyContainer, QtCore.QObject):
self._project.setup()
if self._window is not None:
self._window.setProperty('d', self._project)
self._project.restartEngine()
@QtCore.Slot(str)
def openProject(self, path: str) -> None:
@ -211,6 +215,7 @@ class App(ui_base.PropertyContainer, QtCore.QObject):
self._project.setup()
if self._window is not None:
self._window.setProperty('d', self._project)
self._project.restartEngine()
@QtCore.Slot()
def showEngineProgramWindow(self) -> None:
@ -226,3 +231,8 @@ class App(ui_base.PropertyContainer, QtCore.QObject):
def __programChanged(self, unused_: object=None) -> None:
self.engineProgram = self._engine.program_code()
def __engineMasterVolumeChanged(self, volume: float) -> None:
volume = max(0.0, min(volume, 1.0))
self.settings.setValue('masterVolume', volume)
self._engine.master_volume = volume

@ -165,8 +165,6 @@ class Project(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObject):
def setup(self) -> None:
self.__undoStack = QtWidgets.QUndoStack(self)
self.restartEngine()
self.__nodesListener = self.__project.nodesChanged.connect(self.__nodesChanged)
self.__connectionsListener = self.__project.connectionsChanged.connect(self.__connectionsChanged)
@ -179,6 +177,7 @@ class Project(ui_base.ProjectMixin, ui_base.PropertyContainer, QtCore.QObject):
self.__project.nodesChanged.disconnect(self.__nodesChanged)
self.__nodesListener = None
self.engine.stop()
self.engine.clear_graph()
self.engine.compile_graph()

@ -228,6 +228,30 @@ ApplicationWindow {
}
}
header: ToolBar {
RowLayout {
anchors.fill: parent
Item {
Layout.fillWidth: true
}
Image {
Layout.alignment: Qt.AlignVCenter
source: "icons:volume.svg"
}
Slider {
Layout.alignment: Qt.AlignVCenter
orientation: Qt.Horizontal
from: 0.0
to: 1.0
value: d.app.engineMasterVolume
onValueChanged: d.app.engineMasterVolume = value
}
}
}
Rectangle {
id: spinner
visible: d.app.enableRenderPerformanceMonitoring

@ -168,6 +168,8 @@ class MainApp(App):
self._engine = engine_lib.Engine()
self._engine.notifications.add(self.__handleEngineNotification)
self.engineMasterVolume = float(settings.value('masterVolume', 1.0))
logger.info("Initialize node db...")
builder = flatbuffers.Builder(1024)
builder.Finish(engine_lib.CreateNodeDBSettings(
@ -215,6 +217,8 @@ class MainApp(App):
self._window.resize(int(width), int(height))
self._window.show()
self._project.restartEngine()
await self.__quit.wait()
logger.info("Closing UI...")

Loading…
Cancel
Save