diff --git a/src/control/AudioController.cpp b/src/control/AudioController.cpp index 4526896a..74eed2de 100644 --- a/src/control/AudioController.cpp +++ b/src/control/AudioController.cpp @@ -10,7 +10,7 @@ AudioController::AudioController(Settings* settings, Control* control) this->settings = settings; this->control = control; this->audioRecorder = new AudioRecorder(settings); - this->audioPlayer = new AudioPlayer(settings); + this->audioPlayer = new AudioPlayer(control, settings); } AudioController::~AudioController() @@ -26,11 +26,11 @@ AudioController::~AudioController() XOJ_RELEASE_TYPE(AudioController); } -bool AudioController::recStart() +bool AudioController::startRecording() { XOJ_CHECK_TYPE(AudioController); - if (!this->getAudioRecorder()->isRecording()) + if (!this->isRecording()) { if (getAudioFolder().isEmpty()) { @@ -52,7 +52,7 @@ bool AudioController::recStart() g_message("Start recording"); - bool isRecording = this->getAudioRecorder()->start(getAudioFolder().str() + "/" + data); + bool isRecording = this->audioRecorder->start(getAudioFolder().str() + "/" + data); if (!isRecording) { @@ -65,8 +65,10 @@ bool AudioController::recStart() return false; } -bool AudioController::recStop() +bool AudioController::stopRecording() { + XOJ_CHECK_TYPE(AudioController); + if (this->audioRecorder->isRecording()) { audioFilename = ""; @@ -79,6 +81,59 @@ bool AudioController::recStop() return true; } +bool AudioController::isRecording() +{ + XOJ_CHECK_TYPE(AudioController); + + return this->audioRecorder->isRecording(); +} + +bool AudioController::isPlaying() +{ + XOJ_CHECK_TYPE(AudioController); + + return this->audioPlayer->isPlaying(); +} + +bool AudioController::startPlayback(string filename, unsigned int timestamp) +{ + XOJ_CHECK_TYPE(AudioController); + + this->audioPlayer->stop(); + bool status = this->audioPlayer->start(std::move(filename), timestamp); + if (status) + { + this->control->getWindow()->getToolMenuHandler()->enableAudioPlaybackButtons(); + } + return status; +} + +void AudioController::pausePlayback() +{ + XOJ_CHECK_TYPE(AudioController); + + this->control->getWindow()->getToolMenuHandler()->setAudioPlaybackPaused(true); + + this->audioPlayer->pause(); +} + +void AudioController::continuePlayback() +{ + XOJ_CHECK_TYPE(AudioController); + + this->control->getWindow()->getToolMenuHandler()->setAudioPlaybackPaused(false); + + this->audioPlayer->play(); +} + +void AudioController::stopPlayback() +{ + XOJ_CHECK_TYPE(AudioController); + + this->control->getWindow()->getToolMenuHandler()->disableAudioPlaybackButtons(); + this->audioPlayer->stop(); +} + string AudioController::getAudioFilename() { XOJ_CHECK_TYPE(AudioController); @@ -111,12 +166,16 @@ size_t AudioController::getStartTime() return this->timestamp; } -AudioRecorder* AudioController::getAudioRecorder() +vector AudioController::getOutputDevices() { - return this->audioRecorder; + XOJ_CHECK_TYPE(AudioController); + + return this->audioPlayer->getOutputDevices(); } -AudioPlayer* AudioController::getAudioPlayer() +vector AudioController::getInputDevices() { - return this->audioPlayer; + XOJ_CHECK_TYPE(AudioController); + + return this->audioRecorder->getInputDevices(); } diff --git a/src/control/AudioController.h b/src/control/AudioController.h index a154a36a..36a6b41c 100644 --- a/src/control/AudioController.h +++ b/src/control/AudioController.h @@ -17,6 +17,7 @@ #include #include #include +#include class AudioController { @@ -25,13 +26,21 @@ public: virtual ~AudioController(); public: - bool recStart(); - bool recStop(); + bool startRecording(); + bool stopRecording(); + bool isRecording(); + + bool isPlaying(); + bool startPlayback(string filename, unsigned int timestamp); + void pausePlayback(); + void continuePlayback(); + void stopPlayback(); + string getAudioFilename(); Path getAudioFolder(); size_t getStartTime(); - AudioRecorder* getAudioRecorder(); - AudioPlayer* getAudioPlayer(); + vector getOutputDevices(); + vector getInputDevices(); protected: string audioFilename; diff --git a/src/control/Control.cpp b/src/control/Control.cpp index 32315e8f..11ff55a7 100644 --- a/src/control/Control.cpp +++ b/src/control/Control.cpp @@ -944,15 +944,15 @@ void Control::actionPerformed(ActionType type, ActionGroup group, GdkEvent* even break; - case ACTION_RECSTOP: + case ACTION_AUDIO_RECORD: { bool result; if (enabled) { - result = audioController->recStart(); + result = audioController->startRecording(); } else { - result = audioController->recStop(); + result = audioController->stopRecording(); } if (!result) @@ -969,6 +969,20 @@ void Control::actionPerformed(ActionType type, ActionGroup group, GdkEvent* even break; } + case ACTION_AUDIO_PAUSE_PLAYBACK: + if (enabled) + { + this->getAudioController()->pausePlayback(); + } else + { + this->getAudioController()->continuePlayback(); + } + break; + + case ACTION_AUDIO_STOP_PLAYBACK: + this->getAudioController()->stopPlayback(); + break; + case ACTION_ROTATION_SNAPPING: rotationSnappingToggle(); break; @@ -2732,7 +2746,7 @@ void Control::quit(bool allowCancel) this->scheduler->lock(); - audioController->recStop(); + audioController->stopRecording(); settings->save(); this->scheduler->removeAllJobs(); diff --git a/src/control/tools/InputHandler.cpp b/src/control/tools/InputHandler.cpp index e1fcec2a..0f5d09ad 100644 --- a/src/control/tools/InputHandler.cpp +++ b/src/control/tools/InputHandler.cpp @@ -67,7 +67,7 @@ void InputHandler::createStroke(Point p) { stroke->setToolType(STROKE_TOOL_PEN); - if (xournal->getControl()->getAudioController()->getAudioRecorder()->isRecording()) + if (xournal->getControl()->getAudioController()->isRecording()) { string audioFilename = xournal->getControl()->getAudioController()->getAudioFilename(); size_t sttime = xournal->getControl()->getAudioController()->getStartTime(); diff --git a/src/enums/ActionGroup.enum.h b/src/enums/ActionGroup.enum.h index 4fcc0e02..8e4a784b 100644 --- a/src/enums/ActionGroup.enum.h +++ b/src/enums/ActionGroup.enum.h @@ -50,7 +50,7 @@ enum ActionGroup GROUP_LINE_STYLE, - GROUP_REC, + GROUP_AUDIO, GROUP_SNAPPING, diff --git a/src/enums/ActionType.enum.h b/src/enums/ActionType.enum.h index dca9e9c1..e2cfe44f 100644 --- a/src/enums/ActionType.enum.h +++ b/src/enums/ActionType.enum.h @@ -154,7 +154,9 @@ enum ActionType ACTION_VIEW_PRESENTATION_MODE, ACTION_MANAGE_TOOLBAR, ACTION_CUSTOMIZE_TOOLBAR, - ACTION_RECSTOP, + ACTION_AUDIO_RECORD, + ACTION_AUDIO_PAUSE_PLAYBACK, + ACTION_AUDIO_STOP_PLAYBACK, ACTION_SET_PAIRS_OFFSET, ACTION_SET_COLUMNS, ACTION_SET_COLUMNS_1, diff --git a/src/enums/generated/ActionGroup.generated.cpp b/src/enums/generated/ActionGroup.generated.cpp index 81211aa6..a421464d 100644 --- a/src/enums/generated/ActionGroup.generated.cpp +++ b/src/enums/generated/ActionGroup.generated.cpp @@ -97,9 +97,9 @@ ActionGroup ActionGroup_fromString(string value) return GROUP_LINE_STYLE; } - if (value == "GROUP_REC") + if (value == "GROUP_AUDIO") { - return GROUP_REC; + return GROUP_AUDIO; } if (value == "GROUP_SNAPPING") @@ -225,9 +225,9 @@ string ActionGroup_toString(ActionGroup value) return "GROUP_LINE_STYLE"; } - if (value == GROUP_REC) + if (value == GROUP_AUDIO) { - return "GROUP_REC"; + return "GROUP_AUDIO"; } if (value == GROUP_SNAPPING) diff --git a/src/enums/generated/ActionType.generated.cpp b/src/enums/generated/ActionType.generated.cpp index 6a12e22c..316060a0 100644 --- a/src/enums/generated/ActionType.generated.cpp +++ b/src/enums/generated/ActionType.generated.cpp @@ -512,9 +512,19 @@ ActionType ActionType_fromString(string value) return ACTION_CUSTOMIZE_TOOLBAR; } - if (value == "ACTION_RECSTOP") + if (value == "ACTION_AUDIO_RECORD") { - return ACTION_RECSTOP; + return ACTION_AUDIO_RECORD; + } + + if (value == "ACTION_AUDIO_PAUSE_PLAYBACK") + { + return ACTION_AUDIO_PAUSE_PLAYBACK; + } + + if (value == "ACTION_AUDIO_STOP_PLAYBACK") + { + return ACTION_AUDIO_STOP_PLAYBACK; } if (value == "ACTION_SET_PAIRS_OFFSET") @@ -1175,9 +1185,19 @@ string ActionType_toString(ActionType value) return "ACTION_CUSTOMIZE_TOOLBAR"; } - if (value == ACTION_RECSTOP) + if (value == ACTION_AUDIO_RECORD) + { + return "ACTION_AUDIO_RECORD"; + } + + if (value == ACTION_AUDIO_PAUSE_PLAYBACK) + { + return "ACTION_AUDIO_PAUSE_PLAYBACK"; + } + + if (value == ACTION_AUDIO_STOP_PLAYBACK) { - return "ACTION_RECSTOP"; + return "ACTION_AUDIO_STOP_PLAYBACK"; } if (value == ACTION_SET_PAIRS_OFFSET) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 28feb1d0..29b01fd5 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -770,6 +770,11 @@ void MainWindow::createToolbarAndMenu() toolbarSelected(td); } + if (!this->control->getAudioController()->isPlaying()) + { + this->getToolMenuHandler()->disableAudioPlaybackButtons(); + } + this->control->getScheduler()->unblockRerenderZoom(); } @@ -890,3 +895,26 @@ ToolMenuHandler* MainWindow::getToolMenuHandler() return this->toolbar; } + +void MainWindow::disableAudioPlaybackButtons() +{ + XOJ_CHECK_TYPE(MainWindow); + + setAudioPlaybackPaused(false); + + this->getToolMenuHandler()->disableAudioPlaybackButtons(); +} + +void MainWindow::enableAudioPlaybackButtons() +{ + XOJ_CHECK_TYPE(MainWindow); + + this->getToolMenuHandler()->enableAudioPlaybackButtons(); +} + +void MainWindow::setAudioPlaybackPaused(bool paused) +{ + XOJ_CHECK_TYPE(MainWindow); + + this->getToolMenuHandler()->setAudioPlaybackPaused(paused); +} diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 8f08d1de..36a97aa2 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -78,6 +78,10 @@ public: ToolbarModel* getToolbarModel(); ToolMenuHandler* getToolMenuHandler(); + void disableAudioPlaybackButtons(); + void enableAudioPlaybackButtons(); + void setAudioPlaybackPaused(bool paused); + void setControlTmpDisabled(bool disabled); void updateToolbarMenu(); diff --git a/src/gui/PageView.cpp b/src/gui/PageView.cpp index cbb1f66a..a66ef87a 100644 --- a/src/gui/PageView.cpp +++ b/src/gui/PageView.cpp @@ -288,7 +288,7 @@ void XojPageView::startText(double x, double y) text->setColor(h->getColor()); text->setFont(settings->getFont()); - if (xournal->getControl()->getAudioController()->getAudioRecorder()->isRecording()) + if (xournal->getControl()->getAudioController()->isRecording()) { string audioFilename = xournal->getControl()->getAudioController()->getAudioFilename(); size_t sttime = xournal->getControl()->getAudioController()->getStartTime(); diff --git a/src/gui/PageViewFindObjectHelper.h b/src/gui/PageViewFindObjectHelper.h index 6b3cabe0..c007a80d 100644 --- a/src/gui/PageViewFindObjectHelper.h +++ b/src/gui/PageViewFindObjectHelper.h @@ -162,9 +162,7 @@ protected: if (!fn.empty()) { - AudioPlayer* audioPlayer = view->getXournal()->getControl()->getAudioController()->getAudioPlayer(); - audioPlayer->abort(); - audioPlayer->start(Path::fromUri(view->settings->getAudioFolder()).str() + "/" + fn, (unsigned int) ts); + view->getXournal()->getControl()->getAudioController()->startPlayback(Path::fromUri(view->settings->getAudioFolder()).str() + "/" + fn, (unsigned int) ts); } } } diff --git a/src/gui/dialog/SettingsDialog.cpp b/src/gui/dialog/SettingsDialog.cpp index 81749765..da6ab63e 100644 --- a/src/gui/dialog/SettingsDialog.cpp +++ b/src/gui/dialog/SettingsDialog.cpp @@ -274,7 +274,7 @@ void SettingsDialog::load() touch.getInt("timeout", timeoutMs); gtk_spin_button_set_value(GTK_SPIN_BUTTON(get("spTouchDisableTimeout")), timeoutMs / 1000.0); - this->audioInputDevices = this->control->getAudioController()->getAudioRecorder()->getInputDevices(); + this->audioInputDevices = this->control->getAudioController()->getInputDevices(); gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(get("cbAudioInputDevice")), "", "System default"); gtk_combo_box_set_active(GTK_COMBO_BOX(get("cbAudioInputDevice")), 0); for (auto &audioInputDevice : this->audioInputDevices) @@ -289,7 +289,7 @@ void SettingsDialog::load() } } - this->audioOutputDevices = this->control->getAudioController()->getAudioPlayer()->getOutputDevices(); + this->audioOutputDevices = this->control->getAudioController()->getOutputDevices(); gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(get("cbAudioOutputDevice")), "", "System default"); gtk_combo_box_set_active(GTK_COMBO_BOX(get("cbAudioOutputDevice")), 0); for (auto &audioOutputDevice : this->audioOutputDevices) diff --git a/src/gui/toolbarMenubar/AbstractToolItem.h b/src/gui/toolbarMenubar/AbstractToolItem.h index a49891b0..05542a87 100644 --- a/src/gui/toolbarMenubar/AbstractToolItem.h +++ b/src/gui/toolbarMenubar/AbstractToolItem.h @@ -33,11 +33,11 @@ public: virtual string getToolDisplayName() = 0; virtual GtkWidget* getNewToolIcon() = 0; + virtual void enable(bool enabled); + protected: virtual GtkToolItem* newItem() = 0; - virtual void enable(bool enabled); - public: XOJ_TYPE_ATTRIB; diff --git a/src/gui/toolbarMenubar/ToolButton.cpp b/src/gui/toolbarMenubar/ToolButton.cpp index 136551ea..bb6ab91f 100644 --- a/src/gui/toolbarMenubar/ToolButton.cpp +++ b/src/gui/toolbarMenubar/ToolButton.cpp @@ -133,3 +133,13 @@ GtkWidget* ToolButton::getNewToolIcon() return gtk_image_new_from_icon_name(iconName.c_str(), GTK_ICON_SIZE_SMALL_TOOLBAR); } + +void ToolButton::setActive(bool active) +{ + XOJ_CHECK_TYPE(ToolButton); + + if (GTK_IS_TOGGLE_TOOL_BUTTON(item)) + { + gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(item), active); + } +} diff --git a/src/gui/toolbarMenubar/ToolButton.h b/src/gui/toolbarMenubar/ToolButton.h index b3bd2132..b1cf5bef 100644 --- a/src/gui/toolbarMenubar/ToolButton.h +++ b/src/gui/toolbarMenubar/ToolButton.h @@ -36,6 +36,7 @@ public: void updateDescription(string description); virtual string getToolDisplayName(); + void setActive(bool active); protected: virtual GtkToolItem* newItem(); diff --git a/src/gui/toolbarMenubar/ToolMenuHandler.cpp b/src/gui/toolbarMenubar/ToolMenuHandler.cpp index ea2aaec1..ed978478 100644 --- a/src/gui/toolbarMenubar/ToolMenuHandler.cpp +++ b/src/gui/toolbarMenubar/ToolMenuHandler.cpp @@ -126,6 +126,12 @@ void ToolMenuHandler::load(ToolbarData* d, GtkWidget* toolbar, const char* toolb { name = "PAIRED_PAGES"; } + + // recognize previous name, V1.08 (Feb 2019) and earlier. + if (name == "RECSTOP") + { + name = "AUDIO_RECORDING"; + } if (name == "SEPARATOR") @@ -478,7 +484,11 @@ void ToolMenuHandler::initToolItems() fontButton = new FontButton(listener, gui, "SELECT_FONT", ACTION_FONT_BUTTON_CHANGED, _("Select Font")); addToolItem(fontButton); - ADD_CUSTOM_ITEM_TGL("RECSTOP", ACTION_RECSTOP, GROUP_REC, false, "rec", _("Rec / Stop")); + ADD_CUSTOM_ITEM_TGL("AUDIO_RECORDING", ACTION_AUDIO_RECORD, GROUP_AUDIO, false, "audio-record", _("Record Audio / Stop Recording")); + audioPausePlaybackButton = new ToolButton(listener, "AUDIO_PAUSE_PLAYBACK", ACTION_AUDIO_PAUSE_PLAYBACK, GROUP_AUDIO, false, "audio-playback-pause", _("Pause / Play")); + addToolItem(audioPausePlaybackButton); + audioStopPlaybackButton = new ToolButton(listener, "AUDIO_STOP_PLAYBACK", ACTION_AUDIO_STOP_PLAYBACK, "audio-playback-stop", _("Stop")); + addToolItem(audioStopPlaybackButton); // Menu Help // ************************************************************************ @@ -600,3 +610,35 @@ vector* ToolMenuHandler::getToolItems() return &this->toolItems; } + +void ToolMenuHandler::disableAudioPlaybackButtons() +{ + XOJ_CHECK_TYPE(ToolMenuHandler); + + setAudioPlaybackPaused(false); + + this->audioPausePlaybackButton->enable(false); + this->audioStopPlaybackButton->enable(false); + + gtk_widget_set_sensitive(GTK_WIDGET(gui->get("menuAudioPausePlayback")), false); + gtk_widget_set_sensitive(GTK_WIDGET(gui->get("menuAudioStopPlayback")), false); +} + +void ToolMenuHandler::enableAudioPlaybackButtons() +{ + XOJ_CHECK_TYPE(ToolMenuHandler); + + this->audioPausePlaybackButton->enable(true); + this->audioStopPlaybackButton->enable(true); + + gtk_widget_set_sensitive(GTK_WIDGET(gui->get("menuAudioPausePlayback")), true); + gtk_widget_set_sensitive(GTK_WIDGET(gui->get("menuAudioStopPlayback")), true); +} + +void ToolMenuHandler::setAudioPlaybackPaused(bool paused) +{ + XOJ_CHECK_TYPE(ToolMenuHandler); + + this->audioPausePlaybackButton->setActive(paused); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gui->get("menuAudioPausePlayback")), paused); +} diff --git a/src/gui/toolbarMenubar/ToolMenuHandler.h b/src/gui/toolbarMenubar/ToolMenuHandler.h index 2a472344..cfb0ead4 100644 --- a/src/gui/toolbarMenubar/ToolMenuHandler.h +++ b/src/gui/toolbarMenubar/ToolMenuHandler.h @@ -71,6 +71,12 @@ public: bool isColorInUse(int color); + void disableAudioPlaybackButtons(); + + void enableAudioPlaybackButtons(); + + void setAudioPlaybackPaused(bool paused); + private: void addToolItem(AbstractToolItem* it); @@ -91,6 +97,9 @@ private: ToolButton* undoButton; ToolButton* redoButton; + ToolButton* audioPausePlaybackButton; + ToolButton* audioStopPlaybackButton; + ToolPageSpinner* toolPageSpinner; ToolPageLayer* toolPageLayer; FontButton* fontButton; diff --git a/src/util/audio/AudioPlayer.cpp b/src/util/audio/AudioPlayer.cpp index 735053b0..4acb2faf 100644 --- a/src/util/audio/AudioPlayer.cpp +++ b/src/util/audio/AudioPlayer.cpp @@ -1,6 +1,7 @@ +#include #include "AudioPlayer.h" -AudioPlayer::AudioPlayer(Settings* settings) : settings(settings) +AudioPlayer::AudioPlayer(Control* control, Settings* settings) : control(control), settings(settings) { XOJ_INIT_TYPE(AudioPlayer); @@ -25,44 +26,78 @@ AudioPlayer::~AudioPlayer() XOJ_RELEASE_TYPE(AudioPlayer); } -void AudioPlayer::start(string filename, unsigned int timestamp) +bool AudioPlayer::start(string filename, unsigned int timestamp) { XOJ_CHECK_TYPE(AudioPlayer); // Start the producer for reading the data - this->vorbisProducer->start(std::move(filename), this->portAudioConsumer->getSelectedOutputDevice(), timestamp); - SF_INFO* vi = this->vorbisProducer->getSignalInformation(); + bool status = this->vorbisProducer->start(std::move(filename), timestamp); // Start playing - this->portAudioConsumer->startPlaying(static_cast(vi->samplerate), static_cast(vi->channels)); - - // Clean up after audio is played - stopThread = std::thread([&] - { - while (this->portAudioConsumer->isPlaying()) - { - Pa_Sleep(100); - } - this->stop(); - }); - stopThread.detach(); + if (status) + { + status = status && this->play(); + } + + return status; } -void AudioPlayer::stop() +bool AudioPlayer::isPlaying() +{ + XOJ_CHECK_TYPE(AudioPlayer); + + return this->portAudioConsumer->isPlaying(); +} + +void AudioPlayer::pause() { XOJ_CHECK_TYPE(AudioPlayer); - // Wait for libsox to read all the data - this->vorbisProducer->stop(); + if (!this->portAudioConsumer->isPlaying()) + { + return; + } // Stop playing audio this->portAudioConsumer->stopPlaying(); +} - // Reset the queue for the next playback - this->audioQueue->reset(); +bool AudioPlayer::play() +{ + XOJ_CHECK_TYPE(AudioPlayer); + + if (this->portAudioConsumer->isPlaying()) + { + return false; + } + + bool status = this->portAudioConsumer->startPlaying(); + + if (status) + { + // Clean up after audio is played + stopThread = std::thread( + [&] + { + while (isPlaying()) + { + Pa_Sleep(100); + } + this->portAudioConsumer->stopPlaying(); + + // If the stream is played completely update the UI elements accordingly + if (this->audioQueue->hasStreamEnded()) + { + this->control->getWindow()->disableAudioPlaybackButtons(); + } + }); + stopThread.detach(); + } + + return status; } -void AudioPlayer::abort() +void AudioPlayer::stop() { XOJ_CHECK_TYPE(AudioPlayer); diff --git a/src/util/audio/AudioPlayer.h b/src/util/audio/AudioPlayer.h index 3d8c6841..f2653639 100644 --- a/src/util/audio/AudioPlayer.h +++ b/src/util/audio/AudioPlayer.h @@ -22,11 +22,13 @@ class AudioPlayer { public: - explicit AudioPlayer(Settings* settings); + explicit AudioPlayer(Control* control, Settings* settings); ~AudioPlayer(); - void start(string filename, unsigned int timestamp = 0); + bool start(string filename, unsigned int timestamp = 0); + bool isPlaying(); void stop(); - void abort(); + bool play(); + void pause(); vector getOutputDevices(); @@ -35,6 +37,7 @@ private: protected: Settings* settings = nullptr; + Control* control = nullptr; AudioQueue* audioQueue = nullptr; PortAudioConsumer* portAudioConsumer = nullptr; diff --git a/src/util/audio/AudioQueue.h b/src/util/audio/AudioQueue.h index 99930db1..6e558ddb 100644 --- a/src/util/audio/AudioQueue.h +++ b/src/util/audio/AudioQueue.h @@ -39,9 +39,13 @@ public: { XOJ_CHECK_TYPE(AudioQueue); - this->notified = false; + this->popNotified = false; + this->pushNotified = false; this->streamEnd = false; this->clear(); + + this->sampleRate = -1; + this->channels = 0; } bool empty() @@ -67,21 +71,37 @@ public: this->push_front(samples[i]); } - this->notified = true; - this->lockCondition.notify_one(); + this->popNotified = false; + + this->pushNotified = true; + this->pushLockCondition.notify_one(); } - void pop(T* returnBuffer, int* bufferLength, unsigned long nSamples, int numChannels) + void pop(T* returnBuffer, unsigned long& returnBufferLength, unsigned long nSamples) { XOJ_CHECK_TYPE(AudioQueue); - *bufferLength = std::min(nSamples, this->size() - this->size() % numChannels); - for (long i = 0; i < *bufferLength; i++) + if (this->channels == 0) + { + returnBufferLength = 0; + + this->popNotified = true; + this->popLockCondition.notify_one(); + + return; + } + + returnBufferLength = std::min(nSamples, this->size() - this->size() % this->channels); + for (long i = 0; i < returnBufferLength; i++) { returnBuffer[i] = this->back(); this->pop_back(); } - this->notified = false; + + this->pushNotified = false; + + this->popNotified = true; + this->popLockCondition.notify_one(); } void signalEndOfStream() @@ -89,17 +109,29 @@ public: XOJ_CHECK_TYPE(AudioQueue); this->streamEnd = true; - this->notified = true; - this->lockCondition.notify_one(); + this->pushNotified = true; + this->pushLockCondition.notify_one(); + this->popNotified = true; + this->popLockCondition.notify_one(); } - void waitForNewElements(std::unique_lock& lock) + void waitForProducer(std::unique_lock& lock) { XOJ_CHECK_TYPE(AudioQueue); - while (!this->notified) + while (!this->pushNotified) { - this->lockCondition.wait(lock); + this->pushLockCondition.wait(lock); + } + } + + void waitForConsumer(std::unique_lock& lock) + { + XOJ_CHECK_TYPE(AudioQueue); + + while (!this->popNotified) + { + this->popLockCondition.wait(lock); } } @@ -117,12 +149,29 @@ public: return this->queueLock; } + void setAudioAttributes(double sampleRate, unsigned int channels) + { + this->sampleRate = sampleRate; + this->channels = channels; + } + + void getAudioAttributes(double &sampleRate, unsigned int &channels) + { + sampleRate = this->sampleRate; + channels = this->channels; + } + private: XOJ_TYPE_ATTRIB; protected: std::mutex queueLock; - std::condition_variable lockCondition; bool streamEnd = false; - bool notified = false; + std::condition_variable pushLockCondition; + bool pushNotified = false; + std::condition_variable popLockCondition; + bool popNotified = false; + + double sampleRate = -1; + unsigned int channels = 0; }; diff --git a/src/util/audio/AudioRecorder.cpp b/src/util/audio/AudioRecorder.cpp index 4c225422..42e9cc05 100644 --- a/src/util/audio/AudioRecorder.cpp +++ b/src/util/audio/AudioRecorder.cpp @@ -32,12 +32,11 @@ bool AudioRecorder::start(string filename) { XOJ_CHECK_TYPE(AudioRecorder); - // Start the consumer for writing the data - int inputChannels = std::min(2, this->portAudioProducer->getSelectedInputDevice().getInputChannels()); - bool status = this->vorbisConsumer->start(std::move(filename), static_cast(inputChannels)); - // Start recording - status &= this->portAudioProducer->startRecording(); + bool status = this->portAudioProducer->startRecording(); + + // Start the consumer for writing the data + status = status && this->vorbisConsumer->start(std::move(filename)); return status; } diff --git a/src/util/audio/PortAudioConsumer.cpp b/src/util/audio/PortAudioConsumer.cpp index 7df7f4b4..6d0e908f 100644 --- a/src/util/audio/PortAudioConsumer.cpp +++ b/src/util/audio/PortAudioConsumer.cpp @@ -56,14 +56,24 @@ bool PortAudioConsumer::isPlaying() return this->outputStream != nullptr && this->outputStream->isActive(); } -void PortAudioConsumer::startPlaying(double sampleRate, unsigned int channels) +bool PortAudioConsumer::startPlaying() { XOJ_CHECK_TYPE(PortAudioConsumer); // Check if there already is a recording if (this->outputStream != nullptr) { - return; + return false; + } + + double sampleRate; + unsigned int channels; + this->audioQueue->getAudioAttributes(sampleRate, channels); + + if (sampleRate == -1) + { + g_warning("PortAudioConsumer: Timing issue - Sample rate requested before known"); + return false; } // Get the device information of our output device @@ -75,14 +85,14 @@ void PortAudioConsumer::startPlaying(double sampleRate, unsigned int channels) catch (portaudio::PaException& e) { g_warning("PortAudioConsumer: Unable to find selected output device"); - return; + return false; } if ((unsigned int) device->maxOutputChannels() < channels) { this->audioQueue->signalEndOfStream(); g_warning("Output device has not enough channels to play audio file. (Requires at least 2 channels)"); - return; + return false; } this->outputChannels = channels; @@ -97,7 +107,7 @@ void PortAudioConsumer::startPlaying(double sampleRate, unsigned int channels) { this->audioQueue->signalEndOfStream(); g_warning("PortAudioConsumer: Unable to open stream to device"); - return; + return false; } // Start the recording try @@ -108,8 +118,9 @@ void PortAudioConsumer::startPlaying(double sampleRate, unsigned int channels) { this->audioQueue->signalEndOfStream(); g_warning("PortAudioConsumer: Unable to start stream"); - return; + return false; } + return true; } int PortAudioConsumer::playCallback(const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, @@ -124,17 +135,17 @@ int PortAudioConsumer::playCallback(const void* inputBuffer, void* outputBuffer, if (outputBuffer != nullptr) { - int outputBufferLength; - this->audioQueue->pop(((int*) outputBuffer), &outputBufferLength, framesPerBuffer * this->outputChannels, this->outputChannels); + unsigned long outputBufferLength; + this->audioQueue->pop(((int*) outputBuffer), outputBufferLength, framesPerBuffer * this->outputChannels); // Fill buffer to requested length if necessary - if ((unsigned int) outputBufferLength < framesPerBuffer * this->outputChannels) + if (outputBufferLength < framesPerBuffer * this->outputChannels) { g_warning("PortAudioConsumer: Frame underflow"); auto outputBufferImpl = (int*) outputBuffer; - for (auto i = (unsigned int) outputBufferLength; i < framesPerBuffer * this->outputChannels; ++i) + for (auto i = outputBufferLength; i < framesPerBuffer * this->outputChannels; ++i) { outputBufferImpl[i] = 0; } diff --git a/src/util/audio/PortAudioConsumer.h b/src/util/audio/PortAudioConsumer.h index a1456fd0..d56cbef7 100644 --- a/src/util/audio/PortAudioConsumer.h +++ b/src/util/audio/PortAudioConsumer.h @@ -32,7 +32,7 @@ public: std::list getOutputDevices(); const DeviceInfo getSelectedOutputDevice(); bool isPlaying(); - void startPlaying(double sampleRate, unsigned int channels); + bool startPlaying(); int playCallback(const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags); void stopPlaying(); diff --git a/src/util/audio/PortAudioProducer.cpp b/src/util/audio/PortAudioProducer.cpp index cfb32425..9aca599e 100644 --- a/src/util/audio/PortAudioProducer.cpp +++ b/src/util/audio/PortAudioProducer.cpp @@ -88,6 +88,8 @@ bool PortAudioProducer::startRecording() portaudio::DirectionSpecificStreamParameters inParams(*device, this->inputChannels, portaudio::INT32, true, device->defaultLowInputLatency(), nullptr); portaudio::StreamParameters params(inParams, portaudio::DirectionSpecificStreamParameters::null(), this->settings->getAudioSampleRate(), this->framesPerBuffer, paNoFlag); + this->audioQueue->setAudioAttributes(this->settings->getAudioSampleRate(), static_cast(this->inputChannels)); + // Specify the callback used for buffering the recorded data try { diff --git a/src/util/audio/VorbisConsumer.cpp b/src/util/audio/VorbisConsumer.cpp index ab8dc833..03943faa 100644 --- a/src/util/audio/VorbisConsumer.cpp +++ b/src/util/audio/VorbisConsumer.cpp @@ -15,12 +15,22 @@ VorbisConsumer::~VorbisConsumer() XOJ_RELEASE_TYPE(VorbisConsumer); } -bool VorbisConsumer::start(string filename, unsigned int inputChannels) +bool VorbisConsumer::start(string filename) { XOJ_CHECK_TYPE(VorbisConsumer); + double sampleRate; + unsigned int channels; + this->audioQueue->getAudioAttributes(sampleRate, channels); + + if (sampleRate == -1) + { + g_warning("VorbisConsumer: Timing issue - Sample rate requested before known"); + return false; + } + SF_INFO sfInfo; - sfInfo.channels = inputChannels; + sfInfo.channels = channels; sfInfo.format = SF_FORMAT_OGG | SF_FORMAT_VORBIS; sfInfo.samplerate = static_cast(this->settings->getAudioSampleRate()); @@ -32,26 +42,26 @@ bool VorbisConsumer::start(string filename, unsigned int inputChannels) } this->consumerThread = new std::thread( - [&, sfFile, inputChannels] + [&, sfFile, channels] { std::unique_lock lock(audioQueue->syncMutex()); - int buffer[64 * inputChannels]; - int bufferLength; + int buffer[64 * channels]; + unsigned long bufferLength; double audioGain = this->settings->getAudioGain(); while (!(this->stopConsumer || (audioQueue->hasStreamEnded() && audioQueue->empty()))) { - audioQueue->waitForNewElements(lock); + audioQueue->waitForProducer(lock); while (!audioQueue->empty()) { - this->audioQueue->pop(buffer, &bufferLength, 64 * inputChannels, inputChannels); + this->audioQueue->pop(buffer, bufferLength, 64 * channels); // apply gain if (audioGain != 1.0) { - for (unsigned int i = 0; i < 64 * inputChannels; ++i) + for (unsigned int i = 0; i < 64 * channels; ++i) { // check for overflow if (std::abs(buffer[i]) < std::floor(INT_MAX / audioGain)) diff --git a/src/util/audio/VorbisConsumer.h b/src/util/audio/VorbisConsumer.h index ecc19816..cf69b82b 100644 --- a/src/util/audio/VorbisConsumer.h +++ b/src/util/audio/VorbisConsumer.h @@ -30,7 +30,7 @@ public: ~VorbisConsumer(); public: - bool start(string filename, unsigned int inputChannels); + bool start(string filename); void join(); void stop(); diff --git a/src/util/audio/VorbisProducer.cpp b/src/util/audio/VorbisProducer.cpp index a9c4a2a0..fb59a92c 100644 --- a/src/util/audio/VorbisProducer.cpp +++ b/src/util/audio/VorbisProducer.cpp @@ -12,7 +12,7 @@ VorbisProducer::~VorbisProducer() XOJ_RELEASE_TYPE(VorbisProducer); } -void VorbisProducer::start(std::string filename, const DeviceInfo& outputDevice, unsigned int timestamp) +bool VorbisProducer::start(std::string filename, unsigned int timestamp) { XOJ_CHECK_TYPE(VorbisProducer); @@ -21,7 +21,7 @@ void VorbisProducer::start(std::string filename, const DeviceInfo& outputDevice, if (sfFile == nullptr) { g_warning("VorbisProducer: input file \"%s\" could not be opened\ncaused by:%s", filename.c_str(), sf_strerror(sfFile)); - return; + return false; } sf_count_t seekPosition = this->sfInfo.samplerate / 1000 * timestamp; @@ -34,6 +34,8 @@ void VorbisProducer::start(std::string filename, const DeviceInfo& outputDevice, g_warning("VorbisProducer: Seeking outside of audio file extent"); } + this->audioQueue->setAudioAttributes(this->sfInfo.samplerate, static_cast(this->sfInfo.channels)); + this->producerThread = new std::thread( [&, filename] { @@ -58,13 +60,7 @@ void VorbisProducer::start(std::string filename, const DeviceInfo& outputDevice, sf_close(this->sfFile); }); -} - -SF_INFO* VorbisProducer::getSignalInformation() -{ - XOJ_CHECK_TYPE(VorbisProducer); - - return &this->sfInfo; + return true; } void VorbisProducer::abort() diff --git a/src/util/audio/VorbisProducer.h b/src/util/audio/VorbisProducer.h index e7054a38..07759c0e 100644 --- a/src/util/audio/VorbisProducer.h +++ b/src/util/audio/VorbisProducer.h @@ -28,8 +28,7 @@ public: ~VorbisProducer(); public: - void start(string filename, const DeviceInfo& outputDevice, unsigned int timestamp); - SF_INFO* getSignalInformation(); + bool start(string filename, unsigned int timestamp); void abort(); void stop(); diff --git a/ui/icons/hicolor/icon-theme.cache b/ui/icons/hicolor/icon-theme.cache index 7b498b99..0ea78142 100644 Binary files a/ui/icons/hicolor/icon-theme.cache and b/ui/icons/hicolor/icon-theme.cache differ diff --git a/ui/icons/hicolor/scalable/actions/audio-playback-pause.svg b/ui/icons/hicolor/scalable/actions/audio-playback-pause.svg new file mode 100644 index 00000000..e359e08b --- /dev/null +++ b/ui/icons/hicolor/scalable/actions/audio-playback-pause.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/ui/icons/hicolor/scalable/actions/rec.svg b/ui/icons/hicolor/scalable/actions/audio-playback-stop.svg similarity index 74% rename from ui/icons/hicolor/scalable/actions/rec.svg rename to ui/icons/hicolor/scalable/actions/audio-playback-stop.svg index f675ac7b..6c0686e3 100644 --- a/ui/icons/hicolor/scalable/actions/rec.svg +++ b/ui/icons/hicolor/scalable/actions/audio-playback-stop.svg @@ -1,5 +1,5 @@ - + diff --git a/ui/icons/hicolor/scalable/actions/audio-record.svg b/ui/icons/hicolor/scalable/actions/audio-record.svg new file mode 100644 index 00000000..59854243 --- /dev/null +++ b/ui/icons/hicolor/scalable/actions/audio-record.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/iconsDark/hicolor/icon-theme.cache b/ui/iconsDark/hicolor/icon-theme.cache index 50ab54a8..9c142937 100644 Binary files a/ui/iconsDark/hicolor/icon-theme.cache and b/ui/iconsDark/hicolor/icon-theme.cache differ diff --git a/ui/iconsDark/hicolor/scalable/actions/audio-playback-pause.svg b/ui/iconsDark/hicolor/scalable/actions/audio-playback-pause.svg new file mode 100644 index 00000000..9ff8c27c --- /dev/null +++ b/ui/iconsDark/hicolor/scalable/actions/audio-playback-pause.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/ui/iconsDark/hicolor/scalable/actions/audio-playback-stop.svg b/ui/iconsDark/hicolor/scalable/actions/audio-playback-stop.svg new file mode 100644 index 00000000..3ff65702 --- /dev/null +++ b/ui/iconsDark/hicolor/scalable/actions/audio-playback-stop.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/main.glade b/ui/main.glade index 08df807f..88b5286c 100644 --- a/ui/main.glade +++ b/ui/main.glade @@ -1,5 +1,5 @@ - + @@ -1512,12 +1512,30 @@ - + False True False - Rec-Stop - + Record / Stop + + + + + + False + True + False + Pause + + + + + + True + False + Stop + True + diff --git a/ui/toolbar.ini b/ui/toolbar.ini index b4950f3d..59d14fcf 100755 --- a/ui/toolbar.ini +++ b/ui/toolbar.ini @@ -35,7 +35,7 @@ name = Portrait # Translated names name[de] = Hochformat name[it] = Ritratto -toolbarTop1 = SAVE,NEW,OPEN,SEPARATOR, CUT,COPY,PASTE,SEPARATOR, UNDO,REDO,SEPARATOR, GOTO_FIRST,GOTO_BACK,GOTO_NEXT_ANNOTATED_PAGE,GOTO_NEXT,GOTO_LAST,INSERT_NEW_PAGE,DELETE_CURRENT_PAGE,SEPARATOR ,FULLSCREEN,SEPARATOR, RECSTOP,SEPARATOR, SELECT_FONT +toolbarTop1 = SAVE,NEW,OPEN,SEPARATOR, CUT,COPY,PASTE,SEPARATOR, UNDO,REDO,SEPARATOR, GOTO_FIRST,GOTO_BACK,GOTO_NEXT_ANNOTATED_PAGE,GOTO_NEXT,GOTO_LAST,INSERT_NEW_PAGE,DELETE_CURRENT_PAGE,SEPARATOR ,FULLSCREEN,SEPARATOR, AUDIO_RECORDING,AUDIO_PAUSE_PLAYBACK,AUDIO_STOP_PLAYBACK,SEPARATOR, SELECT_FONT toolbarTop2 = PEN,ERASER,HILIGHTER,IMAGE,TEXT,DRAW,SEPARATOR, ROTATION_SNAPPING,GRID_SNAPPING,SEPARATOR, SELECT,VERTICAL_SPACE,HAND,SEPARATOR, DEFAULT_TOOL,SEPARATOR, FINE,MEDIUM,THICK,SEPARATOR,TOOL_FILL,SEPARATOR,COLOR(0x000000),COLOR(0x008000),COLOR(0x00c0ff),COLOR(0x00ff00),COLOR(0x3333cc),COLOR(0x808080),COLOR(0xff0000),COLOR(0xff00ff),COLOR(0xff8000),COLOR(0xffff00),COLOR(0xffffff),COLOR_SELECT toolbarBottom1 = PAGE_SPIN,SEPARATOR,LAYER, SPACER, PAIRED_PAGES,ZOOM_100,ZOOM_FIT,ZOOM_OUT,ZOOM_SLIDER,ZOOM_IN @@ -47,7 +47,7 @@ toolbarTop1 = SAVE,NEW,OPEN,SEPARATOR, CUT,COPY,PASTE,SEPARATOR, UNDO,REDO,SEPAR toolbarBottom1 = PAGE_SPIN,SEPARATOR,LAYER,GOTO_FIRST,GOTO_NEXT_ANNOTATED_PAGE,GOTO_LAST,INSERT_NEW_PAGE,DELETE_CURRENT_PAGE,SPACER, PAIRED_PAGES,ZOOM_100,ZOOM_FIT,ZOOM_OUT,ZOOM_SLIDER,ZOOM_IN,SEPARATOR, FULLSCREEN [Right hand Note Taking] -toolbarTop1=SAVE,NEW,OPEN,SEPARATOR,CUT,COPY,PASTE,SEPARATOR,UNDO,REDO,SEPARATOR,PEN,ERASER,HILIGHTER,IMAGE,TEXT,SEPARATOR,DEFAULT_TOOL,SEPARATOR,INSERT_NEW_PAGE,DELETE_CURRENT_PAGE,SEPARATOR,GOTO_BACK,GOTO_NEXT,SEPARATOR,FULLSCREEN,SEPARATOR,RECSTOP +toolbarTop1=SAVE,NEW,OPEN,SEPARATOR,CUT,COPY,PASTE,SEPARATOR,UNDO,REDO,SEPARATOR,PEN,ERASER,HILIGHTER,IMAGE,TEXT,SEPARATOR,DEFAULT_TOOL,SEPARATOR,INSERT_NEW_PAGE,DELETE_CURRENT_PAGE,SEPARATOR,GOTO_BACK,GOTO_NEXT,SEPARATOR,FULLSCREEN,SEPARATOR,AUDIO_RECORDING,AUDIO_PAUSE_PLAYBACK,AUDIO_STOP_PLAYBACK toolbarLeft1=COLOR(0xffffff),COLOR(0xffff00),COLOR(0xff8000),COLOR(0xff00ff),COLOR(0x00ff00),COLOR(0x00c0ff),COLOR(0x808080),COLOR(0x008000),COLOR(0xff0000),COLOR(0x3333cc),COLOR(0x000000),COLOR_SELECT,SEPARATOR,ZOOM_100,ZOOM_FIT,ZOOM_IN,ZOOM_OUT toolbarLeft2=FINE,MEDIUM,THICK,SEPARATOR,TOOL_FILL,SEPARATOR,DRAW_CIRCLE,DRAW_RECTANGLE,DRAW_ARROW,RULER,SEPARATOR,ROTATION_SNAPPING,GRID_SNAPPING,SEPARATOR,VERTICAL_SPACE,SELECT_REGION,SELECT_RECTANGLE,SELECT_OBJECT,PLAY_OBJECT name=Right hand Note Taking