Skip to content

Commit

Permalink
Improve the unit test for patternal
Browse files Browse the repository at this point in the history
  • Loading branch information
aalex committed Dec 13, 2024
1 parent 9ed6174 commit 5c672a1
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 76 deletions.
20 changes: 19 additions & 1 deletion include/halp/midi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

#include <algorithm>
#include <string_view>
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <sstream>

HALP_MODULE_EXPORT
namespace halp
Expand All @@ -17,15 +20,30 @@ struct midi_msg
{
boost::container::small_vector<uint8_t, 15> bytes;
int64_t timestamp{};

// Equality operator.
bool operator==(const midi_msg&) const = default;
};

std::ostream& operator<<(std::ostream &o, const midi_msg& message)
{
return o << "bytes: " << fmt::format("{}", message.bytes) << std::endl;
}

struct midi_note_msg
{
uint8_t bytes[8];
int64_t timestamp{};

bool operator==(const &midi_note_msg) const = default;
// Equality operator.
bool operator==(const midi_note_msg&) const = default;
};

std::ostream& operator<<(std::ostream &o, const midi_note_msg& message)
{
return o << "bytes: " << fmt::format("{}", message.bytes) << "\ttimestamp: " << message.timestamp << std::endl;
}

template <static_string lit, typename MessageType = midi_msg>
struct midi_bus
{
Expand Down
161 changes: 86 additions & 75 deletions tests/objects/patternal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,90 +10,101 @@ using Pattern = patternal::Pattern;
using tick_musical = halp::tick_musical;
using midi_msg = halp::midi_msg;

// Tests for the Patternal object
TEST_CASE("Test Patternal patterns", "[advanced][patternal]")
{

SECTION("The input pattern is stored correctly") {
// Instanciate the object:
Processor patternalProcessor;
static const int noteHiHat = 42;
static const int noteSnare = 38;
static const int noteBassDrum = 35;
static const uint8_t messageNoteOn = 0x90; // on the first channel
static const uint8_t velocityZero = 0;
static const uint8_t velocityFull = 127;

// Create the input pattern:
patternalProcessor.inputs.patterns.value = {
(Pattern) {42, {127, 127, 127, 127}}, // hi-hat
(Pattern) {38, {0, 127, 0, 127}}, // snare
(Pattern) {35, {127, 0, 127, 0}}, // bass drum
};

// Check that the input pattern is stored correctly:
REQUIRE(patternalProcessor.inputs.patterns.value[0].note == 42);
REQUIRE(patternalProcessor.inputs.patterns.value[0].pattern[0] == 127);
REQUIRE(patternalProcessor.inputs.patterns.value[1].note == 38);
REQUIRE(patternalProcessor.inputs.patterns.value[1].pattern[0] == 0);
REQUIRE(patternalProcessor.inputs.patterns.value[2].note == 35);
REQUIRE(patternalProcessor.inputs.patterns.value[2].pattern[0] == 127);
}
/**
* Create a MIDI note message.
*/
void makeNote(midi_msg& ret, uint8_t note, uint8_t velocity) {
ret.bytes = { messageNoteOn, note, velocity};
// ret.bytes[0] = messageNoteOn;
// ret.bytes[1] = note;
// ret.bytes[2] = velocity;
}

SECTION("The beats are as expected") {
// Our sampling rate is 48kHz and the buffer size is 512
static const double samplingRate = 48000;
static const int bufferSize = 512;
static const double tickDuration = samplingRate / bufferSize; // seconds
static const double ticksPerSecond = 1.0 / tickDuration;
static const double testDuration = 3.0; // seconds
static const int noteHiHat = 42;
static const int noteSnare = 38;
static const int noteBassDrum = 35;
static const uint8_t messageNoteOn = 0x90; // on the first channel
static const uint8_t velocityZero = 0;
static const uint8_t velocityFull = 127;
// Tests for the Patternal object
TEST_CASE("The input pattern is stored correctly", "[advanced][patternal]")
{
// Instanciate the object:
Processor patternalProcessor;

// Create the input pattern:
patternalProcessor.inputs.patterns.value = {
(Pattern) {42, {127, 127, 127, 127}}, // hi-hat
(Pattern) {38, {0, 127, 0, 127}}, // snare
(Pattern) {35, {127, 0, 127, 0}}, // bass drum
};

Processor patternalProcessor;
patternalProcessor.inputs.patterns.value = {
(Pattern) {noteHiHat, {127, 127, 127, 127}}, // hi-hat
(Pattern) {noteSnare, {0, 127, 0, 127}}, // snare
(Pattern) {noteBassDrum, {127, 0, 127, 0}}, // bass drum
};
// Check that the input pattern is stored correctly:
REQUIRE(patternalProcessor.inputs.patterns.value[0].note == 42);
REQUIRE(patternalProcessor.inputs.patterns.value[0].pattern[0] == 127);
REQUIRE(patternalProcessor.inputs.patterns.value[1].note == 38);
REQUIRE(patternalProcessor.inputs.patterns.value[1].pattern[0] == 0);
REQUIRE(patternalProcessor.inputs.patterns.value[2].note == 35);
REQUIRE(patternalProcessor.inputs.patterns.value[2].pattern[0] == 127);
}

for (int tickIndex = 0; tickIndex < ticksPerSecond * testDuration; tickIndex++) {
tick_musical tk;
tk.tempo = 120; // 120 BPM. One beat lasts 500 ms.
tk.signature.num = 4;
tk.signature.denum = 4;
tk.frames = bufferSize;
double timeNow = tickIndex * tickDuration;
patternalProcessor(tk);
TEST_CASE("MIDI messages are output properly", "[advanced][patternal]")
{
// Our sampling rate is 48kHz and the buffer size is 512
static const double samplingRate = 48000;
static const int bufferSize = 512;
static const double tickDuration = samplingRate / bufferSize; // seconds
static const double ticksPerSecond = 1.0 / tickDuration;
static const double testDuration = 3.0; // seconds

// Test the first tick:
if (tickIndex == 0) {
// We expect a hi-hat note:
midi_msg expectedNoteHiHat = midi_msg;
expectedNoteHiHat.bytes[0] = messageNoteOn;
expectedNoteHiHat.bytes[1] = (uint8_t) noteHiHat;
expectedNoteHiHat.bytes[2] = velocityFull;
REQUIRE(patternalProcessor.outputs.midi_messages[0], expectedNoteHiHat);
Processor patternalProcessor;
patternalProcessor.inputs.patterns.value = {
(Pattern) {noteHiHat, {127, 127, 127, 127}}, // hi-hat
(Pattern) {noteSnare, {0, 127, 0, 127}}, // snare
(Pattern) {noteBassDrum, {127, 0, 127, 0}}, // bass drum
};

// We expect a snare note:
midi_msg expectedNoteHiHat = midi_msg;
expectedNoteHiHat.bytes[0] = messageNoteOn;
expectedNoteHiHat.bytes[1] = (uint8_t) noteSnare;
expectedNoteHiHat.bytes[2] = velocityZero;
REQUIRE(patternalProcessor.outputs.midi_messages[1], expectedNoteSnare);
for (int tickIndex = 0; tickIndex < ticksPerSecond * testDuration; tickIndex++) {
INFO("Test tick " << tickIndex);
tick_musical tk;
tk.tempo = 120; // 120 BPM. One beat lasts 500 ms.
tk.signature.num = 4;
tk.signature.denom = 4;
tk.frames = bufferSize;
double timeNow = tickIndex * tickDuration;
patternalProcessor(tk);

// We expect a bass drum note:
midi_msg expectedNoteHiHat = midi_msg;
expectedNoteHiHat.bytes[0] = messageNoteOn;
expectedNoteHiHat.bytes[1] = (uint8_t) noteBassDrum;
expectedNoteHiHat.bytes[2] = velocityFull;
REQUIRE(patternalProcessor.outputs.midi_messages[2], expectedNoteSnare);
}
// Test the 1st tick:
if (tickIndex == 0) {
// FIXME: why are there 4 MIDI messages and not 3?
REQUIRE(patternalProcessor.outputs.midi.midi_messages.size() == 4);
// Hi-hat note:
midi_msg expectedNoteHiHat;
makeNote(expectedNoteHiHat, noteHiHat, velocityFull);
REQUIRE(patternalProcessor.outputs.midi.midi_messages[0] == expectedNoteHiHat);
// Snare note:
midi_msg expectedNoteSnare;
makeNote(expectedNoteSnare, noteSnare, velocityZero);
REQUIRE(patternalProcessor.outputs.midi.midi_messages[1] == expectedNoteSnare);
// Bass drum note:
midi_msg expectedNoteBassDrum;
makeNote(expectedNoteBassDrum, noteBassDrum, velocityFull);
REQUIRE(patternalProcessor.outputs.midi.midi_messages[2] == expectedNoteBassDrum);
}

// TODO: check the other ticks
}
// Test the 2nd tick:
else if (tickIndex == 1) {
// There should be no MIDI messages in the output on this tick.
REQUIRE(patternalProcessor.outputs.midi.midi_messages.size() == 0);
// REQUIRE(patternalProcessor.outputs.midi.midi_messages.size() == 1);
}

// TODO check somethig with:
// - start_position_quarter
// - last_signature_change
// TODO: check the other ticks
}

// TODO check somethig with:
// - start_position_quarter
// - last_signature_change
}

0 comments on commit 5c672a1

Please sign in to comment.