Merge pull request #6354 from igorkorsukov/mu4/audio_midi_events

[MU4] Audio midi playback
This commit is contained in:
Igor Korsukov 2020-07-23 12:29:14 +02:00 committed by GitHub
commit 7976244cd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 1519 additions and 326 deletions

View file

@ -80,13 +80,13 @@ freely, subject to the following restrictions:
// Configuration defines
// Maximum number of filters per stream
#define FILTERS_PER_STREAM 8
#define FILTERS_PER_STREAM 0
// Number of samples to process on one go
#define SAMPLE_GRANULARITY 512
#define SAMPLE_GRANULARITY 1024
// Maximum number of concurrent voices (hard limit is 4095)
#define VOICE_COUNT 1024
#define VOICE_COUNT 96
// Use linear resampler
#define RESAMPLER_LINEAR

View file

@ -15,9 +15,9 @@ result muaudio_init(SoLoud::Soloud* aSoloud, unsigned int aFlags, unsigned int a
#include <math.h>
#include "log.h"
#include "modularity/ioc.h"
#include "audio/engine/iaudiodriver.h"
#include "audio/engine/audioerrors.h"
#include "audio/engine/internal/audioengine.h"
//#define DEBUG_AUDIO_DRIVER
@ -29,13 +29,6 @@ result muaudio_init(SoLoud::Soloud* aSoloud, unsigned int aFlags, unsigned int a
using namespace mu::audio::engine;
namespace {
std::shared_ptr<IAudioDriver> adriver()
{
return mu::framework::ioc()->resolve<mu::audio::engine::IAudioDriver>("soloud");
}
}
namespace SoLoud {
static IAudioDriver::Spec gActiveAudioSpec;
static bool gInited{ false };
@ -59,24 +52,30 @@ void soloud_muaudio_audiomixer(void* userdata, uint8_t* stream, int len)
int samples = len / (gActiveAudioSpec.channels * sizeof(short));
soloud->mixSigned16(buf, samples);
}
AudioEngine* engine = (AudioEngine*)soloud->mBackendData;
engine->onPlayCallbackCalled();
}
static void soloud_muaudio_deinit(SoLoud::Soloud* aSoloud)
{
gInited = false;
UNUSED(aSoloud);
std::shared_ptr<IAudioDriver> driver = adriver();
AudioEngine* engine = (AudioEngine*)aSoloud->mBackendData;
std::shared_ptr<IAudioDriver> driver = engine->driver();
IF_ASSERT_FAILED(driver) {
LOGE() << "no audio driver \n";
return;
}
driver->close();
}
result muaudio_init(SoLoud::Soloud* aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer,
unsigned int aChannels)
{
std::shared_ptr<IAudioDriver> driver = adriver();
AudioEngine* engine = (AudioEngine*)aSoloud->mBackendData;
std::shared_ptr<IAudioDriver> driver = engine->driver();
IF_ASSERT_FAILED(driver) {
LOGE() << "no audio driver \n";

View file

@ -18,6 +18,8 @@
//=============================================================================
#include "audioenginedevtools.h"
#include "log.h"
using namespace mu::audio::engine;
using namespace mu::audio::midi;
@ -45,10 +47,10 @@ void AudioEngineDevTools::playSourceMidi()
m_midiSource = std::make_shared<MidiSource>();
}
if (!m_midiData) {
m_midiData = makeArpeggio();
if (!m_midiStream) {
makeArpeggio();
m_midiSource->init(audioEngine()->sampleRate());
m_midiSource->loadMIDI(m_midiData);
m_midiSource->loadMIDI(m_midiStream);
}
m_midiHandel = audioEngine()->play(m_midiSource);
@ -61,11 +63,9 @@ void AudioEngineDevTools::stopSourceMidi()
void AudioEngineDevTools::playPlayerMidi()
{
if (!m_midiData) {
m_midiData = makeArpeggio();
}
makeArpeggio();
player()->setMidiData(m_midiData);
player()->setMidiStream(m_midiStream);
player()->play();
}
@ -74,27 +74,72 @@ void AudioEngineDevTools::stopPlayerMidi()
player()->stop();
}
std::shared_ptr<MidiData> AudioEngineDevTools::makeArpeggio() const
void AudioEngineDevTools::playNotation()
{
/* notes of the arpeggio */
static std::vector<int> notes = { 60, 64, 67, 72, 76, 79, 84, 79, 76, 72, 67, 64 };
static uint64_t duration = 4440;
uint64_t note_duration = duration / notes.size();
uint64_t note_time = 0;
Channel ch;
for (int n : notes) {
ch.events.push_back(Event(note_time, ME_NOTEON, n, 100));
note_time += note_duration;
ch.events.push_back(Event(note_time, ME_NOTEOFF, n, 100));
auto notation = globalContext()->currentNotation();
if (!notation) {
LOGE() << "no notation";
return;
}
Track t;
t.channels.push_back(ch);
std::shared_ptr<MidiData> data = std::make_shared<MidiData>();
data->tracks.push_back(t);
return data;
auto stream = notation->playback()->midiStream();
player()->setMidiStream(stream);
player()->play();
}
void AudioEngineDevTools::stopNotation()
{
player()->stop();
}
void AudioEngineDevTools::makeArpeggio()
{
if (m_midiStream) {
return;
}
m_midiStream = std::make_shared<midi::MidiStream>();
auto makeEvents = [](Channel& ch, uint32_t tick, int pitch) {
/* notes of the arpeggio */
static std::vector<int> notes = { 60, 64, 67, 72, 76, 79, 84, 79, 76, 72, 67, 64 };
static uint32_t duration = 4440;
uint32_t note_duration = duration / notes.size();
uint32_t note_time = tick;
for (int n : notes) {
ch.events.push_back(Event(note_time, ME_NOTEON, n + pitch, 100));
note_time += note_duration;
ch.events.push_back(Event(note_time, ME_NOTEOFF, n + pitch, 100));
}
};
Channel ch;
Track t;
t.num = 1;
t.channels.push_back(ch);
m_midiStream->initData.tracks.push_back(t);
m_midiStream->request.onReceive(this, [this, makeEvents](uint32_t tick) {
static int pitch = -11;
++pitch;
if (pitch > 11) {
pitch = -10;
}
if (tick > 20000) {
m_midiStream->stream.close();
return;
}
Channel ch;
makeEvents(ch, tick, pitch);
Track t;
t.num = 1;
t.channels.push_back(ch);
MidiData data;
data.tracks.push_back(t);
m_midiStream->stream.send(data);
});
}

View file

@ -20,21 +20,25 @@
#define MU_AUDIO_AUDIOENGINEDEVTOOLS_H
#include <QObject>
#include <QTimer>
#include "modularity/ioc.h"
#include "audio/engine/iaudioengine.h"
#include "audio/engine/iaudioplayer.h"
#include "context/iglobalcontext.h"
#include "sinesource.h"
#include "midisource.h"
#include "async/asyncable.h"
namespace mu {
namespace audio {
namespace engine {
class AudioEngineDevTools : public QObject
class AudioEngineDevTools : public QObject, public async::Asyncable
{
Q_OBJECT
INJECT(audio, IAudioEngine, audioEngine)
INJECT(audio, IAudioPlayer, player)
INJECT(audio, context::IGlobalContext, globalContext)
public:
explicit AudioEngineDevTools(QObject* parent = nullptr);
@ -48,14 +52,18 @@ public:
Q_INVOKABLE void playPlayerMidi();
Q_INVOKABLE void stopPlayerMidi();
Q_INVOKABLE void playNotation();
Q_INVOKABLE void stopNotation();
private:
std::shared_ptr<midi::MidiData> makeArpeggio() const;
void makeArpeggio();
std::shared_ptr<SineSource> m_sineSource;
IAudioEngine::handle m_sineHandle = 0;
std::shared_ptr<midi::MidiData> m_midiData;
std::shared_ptr<midi::MidiStream> m_midiStream;
std::shared_ptr<MidiSource> m_midiSource;
IAudioEngine::handle m_midiHandel = 0;
};

View file

@ -24,6 +24,7 @@
#include "modularity/imoduleexport.h"
#include "ret.h"
#include "async/notification.h"
#include "iaudiosource.h"
namespace mu {
@ -46,7 +47,7 @@ public:
virtual float sampleRate() const = 0;
virtual handle play(std::shared_ptr<IAudioSource> src, float volume = -1, float pan = 0, bool paused = false) = 0;
virtual void seek(time sec) = 0;
virtual void seek(handle h, time sec) = 0;
virtual void stop(handle h) = 0;
virtual void setPause(handle h, bool paused) = 0;
@ -56,6 +57,8 @@ public:
virtual void setVolume(handle h, float volume) = 0; // 0. - 1.
virtual void setPan(handle h, float val) = 0; // -1 only left, 0 center, 1 only right
virtual void setPlaySpeed(handle h, float speed) = 0;
virtual async::Notification playCallbackCalled() const = 0; //! NOTE A portion of data was given to the driver
};
}
}

View file

@ -46,7 +46,7 @@ public:
virtual ValCh<PlayStatus> status() const = 0;
// data
virtual void setMidiData(std::shared_ptr<midi::MidiData> midi) = 0;
virtual void setMidiStream(const std::shared_ptr<midi::MidiStream>& stream) = 0;
// Action
virtual bool play() = 0;

View file

@ -32,8 +32,6 @@ public:
virtual ~IAudioSource() = default;
virtual void setSampleRate(float sampleRate) = 0;
virtual void sync(float sec) = 0;
virtual SoLoud::AudioSource* source() = 0;
};
}

View file

@ -27,13 +27,6 @@
using namespace mu::audio::engine;
//! Defines the buffer size for the audio driver.
//! If the value is too small, there may be stuttering sound
//! If the value is too large, there will be a big latency
//! before the start of playback and after the end of playback
constexpr int BUF_SIZE{ 1024 };
struct AudioEngine::SL {
SoLoud::Soloud engine;
};
@ -41,6 +34,7 @@ struct AudioEngine::SL {
AudioEngine::AudioEngine()
{
m_sl = std::shared_ptr<SL>(new SL);
m_sl->engine.mBackendData = this;
}
bool AudioEngine::isInited() const
@ -70,7 +64,7 @@ mu::Ret AudioEngine::init()
int res = m_sl->engine.init(SoLoud::Soloud::CLIP_ROUNDOFF,
SoLoud::Soloud::MUAUDIO,
SoLoud::Soloud::AUTO,
BUF_SIZE,
SAMPLE_GRANULARITY, // 1024
2);
if (res == SoLoud::SO_NO_ERROR) {
@ -123,50 +117,24 @@ IAudioEngine::handle AudioEngine::play(std::shared_ptr<IAudioSource> s, float vo
}
handle h = m_sl->engine.play(*sa, volume, pan, paused);
Source ss;
ss.handel = h;
ss.source = s;
ss.playing = !paused;
m_sources.insert({ h, ss });
return h;
}
void AudioEngine::seek(time sec)
void AudioEngine::seek(handle h, time sec)
{
LOGD() << "seek to " << sec;
syncAll(sec);
m_sl->engine.seek(h, sec);
}
void AudioEngine::stop(handle h)
{
LOGD() << "stop";
m_sl->engine.stop(h);
m_sources.erase(h);
}
void AudioEngine::setPause(handle h, bool paused)
{
LOGI() << (paused ? "pause" : "resume");
auto it = m_sources.find(h);
if (it != m_sources.end()) {
it->second.playing = !paused;
}
m_sl->engine.setPause(h, paused);
}
void AudioEngine::syncAll(time sec)
{
for (auto it = m_sources.begin(); it != m_sources.end(); ++it) {
if (it->second.playing) {
it->second.source->sync(sec);
}
}
}
void AudioEngine::stopAll()
{
m_sl->engine.stopAll();
@ -198,3 +166,13 @@ void AudioEngine::setPlaySpeed(handle h, float speed)
{
m_sl->engine.setRelativePlaySpeed(h, speed);
}
mu::async::Notification AudioEngine::playCallbackCalled() const
{
return m_playCallbackCalled;
}
void AudioEngine::onPlayCallbackCalled()
{
m_playCallbackCalled.notify();
}

View file

@ -46,11 +46,10 @@ public:
float sampleRate() const override;
handle play(std::shared_ptr<IAudioSource> s, float volume = -1, float pan = 0, bool paused = false) override;
void seek(time sec) override;
void seek(handle h, time sec) override;
void stop(handle h) override;
void setPause(handle h, bool paused) override;
void syncAll(time sec);
void stopAll();
time position(handle h) const override;
@ -60,19 +59,17 @@ public:
void setPan(handle h, float val) override;
void setPlaySpeed(handle h, float speed) override;
async::Notification playCallbackCalled() const override;
// internal
void onPlayCallbackCalled();
private:
struct SL;
std::shared_ptr<SL> m_sl;
bool m_inited = false;
struct Source {
handle handel = 0;
std::shared_ptr<IAudioSource> source;
bool playing = false;
};
std::map<handle, Source> m_sources;
async::Notification m_playCallbackCalled;
};
}
}

View file

@ -20,6 +20,8 @@
#include "log.h"
#include <QElapsedTimer>
using namespace mu;
using namespace mu::audio;
using namespace mu::audio::engine;
@ -35,14 +37,14 @@ AudioPlayer::AudioPlayer()
m_status.val = PlayStatus::UNDEFINED;
}
void AudioPlayer::setMidiData(std::shared_ptr<midi::MidiData> midi)
void AudioPlayer::setMidiStream(const std::shared_ptr<midi::MidiStream>& stream)
{
if (midi) {
if (stream) {
m_midiSource = std::make_shared<MidiSource>();
m_midiSource->loadMIDI(midi);
m_midiSource->loadMIDI(stream);
m_tracks.clear();
for (size_t num = 0; num < midi->tracks.size(); ++num) {
for (size_t num = 0; num < stream->initData.tracks.size(); ++num) {
m_tracks[num] = std::make_shared<Track>();
}
@ -101,7 +103,9 @@ bool AudioPlayer::init()
m_midiSource->init(samplerate);
}
m_inited = true;
audioEngine()->playCallbackCalled().onNotify(this, [this]() {
onPlayCallbackCalled();
});
}
return m_inited;
}
@ -129,7 +133,7 @@ bool AudioPlayer::doPlay()
m_midiHandle = audioEngine()->play(m_midiSource, -1, 0, true); // paused
}
audioEngine()->seek(m_beginPlayPosition);
audioEngine()->seek(m_midiHandle, m_beginPlayPosition);
audioEngine()->setPause(m_midiHandle, false);
return true;
@ -137,7 +141,7 @@ bool AudioPlayer::doPlay()
void AudioPlayer::doPause()
{
m_beginPlayPosition = m_currentPlayPosition;
m_beginPlayPosition = currentPlayPosition();
if (m_midiHandle) {
audioEngine()->setPause(m_midiHandle, true);
}
@ -146,15 +150,19 @@ void AudioPlayer::doPause()
void AudioPlayer::doStop()
{
m_beginPlayPosition = 0;
m_currentPlayPosition = 0;
audioEngine()->stop(m_midiHandle);
m_midiHandle = 0;
}
float AudioPlayer::currentPlayPosition() const
{
return audioEngine()->position(m_midiHandle);
}
float AudioPlayer::playbackPosition() const
{
if (m_status.val == PlayStatus::PLAYING) {
return m_currentPlayPosition;
return currentPlayPosition();
}
return m_beginPlayPosition;
}
@ -164,10 +172,9 @@ void AudioPlayer::setPlaybackPosition(float sec)
sec = std::max(sec, 0.f);
m_beginPlayPosition = sec;
m_currentPlayPosition = sec;
if (m_status.val == PlayStatus::PLAYING) {
audioEngine()->seek(sec);
audioEngine()->seek(m_midiHandle, sec);
}
}
@ -236,3 +243,29 @@ ValCh<PlayStatus> AudioPlayer::status() const
{
return m_status;
}
void AudioPlayer::onPlayCallbackCalled()
{
//! NOTE For tests in development
// struct Time {
// QElapsedTimer time;
// int last = 0;
// Time() { time.start(); }
// };
// static Time time;
// static float lastPos = 0;
// if (m_status.val == PlayStatus::PLAYING) {
// float p = playbackPosition();
// float deltaPos = p - lastPos;
// lastPos = p;
// int cur = time.time.elapsed();
// int delta = cur - time.last;
// time.last = cur;
// LOGI() << "[onPlayCallbackCalled] pos: " << p << ", delta pos: " << deltaPos << ", timer: " << delta;
// }
}

View file

@ -24,11 +24,12 @@
#include "modularity/ioc.h"
#include "../iaudioengine.h"
#include "async/asyncable.h"
#include "midisource.h"
namespace mu {
namespace audio {
class AudioPlayer : public IAudioPlayer
class AudioPlayer : public IAudioPlayer, public async::Asyncable
{
INJECT(audio, engine::IAudioEngine, audioEngine)
@ -38,7 +39,7 @@ public:
ValCh<PlayStatus> status() const override;
// data
void setMidiData(std::shared_ptr<midi::MidiData> midi) override;
void setMidiStream(const std::shared_ptr<midi::MidiStream>& stream) override;
// Action
bool play() override;
@ -69,6 +70,8 @@ private:
void doPause();
void doStop();
float currentPlayPosition() const;
bool hasTracks() const;
float normalizedVolume(float volume) const;
@ -77,15 +80,16 @@ private:
void applyCurrentVolume();
void applyCurrentBalance();
void onPlayCallbackCalled();
bool m_inited = false;
ValCh<PlayStatus> m_status;
std::shared_ptr<midi::MidiData> m_midi;
std::shared_ptr<midi::MidiStream> m_midiStream;
std::shared_ptr<engine::MidiSource> m_midiSource;
engine::IAudioEngine::handle m_midiHandle = 0;
float m_beginPlayPosition = 0.0f;
float m_currentPlayPosition = 0.0f;
float m_generalVolume = 1.0f;
float m_generalBalance = 0.0f;

View file

@ -48,7 +48,8 @@ struct MidiSource::SLInstance : public SoLoud::AudioSourceInstance {
unsigned int getAudio(float* aBuffer, unsigned int aSamplesToRead, unsigned int /*aBufferSize*/) override
{
seq->getAudio(mStreamTime, aBuffer, aSamplesToRead);
float sec = seq->getAudio(mStreamTime, aBuffer, aSamplesToRead);
LOGI() << "MidiSource getAudio: sec: " << sec << ", mStreamTime: " << mStreamTime;
return aSamplesToRead;
}
@ -82,19 +83,14 @@ void MidiSource::setSampleRate(float samplerate)
m_sl->mBaseSamplerate = samplerate;
}
void MidiSource::sync(float sec)
{
m_seq->seek(sec);
}
SoLoud::AudioSource* MidiSource::source()
{
return m_sl.get();
}
void MidiSource::loadMIDI(const std::shared_ptr<midi::MidiData>& midi)
void MidiSource::loadMIDI(const std::shared_ptr<midi::MidiStream>& stream)
{
m_seq->loadMIDI(midi);
m_seq->loadMIDI(stream);
}
void MidiSource::init(float samplerate)

View file

@ -40,10 +40,9 @@ public:
MidiSource();
void setSampleRate(float samplerate) override;
void sync(float sec) override;
SoLoud::AudioSource* source() override;
void loadMIDI(const std::shared_ptr<midi::MidiData>& midi);
void loadMIDI(const std::shared_ptr<midi::MidiStream>& stream);
void init(float samplerate);
float playbackSpeed() const;

View file

@ -96,11 +96,6 @@ void SineSource::setSampleRate(float samplerate)
generateSine(*m_samples.get(), samplerate, 340.0, 10);
}
void SineSource::sync(float sec)
{
UNUSED(sec);
}
SoLoud::AudioSource* SineSource::source()
{
return m_sl.get();

View file

@ -34,8 +34,6 @@ public:
~SineSource() = default;
void setSampleRate(float samplerate) override;
void sync(float sec) override;
SoLoud::AudioSource* source() override;
private:

View file

@ -33,15 +33,6 @@ Sequencer::~Sequencer()
}
}
void Sequencer::loadMIDI(const std::shared_ptr<MidiData>& midi)
{
m_midi = midi;
buildTempoMap();
synth()->loadSF(m_midi->programs(), "", [this](uint16_t percent) {
LOGI() << "sf loading: " << percent;
});
}
void Sequencer::init(float samplerate, float gain)
{
reset();
@ -58,6 +49,57 @@ void Sequencer::init(float samplerate, float gain)
});
}
void Sequencer::loadMIDI(const std::shared_ptr<MidiStream>& stream)
{
m_midiStream = stream;
m_streamState = StreamState();
m_midiData = stream->initData;
m_midiStream->stream.onReceive(this, [this](const MidiData& data) { onDataReceived(data); });
m_midiStream->stream.onClose(this, [this]() { onStreamClosed(); });
if (maxTicks(m_midiData.tracks) == 0) {
//! NOTE If there is no data, then we will immediately request them from 0 tick,
//! so that there is something to play.
requestData(0);
}
buildTempoMap();
synth()->loadSF(m_midiData.programs(), "", [this](uint16_t percent) {
LOGI() << "sf loading: " << percent;
});
}
void Sequencer::requestData(uint32_t tick)
{
LOGI() << "requestData: " << tick;
if (m_streamState.closed) {
LOGE() << "stream closed";
m_streamState.requested = false;
return;
}
m_streamState.requested = true;
m_midiStream->request.send(tick);
}
void Sequencer::onDataReceived(const MidiData& data)
{
LOGI() << "onDataReceived: " << data.tracks.front().channels.front().events.front().tick;
//! TODO implement merge
m_midiData = data;
m_streamState.requested = false;
uint32_t curTick = ticks(m_curMsec);
doSeekTracks(curTick, m_midiData.tracks);
}
void Sequencer::onStreamClosed()
{
m_streamState.requested = false;
m_streamState.closed = true;
}
void Sequencer::changeGain(float gain)
{
synth()->setGain(gain);
@ -70,14 +112,22 @@ void Sequencer::process(float sec)
}
uint64_t msec = static_cast<uint64_t>(sec * 1000);
uint64_t delta = msec - m_lastTimerMsec;
uint64_t delta = msec - m_lastTimeMsec;
if (delta < 1) {
return;
}
player_callback(delta);
m_lastTimerMsec = msec;
m_curMsec += (delta * m_playSpeed);
uint32_t curTicks = ticks(m_curMsec);
uint32_t maxTicks = this->maxTicks(m_midiData.tracks);
if (curTicks >= maxTicks) {
requestData(curTicks);
}
sendEvents(curTicks);
m_lastTimeMsec = msec;
}
float Sequencer::getAudio(float sec, float* buf, unsigned int len)
@ -86,15 +136,19 @@ float Sequencer::getAudio(float sec, float* buf, unsigned int len)
synth()->writeBuf(buf, len);
float cur_sec = static_cast<float>(m_curMsec) / 1000.f;
float cur_sec = static_cast<float>(m_curMsec) / 1000.f;
return cur_sec;
}
bool Sequencer::hasEnded() const
{
for (const Track& t : m_midi->tracks) {
if (m_streamState.requested) {
return false;
}
for (const Track& t : m_midiData.tracks) {
for (const Channel& c : t.channels) {
if (!channel_eot(c)) {
if (!channelEOT(c)) {
return false;
}
}
@ -117,11 +171,7 @@ bool Sequencer::run(float init_sec)
return false;
}
IF_ASSERT_FAILED(m_midi) {
return false;
}
m_lastTimerMsec = static_cast<uint64_t>(init_sec * 1000);
m_lastTimeMsec = static_cast<uint64_t>(init_sec * 1000);
doRun();
m_status = Running;
@ -158,7 +208,16 @@ void Sequencer::doStop()
synth()->flushSound();
}
void Sequencer::doSeekChan(uint32_t seek_ticks, const Channel& c)
void Sequencer::doSeekTracks(uint32_t seekTicks, const std::vector<Track>& tracks)
{
for (const Track& t : tracks) {
for (const Channel& c : t.channels) {
doSeekChan(seekTicks, c);
}
}
}
void Sequencer::doSeekChan(uint32_t seekTicks, const Channel& c)
{
ChanState& state = m_chanStates[c.num];
state.eventIndex = 0;
@ -166,24 +225,20 @@ void Sequencer::doSeekChan(uint32_t seek_ticks, const Channel& c)
for (size_t i = 0; i < c.events.size(); ++i) {
state.eventIndex = i;
const Event& event = c.events.at(i);
if (event.tick >= seek_ticks) {
if (event.tick >= seekTicks) {
break;
}
}
}
void Sequencer::doSeek(uint64_t seek_msec)
void Sequencer::doSeek(uint64_t seekMsec)
{
m_internalRunning = false;
m_seekMsec = seek_msec;
uint32_t seek_ticks = ticks(m_seekMsec);
m_seekMsec = seekMsec;
uint32_t seekTicks = ticks(m_seekMsec);
for (const Track& t : m_midi->tracks) {
for (const Channel& c : t.channels) {
doSeekChan(seek_ticks, c);
}
}
doSeekTracks(seekTicks, m_midiData.tracks);
m_curMsec = m_seekMsec;
m_internalRunning = true;
@ -200,7 +255,7 @@ void Sequencer::seek(float sec)
synth()->flushSound();
}
uint64_t Sequencer::max_ticks(const std::vector<Track>& tracks) const
uint64_t Sequencer::maxTicks(const std::vector<Track>& tracks) const
{
uint64_t maxTicks = 0;
@ -221,7 +276,7 @@ uint64_t Sequencer::max_ticks(const std::vector<Track>& tracks) const
return maxTicks;
}
bool Sequencer::channel_eot(const Channel& chan) const
bool Sequencer::channelEOT(const Channel& chan) const
{
const ChanState& s = m_chanStates[chan.num];
if (s.eventIndex >= chan.events.size()) {
@ -232,14 +287,10 @@ bool Sequencer::channel_eot(const Channel& chan) const
void Sequencer::buildTempoMap()
{
IF_ASSERT_FAILED(m_midi) {
return;
}
m_tempoMap.clear();
std::vector<std::pair<uint32_t, uint32_t> > tempos;
for (const auto& it : m_midi->tempomap) {
for (const auto& it : m_midiData.tempomap) {
tempos.push_back({ it.first, it.second });
}
@ -255,9 +306,7 @@ void Sequencer::buildTempoMap()
t.tempo = tempos.at(i).second;
t.startTicks = tempos.at(i).first;
t.startMsec = msec;
t.onetickMsec = static_cast<double>(t.tempo)
/ static_cast<double>(m_midi->division)
/ 1000.;
t.onetickMsec = static_cast<double>(t.tempo) / static_cast<double>(m_midiData.division) / 1000.;
uint32_t end_ticks = ((i + 1) < tempos.size()) ? tempos.at(i + 1).first : std::numeric_limits<uint32_t>::max();
@ -284,38 +333,20 @@ uint32_t Sequencer::ticks(uint64_t msec) const
return t.startTicks + ticks;
}
bool Sequencer::player_callback(uint64_t timer_msec)
bool Sequencer::sendEvents(uint32_t curTicks)
{
// static uint64_t last_msec{0};
// LOGI() << "msec: " << timer_msec << ", delta: " << (timer_msec - last_msec) << "\n";
// last_msec = timer_msec;
m_curMsec += (timer_msec * m_playSpeed);
uint32_t cur_ticks = ticks(m_curMsec);
// LOGI() << "timer_msec: " << timer_msec
// << ", cur_msec: " << _cur_msec
// << ", cur_ticks: " << cur_ticks
// << "\n";
auto sendEvents = [this, cur_ticks](const Channel& c) {
if (!channel_eot(c)) {
if (!send_chan_events(c, cur_ticks)) {
LOGE() << "failed send events\n";
}
}
};
for (const Track& t : m_midi->tracks) {
for (const Track& t : m_midiData.tracks) {
for (const Channel& c : t.channels) {
sendEvents(c);
if (!sendChanEvents(c, curTicks)) {
LOGE() << "failed send events\n";
}
}
}
return true;
}
bool Sequencer::send_chan_events(const Channel& chan, uint32_t ticks)
bool Sequencer::sendChanEvents(const Channel& chan, uint32_t ticks)
{
bool ret = true;
@ -323,7 +354,7 @@ bool Sequencer::send_chan_events(const Channel& chan, uint32_t ticks)
while (1)
{
if (channel_eot(chan)) {
if (channelEOT(chan)) {
return ret;
}
@ -354,22 +385,22 @@ void Sequencer::setPlaybackSpeed(float speed)
m_playSpeed = speed;
}
bool Sequencer::isHasTrack(uint16_t ti) const
bool Sequencer::hasTrack(uint16_t ti) const
{
if (!m_midi) {
if (!m_midiData.isValid()) {
return false;
}
if (ti < m_midi->tracks.size()) {
if (ti < m_midiData.tracks.size()) {
return true;
}
return false;
}
void Sequencer::setIsTrackMuted(int ti, bool mute)
void Sequencer::setIsTrackMuted(uint16_t trackIndex, bool mute)
{
IF_ASSERT_FAILED(isHasTrack(ti)) {
IF_ASSERT_FAILED(hasTrack(trackIndex)) {
return;
}
@ -379,31 +410,31 @@ void Sequencer::setIsTrackMuted(int ti, bool mute)
synth()->channelSoundsOff(c.num);
};
const Track& track = m_midi->tracks[ti];
const Track& track = m_midiData.tracks[trackIndex];
for (const Channel& c : track.channels) {
setMuted(c);
}
}
void Sequencer::setTrackVolume(int ti, float volume)
void Sequencer::setTrackVolume(uint16_t trackIndex, float volume)
{
IF_ASSERT_FAILED(isHasTrack(ti)) {
IF_ASSERT_FAILED(hasTrack(trackIndex)) {
return;
}
const Track& track = m_midi->tracks[ti];
const Track& track = m_midiData.tracks[trackIndex];
for (const Channel& c : track.channels) {
synth()->channelVolume(c.num, volume);
}
}
void Sequencer::setTrackBalance(int ti, float balance)
void Sequencer::setTrackBalance(uint16_t trackIndex, float balance)
{
IF_ASSERT_FAILED(isHasTrack(ti)) {
IF_ASSERT_FAILED(hasTrack(trackIndex)) {
return;
}
const Track& track = m_midi->tracks[ti];
const Track& track = m_midiData.tracks[trackIndex];
for (const Channel& c : track.channels) {
synth()->channelBalance(c.num, balance);
}

View file

@ -32,12 +32,13 @@
#include "../miditypes.h"
#include "modularity/ioc.h"
#include "../isynthesizer.h"
#include "async/asyncable.h"
namespace mu {
namespace audio {
namespace midi {
class ISynthesizer;
class Sequencer : public ISequencer
class Sequencer : public ISequencer, public async::Asyncable
{
INJECT(midi, ISynthesizer, synth)
@ -53,7 +54,7 @@ public:
Status status() const;
void loadMIDI(const std::shared_ptr<MidiData>& midi);
void loadMIDI(const std::shared_ptr<midi::MidiStream>& stream);
void init(float samplerate, float gain = 1);
void changeGain(float gain);
@ -70,30 +71,35 @@ public:
float playbackSpeed() const override;
void setPlaybackSpeed(float speed) override;
void setIsTrackMuted(int t, bool mute) override;
void setTrackVolume(int ti, float volume) override;
void setTrackBalance(int ti, float balance) override;
void setIsTrackMuted(uint16_t trackIndex, bool mute) override;
void setTrackVolume(uint16_t trackIndex, float volume) override;
void setTrackBalance(uint16_t trackIndex, float balance) override;
private:
void process(float sec);
void reset();
uint64_t max_ticks(const std::vector<Track>& tracks) const;
bool channel_eot(const Channel& chan) const;
bool player_callback(uint64_t msec);
bool send_chan_events(const Channel& chan, uint32_t ticks);
uint64_t maxTicks(const std::vector<Track>& tracks) const;
bool channelEOT(const Channel& chan) const;
bool sendEvents(uint32_t curTicks);
bool sendChanEvents(const Channel& chan, uint32_t ticks);
void buildTempoMap();
uint32_t ticks(uint64_t msec) const;
bool isHasTrack(uint16_t num) const;
bool hasTrack(uint16_t num) const;
bool doRun();
void doStop();
void doSeek(uint64_t seek_msec);
void doSeekChan(uint32_t seek_ticks, const Channel& c);
void doSeek(uint64_t seekMsec);
void doSeekTracks(uint32_t seekTicks, const std::vector<Track>& tracks);
void doSeekChan(uint32_t seekTicks, const Channel& c);
void requestData(uint32_t tick);
void onDataReceived(const MidiData& data);
void onStreamClosed();
struct TempoItem {
uint32_t tempo = 500000;
@ -106,14 +112,22 @@ private:
Status m_status = Stoped;
bool m_internalRunning = false;
std::shared_ptr<MidiData> m_midi;
MidiData m_midiData;
std::shared_ptr<MidiStream> m_midiStream;
struct StreamState {
bool requested = false;
bool closed = false;
};
StreamState m_streamState;
double m_oneTickMsec = 1;
float m_sampleRate = 44100.0f;
float m_playSpeed = 1.0;
uint64_t m_lastTimerMsec = 0;
uint64_t m_lastTimeMsec = 0;
uint64_t m_curMsec = 0;
uint64_t m_seekMsec = 0;

View file

@ -37,10 +37,10 @@ class ISequencer : MODULE_EXPORT_INTERFACE
public:
virtual ~ISequencer() = default;
virtual void loadMIDI(const std::shared_ptr<MidiData>& midi) = 0;
virtual void loadMIDI(const std::shared_ptr<midi::MidiStream>& stream) = 0;
virtual void init(float samplerate, float gain = 1) = 0;
virtual bool run(float init_sec) = 0;
virtual bool run(float initSec) = 0;
virtual void seek(float sec) = 0;
virtual void stop() = 0;
@ -50,9 +50,9 @@ public:
virtual float playbackSpeed() const = 0;
virtual void setPlaybackSpeed(float speed) = 0;
virtual void setIsTrackMuted(int t, bool mute) = 0;
virtual void setTrackVolume(int ti, float volume) = 0;
virtual void setTrackBalance(int ti, float balance) = 0;
virtual void setIsTrackMuted(uint16_t trackIndex, bool mute) = 0;
virtual void setTrackVolume(uint16_t trackIndex, float volume) = 0;
virtual void setTrackBalance(uint16_t trackIndex, float balance) = 0;
};
}
}

View file

@ -27,11 +27,13 @@
#include <map>
#include <functional>
#include "async/channel.h"
namespace mu {
namespace audio {
namespace midi {
enum EventType {
ME_INVALID = 0,
ME_INVALID = 0,
ME_NOTEOFF,
ME_NOTEON,
ME_CONTROLLER,
@ -50,10 +52,10 @@ enum CntrType {
};
struct Event {
uint32_t tick{ 0 };
EventType type{ ME_INVALID };
int a{ 0 };
int b{ 0 };
uint32_t tick = 0;
EventType type = ME_INVALID;
int a = 0;
int b = 0;
Event() = default;
Event(uint32_t tick, EventType type, int a, int b)
@ -126,29 +128,32 @@ struct Event {
};
struct Channel {
uint16_t num{ 0 };
uint16_t bank{ 0 };
uint16_t program{ 0 };
uint16_t num = 0;
uint16_t bank = 0;
uint16_t program = 0;
std::vector<Event> events;
};
struct Track {
uint16_t num = 0;
std::vector<Channel> channels;
};
struct Program {
uint16_t ch{ 0 };
uint16_t prog{ 0 };
uint16_t bank{ 0 };
uint16_t ch = 0;
uint16_t prog = 0;
uint16_t bank = 0;
};
using Programs = std::vector<midi::Program>;
struct MidiData {
uint16_t division{ 480 };
uint16_t division = 480;
std::map<uint32_t /*tick*/, uint32_t /*tempo*/> tempomap;
std::vector<Track> tracks;
bool isValid() const { return !tracks.empty(); }
Programs programs() const
{
Programs progs;
@ -206,6 +211,15 @@ struct MidiData {
return ss.str();
}
};
struct MidiStream {
MidiData initData;
async::Channel<MidiData> stream;
async::Channel<uint32_t> request;
bool isValid() const { return initData.isValid(); }
};
}
}
}

View file

@ -21,6 +21,7 @@
#include "ret.h"
#include "async/channel.h"
#include "async/notification.h"
namespace mu {
template<typename T>
@ -62,6 +63,14 @@ struct ValCh {
void set(const T& v) { val = v; ch.send(v); }
};
template<typename T>
struct ValNt {
T val = T();
async::Notification notification;
void set(const T& v) { val = v; notification.notify(); }
};
}
#endif // MU_FRAMEWORK_RETVAL_H

View file

@ -2294,7 +2294,7 @@ void Score::renderMidi(EventMap* events, bool metronome, bool expandRepeats, con
masterScore()->setExpandRepeats(expandRepeats);
MidiRenderer::Context ctx(synthState);
ctx.metronome = metronome;
ctx.renderHarmony = preferences.getBool(PREF_SCORE_HARMONY_PLAY);
ctx.renderHarmony = true; //! TODO preferences.getBool(PREF_SCORE_HARMONY_PLAY);
MidiRenderer(this).renderScore(events, ctx);
}

View file

@ -96,6 +96,7 @@ if (BUILD_UI_MU4)
notation_scene
common_scene
palette
playback
)
endif(BUILD_UI_MU4)

View file

@ -34,10 +34,11 @@
#include "mu4/scores/scoresmodule.h"
#include "mu4/extensions/extensionsmodule.h"
#include "mu4/domain/notation/notationdomainmodule.h"
#include "mu4/domain/importexport/importexportmodule.h"
#include "mu4/scenes/common/commonscenemodule.h"
#include "mu4/scenes/notation/notationscenemodule.h"
#include "mu4/scenes/palette/palettemodule.h"
#include "mu4/domain/importexport/importexportmodule.h"
#include "mu4/scenes/playback/playbackmodule.h"
#ifdef BUILD_TELEMETRY_MODULE
#include "framework/telemetry/telemetrysetup.h"
@ -70,6 +71,7 @@ ModulesSetup::ModulesSetup()
<< new mu::domain::notation::NotationDomainModule()
<< new mu::scene::common::CommonSceneModule()
<< new mu::scene::notation::NotationSceneModule()
<< new mu::scene::playback::PlaybackModule()
#endif
#ifdef BUILD_TELEMETRY_MODULE

View file

@ -14,6 +14,7 @@ add_subdirectory(extensions)
# Scenes common
add_subdirectory(scenes/common)
add_subdirectory(scenes/playback)
# Notation
add_subdirectory(scenes/notation)

View file

@ -9,7 +9,6 @@
<file>qml/NotationPage/NotationPage.qml</file>
<file>qml/NotationPage/NotationToolBar.qml</file>
<file>qml/Settings/SettingsPage.qml</file>
<file>qml/PlayToolBar.qml</file>
<file>qml/Window.qml</file>
<file>qml/DevTools/Launcher/SampleDialog.qml</file>
<file>qml/LaunchResolver.qml</file>

View file

@ -66,5 +66,23 @@ Rectangle {
onClicked: devtools.stopPlayerMidi()
}
}
Row {
anchors.left: parent.left
anchors.right: parent.right
height: 40
spacing: 8
FlatButton {
text: "Play Notation"
width: 120
onClicked: devtools.playNotation()
}
FlatButton {
text: "Stop Notation"
width: 120
onClicked: devtools.stopNotation()
}
}
}
}

View file

@ -1,6 +1,7 @@
import QtQuick 2.7
import MuseScore.Dock 1.0
import MuseScore.Ui 1.0
import MuseScore.Playback 1.0
import "./HomePage"
import "./NotationPage"
@ -51,7 +52,7 @@ DockWindow {
height: 32
color: dockWindow.color
PlayToolBar {
PlaybackToolBar {
color: dockWindow.color
}
}

View file

@ -40,10 +40,6 @@ public:
virtual void setCurrentNotation(const std::shared_ptr<domain::notation::INotation>& notation) = 0;
virtual std::shared_ptr<domain::notation::INotation> currentNotation() const = 0;
virtual async::Notification currentNotationChanged() const = 0;
virtual bool isPlaying() const = 0;
virtual void setIsPlaying(bool arg) = 0;
virtual async::Notification isPlayingChanged() const = 0;
};
}
}

View file

@ -65,25 +65,9 @@ mu::async::Notification GlobalContext::currentNotationChanged() const
return m_notationChanged;
}
bool GlobalContext::isPlaying() const
{
return m_isPlaying;
}
void GlobalContext::setIsPlaying(bool arg)
{
m_isPlaying = arg;
m_isPlayingChanged.notify();
}
mu::async::Notification GlobalContext::isPlayingChanged() const
{
return m_isPlayingChanged;
}
ShortcutContext GlobalContext::currentShortcutContext() const
{
if (isPlaying()) {
if (playbackController()->isPlaying()) {
return ShortcutContext::Playing;
} else if (launcher()->currentUri().val == NOTAION_PAGE) {
return ShortcutContext::NotationActive;

View file

@ -25,12 +25,14 @@
#include "shortcuts/ishortcutcontextresolver.h"
#include "modularity/ioc.h"
#include "ilauncher.h"
#include "scenes/playback/iplaybackcontroller.h"
namespace mu {
namespace context {
class GlobalContext : public IGlobalContext, public shortcuts::IShortcutContextResolver
{
INJECT(context, framework::ILauncher, launcher)
INJECT(context, scene::playback::IPlaybackController, playbackController)
public:
GlobalContext() = default;
@ -44,10 +46,6 @@ public:
std::shared_ptr<domain::notation::INotation> currentNotation() const override;
async::Notification currentNotationChanged() const override;
bool isPlaying() const override;
void setIsPlaying(bool arg) override;
async::Notification isPlayingChanged() const override;
shortcuts::ShortcutContext currentShortcutContext() const;
private:
@ -55,9 +53,6 @@ private:
std::vector<std::shared_ptr<domain::notation::INotation> > m_notations;
std::shared_ptr<domain::notation::INotation> m_notation;
async::Notification m_notationChanged;
bool m_isPlaying = false;
async::Notification m_isPlayingChanged;
};
}
}

View file

@ -32,10 +32,11 @@ set(MODULE_SRC
${CMAKE_CURRENT_LIST_DIR}/inotationselection.h
${CMAKE_CURRENT_LIST_DIR}/inotationinteraction.h
${CMAKE_CURRENT_LIST_DIR}/notationtypes.h
${CMAKE_CURRENT_LIST_DIR}/internal/notationactions.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/notationactions.h
${CMAKE_CURRENT_LIST_DIR}/inotationconfiguration.h
${CMAKE_CURRENT_LIST_DIR}/notationerrors.h
${CMAKE_CURRENT_LIST_DIR}/inotationplayback.h
${CMAKE_CURRENT_LIST_DIR}/internal/notationactions.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/notationactions.h
${CMAKE_CURRENT_LIST_DIR}/internal/notation.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/notation.h
${CMAKE_CURRENT_LIST_DIR}/internal/notationcreator.cpp
@ -59,6 +60,8 @@ set(MODULE_SRC
${CMAKE_CURRENT_LIST_DIR}/internal/notationreadersregister.h
${CMAKE_CURRENT_LIST_DIR}/internal/msczmetareader.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/msczmetareader.h
${CMAKE_CURRENT_LIST_DIR}/internal/notationplayback.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/notationplayback.h
)
set(FREETYPE_LIB )

View file

@ -29,6 +29,7 @@
#include "inotationinteraction.h"
#include "notationtypes.h"
#include "inotationreader.h"
#include "inotationplayback.h"
class QPainter;
namespace mu {
@ -53,6 +54,9 @@ public:
// input (mouse)
virtual INotationInteraction* interaction() const = 0;
// playback (midi)
virtual INotationPlayback* playback() const = 0;
// notify
virtual async::Notification notationChanged() const = 0;
};

View file

@ -0,0 +1,41 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#ifndef MU_DOMAIN_INOTATIONPLAYBACK_H
#define MU_DOMAIN_INOTATIONPLAYBACK_H
#include <QRect>
#include "audio/midi/miditypes.h"
namespace mu {
namespace domain {
namespace notation {
class INotationPlayback
{
public:
virtual ~INotationPlayback() = default;
virtual std::shared_ptr<audio::midi::MidiStream> midiStream() const = 0;
virtual QRect playbackCursorRect(float sec) const = 0;
};
}
}
}
#endif // MU_DOMAIN_INOTATIONPLAYBACK_H

View file

@ -45,7 +45,6 @@
#include "libmscore/synthesizerstate.h"
#include "../notationerrors.h"
#include "notationinteraction.h"
//#ifdef BUILD_UI_MU4
////! HACK Temporary hack to link libmscore
@ -84,6 +83,8 @@ Notation::Notation()
m_interaction->dropChanged().onNotify(this, [this]() {
notifyAboutNotationChanged();
});
m_playback = new NotationPlayback(this);
}
Notation::~Notation()
@ -183,7 +184,7 @@ mu::io::path Notation::path() const
return io::pathFromQString(m_score->fileInfo()->canonicalFilePath());
}
mu::Ret Notation::createNew(const ScoreCreateOptions &scoreOptions)
mu::Ret Notation::createNew(const ScoreCreateOptions& scoreOptions)
{
RetVal<MasterScore*> score = newScore(scoreOptions);
@ -270,7 +271,7 @@ mu::RetVal<MasterScore*> Notation::newScore(const ScoreCreateOptions& scoreOptio
auto reader = readers()->reader(syffix);
if (!reader) {
LOGE() << "not found reader for file: " << templatePath;
result.ret = make_ret(Ret::Code::InternalError);;
result.ret = make_ret(Ret::Code::InternalError);
return result;
}
@ -345,7 +346,8 @@ mu::RetVal<MasterScore*> Notation::newScore(const ScoreCreateOptions& scoreOptio
score->sigmap()->add(0, timesig);
Fraction firstMeasureTicks = pickupMeasure ? Fraction(scoreOptions.measureTimesigNumerator, scoreOptions.measureTimesigDenominator) : timesig;
Fraction firstMeasureTicks = pickupMeasure ? Fraction(scoreOptions.measureTimesigNumerator,
scoreOptions.measureTimesigDenominator) : timesig;
for (int i = 0; i < measures; ++i) {
Fraction tick = firstMeasureTicks + timesig * (i - 1);
@ -362,7 +364,8 @@ mu::RetVal<MasterScore*> Notation::newScore(const ScoreCreateOptions& scoreOptio
if (pickupMeasure && tick.isZero()) {
measure->setIrregular(true); // dont count pickup measure
measure->setTicks(Fraction(scoreOptions.measureTimesigNumerator, scoreOptions.measureTimesigDenominator));
measure->setTicks(Fraction(scoreOptions.measureTimesigNumerator,
scoreOptions.measureTimesigDenominator));
}
_score->measures()->add(measure);
@ -454,7 +457,8 @@ mu::RetVal<MasterScore*> Notation::newScore(const ScoreCreateOptions& scoreOptio
}
}
if (!scoreOptions.title.isEmpty() || !scoreOptions.subtitle.isEmpty() || !scoreOptions.composer.isEmpty() || !scoreOptions.lyricist.isEmpty()) {
if (!scoreOptions.title.isEmpty() || !scoreOptions.subtitle.isEmpty() || !scoreOptions.composer.isEmpty()
|| !scoreOptions.lyricist.isEmpty()) {
MeasureBase* measure = score->measures()->first();
if (measure->type() != ElementType::VBOX) {
MeasureBase* nm = nvb ? nvb : new VBox(score);
@ -598,6 +602,11 @@ INotationInteraction* Notation::interaction() const
return m_interaction;
}
INotationPlayback* Notation::playback() const
{
return m_playback;
}
mu::async::Notification Notation::notationChanged() const
{
return m_notationChanged;

View file

@ -27,6 +27,8 @@
#include "retval.h"
#include "igetscore.h"
#include "notationinteraction.h"
#include "notationplayback.h"
namespace Ms {
class MScore;
@ -36,7 +38,6 @@ class MasterScore;
namespace mu {
namespace domain {
namespace notation {
class NotationInteraction;
class Notation : public INotation, public IGetScore, public async::Asyncable
{
INJECT(notation, INotationReadersRegister, readers)
@ -60,6 +61,9 @@ public:
// Input (mouse)
INotationInteraction* interaction() const override;
// midi
INotationPlayback* playback() const override;
// notify
async::Notification notationChanged() const override;
@ -80,6 +84,7 @@ private:
Ms::MScore* m_scoreGlobal = nullptr;
Ms::MasterScore* m_score = nullptr;
NotationInteraction* m_interaction = nullptr;
NotationPlayback* m_playback = nullptr;
async::Notification m_notationChanged;
};
}

View file

@ -0,0 +1,306 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "notationplayback.h"
#include <cmath>
#include "log.h"
#include "libmscore/score.h"
#include "libmscore/tempo.h"
#include "libmscore/part.h"
#include "libmscore/instrument.h"
#include "libmscore/repeatlist.h"
#include "libmscore/measure.h"
#include "libmscore/segment.h"
#include "libmscore/system.h"
#include "libmscore/sym.h"
#include "libmscore/page.h"
#include "libmscore/staff.h"
#include "audio/midi/event.h" //! TODO Remove me
using namespace mu::domain::notation;
using namespace mu::audio::midi;
static EventType convertType(int type)
{
switch (type) {
case Ms::ME_NOTEON: return ME_NOTEON;
case Ms::ME_NOTEOFF: return ME_NOTEOFF;
case Ms::ME_CONTROLLER: return ME_CONTROLLER;
case Ms::ME_PITCHBEND: return ME_PITCHBEND;
case Ms::ME_META: return ME_META;
case Ms::ME_TICK1: return ME_INVALID;
case Ms::ME_TICK2: return ME_INVALID;
default: {
LOGE() << "unknown midi type: " << type;
}
}
return ME_INVALID;
}
NotationPlayback::NotationPlayback(IGetScore* getScore)
: m_getScore(getScore)
{
}
std::shared_ptr<MidiStream> NotationPlayback::midiStream() const
{
Ms::Score* score = m_getScore->score();
if (!score) {
return nullptr;
}
std::shared_ptr<MidiStream> stream = std::make_shared<MidiStream>();
makeInitData(stream->initData, score);
stream->request.onReceive(this, [stream](uint32_t tick) {
UNUSED(tick);
stream->stream.close();
});
return stream;
}
void NotationPlayback::makeInitData(MidiData& data, Ms::Score* score) const
{
MetaInfo meta;
makeMetaInfo(meta, score);
data.division = Ms::MScore::division;
data.tracks.resize(meta.tracksCount);
Ms::EventMap eventMap;
makeEventMap(eventMap, score);
fillTracks(data.tracks, eventMap, meta);
fillTempoMap(data.tempomap, score);
//! TODO Not implemented, left not to be forgotten
//fillMetronome(data.metronome, score, midiSpec);
}
void NotationPlayback::makeEventMap(Ms::EventMap& eventMap, Ms::Score* score) const
{
score->masterScore()->setExpandRepeats(true);
score->renderMidi(&eventMap, Ms::SynthesizerState());
}
void NotationPlayback::makeMetaInfo(MetaInfo& meta, const Ms::Score* score) const
{
auto parts = score->parts();
meta.tracksCount = parts.size();
auto bankForInstrument = [](const Ms::Instrument* instr) {
//! NOTE Temporary solution
if (instr->useDrumset()) {
return 128;
}
return 0;
};
for (int pi = 0; pi < parts.size(); ++pi) {
const Ms::Part* part = parts.at(pi);
const Ms::InstrumentList* instList = part->instruments();
for (auto it = instList->cbegin(); it != instList->cend(); ++it) {
const Ms::Instrument* instrument = it->second;
uint16_t bank = bankForInstrument(instrument);
for (const Ms::Channel* ch : instrument->channel()) {
ChanInfo chi;
chi.trackIdx = pi;
chi.bank = bank;
chi.program = ch->program();
meta.channels.insert({ ch->channel(), chi });
}
}
}
}
void NotationPlayback::fillTracks(std::vector<audio::midi::Track>& tracks, const Ms::EventMap& eventMap,
const MetaInfo& meta) const
{
uint16_t ch_num = 1; //! NOTE channel 0 reserved for metronome
auto findOrAddChannel = [&ch_num](audio::midi::Track& t, const ChanInfo& chi) -> audio::midi::Channel& {
for (auto& ch : t.channels) {
if (ch.program == chi.program && ch.bank == chi.bank) {
return ch;
}
}
audio::midi::Channel ch;
ch.num = ch_num;
ch.program = chi.program;
ch.bank = chi.bank;
++ch_num;
t.channels.push_back(std::move(ch));
return t.channels.back();
};
for (const auto& evp : eventMap) {
int tick = evp.first;
const Ms::NPlayEvent ev = evp.second;
if (ev.type() == Ms::ME_CONTROLLER && ev.controller() == 2) {
//! TODO Understand why these events
continue;
}
auto foundIt = meta.channels.find(ev.channel());
if (foundIt == meta.channels.end()) {
Q_ASSERT(foundIt != meta.channels.end());
continue;
}
const ChanInfo& chi = foundIt->second;
audio::midi::Track& track = tracks.at(chi.trackIdx);
audio::midi::Channel& ch = findOrAddChannel(track, chi);
audio::midi::EventType etype = convertType(ev.type());
if (audio::midi::ME_INVALID == etype) {
continue;
} else if (audio::midi::ME_META == etype) {
continue;
} else {
audio::midi::Event e
{ static_cast<uint32_t>(tick),
etype,
ev.dataA(), ev.dataB()
};
ch.events.push_back(std::move(e));
}
}
}
void NotationPlayback::fillTempoMap(std::map<uint32_t, uint32_t>& tempos, const Ms::Score* score) const
{
Ms::TempoMap* tempomap = score->tempomap();
qreal relTempo = tempomap->relTempo();
for (const Ms::RepeatSegment* rs : score->repeatList()) {
int startTick = rs->tick, endTick = startTick + rs->len();
int tickOffset = rs->utick - rs->tick;
auto se = tempomap->lower_bound(startTick);
auto ee = tempomap->lower_bound(endTick);
for (auto it = se; it != ee; ++it) {
//
// compute midi tempo: microseconds / quarter note
//
uint32_t tempo = (uint32_t)lrint((1.0 / (it->second.tempo * relTempo)) * 1000000.0);
tempos.insert({ it->first + tickOffset, tempo });
}
}
}
//! NOTE Copied from ScoreView::moveCursor(const Fraction& tick)
QRect NotationPlayback::playbackCursorRect(float sec) const
{
using namespace Ms;
Score* score = m_getScore->score();
if (!score) {
return QRect();
}
int _tick = score->utime2utick(sec);
Fraction tick = Fraction::fromTicks(_tick);
Measure* measure = score->tick2measureMM(tick);
if (!measure) {
return QRect();
}
System* system = measure->system();
if (!system) {
return QRect();
}
qreal x = 0.0;
Segment* s = nullptr;
for (s = measure->first(Ms::SegmentType::ChordRest); s;) {
Fraction t1 = s->tick();
int x1 = s->canvasPos().x();
qreal x2;
Fraction t2;
Segment* ns = s->next(SegmentType::ChordRest);
while (ns && !ns->visible()) {
ns = ns->next(SegmentType::ChordRest);
}
if (ns) {
t2 = ns->tick();
x2 = ns->canvasPos().x();
} else {
t2 = measure->endTick();
// measure->width is not good enough because of courtesy keysig, timesig
Segment* seg = measure->findSegment(SegmentType::EndBarLine, measure->tick() + measure->ticks());
if (seg) {
x2 = seg->canvasPos().x();
} else {
x2 = measure->canvasPos().x() + measure->width(); //safety, should not happen
}
}
if (tick >= t1 && tick < t2) {
Fraction dt = t2 - t1;
qreal dx = x2 - x1;
x = x1 + dx * (tick - t1).ticks() / dt.ticks();
break;
}
s = ns;
}
if (!s) {
return QRect();
}
double y = system->staffYpage(0) + system->page()->pos().y();
double _spatium = score->spatium();
qreal mag = _spatium / SPATIUM20;
double w = _spatium * 2.0 + score->scoreFont()->width(SymId::noteheadBlack, mag);
double h = 6 * _spatium;
//
// set cursor height for whole system
//
double y2 = 0.0;
for (int i = 0; i < score->nstaves(); ++i) {
SysStaff* ss = system->staff(i);
if (!ss->show() || !score->staff(i)->show()) {
continue;
}
y2 = ss->bbox().bottom();
}
h += y2;
x -= _spatium;
y -= 3 * _spatium;
return QRect(x, y, w, h);
}

View file

@ -0,0 +1,67 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#ifndef MU_DOMAIN_NOTATIONPLAYBACK_H
#define MU_DOMAIN_NOTATIONPLAYBACK_H
#include "../inotationplayback.h"
#include "igetscore.h"
#include "async/asyncable.h"
namespace Ms {
class EventMap;
}
namespace mu {
namespace domain {
namespace notation {
class NotationPlayback : public INotationPlayback, public async::Asyncable
{
public:
NotationPlayback(IGetScore* getScore);
std::shared_ptr<audio::midi::MidiStream> midiStream() const override;
QRect playbackCursorRect(float sec) const override;
private:
struct ChanInfo {
size_t trackIdx = 0;
uint16_t bank = 0;
uint16_t program = 0;
};
struct MetaInfo {
size_t tracksCount = 0;
std::map<uint16_t, ChanInfo> channels;
};
void makeInitData(audio::midi::MidiData& data, Ms::Score* score) const;
void makeEventMap(Ms::EventMap& eventMap, Ms::Score* score) const;
void makeMetaInfo(MetaInfo& meta, const Ms::Score* score) const;
void fillTracks(std::vector<audio::midi::Track>& tracks, const Ms::EventMap& eventMap, const MetaInfo& meta) const;
void fillTempoMap(std::map<uint32_t /*tick*/, uint32_t /*tempo*/>& tempos, const Ms::Score* score) const;
IGetScore* m_getScore = nullptr;
};
}
}
}
#endif // MU_DOMAIN_NOTATIONPLAYBACK_H

View file

@ -22,8 +22,6 @@ set(MODULE common_scene)
set(MODULE_SRC
${CMAKE_CURRENT_LIST_DIR}/commonscenemodule.cpp
${CMAKE_CURRENT_LIST_DIR}/commonscenemodule.h
${CMAKE_CURRENT_LIST_DIR}/playtoolbarmodel.cpp
${CMAKE_CURRENT_LIST_DIR}/playtoolbarmodel.h
${CMAKE_CURRENT_LIST_DIR}/exampleview.cpp
${CMAKE_CURRENT_LIST_DIR}/exampleview.h
${CMAKE_CURRENT_LIST_DIR}/commonscenetypes.h

View file

@ -21,8 +21,6 @@
#include <QtQml>
#include "modularity/ioc.h"
#include "playtoolbarmodel.h"
using namespace mu::scene::common;
std::string CommonSceneModule::moduleName() const
@ -44,5 +42,4 @@ void CommonSceneModule::registerResources()
void CommonSceneModule::registerUiTypes()
{
qmlRegisterType<PlayToolBarModel>("MuseScore.CommonScene", 1, 0, "PlayToolBarModel");
}

View file

@ -20,22 +20,26 @@
set(MODULE notation_scene)
set(MODULE_QRC
${CMAKE_CURRENT_LIST_DIR}/view/notation_view.qrc
${CMAKE_CURRENT_LIST_DIR}/notation_view.qrc
)
set(MODULE_QML_IMPORT ${CMAKE_CURRENT_LIST_DIR}/view/qml)
include(${CMAKE_CURRENT_LIST_DIR}/view/notationview.cmake)
set(MODULE_QML_IMPORT ${CMAKE_CURRENT_LIST_DIR}/qml)
set(MODULE_SRC
${NOTATIONVIEW_SRC}
${CMAKE_CURRENT_LIST_DIR}/notationscenemodule.cpp
${CMAKE_CURRENT_LIST_DIR}/notationscenemodule.h
${CMAKE_CURRENT_LIST_DIR}/toolbar/notationtoolbarmodel.cpp
${CMAKE_CURRENT_LIST_DIR}/toolbar/notationtoolbarmodel.h
${CMAKE_CURRENT_LIST_DIR}/iscenenotationconfiguration.h
${CMAKE_CURRENT_LIST_DIR}/internal/scenenotationconfiguration.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/scenenotationconfiguration.h
${CMAKE_CURRENT_LIST_DIR}/view/notationpaintview.cpp
${CMAKE_CURRENT_LIST_DIR}/view/notationpaintview.h
${CMAKE_CURRENT_LIST_DIR}/view/notationviewinputcontroller.cpp
${CMAKE_CURRENT_LIST_DIR}/view/notationviewinputcontroller.h
${CMAKE_CURRENT_LIST_DIR}/view/notationtoolbarmodel.cpp
${CMAKE_CURRENT_LIST_DIR}/view/notationtoolbarmodel.h
${CMAKE_CURRENT_LIST_DIR}/view/playbackcursor.h
${CMAKE_CURRENT_LIST_DIR}/view/playbackcursor.cpp
)
set(MODULE_LINK

View file

@ -86,6 +86,14 @@ Channel<QColor> SceneNotationConfiguration::foregroundColorChanged() const
return m_foregroundColorChanged;
}
QColor SceneNotationConfiguration::playbackCursorColor() const
{
//! TODO Figure out what color to use
QColor c("#ff0000");
c.setAlpha(50);
return c;
}
int SceneNotationConfiguration::selectionProximity() const
{
return settings()->value(SELECTION_PROXIMITY).toInt();

View file

@ -37,6 +37,8 @@ public:
QColor defaultForegroundColor() const override;
async::Channel<QColor> foregroundColorChanged() const override;
QColor playbackCursorColor() const override;
int selectionProximity() const override;
private:

View file

@ -40,6 +40,8 @@ public:
virtual QColor foregroundColor() const = 0;
virtual async::Channel<QColor> foregroundColorChanged() const = 0;
virtual QColor playbackCursorColor() const = 0;
virtual int selectionProximity() const = 0;
};
}

View file

@ -21,7 +21,7 @@
#include "modularity/ioc.h"
#include "internal/scenenotationconfiguration.h"
#include "view/notationpaintview.h"
#include "toolbar/notationtoolbarmodel.h"
#include "view/notationtoolbarmodel.h"
using namespace mu::scene::notation;

View file

@ -30,6 +30,8 @@ using namespace mu::domain::notation;
static constexpr int PREF_UI_CANVAS_MISC_SELECTIONPROXIMITY = 6;
static constexpr int PLAYBACK_UPDATE_INTERVAL_MSEC = 20;
NotationPaintView::NotationPaintView()
: QQuickPaintedItem()
{
@ -37,17 +39,28 @@ NotationPaintView::NotationPaintView()
setFlag(ItemAcceptsDrops, true);
setAcceptedMouseButtons(Qt::AllButtons);
// view
//! TODO
double mag = 0.267;//preferences.getDouble(PREF_SCORE_MAGNIFICATION) * (mscore->physicalDotsPerInch() / DPI);
m_matrix = QTransform::fromScale(mag, mag);
m_inputController = new NotationViewInputController(this);
connect(this, &QQuickPaintedItem::widthChanged, this, &NotationPaintView::onViewSizeChanged);
connect(this, &QQuickPaintedItem::heightChanged, this, &NotationPaintView::onViewSizeChanged);
dispatcher()->reg(this, "copy", [this](const actions::ActionName&) {
LOGI() << "NotationPaintView copy";
// input
m_inputController = new NotationViewInputController(this);
// playback
m_playbackCursor = new PlaybackCursor();
m_playbackCursor->setColor(configuration()->playbackCursorColor());
m_playbackCursor->setVisible(false);
m_playbackUpdateTimer.setInterval(PLAYBACK_UPDATE_INTERVAL_MSEC);
connect(&m_playbackUpdateTimer, &QTimer::timeout, [this]() {
updatePlaybackCursor();
});
playbackController()->isPlayingChanged().onNotify(this, [this]() {
onPlayingChanged();
});
// configuration
@ -62,6 +75,17 @@ NotationPaintView::NotationPaintView()
globalContext()->currentNotationChanged().onNotify(this, [this]() {
onCurrentNotationChanged();
});
// test
dispatcher()->reg(this, "copy", [this](const actions::ActionName&) {
LOGI() << "NotationPaintView copy";
});
}
NotationPaintView::~NotationPaintView()
{
delete m_inputController;
delete m_playbackCursor;
}
bool NotationPaintView::canReceiveAction(const actions::ActionName& action) const
@ -156,6 +180,8 @@ void NotationPaintView::paint(QPainter* p)
if (m_notation) {
m_notation->paint(p, rect);
m_playbackCursor->paint(p);
} else {
p->drawText(10, 10, "no notation");
}
@ -391,3 +417,24 @@ qreal NotationPaintView::scale() const
{
return QQuickPaintedItem::scale();
}
void NotationPaintView::onPlayingChanged()
{
bool isPlaying = playbackController()->isPlaying();
m_playbackCursor->setVisible(isPlaying);
if (isPlaying) {
m_playbackUpdateTimer.start();
updatePlaybackCursor();
} else {
m_playbackUpdateTimer.stop();
}
}
void NotationPaintView::updatePlaybackCursor()
{
float sec = playbackController()->playbackPosition();
QRect rec = m_notation->playback()->playbackCursorRect(sec);
m_playbackCursor->move(rec);
update(); //! TODO set rect to optimization
}

View file

@ -22,6 +22,7 @@
#include <QObject>
#include <QQuickPaintedItem>
#include <QTransform>
#include <QTimer>
#include "modularity/ioc.h"
#include "../iscenenotationconfiguration.h"
@ -30,8 +31,10 @@
#include "actions/actionable.h"
#include "context/iglobalcontext.h"
#include "async/asyncable.h"
#include "scenes/playback/iplaybackcontroller.h"
#include "notationviewinputcontroller.h"
#include "playbackcursor.h"
namespace mu {
namespace scene {
@ -45,9 +48,11 @@ class NotationPaintView : public QQuickPaintedItem, public IControlledView, publ
INJECT(notation_scene, ISceneNotationConfiguration, configuration)
INJECT(notation_scene, actions::IActionsDispatcher, dispatcher)
INJECT(notation_scene, context::IGlobalContext, globalContext)
INJECT(notation_scene, playback::IPlaybackController, playbackController)
public:
NotationPaintView();
~NotationPaintView();
// IControlledView
qreal width() const override;
@ -111,10 +116,15 @@ private:
void onInputStateChanged();
void onSelectionChanged();
void onPlayingChanged();
void updatePlaybackCursor();
QColor m_backgroundColor;
std::shared_ptr<domain::notation::INotation> m_notation;
QTransform m_matrix;
NotationViewInputController* m_inputController = nullptr;
PlaybackCursor* m_playbackCursor = nullptr;
QTimer m_playbackUpdateTimer;
};
}
}

View file

@ -81,7 +81,7 @@ void NotationToolBarModel::load()
onNotationChanged();
});
globalContext()->isPlayingChanged().onNotify(this, [this]() {
playbackController()->isPlayingChanged().onNotify(this, [this]() {
updateState();
});
}
@ -120,7 +120,7 @@ void NotationToolBarModel::onNotationChanged()
void NotationToolBarModel::updateState()
{
std::shared_ptr<INotation> notation = globalContext()->currentNotation();
bool isPlaying = globalContext()->isPlaying();
bool isPlaying = playbackController()->isPlaying();
if (!notation || isPlaying) {
for (ActionItem& item : m_items) {
item.enabled = false;

View file

@ -25,6 +25,7 @@
#include "actions/iactionsregister.h"
#include "actions/iactionsdispatcher.h"
#include "context/iglobalcontext.h"
#include "scenes/playback/iplaybackcontroller.h"
#include "async/asyncable.h"
namespace mu {
@ -36,6 +37,7 @@ class NotationToolBarModel : public QAbstractListModel, public async::Asyncable
INJECT(notation_scene, actions::IActionsRegister, aregister)
INJECT(notation_scene, actions::IActionsDispatcher, dispatcher)
INJECT(notation_scene, context::IGlobalContext, globalContext)
INJECT(notation_scene, playback::IPlaybackController, playbackController)
public:
explicit NotationToolBarModel(QObject* parent = nullptr);

View file

@ -0,0 +1,52 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "playbackcursor.h"
#include <QPainter>
using namespace mu::scene::notation;
void PlaybackCursor::paint(QPainter* painter)
{
if (!m_visible) {
return;
}
painter->fillRect(m_rect, m_color);
}
void PlaybackCursor::move(const QRect& rect)
{
m_rect = rect;
}
const QRect& PlaybackCursor::rect() const
{
return m_rect;
}
void PlaybackCursor::setVisible(bool arg)
{
m_visible = arg;
}
void PlaybackCursor::setColor(const QColor& c)
{
m_color = c;
}

View file

@ -0,0 +1,52 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#ifndef MU_NOTATIONSCENE_PLAYBACKCURSOR_H
#define MU_NOTATIONSCENE_PLAYBACKCURSOR_H
#include <QRect>
#include <QColor>
class QPainter;
namespace mu {
namespace scene {
namespace notation {
class PlaybackCursor
{
public:
PlaybackCursor() = default;
void paint(QPainter* painter);
void move(const QRect& rect);
const QRect& rect() const;
void setVisible(bool arg);
void setColor(const QColor& c);
private:
bool m_visible = false;
QRect m_rect;
QColor m_color;
};
}
}
}
#endif // MU_NOTATIONSCENE_PLAYBACKCURSOR_H

View file

@ -0,0 +1,40 @@
#=============================================================================
# MuseScore
# Music Composition & Notation
#
# Copyright (C) 2020 MuseScore BVBA and others
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#=============================================================================
set(MODULE playback)
set(MODULE_QRC
${CMAKE_CURRENT_LIST_DIR}/playback.qrc
)
set(MODULE_QML_IMPORT ${CMAKE_CURRENT_LIST_DIR}/qml)
set(MODULE_SRC
${CMAKE_CURRENT_LIST_DIR}/playbackmodule.cpp
${CMAKE_CURRENT_LIST_DIR}/playbackmodule.h
${CMAKE_CURRENT_LIST_DIR}/iplaybackcontroller.h
${CMAKE_CURRENT_LIST_DIR}/internal/playbackcontroller.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/playbackcontroller.h
${CMAKE_CURRENT_LIST_DIR}/internal/playbackactions.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/playbackactions.h
${CMAKE_CURRENT_LIST_DIR}/view/playbacktoolbarmodel.cpp
${CMAKE_CURRENT_LIST_DIR}/view/playbacktoolbarmodel.h
)
include(${PROJECT_SOURCE_DIR}/build/module.cmake)

View file

@ -0,0 +1,44 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "playbackactions.h"
using namespace mu::scene::playback;
using namespace mu::actions;
using namespace mu::shortcuts;
//! NOTE Only actions processed by notation
const std::vector<Action> PlaybackActions::m_actions = {
Action("play",
QT_TRANSLATE_NOOP("action", "Play"),
ShortcutContext::Any
),
};
const Action& PlaybackActions::action(const ActionName& name) const
{
for (const Action& a : m_actions) {
if (a.name == name) {
return a;
}
}
static Action null;
return null;
}

View file

@ -0,0 +1,42 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#ifndef MU_PLAYBACK_PLAYBACKACTIONS_H
#define MU_PLAYBACK_PLAYBACKACTIONS_H
#include <vector>
#include "actions/imoduleactions.h"
namespace mu {
namespace scene {
namespace playback {
class PlaybackActions : public actions::IModuleActions
{
public:
const actions::Action& action(const actions::ActionName& name) const override;
private:
static const std::vector<actions::Action> m_actions;
};
}
}
}
#endif // MU_PLAYBACK_PLAYBACKACTIONS_H

View file

@ -0,0 +1,106 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "playbackcontroller.h"
#include "log.h"
using namespace mu;
using namespace mu::scene::playback;
void PlaybackController::init()
{
dispatcher()->reg(this, "play", this, &PlaybackController::togglePlay);
updatePlayAllowance();
globalContext()->currentNotationChanged().onNotify(this, [this]() {
updatePlayAllowance();
});
}
bool PlaybackController::isPlayAllowed() const
{
return m_isPlayAllowed.val;
}
async::Notification PlaybackController::isPlayAllowedChanged() const
{
return m_isPlayAllowed.notification;
}
bool PlaybackController::isPlaying() const
{
return m_isPlaying.val;
}
async::Notification PlaybackController::isPlayingChanged() const
{
return m_isPlaying.notification;
}
float PlaybackController::playbackPosition() const
{
return audioPlayer()->playbackPosition();
}
void PlaybackController::updatePlayAllowance()
{
auto notation = globalContext()->currentNotation();
if (notation) {
m_isPlayAllowed.set(true);
} else {
m_isPlayAllowed.set(false);
}
}
void PlaybackController::togglePlay()
{
if (!isPlayAllowed()) {
LOGW() << "playback not allowed";
return;
}
if (isPlaying()) {
pause();
} else {
play();
}
}
void PlaybackController::play()
{
auto notation = globalContext()->currentNotation();
IF_ASSERT_FAILED(notation) {
return;
}
auto stream = notation->playback()->midiStream();
audioPlayer()->setMidiStream(stream);
bool ok = audioPlayer()->play();
if (!ok) {
LOGE() << "failed play";
return;
}
m_isPlaying.set(true);
}
void PlaybackController::pause()
{
audioPlayer()->stop();
m_isPlaying.set(false);
}

View file

@ -0,0 +1,66 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#ifndef MU_PLAYBACK_PLAYBACKCONTROLLER_H
#define MU_PLAYBACK_PLAYBACKCONTROLLER_H
#include "../iplaybackcontroller.h"
#include "modularity/ioc.h"
#include "actions/iactionsdispatcher.h"
#include "actions/actionable.h"
#include "context/iglobalcontext.h"
#include "audio/engine/iaudioplayer.h"
#include "retval.h"
#include "async/asyncable.h"
namespace mu {
namespace scene {
namespace playback {
class PlaybackController : public IPlaybackController, public actions::Actionable, public async::Asyncable
{
INJECT(playback, actions::IActionsDispatcher, dispatcher)
INJECT(playback, context::IGlobalContext, globalContext)
INJECT(audio, audio::IAudioPlayer, audioPlayer)
public:
void init();
bool isPlayAllowed() const override;
async::Notification isPlayAllowedChanged() const override;
bool isPlaying() const override;
async::Notification isPlayingChanged() const override;
float playbackPosition() const override;
private:
void updatePlayAllowance();
void togglePlay();
void play();
void pause();
ValNt<bool> m_isPlayAllowed;
ValNt<bool> m_isPlaying;
};
}
}
}
#endif // MU_PLAYBACK_PLAYBACKCONTROLLER_H

View file

@ -0,0 +1,47 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#ifndef MU_PLAYBACK_IPLAYBACKCONTROLLER_H
#define MU_PLAYBACK_IPLAYBACKCONTROLLER_H
#include "modularity/imoduleexport.h"
#include "async/notification.h"
namespace mu {
namespace scene {
namespace playback {
class IPlaybackController : MODULE_EXPORT_INTERFACE
{
INTERFACE_ID(IPlaybackController)
public:
virtual ~IPlaybackController() = default;
virtual bool isPlayAllowed() const = 0;
virtual async::Notification isPlayAllowedChanged() const = 0;
virtual bool isPlaying() const = 0;
virtual async::Notification isPlayingChanged() const = 0;
virtual float playbackPosition() const = 0;
};
}
}
}
#endif // MU_PLAYBACK_IPLAYBACKCONTROLLER_H

View file

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/">
<file>qml/MuseScore/Playback/qmldir</file>
<file>qml/MuseScore/Playback/PlaybackToolBar.qml</file>
</qresource>
</RCC>

View file

@ -0,0 +1,71 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "playbackmodule.h"
#include <QQmlEngine>
#include "modularity/ioc.h"
#include "actions/iactionsregister.h"
#include "internal/playbackcontroller.h"
#include "internal/playbackactions.h"
#include "view/playbacktoolbarmodel.h"
using namespace mu::scene::playback;
static std::shared_ptr<PlaybackController> pcontroller = std::make_shared<PlaybackController>();
static void playback_init_qrc()
{
Q_INIT_RESOURCE(playback);
}
std::string PlaybackModule::moduleName() const
{
return "playback";
}
void PlaybackModule::registerExports()
{
framework::ioc()->registerExport<IPlaybackController>(moduleName(), pcontroller);
}
void PlaybackModule::resolveImports()
{
auto ar = framework::ioc()->resolve<actions::IActionsRegister>(moduleName());
if (ar) {
ar->reg(std::make_shared<PlaybackActions>());
}
}
void PlaybackModule::registerResources()
{
playback_init_qrc();
}
void PlaybackModule::registerUiTypes()
{
qmlRegisterType<PlaybackToolBarModel>("MuseScore.Playback", 1, 0, "PlaybackToolBarModel");
}
void PlaybackModule::onInit()
{
pcontroller->init();
}

View file

@ -0,0 +1,42 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#ifndef MU_PLAYBACK_PLAYBACKMODULE_H
#define MU_PLAYBACK_PLAYBACKMODULE_H
#include "modularity/imodulesetup.h"
namespace mu {
namespace scene {
namespace playback {
class PlaybackModule : public framework::IModuleSetup
{
public:
std::string moduleName() const override;
void registerExports() override;
void resolveImports() override;
void registerResources() override;
void registerUiTypes() override;
void onInit() override;
};
}
}
}
#endif // MU_PLAYBACK_PLAYBACKMODULE_H

View file

@ -1,6 +1,6 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import MuseScore.CommonScene 1.0
import MuseScore.Playback 1.0
Rectangle {
@ -43,7 +43,7 @@ Rectangle {
}
}
PlayToolBarModel {
PlaybackToolBarModel {
id: toolModel
}

View file

@ -0,0 +1,2 @@
module MuseScore.Playback
PlaybackToolBar 1.0 PlaybackToolBar.qml

View file

@ -16,20 +16,19 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "playtoolbarmodel.h"
#include "playbacktoolbarmodel.h"
#include "log.h"
using namespace mu::scene::common;
using namespace mu::scene::playback;
using namespace mu::actions;
using namespace mu::domain::notation;
PlayToolBarModel::PlayToolBarModel(QObject* parent)
PlaybackToolBarModel::PlaybackToolBarModel(QObject* parent)
: QAbstractListModel(parent)
{
}
QVariant PlayToolBarModel::data(const QModelIndex& index, int role) const
QVariant PlaybackToolBarModel::data(const QModelIndex& index, int role) const
{
const ActionItem& item = m_items.at(index.row());
switch (role) {
@ -41,12 +40,12 @@ QVariant PlayToolBarModel::data(const QModelIndex& index, int role) const
return QVariant();
}
int PlayToolBarModel::rowCount(const QModelIndex&) const
int PlaybackToolBarModel::rowCount(const QModelIndex&) const
{
return m_items.count();
}
QHash<int,QByteArray> PlayToolBarModel::roleNames() const
QHash<int,QByteArray> PlaybackToolBarModel::roleNames() const
{
static const QHash<int, QByteArray> roles = {
{ NameRole, "nameRole" },
@ -57,7 +56,7 @@ QHash<int,QByteArray> PlayToolBarModel::roleNames() const
return roles;
}
void PlayToolBarModel::load()
void PlaybackToolBarModel::load()
{
auto makeItem = [](const Action& action) {
ActionItem item;
@ -67,35 +66,33 @@ void PlayToolBarModel::load()
beginResetModel();
m_items << makeItem(Action("domain/audio/play", "Play", shortcuts::ShortcutContext::Any));
m_items << makeItem(Action("play", "Play", shortcuts::ShortcutContext::Any));
endResetModel();
updateState();
globalContext()->currentNotationChanged().onNotify(this, [this]() {
playbackController()->isPlayAllowedChanged().onNotify(this, [this]() {
updateState();
});
globalContext()->isPlayingChanged().onNotify(this, [this]() {
playbackController()->isPlayingChanged().onNotify(this, [this]() {
updateState();
});
}
void PlayToolBarModel::click(const QString& action)
void PlaybackToolBarModel::click(const QString& action)
{
LOGI() << action;
//! NOTE This is fake play, added for demonstration
if (action == "domain/audio/play") {
globalContext()->setIsPlaying(!globalContext()->isPlaying());
}
dispatcher()->dispatch(actions::namefromQString(action));
}
void PlayToolBarModel::updateState()
void PlaybackToolBarModel::updateState()
{
std::shared_ptr<INotation> notation = globalContext()->currentNotation();
bool isPlayAllowed = playbackController()->isPlayAllowed();
if (!notation) {
if (!isPlayAllowed) {
for (ActionItem& item : m_items) {
item.enabled = false;
item.checked = false;
@ -106,13 +103,13 @@ void PlayToolBarModel::updateState()
item.checked = false;
}
item("domain/audio/play").checked = globalContext()->isPlaying();
item("play").checked = playbackController()->isPlaying();
}
emit dataChanged(index(0), index(rowCount() - 1));
}
PlayToolBarModel::ActionItem& PlayToolBarModel::item(const actions::ActionName& name)
PlaybackToolBarModel::ActionItem& PlaybackToolBarModel::item(const actions::ActionName& name)
{
for (ActionItem& item : m_items) {
if (item.action.name == name) {

View file

@ -16,26 +16,28 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#ifndef MU_SCENECOMMON_PLAYTOOLBARMODEL_H
#define MU_SCENECOMMON_PLAYTOOLBARMODEL_H
#ifndef MU_PLAYBACK_PLAYBACKTOOLBARMODEL_H
#define MU_PLAYBACK_PLAYBACKTOOLBARMODEL_H
#include <QAbstractListModel>
#include "modularity/ioc.h"
#include "context/iglobalcontext.h"
#include "iplaybackcontroller.h"
#include "actions/iactionsdispatcher.h"
#include "async/asyncable.h"
#include "actions/actiontypes.h"
namespace mu {
namespace scene {
namespace common {
class PlayToolBarModel : public QAbstractListModel, public async::Asyncable
namespace playback {
class PlaybackToolBarModel : public QAbstractListModel, public async::Asyncable
{
Q_OBJECT
INJECT(notation_scene, context::IGlobalContext, globalContext)
INJECT(playback, actions::IActionsDispatcher, dispatcher)
INJECT(playback, IPlaybackController, playbackController)
public:
explicit PlayToolBarModel(QObject* parent = nullptr);
explicit PlaybackToolBarModel(QObject* parent = nullptr);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role) const override;
@ -68,4 +70,4 @@ private:
}
}
#endif // MU_SCENECOMMON_PLAYTOOLBARMODEL_H
#endif // MU_PLAYBACK_PLAYBACKTOOLBARMODEL_H

View file

@ -25,7 +25,8 @@ void AbstractInvoker::invokeMethod(int callKey, const NotifyData& data)
onInvoke(callKey, data);
} else {
// todo
assert(std::this_thread::get_id() == m_threadID);
// assert(std::this_thread::get_id() == m_threadID);
onInvoke(callKey, data); //! TODO Temporary solution
}
}