From 36a62ae3403f7cfb02bd8dde43b6b2fa96fe867b Mon Sep 17 00:00:00 2001
From: Bent Bisballe Nyeng <deva@aasimon.org>
Date: Sat, 14 May 2016 20:27:56 +0200
Subject: Make all components use the same Random instance and add seed method
 on DrumGizmo class. Added dgreftest application for doing reference midifile
 rendering tests.

---
 configure.ac                          |   3 +-
 src/drumgizmo.cc                      |   9 +-
 src/drumgizmo.h                       |   3 +
 src/drumkitloader.cc                  |   6 +-
 src/drumkitloader.h                   |   3 +-
 src/drumkitparser.cc                  |   5 +-
 src/drumkitparser.h                   |   3 +-
 src/instrument.cc                     |   4 +-
 src/instrument.h                      |   6 +-
 src/powerlist.cc                      |   3 +-
 src/powerlist.h                       |   4 +-
 src/random.cc                         |   6 +-
 src/random.h                          |   2 +
 src/velocity.cc                       |   3 +-
 src/velocity.h                        |   4 +-
 test/Makefile.am                      |   2 +
 test/dgreftest/Makefile.am            |  36 +++++++
 test/dgreftest/compareoutputengine.cc | 138 ++++++++++++++++++++++++
 test/dgreftest/compareoutputengine.h  |  55 ++++++++++
 test/dgreftest/dgreftest.cc           | 136 ++++++++++++++++++++++++
 test/dgreftest/midiinputengine.cc     | 192 ++++++++++++++++++++++++++++++++++
 test/dgreftest/midiinputengine.h      |  64 ++++++++++++
 test/dgreftest/wavfileoutputengine.cc | 125 ++++++++++++++++++++++
 test/dgreftest/wavfileoutputengine.h  |  54 ++++++++++
 24 files changed, 847 insertions(+), 19 deletions(-)
 create mode 100644 test/dgreftest/Makefile.am
 create mode 100644 test/dgreftest/compareoutputengine.cc
 create mode 100644 test/dgreftest/compareoutputengine.h
 create mode 100644 test/dgreftest/dgreftest.cc
 create mode 100644 test/dgreftest/midiinputengine.cc
 create mode 100644 test/dgreftest/midiinputengine.h
 create mode 100644 test/dgreftest/wavfileoutputengine.cc
 create mode 100644 test/dgreftest/wavfileoutputengine.h

diff --git a/configure.ac b/configure.ac
index f44226a..b28830e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -582,7 +582,8 @@ AC_CONFIG_FILES(
 	plugingui/Makefile
 	include/Makefile
 	man/Makefile
-  test/Makefile
+	test/Makefile
+	test/dgreftest/Makefile
 	drumgizmo/Makefile)
 
 AC_OUTPUT()
diff --git a/src/drumgizmo.cc b/src/drumgizmo.cc
index 1ccefc5..7d9d88c 100644
--- a/src/drumgizmo.cc
+++ b/src/drumgizmo.cc
@@ -50,7 +50,7 @@
 
 DrumGizmo::DrumGizmo(Settings& settings,
                      AudioOutputEngine *o, AudioInputEngine *i)
-	: loader(settings, kit, *i, resamplers)
+	: loader(settings, kit, *i, resamplers, rand)
 	, oe(o)
 	, ie(i)
 	, kit()
@@ -115,6 +115,11 @@ void DrumGizmo::setFreeWheel(bool freewheel)
 	}
 }
 
+void DrumGizmo::setRandomSeed(unsigned int seed)
+{
+	rand.setSeed(seed);
+}
+
 void DrumGizmo::run(int endpos)
 {
 	size_t pos = 0;
@@ -143,6 +148,8 @@ void DrumGizmo::run(int endpos)
 
 bool DrumGizmo::run(size_t pos, sample_t *samples, size_t nsamples)
 {
+	std::lock_guard<std::mutex> guard(resamplers.mutex);
+
 	setFrameSize(nsamples);
 
 	ie->pre();
diff --git a/src/drumgizmo.h b/src/drumgizmo.h
index e25db2f..ea04603 100644
--- a/src/drumgizmo.h
+++ b/src/drumgizmo.h
@@ -70,6 +70,8 @@ public:
 
 	void setFreeWheel(bool freewheel);
 
+	void setRandomSeed(unsigned int seed);
+
 private:
 	static constexpr int MAX_NUM_CHANNELS = 64;
 	static constexpr int RESAMPLER_OUTPUT_BUFFER = 4096;
@@ -101,4 +103,5 @@ protected:
 	std::vector<event_t> events;
 	Settings& settings;
 	SettingsGetter getter{settings};
+	Random rand;
 };
diff --git a/src/drumkitloader.cc b/src/drumkitloader.cc
index a9b87f0..814a98d 100644
--- a/src/drumkitloader.cc
+++ b/src/drumkitloader.cc
@@ -36,12 +36,14 @@
 
 DrumKitLoader::DrumKitLoader(Settings& settings, DrumKit& kit,
                              AudioInputEngine& ie,
-                             Resamplers& resamplers)
+                             Resamplers& resamplers,
+                             Random& rand)
 	: settings(settings)
 	, getter(settings)
 	, kit(kit)
 	, ie(ie)
 	, resamplers(resamplers)
+	, rand(rand)
 {
 	run();
 	run_semaphore.wait(); // Wait for the thread to actually start.
@@ -80,7 +82,7 @@ bool DrumKitLoader::loadkit(const std::string& file)
 
 	settings.drumkit_load_status.store(LoadStatus::Loading);
 
-	DrumKitParser parser(settings, kit);
+	DrumKitParser parser(settings, kit, rand);
 	if(parser.parseFile(file))
 	{
 		ERR(drumgizmo, "Drumkit parser failed: %s\n", file.c_str());
diff --git a/src/drumkitloader.h b/src/drumkitloader.h
index f99f439..cca11af 100644
--- a/src/drumkitloader.h
+++ b/src/drumkitloader.h
@@ -51,7 +51,7 @@ class DrumKitLoader
 public:
 	//! The constrcutor starts the loader thread.
 	DrumKitLoader(Settings& settings, DrumKit& kit, AudioInputEngine& ie,
-	              Resamplers& resamplers);
+	              Resamplers& resamplers, Random& rand);
 
 	//! The destructor signals the thread to stop and waits to merge before
 	//! returning (ie. deleting the object will garantuee that the thread has
@@ -91,4 +91,5 @@ protected:
 	AudioInputEngine& ie;
 	Resamplers& resamplers;
 	MemChecker memchecker;
+	Random& rand;
 };
diff --git a/src/drumkitparser.cc b/src/drumkitparser.cc
index 048a05b..073d240 100644
--- a/src/drumkitparser.cc
+++ b/src/drumkitparser.cc
@@ -34,10 +34,11 @@
 #include "path.h"
 #include "drumgizmo.h"
 
-DrumKitParser::DrumKitParser(Settings& settings, DrumKit& k)
+DrumKitParser::DrumKitParser(Settings& settings, DrumKit& k, Random& rand)
 	: kit(k)
 	, refs(REFSFILE)
 	, settings(settings)
+	, rand(rand)
 {
 }
 
@@ -183,7 +184,7 @@ void DrumKitParser::endTag(const std::string& name)
 {
 	if(name == "instrument")
 	{
-		Instrument* instrument = new Instrument(settings);
+		Instrument* instrument = new Instrument(settings, rand);
 		instrument->setGroup(instr_group);
 
 		InstrumentParser parser(*instrument);
diff --git a/src/drumkitparser.h b/src/drumkitparser.h
index 0adccb9..444b459 100644
--- a/src/drumkitparser.h
+++ b/src/drumkitparser.h
@@ -34,7 +34,7 @@ class DrumKitParser
 	: public SAXParser
 {
 public:
-	DrumKitParser(Settings& setting, DrumKit& kit);
+	DrumKitParser(Settings& setting, DrumKit& kit, Random& rand);
 
 	virtual int parseFile(const std::string& filename) override;
 
@@ -53,4 +53,5 @@ private:
 
 	ConfigFile refs;
 	Settings& settings;
+	Random& rand;
 };
diff --git a/src/instrument.cc b/src/instrument.cc
index cc052e9..9a4c0b7 100644
--- a/src/instrument.cc
+++ b/src/instrument.cc
@@ -30,8 +30,10 @@
 
 #include "sample.h"
 
-Instrument::Instrument(Settings& settings)
+Instrument::Instrument(Settings& settings, Random& rand)
 	: settings(settings)
+	, rand(rand)
+	, powerlist(rand)
 {
 	DEBUG(instrument, "new %p\n", this);
 	mod = 1.0;
diff --git a/src/instrument.h b/src/instrument.h
index a531aec..621dddb 100644
--- a/src/instrument.h
+++ b/src/instrument.h
@@ -42,7 +42,7 @@ class Instrument
 {
 	friend class InstrumentParser;
 public:
-	Instrument(Settings& settings);
+	Instrument(Settings& settings, Random& rand);
 	~Instrument();
 
 	Sample* sample(level_t level, size_t pos);
@@ -69,7 +69,6 @@ private:
 	VersionStr version;
 
 	RangeMap<level_t, Sample*> samples;
-	PowerList powerlist;
 
 	void addSample(level_t a, level_t b, Sample* s);
 	void finalise(); ///< Signal instrument that no more samples will be added.
@@ -79,7 +78,8 @@ private:
 	size_t lastpos;
 	float mod;
 	Settings& settings;
-	Random rand;
+	Random& rand;
+	PowerList powerlist;
 };
 
 // typedef std::map< std::string, Instrument > Instruments;
diff --git a/src/powerlist.cc b/src/powerlist.cc
index f94dbb2..8fec8ce 100644
--- a/src/powerlist.cc
+++ b/src/powerlist.cc
@@ -51,7 +51,8 @@
 
 #define SIZE 500
 
-PowerList::PowerList()
+PowerList::PowerList(Random& rand)
+	: rand(rand)
 {
 	power_max = 0;
 	power_min = 100000000;
diff --git a/src/powerlist.h b/src/powerlist.h
index a3af475..53a42b8 100644
--- a/src/powerlist.h
+++ b/src/powerlist.h
@@ -34,7 +34,7 @@
 class PowerList
 {
 public:
-	PowerList();
+	PowerList(Random& rand);
 
 	void add(Sample* s);
 	void finalise(); ///< Call this when no more samples will be added.
@@ -49,7 +49,7 @@ private:
 		float power;
 	};
 
-	Random rand;
+	Random& rand;
 
 	std::vector<PowerListItem> samples;
 	float power_max;
diff --git a/src/random.cc b/src/random.cc
index 1df9a62..3d94a25 100644
--- a/src/random.cc
+++ b/src/random.cc
@@ -31,10 +31,14 @@
 Random::Random()
 	: Random(std::chrono::system_clock::now().time_since_epoch().count())
 {
-
 }
 
 Random::Random(unsigned int seed)
+{
+	setSeed(seed);
+}
+
+void Random::setSeed(unsigned int seed)
 {
 	generator.seed(seed);
 }
diff --git a/src/random.h b/src/random.h
index 9eaefad..c7fd599 100644
--- a/src/random.h
+++ b/src/random.h
@@ -35,6 +35,8 @@ public:
 	Random();
 	Random(unsigned int seed);
 
+	void setSeed(unsigned int seed);
+
 	//! \return random int in range [<lower_bound>, <upper_bound>].
 	int intInRange(int lower_bound, int upper_bound);
 
diff --git a/src/velocity.cc b/src/velocity.cc
index c8faa32..2d9bf8f 100644
--- a/src/velocity.cc
+++ b/src/velocity.cc
@@ -28,10 +28,11 @@
 
 #include <stdlib.h>
 
-Velocity::Velocity(unsigned int lower, unsigned int upper)
+Velocity::Velocity(unsigned int lower, unsigned int upper, Random& rand)
 	: lower{lower}
 	, upper{upper}
 	, samples{}
+	, rand(rand)
 {
 }
 
diff --git a/src/velocity.h b/src/velocity.h
index 19284a4..8392494 100644
--- a/src/velocity.h
+++ b/src/velocity.h
@@ -34,7 +34,7 @@
 class Velocity
 {
 public:
-	Velocity(unsigned int lower, unsigned int upper);
+	Velocity(unsigned int lower, unsigned int upper, Random& rand);
 
 	void addSample(Sample* sample, float probability);
 	Sample* getSample();
@@ -46,5 +46,5 @@ private:
 	typedef std::map<Sample*, float> Samples;
 	Samples samples;
 
-	Random rand;
+	Random& rand;
 };
diff --git a/test/Makefile.am b/test/Makefile.am
index a3a6294..594aa0d 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,4 +1,6 @@
 # Rules for the test code (use `make check` to execute)
+SUBDIRS = dgreftest
+
 include $(top_srcdir)/src/Makefile.am.drumgizmo
 
 TESTS = resource engine gui resampler lv2 configfile audiocache \
diff --git a/test/dgreftest/Makefile.am b/test/dgreftest/Makefile.am
new file mode 100644
index 0000000..cacf26a
--- /dev/null
+++ b/test/dgreftest/Makefile.am
@@ -0,0 +1,36 @@
+if ENABLE_CLI
+if HAVE_INPUT_MIDIFILE
+
+include $(top_srcdir)/src/Makefile.am.drumgizmo
+
+bin_PROGRAMS = dgreftest
+
+dgreftest_LDADD = $(DRUMGIZMO_LIBS) $(PTHREAD_LIBS) -ldl $(JACK_LIBS) \
+	$(SMF_LIBS)
+
+dgreftest_LDFLAGS =
+
+dgreftest_CXXFLAGS = $(SNDFILE_CXXFLAGS) $(PTHREAD_CFLAGS) $(EXPAT_CFLAGS) \
+	-I$(top_srcdir)/include -I$(top_srcdir)/src \
+	-I$(top_srcdir)/hugin -DWITH_HUG_MUTEX -DWITH_HUG_FILTER \
+	$(JACK_CFLAGS) $(SSEFLAGS) $(SMF_CFLAGS)
+
+dgreftest_CFLAGS = -DWITH_HUG_MUTEX -DWITH_HUG_FILTER
+
+dgreftest_SOURCES = \
+	dgreftest.cc \
+	midiinputengine.cc \
+	wavfileoutputengine.cc \
+	compareoutputengine.cc \
+	$(DRUMGIZMO_SOURCES) \
+	$(top_srcdir)/hugin/hugin.c \
+	$(top_srcdir)/hugin/hugin_filter.c
+
+endif # HAVE_INPUT_MIDIFILE
+
+EXTRA_DIST = \
+	midiinputengine.h \
+	wavfileoutputengine.h \
+	compareoutputengine.h
+
+endif # ENABLE_CLI
diff --git a/test/dgreftest/compareoutputengine.cc b/test/dgreftest/compareoutputengine.cc
new file mode 100644
index 0000000..4b13566
--- /dev/null
+++ b/test/dgreftest/compareoutputengine.cc
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ *            compareoutputengine.cc
+ *
+ *  Sat May 14 13:27:04 CEST 2016
+ *  Copyright 2016 Bent Bisballe Nyeng
+ *  deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ *  This file is part of DrumGizmo.
+ *
+ *  DrumGizmo is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  DrumGizmo 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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with DrumGizmo; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
+ */
+#include "compareoutputengine.h"
+
+#include <iostream>
+
+CompareOutputEngine::CompareOutputEngine()
+	: AudioOutputEngine{}
+	, info{}
+	, file{"output"}
+{
+	info.samplerate = 44100;
+	info.channels = 1;
+	info.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
+}
+
+CompareOutputEngine::~CompareOutputEngine()
+{
+	std::cout << "diff_samples: " << diff_samples << std::endl;
+	sf_close(handle);
+}
+
+bool CompareOutputEngine::init(const Channels& data)
+{
+	info.channels = data.size();
+
+	handle = sf_open(file.c_str(), SFM_READ, &info);
+	if(handle == nullptr)
+	{
+		std::cerr << "[CompareOutputEngine] Failed to open "
+		          << file << " for writing.\n";
+		return false;
+	}
+
+	return true;
+}
+
+void CompareOutputEngine::setParm(const std::string& parm, const std::string& value)
+{
+	if(parm == "file")
+	{
+		// apply output filename prefix
+		file = value;
+	}
+	else if(parm == "srate")
+	{
+		// try to apply samplerate
+		try
+		{
+			info.samplerate = std::stoi(value);
+		}
+		catch(...)
+		{
+			std::cerr << "[CompareOutputEngine] Invalid samplerate " << value
+			          << "\n";
+		}
+	}
+	else
+	{
+		std::cerr << "[CompareOutputEngine] Unsupported parameter '" << parm
+		          << "'\n";
+	}
+}
+
+bool CompareOutputEngine::start()
+{
+	return true;
+}
+
+void CompareOutputEngine::stop()
+{
+}
+
+void CompareOutputEngine::pre(size_t nsamples)
+{
+}
+
+void CompareOutputEngine::run(int ch, sample_t* samples, size_t nsamples)
+{
+	if(ch >= info.channels)
+	{
+		std::cerr << "[CompareOutputEngine] cannot access channel #" << ch
+		          << " (" << info.channels << " channels available)\n";
+		return;
+	}
+
+	for(std::size_t i = 0; i < nsamples; ++i)
+	{
+		buffer[i * info.channels + ch] = samples[i];
+	}
+}
+
+void CompareOutputEngine::post(size_t nsamples)
+{
+	sample_t ref_buffer[sizeof(buffer) / sizeof(sample_t)];
+	sf_readf_float(handle, ref_buffer, nsamples);
+
+	for(std::size_t i = 0; i < nsamples; ++i)
+	{
+		for(std::size_t ch = 0; ch < (std::size_t)info.channels; ++ch)
+		{
+			if(buffer[i * info.channels + ch] != ref_buffer[i * info.channels + ch])
+			{
+				++diff_samples;
+			}
+		}
+	}
+
+}
+
+size_t CompareOutputEngine::getSamplerate() const
+{
+	return info.samplerate;
+}
diff --git a/test/dgreftest/compareoutputengine.h b/test/dgreftest/compareoutputengine.h
new file mode 100644
index 0000000..fb610d6
--- /dev/null
+++ b/test/dgreftest/compareoutputengine.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ *            compareoutputengine.h
+ *
+ *  Sat May 14 13:27:04 CEST 2016
+ *  Copyright 2016 Bent Bisballe Nyeng
+ *  deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ *  This file is part of DrumGizmo.
+ *
+ *  DrumGizmo is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  DrumGizmo 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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with DrumGizmo; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
+ */
+#pragma once
+
+#include <sndfile.h>
+#include <audiooutputengine.h>
+
+class CompareOutputEngine
+	: public AudioOutputEngine
+{
+public:
+	CompareOutputEngine();
+	~CompareOutputEngine();
+
+	// based on AudioOutputEngine
+	bool init(const Channels& data) override;
+	void setParm(const std::string& parm, const std::string& value) override;
+	bool start() override;
+	void stop() override;
+	void pre(size_t nsamples) override;
+	void run(int ch, sample_t* samples, size_t nsamples) override;
+	void post(size_t nsamples) override;
+	size_t getSamplerate() const override;
+
+private:
+	SF_INFO info;
+	SNDFILE* handle;
+	std::string file;
+	sample_t buffer[4096 * 16];
+	std::size_t diff_samples{0};
+};
diff --git a/test/dgreftest/dgreftest.cc b/test/dgreftest/dgreftest.cc
new file mode 100644
index 0000000..072dcdc
--- /dev/null
+++ b/test/dgreftest/dgreftest.cc
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ *            dgreftest.cc
+ *
+ *  Thu May 12 17:50:08 CEST 2016
+ *  Copyright 2016 Bent Bisballe Nyeng
+ *  deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ *  This file is part of DrumGizmo.
+ *
+ *  DrumGizmo is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  DrumGizmo 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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with DrumGizmo; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
+ */
+#include <drumgizmo.h>
+
+#include <string>
+#include <iostream>
+#include <cassert>
+#include <unistd.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "compareoutputengine.h"
+#include "midiinputengine.h"
+#include "wavfileoutputengine.h"
+
+#include <cpp11fix.h> // required for c++11
+
+std::unique_ptr<AudioOutputEngine> createOutput(const std::string& name)
+{
+	struct stat sb;
+	if((stat(name.c_str(), &sb) == -1) && (errno == ENOENT))
+	{
+		// Ref file doesn't exist - create it.
+		return std::make_unique<WavfileOutputEngine>();
+	}
+	else
+	{
+		// Ref file exists, do compare.
+		return std::make_unique<CompareOutputEngine>();
+	}
+}
+
+int main(int argc, char* argv[])
+{
+	if(argc < 4)
+	{
+		std::cerr << "Usage: " << argv[0] << " testname drumkit midimap [seed]" << std::endl;
+		return 1;
+	}
+
+	Settings settings;
+
+	std::string test = argv[1];
+	std::string kitfile = argv[2];
+	std::string midimap = argv[3];
+
+	unsigned int seed = 0;
+	if(argc == 5)
+	{
+		seed = atoi(argv[4]);
+	}
+
+	std::string midifile = test + ".mid";
+	std::string reffile = test + ".wav";
+
+	MidifileInputEngine ie;
+	ie.setParm("file", midifile.c_str());
+
+	auto oe = createOutput(reffile);
+
+	oe->setParm("file", reffile.c_str());
+	oe->setParm("srate", "44100");
+
+	DrumGizmo drumgizmo(settings, oe.get(), &ie);
+	drumgizmo.setRandomSeed(seed);
+	drumgizmo.setFreeWheel(true); // Run in-sync with disk-cache
+	drumgizmo.setFrameSize(oe->getBufferSize());
+
+//	settings.enable_resampling.store(false); // Bypass resampler - BROKEN
+
+	settings.drumkit_file.store(kitfile);
+	settings.midimap_file.store(midimap);
+
+	printf("Loading drumkit, this may take a while:\n");
+
+	while(settings.drumkit_load_status.load() != LoadStatus::Done)
+	{
+		usleep(10000);
+
+		int total = settings.number_of_files.load();
+		int loaded = settings.number_of_files_loaded.load();
+
+		printf("\r%d of %d     ", loaded, total);
+		fflush(stdout);
+
+		if(settings.drumkit_load_status.load() == LoadStatus::Error)
+		{
+			printf("\nFailed to load \"%s\".\n", kitfile.c_str());
+			return 1;
+		}
+
+		if(loaded == total)
+		{
+			//break;
+		}
+	}
+	printf("\ndone\n");
+
+	drumgizmo.setSamplerate(oe->getSamplerate());
+
+
+	if(!drumgizmo.init())
+	{
+		printf("Failed init engine.\n");
+		return 1;
+	}
+
+	drumgizmo.run(-1);
+
+	return 0;
+}
diff --git a/test/dgreftest/midiinputengine.cc b/test/dgreftest/midiinputengine.cc
new file mode 100644
index 0000000..bc0fa87
--- /dev/null
+++ b/test/dgreftest/midiinputengine.cc
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ *            midiinputengine.cc
+ *
+ *  Sat May 14 13:26:23 CEST 2016
+ *  Copyright 2016 Bent Bisballe Nyeng
+ *  deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ *  This file is part of DrumGizmo.
+ *
+ *  DrumGizmo is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  DrumGizmo 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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with DrumGizmo; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
+ */
+#include "midiinputengine.h"
+
+#include <iostream>
+
+static int const NOTE_ON = 0x90;
+
+MidifileInputEngine::MidifileInputEngine()
+	: AudioInputEngineMidi{}
+	, smf{nullptr}
+	, current_event{nullptr}
+	, file{}
+	, speed{1.f}
+	, track{-1} // all tracks
+	, loop{false}
+	, offset{0.0}
+	, samplerate{44100.0} // todo: via ctor arg
+{
+}
+
+MidifileInputEngine::~MidifileInputEngine()
+{
+	if(smf != nullptr)
+	{
+		smf_delete(smf);
+	}
+}
+
+bool MidifileInputEngine::init(const Instruments& instruments)
+{
+	if(file == "")
+	{
+		std::cerr << "[MidifileInputEngine] Missing midi filename\n";
+		return false;
+	}
+	//if(midimap_filename == "")
+	//{
+	//	std::cerr << "[MidifileInputEngine] Missing midimap filename\n";
+	//	return false;
+	//}
+	smf = smf_load(file.c_str());
+	if(smf == nullptr)
+	{
+		std::cerr << "[MidifileInputEngine] Failed to load midifile '" << file
+		          << "'\n";
+		return false;
+	}
+	//if(!loadMidiMap(midimap_filename, instruments))
+	//{
+	//	std::cerr << "[MidifileInputEngine] Failed to parse midimap '"
+	//	          << midimap_filename << "'\n";
+	//	return false;
+	//}
+
+	return true;
+}
+
+void MidifileInputEngine::setParm(const std::string& parm, const std::string& value)
+{
+	if(parm == "file")
+	{
+		// apply midi input filename
+		file = value;
+	}
+	else if(parm == "speed")
+	{
+		// try to apply speed
+		try
+		{
+			speed = std::stof(value);
+		}
+		catch(...)
+		{
+			std::cerr << "[MidifileInputEngine] Invalid speed " << value
+			          << "\n";
+		}
+	}
+	//else if(parm == "midimap")
+	//{
+	//	// apply midimap filename
+	//	midimap_filename = value;
+	//}
+	else if(parm == "loop")
+	{
+		// apply looping
+		loop = true;
+	}
+	else
+	{
+		std::cerr << "[MidifileInputEngine] Unsupported parameter '" << parm
+		          << "'\n";
+	}
+}
+
+bool MidifileInputEngine::start()
+{
+	return true;
+}
+
+void MidifileInputEngine::stop()
+{
+}
+
+void MidifileInputEngine::pre()
+{
+}
+
+void MidifileInputEngine::run(size_t pos, size_t len, std::vector<event_t>& events)
+{
+	assert(events.empty());
+
+	double current_max_time = (1.0 + pos + len) / (samplerate / speed);
+	current_max_time -= offset;
+
+	if(!current_event)
+	{
+		current_event = smf_get_next_event(smf);
+	}
+
+	while(current_event && current_event->time_seconds < current_max_time)
+	{
+		if(!smf_event_is_metadata(current_event))
+		{
+			if((current_event->midi_buffer_length == 3) &&
+			    ((current_event->midi_buffer[0] & NOTE_ON) == NOTE_ON) &&
+			    (track == -1 || current_event->track_number == track) &&
+			    current_event->midi_buffer[2] > 0)
+			{
+				int key = current_event->midi_buffer[1];
+				int velocity = current_event->midi_buffer[2];
+
+				events.emplace_back();
+				auto& event = events.back();
+				event.type = TYPE_ONSET;
+				size_t evpos = current_event->time_seconds * (samplerate / speed);
+				event.offset = evpos - pos;
+
+				int i = mmap.lookup(key);
+				if(i != -1)
+				{
+					event.instrument = i;
+					event.velocity = velocity / 127.0;
+				}
+			}
+		}
+
+		current_event = smf_get_next_event(smf);
+	}
+
+	if(!current_event)
+	{
+		if(loop)
+		{
+			smf_rewind(smf);
+			offset += current_max_time;
+		}
+		else
+		{
+			assert(len >= 1);
+			events.push_back({TYPE_STOP, 0, len-1, 0.f});
+		}
+	}
+}
+
+void MidifileInputEngine::post()
+{
+}
diff --git a/test/dgreftest/midiinputengine.h b/test/dgreftest/midiinputengine.h
new file mode 100644
index 0000000..3740d44
--- /dev/null
+++ b/test/dgreftest/midiinputengine.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ *            midiinputengine.h
+ *
+ *  Sat May 14 13:26:22 CEST 2016
+ *  Copyright 2016 Bent Bisballe Nyeng
+ *  deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ *  This file is part of DrumGizmo.
+ *
+ *  DrumGizmo is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  DrumGizmo 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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with DrumGizmo; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
+ */
+#pragma once
+
+#include <audioinputenginemidi.h>
+#include <midimapper.h>
+#include <midimapparser.h>
+#include <string>
+#include <vector>
+
+#include <event.h>
+#include <smf.h>
+
+class MidifileInputEngine
+	: public AudioInputEngineMidi
+{
+public:
+	MidifileInputEngine();
+	~MidifileInputEngine();
+
+	// based on AudioInputEngineMidi
+	bool init(const Instruments& instruments) override;
+	void setParm(const std::string& parm, const std::string& value) override;
+	bool start() override;
+	void stop() override;
+	void pre() override;
+	void run(size_t pos, size_t len, std::vector<event_t>& events) override;
+	void post() override;
+
+private:
+	smf_t* smf;
+	smf_event_t* current_event;
+
+	std::string midimap_filename;
+	std::string file;
+	float speed;
+	int track;
+	bool loop;
+	double offset, samplerate;
+};
diff --git a/test/dgreftest/wavfileoutputengine.cc b/test/dgreftest/wavfileoutputengine.cc
new file mode 100644
index 0000000..6f971ad
--- /dev/null
+++ b/test/dgreftest/wavfileoutputengine.cc
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ *            wavfileoutputengine.cc
+ *
+ *  Sat May 14 13:26:51 CEST 2016
+ *  Copyright 2016 Bent Bisballe Nyeng
+ *  deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ *  This file is part of DrumGizmo.
+ *
+ *  DrumGizmo is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  DrumGizmo 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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with DrumGizmo; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
+ */
+#include "wavfileoutputengine.h"
+
+#include <iostream>
+
+WavfileOutputEngine::WavfileOutputEngine()
+	: AudioOutputEngine{}
+	, info{}
+	, file{"output"}
+{
+	info.samplerate = 44100;
+	info.channels = 1;
+	info.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
+}
+
+WavfileOutputEngine::~WavfileOutputEngine()
+{
+	sf_close(handle);
+}
+
+bool WavfileOutputEngine::init(const Channels& data)
+{
+	info.channels = data.size();
+
+	handle = sf_open(file.c_str(), SFM_WRITE, &info);
+	if(handle == nullptr)
+	{
+		std::cerr << "[WavfileOutputEngine] Failed to open "
+		          << file << " for writing.\n";
+		return false;
+	}
+
+	return true;
+}
+
+void WavfileOutputEngine::setParm(const std::string& parm, const std::string& value)
+{
+	if(parm == "file")
+	{
+		// apply output filename prefix
+		file = value;
+	}
+	else if(parm == "srate")
+	{
+		// try to apply samplerate
+		try
+		{
+			info.samplerate = std::stoi(value);
+		}
+		catch(...)
+		{
+			std::cerr << "[WavfileOutputEngine] Invalid samplerate " << value
+			          << "\n";
+		}
+	}
+	else
+	{
+		std::cerr << "[WavfileOutputEngine] Unsupported parameter '" << parm
+		          << "'\n";
+	}
+}
+
+bool WavfileOutputEngine::start()
+{
+	return true;
+}
+
+void WavfileOutputEngine::stop()
+{
+}
+
+void WavfileOutputEngine::pre(size_t nsamples)
+{
+}
+
+void WavfileOutputEngine::run(int ch, sample_t* samples, size_t nsamples)
+{
+	if(ch >= info.channels)
+	{
+		std::cerr << "[WavfileOutputEngine] cannot access channel #" << ch
+		          << " (" << info.channels << " channels available)\n";
+		return;
+	}
+
+	for(std::size_t i = 0; i < nsamples; ++i)
+	{
+		buffer[i * info.channels + ch] = samples[i];
+	}
+}
+
+void WavfileOutputEngine::post(size_t nsamples)
+{
+
+	sf_writef_float(handle, buffer, nsamples);
+}
+
+size_t WavfileOutputEngine::getSamplerate() const
+{
+	return info.samplerate;
+}
diff --git a/test/dgreftest/wavfileoutputengine.h b/test/dgreftest/wavfileoutputengine.h
new file mode 100644
index 0000000..6937ed2
--- /dev/null
+++ b/test/dgreftest/wavfileoutputengine.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ *            wavfileoutputengine.h
+ *
+ *  Sat May 14 13:26:51 CEST 2016
+ *  Copyright 2016 Bent Bisballe Nyeng
+ *  deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ *  This file is part of DrumGizmo.
+ *
+ *  DrumGizmo is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  DrumGizmo 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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with DrumGizmo; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
+ */
+#pragma once
+
+#include <sndfile.h>
+#include <audiooutputengine.h>
+
+class WavfileOutputEngine
+	: public AudioOutputEngine
+{
+public:
+	WavfileOutputEngine();
+	~WavfileOutputEngine();
+
+	// based on AudioOutputEngine
+	bool init(const Channels& data) override;
+	void setParm(const std::string& parm, const std::string& value) override;
+	bool start() override;
+	void stop() override;
+	void pre(size_t nsamples) override;
+	void run(int ch, sample_t* samples, size_t nsamples) override;
+	void post(size_t nsamples) override;
+	size_t getSamplerate() const override;
+
+private:
+	SF_INFO info;
+	SNDFILE* handle;
+	std::string file;
+	sample_t buffer[4096 * 16];
+};
-- 
cgit v1.2.3