diff --git a/effects/paneffect.cpp b/effects/paneffect.cpp index 6a446e673..fa7085f3f 100644 --- a/effects/paneffect.cpp +++ b/effects/paneffect.cpp @@ -21,10 +21,9 @@ PanEffect::PanEffect(Clip* c) : Effect(c, EFFECT_TYPE_AUDIO, AUDIO_PAN_EFFECT) { void PanEffect::refresh() {} Effect* PanEffect::copy(Clip* c) { - /*PanEffect* p = new PanEffect(c); - p->pan_val->set_value(pan_val->value()); - return p;*/ - return NULL; + PanEffect* p = new PanEffect(c); + p->pan_val->set_double_value(pan_val->get_double_value()); + return p; } void PanEffect::process_audio(quint8 *samples, int nb_bytes) { diff --git a/effects/shakeeffect.cpp b/effects/shakeeffect.cpp index 7e98d4b45..5b7a756b5 100644 --- a/effects/shakeeffect.cpp +++ b/effects/shakeeffect.cpp @@ -44,12 +44,11 @@ void ShakeEffect::refresh() { } Effect* ShakeEffect::copy(Clip* c) { - /*ShakeEffect* e = new ShakeEffect(c); - e->intensity_val->set_value(intensity_val->value()); - e->rotation_val->set_value(rotation_val->value()); - e->frequency_val->set_value(frequency_val->value()); - return e;*/ - return NULL; + ShakeEffect* e = new ShakeEffect(c); + e->intensity_val->set_double_value(intensity_val->get_double_value()); + e->rotation_val->set_double_value(rotation_val->get_double_value()); + e->frequency_val->set_double_value(frequency_val->get_double_value()); + return e; } void ShakeEffect::process_gl(int*, int*) { diff --git a/io/config.h b/io/config.h index f18f53c1e..72ba4de91 100644 --- a/io/config.h +++ b/io/config.h @@ -3,7 +3,7 @@ #include -#define SAVE_VERSION "180727" +#define SAVE_VERSION "180814" struct Config { Config(); diff --git a/mainwindow.cpp b/mainwindow.cpp index b3523e48a..ba868978b 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -20,6 +20,8 @@ #include "dialogs/exportdialog.h" #include "dialogs/preferencesdialog.h" +#include "playback/audio.h" + #include "ui_timeline.h" #include @@ -149,6 +151,8 @@ MainWindow::~MainWindow() { config.save(config_dir); } + stop_audio(); + delete ui; delete panel_project; diff --git a/panels/panels.cpp b/panels/panels.cpp index 2761c74da..f6bc97d14 100644 --- a/panels/panels.cpp +++ b/panels/panels.cpp @@ -1,6 +1,6 @@ #include "panels.h" -Project* panel_project; -EffectControls* panel_effect_controls; -Viewer* panel_viewer; -Timeline* panel_timeline; +Project* panel_project = 0; +EffectControls* panel_effect_controls = 0; +Viewer* panel_viewer = 0; +Timeline* panel_timeline = 0; diff --git a/panels/project.cpp b/panels/project.cpp index eac9ec342..3edf91834 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -1110,7 +1110,7 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int for (int k=0;keffects.size();k++) { stream.writeStartElement("effect"); // effect Effect* e = c->effects.at(k); - stream.writeAttribute("id", QString::number(e->id)); + stream.writeAttribute("id", QString::number(e->id)); e->save(&stream); stream.writeEndElement(); // effect } diff --git a/panels/timeline.cpp b/panels/timeline.cpp index 9aed6d8af..96f3e9018 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -138,16 +138,16 @@ void Timeline::reset_all_audio() { } ui->audio_monitor->reset(); clear_audio_ibuffer(); + audio_ibuffer_frame = playhead; } void Timeline::seek(long p) { pause(); - reset_all_audio(); - audio_ibuffer_frame = p; - playhead = p; + reset_all_audio(); + repaint_timeline(); } @@ -170,6 +170,7 @@ void Timeline::play() { void Timeline::pause() { playing = false; panel_viewer->set_playpause_icon(true); + playback_updater.stop(); } void Timeline::go_to_end() { diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 807138027..fbdbf7fd7 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -29,9 +29,7 @@ Viewer::Viewer(QWidget *parent) : update_end_timecode(); } -Viewer::~Viewer() -{ - init_audio(); +Viewer::~Viewer() { delete ui; } diff --git a/playback/audio.cpp b/playback/audio.cpp index 7b7b64fc5..b40828b32 100644 --- a/playback/audio.cpp +++ b/playback/audio.cpp @@ -2,6 +2,10 @@ #include "project/sequence.h" +#include "panels/panels.h" +#include "panels/timeline.h" +#include "ui_timeline.h" + #include #include #include @@ -18,12 +22,10 @@ qint8 audio_ibuffer[audio_ibuffer_size]; int audio_ibuffer_read = 0; long audio_ibuffer_frame = 0; +AudioSenderThread* audio_thread; + void init_audio() { - if (audio_device_set) { - audio_output->stop(); - delete audio_output; - audio_device_set = false; - } + stop_audio(); if (sequence != NULL) { QAudioFormat audio_format; @@ -45,11 +47,25 @@ void init_audio() { audio_io_device = audio_output->start(); audio_device_set = true; + // start sender thread + audio_thread = new AudioSenderThread(); + audio_thread->start(); + clear_audio_ibuffer(); } } } +void stop_audio() { + if (audio_device_set) { + audio_thread->stop(); + + audio_output->stop(); + delete audio_output; + audio_device_set = false; + } +} + void clear_audio_ibuffer() { memset(audio_ibuffer, 0, audio_ibuffer_size); audio_ibuffer_read = 0; @@ -63,3 +79,74 @@ int get_buffer_offset_from_frame(long frame) { return 0; } } + +AudioSenderThread::AudioSenderThread() : close(false) { + connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); +} + +void AudioSenderThread::stop() { + close = true; + cond.wakeAll(); + wait(); +} + +void AudioSenderThread::run() { + // start data loop + send_audio_to_output(0, audio_ibuffer_size); + + lock.lock(); + while (true) { + msleep(20); + if (close) { + break; + } else if (panel_timeline->playing) { + int written_bytes = 0; + + int adjusted_read_index = audio_ibuffer_read%audio_ibuffer_size; + int max_write = audio_ibuffer_size - adjusted_read_index; + int actual_write = send_audio_to_output(adjusted_read_index, max_write); + written_bytes += actual_write; + if (actual_write == max_write) { + // got all the bytes, write again + written_bytes += send_audio_to_output(0, audio_ibuffer_size); + } + } + } + lock.unlock(); +} + +int AudioSenderThread::send_audio_to_output(int offset, int max) { + // send audio to device + int actual_write = audio_io_device->write((const char*) audio_ibuffer+offset, max); + audio_ibuffer_read += actual_write; + + // send samples to audio monitor cache + if (panel_timeline->ui->audio_monitor->sample_cache_offset == -1) { + panel_timeline->ui->audio_monitor->sample_cache_offset = panel_timeline->playhead; + } + int channel_count = av_get_channel_layout_nb_channels(sequence->audio_layout); + long sample_cache_playhead = panel_timeline->ui->audio_monitor->sample_cache_offset + (panel_timeline->ui->audio_monitor->sample_cache.size()/channel_count); + int next_buffer_offset, buffer_offset_adjusted, i; + int buffer_offset = get_buffer_offset_from_frame(sample_cache_playhead); + if (samples.size() != channel_count) samples.resize(channel_count); + samples.fill(0); + + // TODO: I don't like this, but i'm not sure if there's a smarter way to do it + while (buffer_offset < audio_ibuffer_read) { + sample_cache_playhead++; + next_buffer_offset = qMin(get_buffer_offset_from_frame(sample_cache_playhead), audio_ibuffer_read); + while (buffer_offset < next_buffer_offset) { + for (i=0;iui->audio_monitor->sample_cache.append(samples); + buffer_offset = next_buffer_offset; + } + + memset(audio_ibuffer+offset, 0, actual_write); + + return actual_write; +} diff --git a/playback/audio.h b/playback/audio.h index dbeba8729..2ea2bba82 100644 --- a/playback/audio.h +++ b/playback/audio.h @@ -2,12 +2,15 @@ #define AUDIO_H #include +#include +#include +#include //#define INT16_MAX 0x7fff //#define INT16_MIN (-INT16_MAX-1) -class QAudioOutput; class QIODevice; +class QAudioOutput; struct Sequence; @@ -21,6 +24,21 @@ extern long audio_ibuffer_frame; void clear_audio_ibuffer(); void init_audio(); +void stop_audio(); int get_buffer_offset_from_frame(long frame); +class AudioSenderThread : public QThread { + Q_OBJECT +public: + AudioSenderThread(); + void run(); + void stop(); + QWaitCondition cond; + bool close; + QMutex lock; +private: + QVector samples; + int send_audio_to_output(int offset, int max); +}; + #endif // AUDIO_H diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 250a3b439..73253552f 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -446,7 +446,7 @@ void Cacher::run() { open_clip_worker(clip); while (caching) { - clip->can_cache.wait(&clip->lock); + clip->can_cache.wait(&clip->lock); if (!caching) { break; } else { diff --git a/project/effect.cpp b/project/effect.cpp index a765e6ceb..cec655950 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -48,9 +48,7 @@ EffectRow* Effect::row(int i) { return rows.at(i); } -void Effect::refresh() { - qDebug() << "[WARNING] Tried to init base Effect class - type:" << type << "- id:" << id; -} +void Effect::refresh() {} void Effect::field_changed() { panel_viewer->viewer_widget->update(); @@ -65,9 +63,80 @@ bool Effect::is_enabled() { return container->enabled_check->isChecked(); } +void Effect::load(QXmlStreamReader* stream) { + for (int i=0;iatEnd()) { + stream->readNext(); + if (stream->name() == "row" && stream->isStartElement()) { + for (int j=0;jfieldCount();j++) { + EffectField* field = row->field(j); + while (!stream->atEnd()) { + stream->readNext(); + if (stream->name() == "field" && stream->isStartElement()) { + stream->readNext(); + switch (field->type) { + case EFFECT_FIELD_DOUBLE: + field->set_double_value(stream->text().toDouble()); + break; + case EFFECT_FIELD_COLOR: + field->set_color_value(QColor(stream->text().toString())); + break; + case EFFECT_FIELD_STRING: + field->set_string_value(stream->text().toString()); + break; + case EFFECT_FIELD_BOOL: + field->set_bool_value(stream->text() == "1"); + break; + case EFFECT_FIELD_COMBO: + field->set_combo_string(stream->text().toString()); + break; + case EFFECT_FIELD_FONT: + field->set_combo_string(stream->text().toString()); + break; + } + break; + } + } + } + break; + } + } + } +} + +void Effect::save(QXmlStreamWriter* stream) { + for (int i=0;iwriteStartElement("row"); + for (int j=0;jfieldCount();j++) { + EffectField* field = row->field(j); + switch (field->type) { + case EFFECT_FIELD_DOUBLE: + stream->writeTextElement("field", QString::number(field->get_double_value())); + break; + case EFFECT_FIELD_COLOR: + stream->writeTextElement("field", field->get_color_value().name()); + break; + case EFFECT_FIELD_STRING: + stream->writeTextElement("field", field->get_string_value()); + break; + case EFFECT_FIELD_BOOL: + stream->writeTextElement("field", QString::number(field->get_bool_value())); + break; + case EFFECT_FIELD_COMBO: + stream->writeTextElement("field", field->get_combo_string()); + break; + case EFFECT_FIELD_FONT: + stream->writeTextElement("field", field->get_font_name()); + break; + } + } + stream->writeEndElement(); // row + } +} + Effect* Effect::copy(Clip*) {return NULL;} -void Effect::load(QXmlStreamReader*) {} -void Effect::save(QXmlStreamWriter*) {} void Effect::process_gl(int*, int*) {} void Effect::post_gl() {} void Effect::process_audio(uint8_t*, int) {} @@ -92,6 +161,14 @@ EffectRow::~EffectRow() { } } +EffectField* EffectRow::field(int i) { + return fields.at(i); +} + +int EffectRow::fieldCount() { + return fields.size(); +} + /* Effect Field Definitions */ EffectField::EffectField(Effect *parent, int t) : type(t) { @@ -191,6 +268,10 @@ void EffectField::set_combo_index(int index) { static_cast(ui_element)->setCurrentIndex(index); } +void EffectField::set_combo_string(const QString& s) { + static_cast(ui_element)->setCurrentText(s); +} + bool EffectField::get_bool_value() { return static_cast(ui_element)->isChecked(); } @@ -214,3 +295,7 @@ const QString EffectField::get_font_name() { QColor EffectField::get_color_value() { return static_cast(ui_element)->get_color(); } + +void EffectField::set_color_value(QColor color) { + static_cast(ui_element)->set_color(color); +} diff --git a/project/effect.h b/project/effect.h index 39a58cf8c..3d8614380 100644 --- a/project/effect.h +++ b/project/effect.h @@ -44,6 +44,7 @@ public: const QVariant get_combo_data(); const QString get_combo_string(); void set_combo_index(int index); + void set_combo_string(const QString& s); bool get_bool_value(); void set_bool_value(bool b); @@ -51,6 +52,7 @@ public: const QString get_font_name(); QColor get_color_value(); + void set_color_value(QColor color); QWidget* get_ui_element(); void set_enabled(bool e); @@ -64,6 +66,7 @@ public: ~EffectRow(); EffectField* add_field(int type); EffectField* field(int i); + int fieldCount(); void set_keyframe(int field, long time); void move_keyframe(int field, long from, long to); void delete_keyframe(int field, long time); diff --git a/ui/audiomonitor.cpp b/ui/audiomonitor.cpp index ed4e52c49..8242fcaa7 100644 --- a/ui/audiomonitor.cpp +++ b/ui/audiomonitor.cpp @@ -37,9 +37,10 @@ void AudioMonitor::resizeEvent(QResizeEvent *e) { void AudioMonitor::paintEvent(QPaintEvent *) { if (sequence != NULL) { + QPainter p(this); int channel_x = AUDIO_MONITOR_GAP; - int channel_count = av_get_channel_layout_nb_channels(sequence->audio_layout); + int channel_count = av_get_channel_layout_nb_channels(sequence->audio_layout); if (peaks.size() != channel_count) { peaks.resize(channel_count); peaks.fill(false); diff --git a/ui/colorbutton.cpp b/ui/colorbutton.cpp index 5a0245bec..9a78b0373 100644 --- a/ui/colorbutton.cpp +++ b/ui/colorbutton.cpp @@ -16,23 +16,24 @@ QColor ColorButton::get_color() { void ColorButton::set_color(QColor c) { color = c; + set_button_color(); } void ColorButton::set_button_color() { QPalette pal = palette(); pal.setColor(QPalette::Button, color); setPalette(pal); + emit color_changed(); } void ColorButton::open_dialog() { QColor old_color = color; QColor new_color = QColorDialog::getColor(color, NULL); - if (old_color != new_color) { + if (new_color.isValid() && old_color != new_color) { ColorCommand* command = new ColorCommand(this, old_color, new_color); undo_stack.push(command); set_button_color(); - emit color_changed(); } } diff --git a/ui/fontcombobox.cpp b/ui/fontcombobox.cpp index 62ec3462b..3d71b41f7 100644 --- a/ui/fontcombobox.cpp +++ b/ui/fontcombobox.cpp @@ -3,20 +3,5 @@ #include FontCombobox::FontCombobox(QWidget* parent) : ComboBoxEx(parent) { - addItem(QFont().family()); -} - -void FontCombobox::showPopup() { - QString current = currentText(); - clear(); - QStringList fonts = QFontDatabase().families(); - bool found = false; - for (int i=0;i #include +#include #include extern "C" { @@ -31,20 +32,11 @@ ViewerWidget::ViewerWidget(QWidget *parent) : format.setDepthBufferSize(24); setFormat(format); - // start audio sending thread - audio_sender_thread.start(); - // error handler - retries after 250ms if we couldn't get the entire image retry_timer.setInterval(250); connect(&retry_timer, SIGNAL(timeout()), this, SLOT(retry())); } -ViewerWidget::~ViewerWidget() { - audio_sender_thread.close = true; - audio_sender_thread.cond.wakeAll(); - audio_sender_thread.wait(); -} - void ViewerWidget::deleteFunction() { // destroy all textures as well for (int i=0;iclip_count();i++) { @@ -229,7 +221,7 @@ void ViewerWidget::compose_sequence(QVector& nests, bool render_audio) { void ViewerWidget::paintGL() { bool loop = true; - while (loop) { + while (loop) { loop = false; texture_failed = false; @@ -241,11 +233,6 @@ void ViewerWidget::paintGL() { QVector nests; compose_sequence(nests, (panel_timeline->playing || force_audio)); - // send audio to IO device - if (panel_timeline->playing) { - audio_sender_thread.cond.wakeAll(); - } - if (texture_failed) { if (multithreaded) { retry_timer.start(); @@ -256,64 +243,3 @@ void ViewerWidget::paintGL() { } } } - -AudioSenderThread::AudioSenderThread() : close(false) { - connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); -} - -void AudioSenderThread::run() { - lock.lock(); - while (true) { - cond.wait(&lock); - if (close) { - break; - } else { - int written_bytes = 0; - - int adjusted_read_index = audio_ibuffer_read%audio_ibuffer_size; - int max_write = audio_ibuffer_size - adjusted_read_index; - int actual_write = send_audio_to_output(adjusted_read_index, max_write); - written_bytes += actual_write; - if (actual_write == max_write) { - // got all the bytes, write again - written_bytes += send_audio_to_output(0, audio_ibuffer_size); - } - - qDebug() << "WB:" << written_bytes; - } - } - lock.unlock(); -} - -int AudioSenderThread::send_audio_to_output(int offset, int max) { - // send audio to device - int actual_write = audio_io_device->write((const char*) audio_ibuffer+offset, max); - audio_ibuffer_read += actual_write; - - // send samples to audio monitor cache - if (panel_timeline->ui->audio_monitor->sample_cache_offset == -1) { - panel_timeline->ui->audio_monitor->sample_cache_offset = panel_timeline->playhead; - } - long sample_cache_playhead = panel_timeline->ui->audio_monitor->sample_cache_offset + panel_timeline->ui->audio_monitor->sample_cache.size(); - int next_buffer_offset, buffer_offset_adjusted, i; - int buffer_offset = get_buffer_offset_from_frame(sample_cache_playhead); - samples.resize(av_get_channel_layout_nb_channels(sequence->audio_layout)); - samples.fill(0); - while (buffer_offset < audio_ibuffer_read) { - sample_cache_playhead++; - next_buffer_offset = qMin(get_buffer_offset_from_frame(sample_cache_playhead), audio_ibuffer_read); - while (buffer_offset < next_buffer_offset) { - for (i=0;iui->audio_monitor->sample_cache.append(samples); - buffer_offset = next_buffer_offset; - } - - memset(audio_ibuffer+offset, 0, actual_write); - - return actual_write; -} diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index e04b0944a..e97c8bcaf 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -13,24 +13,11 @@ struct Clip; struct Sequence; -class AudioSenderThread : public QThread { -public: - AudioSenderThread(); - void run(); - QWaitCondition cond; - bool close; - QMutex lock; -private: - QVector samples; - int send_audio_to_output(int offset, int max); -}; - class ViewerWidget : public QOpenGLWidget, public QOpenGLFunctions { Q_OBJECT public: - ViewerWidget(QWidget *parent = 0); - ~ViewerWidget(); + ViewerWidget(QWidget *parent = 0); bool multithreaded; bool force_audio; @@ -43,7 +30,6 @@ protected: // void resizeGL(int w, int h); private: QTimer retry_timer; - AudioSenderThread audio_sender_thread; private slots: void retry(); void deleteFunction();