Merge pull request #6354 from igorkorsukov/mu4/audio_midi_events
[MU4] Audio midi playback
This commit is contained in:
commit
7976244cd4
69 changed files with 1519 additions and 326 deletions
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -34,8 +34,6 @@ public:
|
|||
~SineSource() = default;
|
||||
|
||||
void setSampleRate(float samplerate) override;
|
||||
void sync(float sec) override;
|
||||
|
||||
SoLoud::AudioSource* source() override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(); }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ if (BUILD_UI_MU4)
|
|||
notation_scene
|
||||
common_scene
|
||||
palette
|
||||
playback
|
||||
)
|
||||
endif(BUILD_UI_MU4)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -14,6 +14,7 @@ add_subdirectory(extensions)
|
|||
|
||||
# Scenes common
|
||||
add_subdirectory(scenes/common)
|
||||
add_subdirectory(scenes/playback)
|
||||
|
||||
# Notation
|
||||
add_subdirectory(scenes/notation)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
41
mu4/domain/notation/inotationplayback.h
Normal file
41
mu4/domain/notation/inotationplayback.h
Normal 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
|
|
@ -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); // don’t 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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
306
mu4/domain/notation/internal/notationplayback.cpp
Normal file
306
mu4/domain/notation/internal/notationplayback.cpp
Normal 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);
|
||||
}
|
67
mu4/domain/notation/internal/notationplayback.h
Normal file
67
mu4/domain/notation/internal/notationplayback.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -37,6 +37,8 @@ public:
|
|||
QColor defaultForegroundColor() const override;
|
||||
async::Channel<QColor> foregroundColorChanged() const override;
|
||||
|
||||
QColor playbackCursorColor() const override;
|
||||
|
||||
int selectionProximity() const override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
52
mu4/scenes/notation/view/playbackcursor.cpp
Normal file
52
mu4/scenes/notation/view/playbackcursor.cpp
Normal 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;
|
||||
}
|
52
mu4/scenes/notation/view/playbackcursor.h
Normal file
52
mu4/scenes/notation/view/playbackcursor.h
Normal 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
|
40
mu4/scenes/playback/CMakeLists.txt
Normal file
40
mu4/scenes/playback/CMakeLists.txt
Normal 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)
|
44
mu4/scenes/playback/internal/playbackactions.cpp
Normal file
44
mu4/scenes/playback/internal/playbackactions.cpp
Normal 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;
|
||||
}
|
42
mu4/scenes/playback/internal/playbackactions.h
Normal file
42
mu4/scenes/playback/internal/playbackactions.h
Normal 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
|
106
mu4/scenes/playback/internal/playbackcontroller.cpp
Normal file
106
mu4/scenes/playback/internal/playbackcontroller.cpp
Normal 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);
|
||||
}
|
66
mu4/scenes/playback/internal/playbackcontroller.h
Normal file
66
mu4/scenes/playback/internal/playbackcontroller.h
Normal 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
|
47
mu4/scenes/playback/iplaybackcontroller.h
Normal file
47
mu4/scenes/playback/iplaybackcontroller.h
Normal 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
|
6
mu4/scenes/playback/playback.qrc
Normal file
6
mu4/scenes/playback/playback.qrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>qml/MuseScore/Playback/qmldir</file>
|
||||
<file>qml/MuseScore/Playback/PlaybackToolBar.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
71
mu4/scenes/playback/playbackmodule.cpp
Normal file
71
mu4/scenes/playback/playbackmodule.cpp
Normal 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();
|
||||
}
|
42
mu4/scenes/playback/playbackmodule.h
Normal file
42
mu4/scenes/playback/playbackmodule.h
Normal 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
|
|
@ -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
|
||||
}
|
||||
|
2
mu4/scenes/playback/qml/MuseScore/Playback/qmldir
Normal file
2
mu4/scenes/playback/qml/MuseScore/Playback/qmldir
Normal file
|
@ -0,0 +1,2 @@
|
|||
module MuseScore.Playback
|
||||
PlaybackToolBar 1.0 PlaybackToolBar.qml
|
|
@ -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) {
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue