Skip to content

Commit

Permalink
Generate MIDI events for the metronome.
Browse files Browse the repository at this point in the history
This won't be used when writing out MIDI files, but will be used once
the MIDI player is refactored to use these MIDI events.

Bug: #55
  • Loading branch information
cameronwhite committed Jun 22, 2015
1 parent 3e00767 commit a0e5ae8
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 35 deletions.
3 changes: 2 additions & 1 deletion source/formats/midi/midiexporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ void MidiExporter::save(const std::string &filename, const Score &score)
std::ofstream os(filename, std::ios::out | std::ios::binary);
os.exceptions(std::ios::failbit | std::ios::badbit | std::ios::eofbit);

MidiFile file(score);
MidiFile file;
file.load(score);
writeHeader(os, file);

for (const MidiEventList &track : file.getTracks())
Expand Down
2 changes: 2 additions & 0 deletions source/midi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ add_library(ptemidi
midifile.h
)

qt5_use_modules(ptemidi Core)

cotire(ptemidi)
38 changes: 29 additions & 9 deletions source/midi/midievent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,41 @@

#include "midievent.h"

MidiEvent::MidiEvent(int tick, StatusByte status, std::vector<uint8_t> data,
int system, int position, int player, int instrument)
: myTick(tick),
enum StatusByte : uint8_t
{
NoteOff = 0x80,
NoteOn = 0x90,
MetaMessage = 0xff
};

MidiEvent::MidiEvent(int ticks, uint8_t status, std::vector<uint8_t> data,
const SystemLocation &location, int player, int instrument)
: myTicks(ticks),
myStatusByte(status),
myData(std::move(data)),
mySystem(system),
myPosition(position),
myLocation(location),
myPlayer(player),
myInstrument(instrument)
{
}

MidiEvent MidiEvent::endOfTrack(int tick)
MidiEvent MidiEvent::endOfTrack(int ticks)
{
return MidiEvent(ticks, StatusByte::MetaMessage,
{ static_cast<uint8_t>(MetaType::TrackEnd), 0 },
SystemLocation(), -1, -1);
}

MidiEvent MidiEvent::noteOn(int ticks, uint8_t channel, uint8_t pitch,
uint8_t velocity, const SystemLocation &location)
{
return MidiEvent(ticks, StatusByte::NoteOn + channel, { pitch, velocity },
location, -1, -1);
}

MidiEvent MidiEvent::noteOff(int ticks, uint8_t channel, uint8_t pitch,
const SystemLocation &location)
{
return MidiEvent(tick, StatusByte::MetaMessage,
{ static_cast<uint8_t>(MetaType::TrackEnd), 0 }, -1, -1,
-1, -1);
return MidiEvent(ticks, StatusByte::NoteOff + channel, { pitch, 127 },
location, -1, -1);
}
29 changes: 15 additions & 14 deletions source/midi/midievent.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,11 @@
#ifndef MIDI_MIDIEVENT_H
#define MIDI_MIDIEVENT_H

#include <score/systemlocation.h>

#include <cstdint>
#include <vector>

enum class StatusByte : uint8_t
{
MetaMessage = 0xff
};

enum class MetaType : uint8_t
{
TrackEnd = 0x2f
Expand All @@ -34,22 +31,26 @@ enum class MetaType : uint8_t
class MidiEvent
{
public:
int getTicks() const { return myTick; }
StatusByte getStatusByte() const { return myStatusByte; }
int getTicks() const { return myTicks; }
void setTicks(int ticks) { myTicks = ticks; }
uint8_t getStatusByte() const { return myStatusByte; }
const std::vector<uint8_t> &getData() const { return myData; }

static MidiEvent endOfTrack(int tick);
static MidiEvent endOfTrack(int ticks);
static MidiEvent noteOn(int ticks, uint8_t channel, uint8_t pitch,
uint8_t velocity, const SystemLocation &location);
static MidiEvent noteOff(int ticks, uint8_t channel, uint8_t pitch,
const SystemLocation &location);

private:
MidiEvent(int tick, StatusByte status, std::vector<uint8_t> data,
int system, int position, int player, int instrument);
MidiEvent(int ticks, uint8_t status, std::vector<uint8_t> data,
const SystemLocation &location, int player, int instrument);

int myTick; // TODO - does this need to be 64-bit for absolute times?
StatusByte myStatusByte;
int myTicks; // TODO - does this need to be 64-bit for absolute times?
uint8_t myStatusByte;
std::vector<uint8_t> myData;

int mySystem;
int myPosition;
SystemLocation myLocation;
int myPlayer;
int myInstrument;
};
Expand Down
21 changes: 21 additions & 0 deletions source/midi/midieventlist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,24 @@
*/

#include "midieventlist.h"

MidiEventList::MidiEventList(bool absolute_ticks)
: myAbsoluteTicks(absolute_ticks)
{
}

void MidiEventList::convertToDeltaTicks()
{
assert(myAbsoluteTicks);
myAbsoluteTicks = false;

if (myEvents.size() <= 1)
return;

for (size_t i = myEvents.size() - 1; i >= 1; --i)
{
MidiEvent &event = myEvents[i];
const MidiEvent &prev_event = myEvents[i - 1];
event.setTicks(event.getTicks() - prev_event.getTicks());
}
}
10 changes: 8 additions & 2 deletions source/midi/midieventlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,27 @@
class MidiEventList
{
public:
typedef std::vector<MidiEvent>::iterator iterator;
typedef std::vector<MidiEvent>::const_iterator const_iterator;
MidiEventList(bool absolute_ticks = true);

// Convert the MIDI events from absolute to delta ticks.
void convertToDeltaTicks();

void append(MidiEvent &&event)
{
myEvents.push_back(std::forward<MidiEvent>(event));
}

typedef std::vector<MidiEvent>::iterator iterator;
typedef std::vector<MidiEvent>::const_iterator const_iterator;

iterator begin() { return myEvents.begin(); }
iterator end() { return myEvents.end(); }
const_iterator begin() const { return myEvents.begin(); }
const_iterator end() const { return myEvents.end(); }

private:
std::vector<MidiEvent> myEvents;
bool myAbsoluteTicks;
};

#endif
129 changes: 121 additions & 8 deletions source/midi/midifile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,127 @@

#include "midifile.h"

static const int theDefaultPPQ = 480;
#include <boost/rational.hpp>
#include <QSettings>

MidiFile::MidiFile(const Score & score)
: myTicksPerBeat(theDefaultPPQ)
#include <app/settings.h>
#include <score/generalmidi.h>
#include <score/score.h>
#include <score/systemlocation.h>
#include <score/utils.h>

static const int PERCUSSION_CHANNEL = 9;
static const int METRONOME_CHANNEL = PERCUSSION_CHANNEL;
static const int DEFAULT_PPQ = 480;

MidiFile::MidiFile() : myTicksPerBeat(0)
{
}

void MidiFile::load(const Score &score)
{
myTicksPerBeat = DEFAULT_PPQ;

MidiEventList metronome_track;
// TODO - set channel volume at start of track.

SystemLocation location(0, 0);
int current_tick = 0;
while (location.getSystem() < score.getSystems().size())
{
const System &system = score.getSystems()[location.getSystem()];
const Barline *current_bar = ScoreUtils::findByPosition(
system.getBarlines(), location.getPosition());
const Barline *next_bar = system.getNextBarline(location.getPosition());

current_tick =
generateMetronome(system, *current_bar, *next_bar, location,
current_tick, metronome_track);

// Move to the next bar.
// TODO - handle repeats.
if (next_bar == &system.getBarlines().back())
{
location.setSystem(location.getSystem() + 1);
location.setPosition(0);
}
else
location.setPosition(next_bar->getPosition());
}


metronome_track.append(MidiEvent::endOfTrack(current_tick));
myTracks.push_back(metronome_track);

for (MidiEventList &track : myTracks)
track.convertToDeltaTicks();
}

int MidiFile::generateMetronome(const System &system,
const Barline &current_bar,
const Barline &next_bar,
const SystemLocation &location,
int current_tick, MidiEventList &event_list)
{
// For now, just add some empty event lists.
MidiEventList temp;
temp.append(MidiEvent::endOfTrack(0));
myTracks.push_back(temp);
myTracks.push_back(temp);
QSettings settings;
const uint8_t strong_vel =
settings.value(Settings::MIDI_METRONOME_STRONG_ACCENT,
Settings::MIDI_METRONOME_STRONG_ACCENT_DEFAULT).toUInt();
const uint8_t weak_vel =
settings.value(Settings::MIDI_METRONOME_WEAK_ACCENT,
Settings::MIDI_METRONOME_WEAK_ACCENT_DEFAULT).toUInt();
const uint8_t preset =
Midi::MIDI_PERCUSSION_PRESET_OFFSET +
settings.value(Settings::MIDI_METRONOME_PRESET,
Settings::MIDI_METRONOME_PRESET_DEFAULT).toUInt();

const TimeSignature &time_sig = current_bar.getTimeSignature();

const int num_pulses = time_sig.getNumPulses();
const int beats_per_measure = time_sig.getBeatsPerMeasure();
const int beat_value = time_sig.getBeatValue();
const int position = current_bar.getPosition();

// Figure out the duration of a pulse.
const int duration = boost::rational_cast<int>(
boost::rational<int>(4, beat_value) *
boost::rational<int>(beats_per_measure, num_pulses) * myTicksPerBeat);

// Check for multi-bar rests, as we need to generate more metronome events
// to fill the extra bars.
int num_repeats = 1;
for (const Staff &staff : system.getStaves())
{
for (const Voice &voice : staff.getVoices())
{
for (const Position &pos : ScoreUtils::findInRange(
voice.getPositions(), current_bar.getPosition(),
next_bar.getPosition()))
{
if (pos.hasMultiBarRest())
{
num_repeats =
std::max(num_repeats, pos.getMultiBarRestCount());
}
}
}
}

for (int repeat = 0; repeat < num_repeats; ++repeat)
{
for (int i = 0; i < num_pulses; ++i)
{
const uint8_t velocity = (i == 0) ? strong_vel : weak_vel;

event_list.append(MidiEvent::noteOn(current_tick, METRONOME_CHANNEL,
preset, velocity, location));

current_tick += duration;

event_list.append(MidiEvent::noteOff(
current_tick, METRONOME_CHANNEL, preset, location));
}
}

return current_tick;
}
12 changes: 11 additions & 1 deletion source/midi/midifile.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,27 @@
#include <midi/midieventlist.h>
#include <vector>

class Barline;
class Score;
class System;
class SystemLocation;

class MidiFile
{
public:
MidiFile(const Score &score);
MidiFile();

void load(const Score &score);

int getTicksPerBeat() const { return myTicksPerBeat; }
const std::vector<MidiEventList> &getTracks() const { return myTracks; }

private:
int generateMetronome(const System &system, const Barline &current_bar,
const Barline &next_bar,
const SystemLocation &location, int current_tick,
MidiEventList &event_list);

int myTicksPerBeat;
std::vector<MidiEventList> myTracks;
};
Expand Down

0 comments on commit a0e5ae8

Please sign in to comment.