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)));
}
* 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]});
}
* re-mock - Quick starting guide
* re-mock - root
Yan