[re-mock] A testing framework for REs

This forum is for developers of Rack Extensions to discuss the RE SDK, share code, and offer tips to other developers.
User avatar
pongasoft
RE Developer
Posts: 402
Joined: 21 Apr 2016
Location: Las Vegas

Post 03 Feb 2022

A few months back I introduced re-mock (beta). Today, I am very proud to announce that re-mock has reached maturity and is fully released, including comprehensive documentation.

Features:

* Allow for testing rack extensions by fully controlling the event loop
* Supports 100% of Jukebox.h functions (ex: JBox_LoadMOMProperty)
* Supports 100% of jbox.lua functions (in realtime_controller.lua) (ex: jbox.load_sample_async)
* Load info.lua, motherboard_def.lua and realtime_controller.lua to build the rack extension exactly as Reason does
* Support loading and saving sample files (wav, aiff...)
* Support loading patch files (for example a patch file saved in Recon/Reason)
* Support for simulating slow loads and errors (when loading samples or blobs)
* Support loading midi files to populate the sequencer track
* Powerful APIs to help in common testing use cases (like "playing an instrument" or "processing a sample through an effect")
* much much more

The project is free and open source of github. Here is the Quick starting guide.

This is an actual example written for the VerySimpleSampler rack extension that comes with the SDK:

Code: Select all

TEST(CVerySimpleSampler, Play)
{
  auto c = DeviceConfig<CVerySimpleSampler>::fromJBoxExport(RE_CMAKE_PROJECT_DIR).disable_trace();
  auto tester = InstrumentTester<CVerySimpleSampler>(c);

  // we wire main out to the proper sockets in the device
  tester.wireMainOut("left", "right");

  // we load the actual samples that will be played so that we can use them to check playback
  auto const bellSample = tester.loadSample("/Public/Samples/Bell.wav");
  auto const chipSample = tester.loadSample("/Public/Samples/Chip.wav");
  auto const machinaSample = tester.loadSample("/Public/Samples/Machina.wav");
  auto const mkivSample = tester.loadSample("/Public/Samples/MkIV.wav");

  // initializes the device
  tester.nextBatch();

  // Make sure we play at max volume (gain = 1.0)
  // The device does not adjust the root key (which I think is a bug/mistake): for example in order to play "chip"
  // you have to press D#3, but that does not mean that it should sound like if its root key is C3, and it has been
  // transposed to D#3...
  tester.device().setNum("/user_samples/0/root_key", Midi::C(3));
  tester.device().setNum("/user_samples/0/preview_volume_level", 127);
  tester.device().setNum("/user_samples/1/root_key", Midi::D_sharp(3));
  tester.device().setNum("/user_samples/1/preview_volume_level", 127);
  tester.device().setNum("/user_samples/2/root_key", Midi::F_sharp(3));
  tester.device().setNum("/user_samples/2/preview_volume_level", 127);
  tester.device().setNum("/user_samples/3/root_key", Midi::A(3));
  tester.device().setNum("/user_samples/3/preview_volume_level", 127);

  // play C3 (Bell) (gain = 1.0)
  ASSERT_EQ(*bellSample,
            *tester.bounce(sample::Duration{bellSample->getFrameCount()},
                           tester.newTimeline()
                             .note(Midi::C(3), sample::Duration{bellSample->getFrameCount()}, 127)));

  // play C3 again (note off/note on) (Bell) (gain = 0.78 (100/127))
  ASSERT_EQ(bellSample->clone().applyGain(100/127.0f),
            *tester.bounce(sample::Duration{bellSample->getFrameCount()},
                           tester.newTimeline()
                             .note(Midi::C(3), sample::Duration{bellSample->getFrameCount()}, 100)));

  // play D#3 (Chip) (gain = 1.0)
  ASSERT_EQ(*chipSample,
            *tester.bounce(sample::Duration{chipSample->getFrameCount()},
                           tester.newTimeline()
                             .note(Midi::D_sharp(3), sample::Duration{chipSample->getFrameCount()}, 127)));

  // play F#3 (Machina) (gain = 1.0)
  ASSERT_EQ(*machinaSample,
            *tester.bounce(sample::Duration{machinaSample->getFrameCount()},
                           tester.newTimeline()
                             .note(Midi::F_sharp(3), sample::Duration{machinaSample->getFrameCount()}, 127)));

  // play A3 (MkIV) (gain = 1.0)
  ASSERT_EQ(*mkivSample,
            *tester.bounce(sample::Duration{mkivSample->getFrameCount()},
                           tester.newTimeline()
                             .note(Midi::A(3), sample::Duration{mkivSample->getFrameCount()}, 127)));

  // Expected result is 4 samples mixed
  auto mixedSample = bellSample->clone().mixWith(*chipSample).mixWith(*machinaSample).mixWith(*mkivSample);

  ASSERT_EQ(mixedSample,
            *tester.bounce(sample::Duration{mixedSample.getFrameCount()},
                           tester.newTimeline()
                             .note(Midi::C(3), sample::Duration{bellSample->getFrameCount()}, 127)
                             .note(Midi::D_sharp(3), sample::Duration{chipSample->getFrameCount()}, 127)
                             .note(Midi::F_sharp(3), sample::Duration{machinaSample->getFrameCount()}, 127)
                             .note(Midi::A(3), sample::Duration{mkivSample->getFrameCount()}, 127)));
}
In fact, the framework is so powerful, that you can actually write a command line tool wrapper for an effect processor is 5 lines of code! Yes, think about what this code does:

* it loads info.lua, motherboard_def.lua and realtime_controller.lua
* it instantiates the rack extension
* it loads the patches from the file system and applies all properties changes to the rack extension so that it is in a known/desired state
* it loads the input file to process and "run it" through the effect
* it then saves the result to the provided file

Code: Select all

// invoke with <path to RE>, <patch>, <input file (to process)>, <output file (result)>
int main(int argc, char *argv[])
{
  // ignoring all error handling for this brief example!!!
  auto c = DeviceConfig<MyEffect>::fromJBoxExport(argv[1]);
  auto tester = StudioEffectTester<MyEffect>(c);
  tester.device.loadPatch(argv[2]); // set the device in a known state (optional of course)
  auto sample = tester.processSample(argv[3]);
  tester.saveSample(*sample, resource::File{argv[4]});
}
If you have any questions about the framework or you need help, do not hesitate to reach out!

* re-mock - Quick starting guide
* re-mock - root

Yan

  • Information
  • Who is online

    Users browsing this forum: No registered users and 2 guests