Skip to content

Commit

Permalink
stop / startable
Browse files Browse the repository at this point in the history
  • Loading branch information
yuxshao committed Nov 22, 2022
1 parent 7b34da6 commit 1022b66
Show file tree
Hide file tree
Showing 15 changed files with 108 additions and 110 deletions.
3 changes: 1 addition & 2 deletions src/editor/EditorWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -806,8 +806,7 @@ void EditorWindow::recordInput(const Input::Event::Event &e) {
Settings::ChordPreview::get() && !m_client->isPlaying();
m_record_note_preview[e.key] = std::make_unique<NotePreview>(
&m_pxtn, &m_client->moo()->params, unit_no, start, e.key,
e.vel(), m_client->audioState()->bufferSize(), chordPreview,
this);
e.vel(), m_client->bufferSize(), chordPreview, this);

m_keyboard_view->currentMidiNotes()[e.key] = e.vel();
}
Expand Down
5 changes: 2 additions & 3 deletions src/editor/NewWoiceDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ void NewWoiceDialog::previewWoice(const QString &path) {
ui->previewVolSlider->value() * 128 / ui->previewVolSlider->maximum();
m_note_preview = std::make_unique<NotePreview>(
m_client->pxtn(), &m_client->moo()->params, key, vel, 48000,
m_preview_woice, m_client->audioState()->bufferSize(), this);
m_preview_woice, m_client->bufferSize(), this);
}

NewWoiceDialog::NewWoiceDialog(bool multi, const PxtoneClient *client,
Expand Down Expand Up @@ -330,8 +330,7 @@ void NewWoiceDialog::inputMidi(const Input::Event::Event &e) {
if (m_preview_woice == nullptr) return;
m_record_note_preview[e.key] = std::make_unique<NotePreview>(
m_client->pxtn(), &m_client->moo()->params, e.key,
e.vel(), 100000000, m_preview_woice,
m_client->audioState()->bufferSize(), this);
e.vel(), 100000000, m_preview_woice, 0 /* TODO */, this);
},
[this](const Input::Event::Off &e) {
if (m_record_note_preview[e.key])
Expand Down
77 changes: 21 additions & 56 deletions src/editor/PxtoneClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

#include "ComboOptions.h"
#include "Settings.h"
#include "audio/AudioFormat.h"

QList<UserListEntry> getUserList(
const std::map<qint64, RemoteEditState> &users) {
Expand All @@ -29,22 +28,6 @@ PxtoneClient::PxtoneClient(pxtnService *pxtn,
m_ping_timer(new QTimer(this)),
m_last_seek(0),
m_clipboard(new Clipboard(this)) {
QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
if (!info.isFormatSupported(pxtoneAudioFormat())) {
qWarning()
<< "Raw audio format not supported by backend, cannot play audio.";
return;
}
m_pxtn_device = new PxtoneIODevice(this, m_controller->pxtn(), &m_moo_state);
m_audio = new QAudioOutput(pxtoneAudioFormat(), this);

// Apparently this reduces latency in pulseaudio, but also makes
// some sounds choppier
// m_audio->setCategory("game");
m_audio->setVolume(1.0);
connect(m_pxtn_device, &PxtoneIODevice::playingChanged, this,
&PxtoneClient::playStateChanged);

connect(m_ping_timer, &QTimer::timeout, [this]() {
sendPlayState(false);
sendAction(Ping{QDateTime::currentMSecsSinceEpoch(), m_last_ping});
Expand Down Expand Up @@ -88,14 +71,13 @@ PxtoneClient::PxtoneClient(pxtnService *pxtn,
&PxtoneClient::processRemoteAction);
}

PxtoneClient::~PxtoneClient() {
m_audio->stop();
m_pxtn_device->setPlaying(false);
}
PxtoneClient::~PxtoneClient() {}

void PxtoneClient::loadDescriptor(pxtnDescriptor &desc) {
// An empty desc is interpreted as an empty file so we don't error.
m_controller->loadDescriptor(desc);
double bufferLength =
Settings::value(BUFFER_LENGTH_KEY, DEFAULT_BUFFER_LENGTH).toDouble();
m_controller->loadDescriptor(desc, bufferLength);
changeEditState(
[this](EditState &e) {
e.m_current_unit_id =
Expand All @@ -104,64 +86,48 @@ void PxtoneClient::loadDescriptor(pxtnDescriptor &desc) {
},
false);
m_following_user.reset();
m_pxtn_device->setPlaying(false);
seekMoo(0);

m_pxtn_device->open(QIODevice::ReadOnly);
{
bool ok;
int v = Settings::value(VOLUME_KEY, QVariant()).toInt(&ok);
if (ok) setVolume(v);
}
{
bool ok;
double v =
Settings::value(BUFFER_LENGTH_KEY, DEFAULT_BUFFER_LENGTH).toDouble(&ok);
if (ok) setBufferSize(v);
}
m_pxtn_device->setPlaying(false);
m_audio->start(m_pxtn_device);
qDebug() << "Actual" << m_audio->bufferSize();
}

void PxtoneClient::setBufferSize(double secs) {
bool started = (m_audio->state() != QAudio::StoppedState &&
m_audio->state() != QAudio::IdleState);
QAudioFormat fmt = pxtoneAudioFormat();

if (started) {
m_audio->stop();
m_pxtn_device->setPlaying(false);
}

if (secs < 0.01) secs = 0.01;
if (secs > 10) secs = 10;
qDebug() << "Setting buffer size: " << secs;
m_audio->setBufferSize(fmt.bytesForDuration(secs * 1e6));
if (started) m_audio->start(m_pxtn_device);
m_controller->m_audio_renderer->setBufferFrames(secs * 44100);
}

bool PxtoneClient::isPlaying() { return m_pxtn_device->playing(); }
bool PxtoneClient::isPlaying() {
return m_controller->m_audio_renderer->isPlaying();
}

// TODO: Factor this out into a PxtoneAudioPlayer class. Setting play state,
// seeking. Unfortunately start / stop don't even fix this because stopping
// still waits for the buffer to drain (instead of flushing and throwing away)
void PxtoneClient::togglePlayState() {
if (Settings::SpacebarStop::get() && m_pxtn_device->playing())
if (Settings::SpacebarStop::get() &&
m_controller->m_audio_renderer->isPlaying())
resetAndSuspendAudio();
else {
m_pxtn_device->setPlaying(!m_pxtn_device->playing());
m_controller->m_audio_renderer->setPlaying(
!m_controller->m_audio_renderer->isPlaying());
sendPlayState(true);
}
}

void PxtoneClient::sendPlayState(bool from_action) {
sendAction(
PlayState{moo()->get_now_clock(), m_pxtn_device->playing(), from_action});
sendAction(PlayState{moo()->get_now_clock(),
m_controller->m_audio_renderer->isPlaying(),
from_action});
}

void PxtoneClient::resetAndSuspendAudio() {
m_pxtn_device->setPlaying(false);
m_controller->m_audio_renderer->setPlaying(false);
if (moo()->get_now_clock() > m_last_seek)
seekMoo(m_last_seek);
else
Expand All @@ -184,7 +150,7 @@ void PxtoneClient::setFollowing(std::optional<qint64> following) {
if (it != m_remote_edit_states.end() && it->second.state.has_value()) {
// stop audio, so that the next playstate msg causes us to sync if the
// other user's playing.
m_pxtn_device->setPlaying(false);
m_controller->m_audio_renderer->setPlaying(false);
emit followActivity(it->second.state.value());
}
}
Expand Down Expand Up @@ -332,10 +298,11 @@ void PxtoneClient::processRemoteAction(const ServerAction &a) {
// Since playstates come with heartbeats, we only reset
// the moo for non-heartbeat updates or if there's a
// drastic diff.
if (m_pxtn_device->playing() != s.playing ||
if (m_controller->m_audio_renderer->isPlaying() !=
s.playing ||
s.from_action)
m_controller->seekMoo(s.clock);
m_pxtn_device->setPlaying(s.playing);
m_controller->m_audio_renderer->setPlaying(s.playing);
}
},
[this, uid](const EditAction &s) {
Expand Down Expand Up @@ -558,6 +525,4 @@ void PxtoneClient::removeUnusedUnitsAndWoices() {
}
}

const std::vector<InterpolatedVolumeMeter> &PxtoneClient::volumeLevels() const {
return m_pxtn_device->volumeLevels();
}
std::vector<int> PxtoneClient::volumeLevels() const { return {0, 0}; }
7 changes: 2 additions & 5 deletions src/editor/PxtoneClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,13 @@ class PxtoneClient : public QObject {
std::optional<qint64> m_following_user;
mooState m_moo_state;
EditState m_edit_state;
QAudioOutput *m_audio;
PxtoneIODevice *m_pxtn_device;
std::optional<quint64> m_last_ping;
QTimer *m_ping_timer;
qint32 m_last_seek;
Clipboard *m_clipboard;

signals:
void editStateChanged(const EditState &m_edit_state);
void playStateChanged(bool playing);
void followActivity(const EditState &r);
void updatePing(std::optional<qint64> ping_length);
void connected();
Expand Down Expand Up @@ -78,7 +75,7 @@ class PxtoneClient : public QObject {
const pxtnService *pxtn() const { return m_controller->pxtn(); }
const EditState &editState() const { return m_edit_state; }
const mooState *moo() const { return m_controller->moo(); }
const QAudioOutput *audioState() const { return m_audio; }
int bufferSize() const { return 256; } // TODO

const NoIdMap &unitIdMap() const { return m_controller->unitIdMap(); }
const std::map<qint64, RemoteEditState> &remoteEditStates() {
Expand Down Expand Up @@ -106,7 +103,7 @@ class PxtoneClient : public QObject {
void setUnitOperated(int unit_no, bool operated);
void cycleSolo(int unit_no);
void removeUnusedUnitsAndWoices();
const std::vector<InterpolatedVolumeMeter> &volumeLevels() const;
std::vector<int> volumeLevels() const;

private:
void processRemoteAction(const ServerAction &a);
Expand Down
13 changes: 10 additions & 3 deletions src/editor/PxtoneController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
#include <QDialog>
#include <QTextCodec>

#include "editor/Settings.h"

const QTextCodec *shift_jis_codec = QTextCodec::codecForName("Shift-JIS");

PxtoneController::PxtoneController(int uid, pxtnService *pxtn,
mooState *moo_state, QObject *parent)
: QObject(parent),
m_audio_renderer(std::make_unique<RtAudioRenderer>(pxtn)),
m_audio_renderer(std::make_unique<RtAudioRenderer>(
pxtn, 256 /* TODO: change this away from default */)),
m_uid(uid),
m_pxtn(pxtn),
m_moo_state(moo_state),
Expand Down Expand Up @@ -416,7 +419,8 @@ void PxtoneController::setSongComment(const QString &comment) {
m_pxtn->text->set_comment_buf(str.data(), str.length());
}

bool PxtoneController::loadDescriptor(pxtnDescriptor &desc) {
bool PxtoneController::loadDescriptor(pxtnDescriptor &desc,
double bufferLength) {
m_audio_renderer.reset();
emit beginRefresh();
if (desc.get_size_bytes() > 0) {
Expand Down Expand Up @@ -448,7 +452,10 @@ bool PxtoneController::loadDescriptor(pxtnDescriptor &desc) {
emit tempoBeatChanged();
emit newSong();

m_audio_renderer = std::make_unique<RtAudioRenderer>(m_pxtn);
m_audio_renderer =
std::make_unique<RtAudioRenderer>(m_pxtn, bufferLength * 44100);
connect(m_audio_renderer.get(), &RtAudioRenderer::playingChanged, this,
&PxtoneController::playStateChanged);

return true;
}
Expand Down
4 changes: 2 additions & 2 deletions src/editor/PxtoneController.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class PxtoneController : public QObject {
qint64 uid();
const NoIdMap &unitIdMap() const { return m_unit_id_map; }
const NoIdMap &woiceIdMap() const { return m_woice_id_map; }
bool loadDescriptor(pxtnDescriptor &desc);
bool loadDescriptor(pxtnDescriptor &desc, double bufferLength);
bool applyAddUnit(const AddUnit &a, qint64 uid);
bool applyAddWoice(const AddWoice &a, qint64 uid);
bool applyRemoveWoice(const RemoveWoice &a, qint64 uid);
Expand Down Expand Up @@ -84,6 +84,7 @@ class PxtoneController : public QObject {
void tempoBeatChanged();
void playedToggled(int unit_no);
void operatedToggled(int unit_no, bool operated);
void playStateChanged(bool playing);
void soloToggled();
void newSong();
void edited();
Expand Down Expand Up @@ -120,7 +121,6 @@ class PxtoneController : public QObject {
qint64 m_uid;
pxtnService *m_pxtn;
mooState *m_moo_state;
PxtoneIODevice *m_moo_io_device;

std::vector<LoggedAction> m_log;
std::list<std::list<Action::Primitive>> m_uncommitted;
Expand Down
65 changes: 42 additions & 23 deletions src/editor/audio/RtAudioRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,61 +9,80 @@ int MooTiming::now_no_wrap(const pxtnMaster *master) {
}

const unsigned int BUFFER_FRAMES =
256; // At higher buffers, this does stuttter. So you will need a
256; // At higher buffers, this does stutter. So you will need a
// MooClock-like thing anyway.
int rtAudioMoo(void *outputBuffer, void * /*inputBuffer*/,
unsigned int nBufferFrames, double /*streamTime*/,
RtAudioStreamStatus status, void *userData) {
RtAudioRenderer *renderer = (RtAudioRenderer *)userData;
if (status) qWarning() << "Stream underflow detected!";

int byte_per_smp;
if (!renderer->m_pxtn->get_byte_per_smp(&byte_per_smp)) return 1;

int size = nBufferFrames * byte_per_smp;
if (!renderer->m_pxtn->Moo(renderer->m_moo_state, outputBuffer, size, nullptr,
nullptr))
return 1;
renderer->m_moo_timing.store(
{renderer->m_moo_state.get_now_clock(), renderer->m_moo_state.num_loop});

if (renderer->m_playing.load()) {
if (!renderer->m_pxtn->Moo(renderer->m_moo_state, outputBuffer, size,
nullptr, nullptr))
return 1;
renderer->m_moo_timing.store({renderer->m_moo_state.get_now_clock(),
renderer->m_moo_state.num_loop});
} else {
memset(outputBuffer, 0, size);
}

return 0;
}

RtAudioRenderer::RtAudioRenderer(const pxtnService *pxtn)
: m_pxtn(pxtn), m_dac() {
if (m_dac.getDeviceCount() < 1)
throw std::runtime_error("No audio devices found");
void RtAudioRenderer::setPlaying(bool playing) {
bool not_playing = !playing;
bool changed = m_playing.compare_exchange_strong(not_playing, playing);
if (changed) emit playingChanged(playing);
}

qDebug() << "lock free" << m_moo_timing.is_lock_free();
RtAudio::StreamParameters parameters;
parameters.deviceId = m_dac.getDefaultOutputDevice();
parameters.nChannels = 2;
parameters.firstChannel = 0;
void RtAudioRenderer::setBufferFrames(unsigned int numFrames) {
stopStream();
startStream(numFrames);
}

bool RtAudioRenderer::isPlaying() { return m_playing.load(); }

RtAudioRenderer::RtAudioRenderer(const pxtnService *pxtn,
unsigned int numFrames)
: m_pxtn(pxtn), m_dac(), m_playing(false) {
pxtnVOMITPREPARATION prep{};
prep.flags |= pxtnVOMITPREPFLAG_loop | pxtnVOMITPREPFLAG_unit_mute;
prep.start_pos_sample = 0;
prep.master_volume = 1; // need to be able to dynamically change moo state
if (!m_pxtn->moo_preparation(&prep, m_moo_state)) {
throw std::runtime_error("moo prep failed");
}
startStream(numFrames);
}

void RtAudioRenderer::startStream(unsigned int numFrames) {
if (m_dac.getDeviceCount() < 1)
throw std::runtime_error("No audio devices found");

RtAudio::StreamParameters parameters;
parameters.deviceId = m_dac.getDefaultOutputDevice();
parameters.nChannels = 2;
parameters.firstChannel = 0;

int sampleRate = 44100;
unsigned int bufferFrames = BUFFER_FRAMES;
m_dac.openStream(&parameters, NULL, RTAUDIO_SINT16, sampleRate, &bufferFrames,
m_dac.openStream(&parameters, NULL, RTAUDIO_SINT16, sampleRate, &numFrames,
&rtAudioMoo, this);
m_dac.startStream();
qDebug() << "started stream" << bufferFrames;
}

MooTiming RtAudioRenderer::moo_timing() const { return m_moo_timing.load(); }

RtAudioRenderer::~RtAudioRenderer() {
void RtAudioRenderer::stopStream() {
try {
m_dac.stopStream();
} catch (RtAudioError &e) {
e.printMessage();
}
if (m_dac.isStreamOpen()) m_dac.closeStream();
}

MooTiming RtAudioRenderer::moo_timing() const { return m_moo_timing.load(); }

RtAudioRenderer::~RtAudioRenderer() { stopStream(); }
Loading

0 comments on commit 1022b66

Please sign in to comment.