added notation midi data
This commit is contained in:
parent
0231e81024
commit
19a9c00810
21 changed files with 617 additions and 144 deletions
|
@ -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.isValid()) {
|
||||
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,70 @@ 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->midiData()->midiStream();
|
||||
player()->setMidiStream(stream);
|
||||
player()->play();
|
||||
}
|
||||
|
||||
void AudioEngineDevTools::stopNotation()
|
||||
{
|
||||
player()->stop();
|
||||
}
|
||||
|
||||
void AudioEngineDevTools::makeArpeggio()
|
||||
{
|
||||
if (m_midiStream.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
midi::MidiStream m_midiStream;
|
||||
|
||||
std::shared_ptr<MidiSource> m_midiSource;
|
||||
IAudioEngine::handle m_midiHandel = 0;
|
||||
};
|
||||
|
|
|
@ -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 midi::MidiStream& stream) = 0;
|
||||
|
||||
// Action
|
||||
virtual bool play() = 0;
|
||||
|
|
|
@ -35,14 +35,14 @@ AudioPlayer::AudioPlayer()
|
|||
m_status.val = PlayStatus::UNDEFINED;
|
||||
}
|
||||
|
||||
void AudioPlayer::setMidiData(std::shared_ptr<midi::MidiData> midi)
|
||||
void AudioPlayer::setMidiStream(const midi::MidiStream& stream)
|
||||
{
|
||||
if (midi) {
|
||||
if (stream.isValid()) {
|
||||
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>();
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ public:
|
|||
ValCh<PlayStatus> status() const override;
|
||||
|
||||
// data
|
||||
void setMidiData(std::shared_ptr<midi::MidiData> midi) override;
|
||||
void setMidiStream(const midi::MidiStream& stream) override;
|
||||
|
||||
// Action
|
||||
bool play() override;
|
||||
|
@ -80,7 +80,7 @@ private:
|
|||
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;
|
||||
|
||||
|
|
|
@ -92,9 +92,9 @@ SoLoud::AudioSource* MidiSource::source()
|
|||
return m_sl.get();
|
||||
}
|
||||
|
||||
void MidiSource::loadMIDI(const std::shared_ptr<midi::MidiData>& midi)
|
||||
void MidiSource::loadMIDI(const midi::MidiStream& stream)
|
||||
{
|
||||
m_seq->loadMIDI(midi);
|
||||
m_seq->loadMIDI(stream);
|
||||
}
|
||||
|
||||
void MidiSource::init(float samplerate)
|
||||
|
|
|
@ -43,7 +43,7 @@ public:
|
|||
void sync(float sec) override;
|
||||
SoLoud::AudioSource* source() override;
|
||||
|
||||
void loadMIDI(const std::shared_ptr<midi::MidiData>& midi);
|
||||
void loadMIDI(const midi::MidiStream& stream);
|
||||
void init(float samplerate);
|
||||
|
||||
float playbackSpeed() const;
|
||||
|
|
|
@ -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,53 @@ void Sequencer::init(float samplerate, float gain)
|
|||
});
|
||||
}
|
||||
|
||||
void Sequencer::loadMIDI(const 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(); });
|
||||
|
||||
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 +108,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 cur_ticks = ticks(m_curMsec);
|
||||
uint32_t max_ticks = maxTicks(m_midiData.tracks);
|
||||
if (cur_ticks >= max_ticks) {
|
||||
requestData(cur_ticks);
|
||||
}
|
||||
|
||||
sendEvents(cur_ticks);
|
||||
m_lastTimeMsec = msec;
|
||||
}
|
||||
|
||||
float Sequencer::getAudio(float sec, float* buf, unsigned int len)
|
||||
|
@ -86,15 +132,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 +167,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,6 +204,15 @@ void Sequencer::doStop()
|
|||
synth()->flushSound();
|
||||
}
|
||||
|
||||
void Sequencer::doSeekTracks(uint32_t seek_ticks, const std::vector<Track>& tracks)
|
||||
{
|
||||
for (const Track& t : tracks) {
|
||||
for (const Channel& c : t.channels) {
|
||||
doSeekChan(seek_ticks, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sequencer::doSeekChan(uint32_t seek_ticks, const Channel& c)
|
||||
{
|
||||
ChanState& state = m_chanStates[c.num];
|
||||
|
@ -179,11 +234,7 @@ void Sequencer::doSeek(uint64_t seek_msec)
|
|||
m_seekMsec = seek_msec;
|
||||
uint32_t seek_ticks = ticks(m_seekMsec);
|
||||
|
||||
for (const Track& t : m_midi->tracks) {
|
||||
for (const Channel& c : t.channels) {
|
||||
doSeekChan(seek_ticks, c);
|
||||
}
|
||||
}
|
||||
doSeekTracks(seek_ticks, m_midiData.tracks);
|
||||
|
||||
m_curMsec = m_seekMsec;
|
||||
m_internalRunning = true;
|
||||
|
@ -200,7 +251,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 +272,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 +283,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 +302,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 +329,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 cur_ticks)
|
||||
{
|
||||
// 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, cur_ticks)) {
|
||||
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 +350,7 @@ bool Sequencer::send_chan_events(const Channel& chan, uint32_t ticks)
|
|||
|
||||
while (1)
|
||||
{
|
||||
if (channel_eot(chan)) {
|
||||
if (channelEOT(chan)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -356,18 +383,18 @@ void Sequencer::setPlaybackSpeed(float speed)
|
|||
|
||||
bool Sequencer::isHasTrack(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 ti, bool mute)
|
||||
{
|
||||
IF_ASSERT_FAILED(isHasTrack(ti)) {
|
||||
return;
|
||||
|
@ -379,31 +406,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[ti];
|
||||
for (const Channel& c : track.channels) {
|
||||
setMuted(c);
|
||||
}
|
||||
}
|
||||
|
||||
void Sequencer::setTrackVolume(int ti, float volume)
|
||||
void Sequencer::setTrackVolume(uint16_t ti, float volume)
|
||||
{
|
||||
IF_ASSERT_FAILED(isHasTrack(ti)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Track& track = m_midi->tracks[ti];
|
||||
const Track& track = m_midiData.tracks[ti];
|
||||
for (const Channel& c : track.channels) {
|
||||
synth()->channelVolume(c.num, volume);
|
||||
}
|
||||
}
|
||||
|
||||
void Sequencer::setTrackBalance(int ti, float balance)
|
||||
void Sequencer::setTrackBalance(uint16_t ti, float balance)
|
||||
{
|
||||
IF_ASSERT_FAILED(isHasTrack(ti)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Track& track = m_midi->tracks[ti];
|
||||
const Track& track = m_midiData.tracks[ti];
|
||||
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 midi::MidiStream& stream);
|
||||
void init(float samplerate, float gain = 1);
|
||||
|
||||
void changeGain(float gain);
|
||||
|
@ -70,19 +71,19 @@ 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 t, bool mute) override;
|
||||
void setTrackVolume(uint16_t ti, float volume) override;
|
||||
void setTrackBalance(uint16_t ti, 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 cur_ticks);
|
||||
bool sendChanEvents(const Channel& chan, uint32_t ticks);
|
||||
|
||||
void buildTempoMap();
|
||||
|
||||
|
@ -93,8 +94,13 @@ private:
|
|||
bool doRun();
|
||||
void doStop();
|
||||
void doSeek(uint64_t seek_msec);
|
||||
void doSeekTracks(uint32_t seek_ticks, const std::vector<Track>& tracks);
|
||||
void doSeekChan(uint32_t seek_ticks, const Channel& c);
|
||||
|
||||
void requestData(uint32_t tick);
|
||||
void onDataReceived(const MidiData& data);
|
||||
void onStreamClosed();
|
||||
|
||||
struct TempoItem {
|
||||
uint32_t tempo = 500000;
|
||||
uint32_t startTicks = 0;
|
||||
|
@ -106,14 +112,22 @@ private:
|
|||
Status m_status = Stoped;
|
||||
bool m_internalRunning = false;
|
||||
|
||||
std::shared_ptr<MidiData> m_midi;
|
||||
MidiData m_midiData;
|
||||
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,7 +37,7 @@ class ISequencer : MODULE_EXPORT_INTERFACE
|
|||
public:
|
||||
virtual ~ISequencer() = default;
|
||||
|
||||
virtual void loadMIDI(const std::shared_ptr<MidiData>& midi) = 0;
|
||||
virtual void loadMIDI(const midi::MidiStream& stream) = 0;
|
||||
virtual void init(float samplerate, float gain = 1) = 0;
|
||||
|
||||
virtual bool run(float init_sec) = 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 t, bool mute) = 0;
|
||||
virtual void setTrackVolume(uint16_t ti, float volume) = 0;
|
||||
virtual void setTrackBalance(uint16_t ti, 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(); }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}/inotationmididata.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/notationmididata.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/internal/notationmididata.h
|
||||
)
|
||||
|
||||
set(FREETYPE_LIB )
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "inotationinteraction.h"
|
||||
#include "notationtypes.h"
|
||||
#include "inotationreader.h"
|
||||
#include "inotationmididata.h"
|
||||
|
||||
class QPainter;
|
||||
namespace mu {
|
||||
|
@ -53,6 +54,9 @@ public:
|
|||
// input (mouse)
|
||||
virtual INotationInteraction* interaction() const = 0;
|
||||
|
||||
// midi
|
||||
virtual INotationMidiData* midiData() const = 0;
|
||||
|
||||
// notify
|
||||
virtual async::Notification notationChanged() const = 0;
|
||||
};
|
||||
|
|
38
mu4/domain/notation/inotationmididata.h
Normal file
38
mu4/domain/notation/inotationmididata.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
//=============================================================================
|
||||
// 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_INOTATIONMIDIDATA_H
|
||||
#define MU_DOMAIN_INOTATIONMIDIDATA_H
|
||||
|
||||
#include "audio/midi/miditypes.h"
|
||||
|
||||
namespace mu {
|
||||
namespace domain {
|
||||
namespace notation {
|
||||
class INotationMidiData
|
||||
{
|
||||
public:
|
||||
virtual ~INotationMidiData() = default;
|
||||
|
||||
virtual audio::midi::MidiStream midiStream() const = 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // MU_DOMAIN_INOTATIONMIDIDATA_H
|
|
@ -46,6 +46,7 @@
|
|||
|
||||
#include "../notationerrors.h"
|
||||
#include "notationinteraction.h"
|
||||
#include "notationmididata.h"
|
||||
|
||||
//#ifdef BUILD_UI_MU4
|
||||
////! HACK Temporary hack to link libmscore
|
||||
|
@ -84,6 +85,8 @@ Notation::Notation()
|
|||
m_interaction->dropChanged().onNotify(this, [this]() {
|
||||
notifyAboutNotationChanged();
|
||||
});
|
||||
|
||||
m_midiData = new NotationMidiData(this);
|
||||
}
|
||||
|
||||
Notation::~Notation()
|
||||
|
@ -183,7 +186,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 +273,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 +348,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 +366,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 +459,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 +604,11 @@ INotationInteraction* Notation::interaction() const
|
|||
return m_interaction;
|
||||
}
|
||||
|
||||
INotationMidiData* Notation::midiData() const
|
||||
{
|
||||
return m_midiData;
|
||||
}
|
||||
|
||||
mu::async::Notification Notation::notationChanged() const
|
||||
{
|
||||
return m_notationChanged;
|
||||
|
|
|
@ -60,6 +60,9 @@ public:
|
|||
// Input (mouse)
|
||||
INotationInteraction* interaction() const override;
|
||||
|
||||
// midi
|
||||
INotationMidiData* midiData() const override;
|
||||
|
||||
// notify
|
||||
async::Notification notationChanged() const override;
|
||||
|
||||
|
@ -80,6 +83,7 @@ private:
|
|||
Ms::MScore* m_scoreGlobal = nullptr;
|
||||
Ms::MasterScore* m_score = nullptr;
|
||||
NotationInteraction* m_interaction = nullptr;
|
||||
INotationMidiData* m_midiData = nullptr;
|
||||
async::Notification m_notationChanged;
|
||||
};
|
||||
}
|
||||
|
|
222
mu4/domain/notation/internal/notationmididata.cpp
Normal file
222
mu4/domain/notation/internal/notationmididata.cpp
Normal file
|
@ -0,0 +1,222 @@
|
|||
//=============================================================================
|
||||
// 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 "notationmididata.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 "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;
|
||||
}
|
||||
|
||||
NotationMidiData::NotationMidiData(IGetScore* getScore)
|
||||
: m_getScore(getScore)
|
||||
{
|
||||
}
|
||||
|
||||
MidiStream NotationMidiData::midiStream() const
|
||||
{
|
||||
Ms::Score* score = m_getScore->score();
|
||||
if (!score) {
|
||||
return MidiStream();
|
||||
}
|
||||
|
||||
makeInitData(m_stream.initData, score);
|
||||
|
||||
m_stream.request.onReceive(this, [this](uint32_t tick) {
|
||||
UNUSED(tick);
|
||||
m_stream.stream.close();
|
||||
});
|
||||
|
||||
return m_stream;
|
||||
}
|
||||
|
||||
void NotationMidiData::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);
|
||||
|
||||
//fillMetronome(stream->initData.metronome, score, midiSpec);
|
||||
}
|
||||
|
||||
void NotationMidiData::makeEventMap(Ms::EventMap& eventMap, Ms::Score* score) const
|
||||
{
|
||||
// int unrenderedUtick = renderEventsStatus.occupiedRangeEnd(utick);
|
||||
// while (unrenderedUtick - utick < minUtickBufferSize) {
|
||||
// const MidiRenderer::Chunk chunk = midi.getChunkAt(unrenderedUtick);
|
||||
// if (!chunk) {
|
||||
// break;
|
||||
// }
|
||||
// renderChunk(chunk, &events);
|
||||
// unrenderedUtick = renderEventsStatus.occupiedRangeEnd(utick);
|
||||
// }
|
||||
|
||||
score->masterScore()->setExpandRepeats(true);
|
||||
score->renderMidi(&eventMap, Ms::SynthesizerState());
|
||||
}
|
||||
|
||||
void NotationMidiData::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 NotationMidiData::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 NotationMidiData::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 });
|
||||
}
|
||||
}
|
||||
}
|
66
mu4/domain/notation/internal/notationmididata.h
Normal file
66
mu4/domain/notation/internal/notationmididata.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_DOMAIN_NOTATIONMIDIDATA_H
|
||||
#define MU_DOMAIN_NOTATIONMIDIDATA_H
|
||||
|
||||
#include "../inotationmididata.h"
|
||||
#include "igetscore.h"
|
||||
#include "async/asyncable.h"
|
||||
|
||||
namespace Ms {
|
||||
class EventMap;
|
||||
}
|
||||
|
||||
namespace mu {
|
||||
namespace domain {
|
||||
namespace notation {
|
||||
class NotationMidiData : public INotationMidiData, public async::Asyncable
|
||||
{
|
||||
public:
|
||||
NotationMidiData(IGetScore* getScore);
|
||||
|
||||
audio::midi::MidiStream midiStream() 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;
|
||||
mutable audio::midi::MidiStream m_stream;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // MU_DOMAIN_NOTATIONMIDIDATA_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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue