added notation midi data

This commit is contained in:
Igor Korsukov 2020-07-17 13:32:18 +02:00
parent 0231e81024
commit 19a9c00810
21 changed files with 617 additions and 144 deletions

View file

@ -18,6 +18,8 @@
//=============================================================================
#include "audioenginedevtools.h"
#include "log.h"
using namespace mu::audio::engine;
using namespace mu::audio::midi;
@ -45,10 +47,10 @@ void AudioEngineDevTools::playSourceMidi()
m_midiSource = std::make_shared<MidiSource>();
}
if (!m_midiData) {
m_midiData = makeArpeggio();
if (!m_midiStream.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);
});
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -33,15 +33,6 @@ Sequencer::~Sequencer()
}
}
void Sequencer::loadMIDI(const std::shared_ptr<MidiData>& midi)
{
m_midi = midi;
buildTempoMap();
synth()->loadSF(m_midi->programs(), "", [this](uint16_t percent) {
LOGI() << "sf loading: " << percent;
});
}
void Sequencer::init(float samplerate, float gain)
{
reset();
@ -58,6 +49,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);
}

View file

@ -32,12 +32,13 @@
#include "../miditypes.h"
#include "modularity/ioc.h"
#include "../isynthesizer.h"
#include "async/asyncable.h"
namespace mu {
namespace audio {
namespace midi {
class ISynthesizer;
class Sequencer : public ISequencer
class Sequencer : public ISequencer, public async::Asyncable
{
INJECT(midi, ISynthesizer, synth)
@ -53,7 +54,7 @@ public:
Status status() const;
void loadMIDI(const std::shared_ptr<MidiData>& midi);
void loadMIDI(const 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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

@ -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); // dont 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;

View file

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

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

View file

@ -0,0 +1,66 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#ifndef MU_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

View file

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