Merge pull request #922 from LittleHuba/enh_playback_controls

Add buttons to control audio playback
presentation
Ulrich Huber 7 years ago committed by GitHub
commit 88942a20b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 77
      src/control/AudioController.cpp
  2. 17
      src/control/AudioController.h
  3. 22
      src/control/Control.cpp
  4. 2
      src/control/tools/InputHandler.cpp
  5. 2
      src/enums/ActionGroup.enum.h
  6. 4
      src/enums/ActionType.enum.h
  7. 8
      src/enums/generated/ActionGroup.generated.cpp
  8. 28
      src/enums/generated/ActionType.generated.cpp
  9. 28
      src/gui/MainWindow.cpp
  10. 4
      src/gui/MainWindow.h
  11. 2
      src/gui/PageView.cpp
  12. 4
      src/gui/PageViewFindObjectHelper.h
  13. 4
      src/gui/dialog/SettingsDialog.cpp
  14. 4
      src/gui/toolbarMenubar/AbstractToolItem.h
  15. 10
      src/gui/toolbarMenubar/ToolButton.cpp
  16. 1
      src/gui/toolbarMenubar/ToolButton.h
  17. 44
      src/gui/toolbarMenubar/ToolMenuHandler.cpp
  18. 9
      src/gui/toolbarMenubar/ToolMenuHandler.h
  19. 79
      src/util/audio/AudioPlayer.cpp
  20. 9
      src/util/audio/AudioPlayer.h
  21. 77
      src/util/audio/AudioQueue.h
  22. 9
      src/util/audio/AudioRecorder.cpp
  23. 31
      src/util/audio/PortAudioConsumer.cpp
  24. 2
      src/util/audio/PortAudioConsumer.h
  25. 2
      src/util/audio/PortAudioProducer.cpp
  26. 26
      src/util/audio/VorbisConsumer.cpp
  27. 2
      src/util/audio/VorbisConsumer.h
  28. 14
      src/util/audio/VorbisProducer.cpp
  29. 3
      src/util/audio/VorbisProducer.h
  30. BIN
      ui/icons/hicolor/icon-theme.cache
  31. 6
      ui/icons/hicolor/scalable/actions/audio-playback-pause.svg
  32. 2
      ui/icons/hicolor/scalable/actions/audio-playback-stop.svg
  33. 5
      ui/icons/hicolor/scalable/actions/audio-record.svg
  34. BIN
      ui/iconsDark/hicolor/icon-theme.cache
  35. 6
      ui/iconsDark/hicolor/scalable/actions/audio-playback-pause.svg
  36. 5
      ui/iconsDark/hicolor/scalable/actions/audio-playback-stop.svg
  37. 26
      ui/main.glade
  38. 4
      ui/toolbar.ini

@ -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<DeviceInfo> AudioController::getOutputDevices()
{
return this->audioRecorder;
XOJ_CHECK_TYPE(AudioController);
return this->audioPlayer->getOutputDevices();
}
AudioPlayer* AudioController::getAudioPlayer()
vector<DeviceInfo> AudioController::getInputDevices()
{
return this->audioPlayer;
XOJ_CHECK_TYPE(AudioController);
return this->audioRecorder->getInputDevices();
}

@ -17,6 +17,7 @@
#include <Path.h>
#include <util/audio/AudioRecorder.h>
#include <util/audio/AudioPlayer.h>
#include <gui/toolbarMenubar/ToolMenuHandler.h>
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<DeviceInfo> getOutputDevices();
vector<DeviceInfo> getInputDevices();
protected:
string audioFilename;

@ -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();

@ -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();

@ -50,7 +50,7 @@ enum ActionGroup
GROUP_LINE_STYLE,
GROUP_REC,
GROUP_AUDIO,
GROUP_SNAPPING,

@ -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,

@ -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)

@ -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)

@ -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);
}

@ -78,6 +78,10 @@ public:
ToolbarModel* getToolbarModel();
ToolMenuHandler* getToolMenuHandler();
void disableAudioPlaybackButtons();
void enableAudioPlaybackButtons();
void setAudioPlaybackPaused(bool paused);
void setControlTmpDisabled(bool disabled);
void updateToolbarMenu();

@ -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();

@ -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);
}
}
}

@ -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)

@ -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;

@ -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);
}
}

@ -36,6 +36,7 @@ public:
void updateDescription(string description);
virtual string getToolDisplayName();
void setActive(bool active);
protected:
virtual GtkToolItem* newItem();

@ -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<AbstractToolItem*>* 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);
}

@ -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;

@ -1,6 +1,7 @@
#include <control/Control.h>
#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<double>(vi->samplerate), static_cast<unsigned int>(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);

@ -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<DeviceInfo> getOutputDevices();
@ -35,6 +37,7 @@ private:
protected:
Settings* settings = nullptr;
Control* control = nullptr;
AudioQueue<int>* audioQueue = nullptr;
PortAudioConsumer* portAudioConsumer = nullptr;

@ -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<std::mutex>& lock)
void waitForProducer(std::unique_lock<std::mutex>& lock)
{
XOJ_CHECK_TYPE(AudioQueue);
while (!this->notified)
while (!this->pushNotified)
{
this->lockCondition.wait(lock);
this->pushLockCondition.wait(lock);
}
}
void waitForConsumer(std::unique_lock<std::mutex>& 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;
};

@ -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<unsigned int>(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;
}

@ -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;
}

@ -32,7 +32,7 @@ public:
std::list<DeviceInfo> 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();

@ -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<unsigned int>(this->inputChannels));
// Specify the callback used for buffering the recorded data
try
{

@ -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<int>(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<std::mutex> 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))

@ -30,7 +30,7 @@ public:
~VorbisConsumer();
public:
bool start(string filename, unsigned int inputChannels);
bool start(string filename);
void join();
void stop();

@ -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<unsigned int>(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()

@ -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();

Binary file not shown.

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!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" version="1.1" width="22" height="22">
<rect id="rect1" height="18" width="7" y="2" x="2"/>
<rect id="rect2" height="18" width="7" y="2" x="13"/>
</svg>

After

Width:  |  Height:  |  Size: 348 B

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!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" version="1.1" width="22" height="22">
<circle r="11" cx="11" cy="11" style="fill:red;stroke:gray;stroke-width:0.1" />
<rect id="rect1" height="18" width="18" y="2" x="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 320 B

After

Width:  |  Height:  |  Size: 294 B

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!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" version="1.1" width="22" height="22">
<circle r="9" cx="11" cy="11" style="fill:red;stroke:gray;stroke-width:0.1" />
</svg>

After

Width:  |  Height:  |  Size: 319 B

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!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" version="1.1" width="22" height="22">
<rect id="rect1" style="fill:#ffffff" height="18" width="7" y="2" x="2"/>
<rect id="rect2" style="fill:#ffffff" height="18" width="7" y="2" x="13"/>
</svg>

After

Width:  |  Height:  |  Size: 390 B

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!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" version="1.1" width="22" height="22">
<rect id="rect1" style="fill:#ffffff" height="18" width="18" y="2" x="2"/>
</svg>

After

Width:  |  Height:  |  Size: 315 B

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkAdjustment" id="adjHorizontal">
@ -1512,12 +1512,30 @@
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="menuRecStop">
<object class="GtkCheckMenuItem" id="menuAudioRecord">
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Rec-Stop</property>
<signal name="toggled" handler="ACTION_RECSTOP" swapped="no"/>
<property name="label" translatable="yes">Record / Stop</property>
<signal name="toggled" handler="ACTION_AUDIO_RECORD" swapped="no"/>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="menuAudioPausePlayback">
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Pause</property>
<signal name="toggled" handler="ACTION_AUDIO_PAUSE_PLAYBACK" swapped="no"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuAudioStopPlayback">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Stop</property>
<property name="use_underline">True</property>
<signal name="activate" handler="ACTION_AUDIO_STOP_PLAYBACK" swapped="no"/>
</object>
</child>
<child>

@ -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

Loading…
Cancel
Save