gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

integrationTest.cpp (8888B)


      1 #include <iostream>
      2 
      3 #include "integrationTest.h"
      4 
      5 #include <fstream>
      6 #include <utility>
      7 
      8 #include "virusConsoleLib/consoleApp.h"
      9 
     10 #include "dsp56kEmu/jitunittests.h"
     11 
     12 #include "baseLib/commandline.h"
     13 #include "baseLib/filesystem.h"
     14 
     15 #include "synthLib/wavReader.h"
     16 
     17 #include "virusLib/romloader.h"
     18 
     19 namespace synthLib
     20 {
     21 	class WavReader;
     22 }
     23 
     24 int main(int _argc, char* _argv[])
     25 {
     26 	if constexpr (true)
     27 	{
     28 		try
     29 		{
     30 			puts("Running Unit Tests...");
     31 //			dsp56k::InterpreterUnitTests tests;
     32 			dsp56k::JitUnittests jitTests(false);
     33 			puts("Unit Tests finished.");
     34 		}
     35 		catch (const std::string& _err)
     36 		{
     37 			std::cout << "Unit test failed: " << _err << '\n';
     38 			return -1;
     39 		}
     40 	}
     41 
     42 	try
     43 	{
     44 		bool forever = true;
     45 
     46 		while(forever)
     47 		{
     48 			const baseLib::CommandLine cmd(_argc, _argv);
     49 
     50 			forever = cmd.contains("forever");
     51 
     52 			std::vector<std::pair<std::string, std::string>> finishedTests;	// rom, preset
     53 			finishedTests.reserve(64);
     54 
     55 			if(cmd.contains("rom") && cmd.contains("preset"))
     56 			{
     57 				const auto romFile = cmd.get("rom");
     58 				const auto preset = cmd.get("preset");
     59 
     60 				IntegrationTest test(cmd, romFile, preset, std::string(), virusLib::DeviceModel::Snow);
     61 
     62 				const auto res = test.run();
     63 				if(0 == res)
     64 					std::cout << "test successful, ROM " << baseLib::filesystem::getFilenameWithoutPath(romFile) << ", preset " << preset << '\n';
     65 				return res;
     66 			}
     67 			if(cmd.contains("folder"))
     68 			{
     69 				std::vector<std::string> subfolders;
     70 				baseLib::filesystem::getDirectoryEntries(subfolders, cmd.get("folder"));
     71 
     72 				if(subfolders.empty())
     73 				{
     74 					std::cout << "Nothing found for testing in folder " << cmd.get("folder") << '\n';
     75 					return -1;
     76 				}
     77 
     78 				for (auto& subfolder : subfolders)
     79 				{
     80 					if(subfolder.find("/.") != std::string::npos)
     81 						continue;
     82 					if(subfolder.find('#') != std::string::npos)
     83 						continue;
     84 
     85 					std::vector<std::string> files;
     86 					baseLib::filesystem::getDirectoryEntries(files, subfolder);
     87 
     88 					std::string romFile;
     89 					std::string presetsFile;
     90 
     91 					if(files.empty())
     92 					{
     93 						std::cout << "Directory " << subfolder << " doesn't contain any files" << '\n';
     94 						return -1;
     95 					}
     96 
     97 					for (auto& file : files)
     98 					{
     99 						if(baseLib::filesystem::hasExtension(file, ".txt"))
    100 							presetsFile = file;
    101 						else if(baseLib::filesystem::hasExtension(file, ".bin"))
    102 							romFile = file;
    103 						else if(baseLib::filesystem::hasExtension(file, ".mid"))
    104 						{
    105 							const auto rom = virusLib::ROMLoader::findROM(file);
    106 							if(rom.isValid())
    107 								romFile = file;
    108 						}
    109 					}
    110 
    111 					if(romFile.empty())
    112 					{
    113 						std::cout << "Failed to find ROM in folder " << subfolder << '\n';
    114 						return -1;
    115 					}
    116 					if(presetsFile.empty())
    117 					{
    118 						std::cout << "Failed to find presets file in folder " << subfolder << '\n';
    119 						return -1;
    120 					}
    121 
    122 					std::vector<std::string> presets;
    123 
    124 					std::ifstream ss;
    125 					ss.open(presetsFile.c_str(), std::ios::in);
    126 
    127 					if(!ss.is_open())
    128 					{
    129 						std::cout << "Failed to open presets file " << presetsFile << '\n';
    130 						return -1;
    131 					}
    132 
    133 					std::string line;
    134 
    135 					while(std::getline(ss, line))
    136 					{
    137 						while(!line.empty() && line.find_last_of("\r\n") != std::string::npos)
    138 							line = line.substr(0, line.size()-1);
    139 						if(!line.empty() && line[0] != '#')
    140 							presets.push_back(line);
    141 					}
    142 
    143 					ss.close();
    144 
    145 					if(presets.empty())
    146 					{
    147 						std::cout << "Presets file " << presetsFile << "  is empty" << '\n';
    148 						return -1;
    149 					}
    150 
    151 					for (auto& preset : presets)
    152 					{
    153 						IntegrationTest test(cmd, romFile, preset, subfolder + '/', virusLib::DeviceModel::Snow);
    154 						if(test.run() != 0)
    155 							return -1;
    156 						finishedTests.emplace_back(romFile, preset);
    157 					}
    158 				}
    159 
    160 				if(!forever)
    161 				{
    162 					std::cout << "All " << finishedTests.size() << " tests finished successfully:" << '\n';
    163 					for (const auto& [rom,preset] : finishedTests)
    164 						std::cout << "ROM " << baseLib::filesystem::getFilenameWithoutPath(rom) << ", preset " << preset << '\n';
    165 					return 0;
    166 				}
    167 			}
    168 		}
    169 		std::cout << "invalid command line arguments" << '\n';
    170 		return -1;
    171 	}
    172 	catch(const std::runtime_error& _err)
    173 	{
    174 		std::cout << _err.what() << '\n';
    175 		return -1;
    176 	}
    177 }
    178 
    179 IntegrationTest::IntegrationTest(const baseLib::CommandLine& _commandLine, std::string _romFile, std::string _presetName, std::string _outputFolder, const virusLib::DeviceModel _tiModel)
    180 	: m_cmd(_commandLine)
    181 	, m_romFile(std::move(_romFile))
    182 	, m_presetName(std::move(_presetName))
    183 	, m_outputFolder(std::move(_outputFolder))
    184 	, m_app(m_romFile, _tiModel)
    185 {
    186 }
    187 
    188 int IntegrationTest::run()
    189 {
    190 	if (!m_app.isValid())
    191 	{
    192 		std::cout << "Failed to load ROM " << m_romFile << ", make sure that the ROM file is valid" << '\n';
    193 		return -1;
    194 	}
    195 
    196 	if (!m_app.loadSingle(m_presetName))
    197 	{
    198 		std::cout << "Failed to find preset '" << m_presetName << "', make sure to use a ROM that contains it" << '\n';
    199 		return -1;
    200 	}
    201 
    202 	const int lengthSeconds = m_cmd.contains("length") ? m_cmd.getInt("length") : 0;
    203 	if(lengthSeconds > 0)
    204 	{
    205 		// create reference file
    206 		return runCreate(lengthSeconds);
    207 	}
    208 
    209 	const auto referenceFile = m_app.getSingleNameAsFilename();
    210 
    211 	if (!loadAudioFile(m_referenceFile, m_outputFolder + referenceFile))
    212 		return -1;
    213 
    214 	return runCompare();
    215 }
    216 
    217 bool IntegrationTest::loadAudioFile(File& _dst, const std::string& _filename) const
    218 {
    219 	const auto hFile = fopen(_filename.c_str(), "rb");
    220 	if (!hFile)
    221 	{
    222 		std::cout << "Failed to load wav file " << _filename << " for comparison" << '\n';
    223 		return false;
    224 	}
    225 	fseek(hFile, 0, SEEK_END);
    226 	const size_t size = ftell(hFile);
    227 	_dst.file.resize(size);
    228 	fseek(hFile, 0, SEEK_SET);
    229 	if (fread(&_dst.file.front(), 1, size, hFile) != size)
    230 	{
    231 		std::cout << "Failed to read data from file " << _filename << '\n';
    232 		fclose(hFile);
    233 		return false;
    234 	}
    235 	fclose(hFile);
    236 
    237 	_dst.data.data = nullptr;
    238 
    239 	if (!synthLib::WavReader::load(_dst.data, nullptr, &_dst.file.front(), _dst.file.size()))
    240 	{
    241 		std::cout << "Failed to interpret file " << _filename << " as wave data, make sure that the file is a valid 24 bit stereo wav file" << '\n';
    242 		return false;
    243 	}
    244 
    245 	if(_dst.data.samplerate != m_app.getRom().getSamplerate())
    246 	{
    247 		std::cout << "Wave file " << _filename << " does not have the correct samplerate, expected " << m_app.getRom().getSamplerate() << " but got " << _dst.data.samplerate << " instead" << '\n';
    248 		return false;
    249 	}
    250 
    251 	if (_dst.data.bitsPerSample != 24 || _dst.data.channels != 2 || _dst.data.isFloat)
    252 	{
    253 		std::cout << "Wave file " << _filename << " has an invalid format, expected 24 bit / 2 channels but got " << _dst.data.bitsPerSample << " bit / " << _dst.data.channels << " channels" << '\n';
    254 		return false;
    255 	}
    256 	return true;
    257 }
    258 
    259 int IntegrationTest::runCompare()
    260 {
    261 	const auto sampleCount = m_referenceFile.data.dataByteSize * 8 / m_referenceFile.data.bitsPerSample;
    262 	const auto frameCount = sampleCount >> 1;
    263 
    264 	std::vector<uint8_t> temp;
    265 
    266 	File compareFile;
    267 	const auto res = createAudioFile(compareFile, "compare_", static_cast<uint32_t>(frameCount));
    268 	if(res)
    269 		return res;
    270 
    271 	auto* ptrA = static_cast<const uint8_t*>(compareFile.data.data);
    272 	auto* ptrB = static_cast<const uint8_t*>(m_referenceFile.data.data);
    273 
    274 	for(uint32_t i=0; i<sampleCount; ++i)
    275 	{
    276 		const uint32_t a = (static_cast<uint32_t>(ptrA[0]) << 24) | (static_cast<uint32_t>(ptrA[1]) << 16) | ptrA[2];
    277 		const uint32_t b = (static_cast<uint32_t>(ptrB[0]) << 24) | (static_cast<uint32_t>(ptrB[1]) << 16) | ptrB[2];
    278 
    279 		if(b != a)
    280 		{
    281 			std::cout << "Test failed, audio output is not identical to reference file, difference starting at frame " << (i>>1) << ", ROM " << m_romFile << ", preset " << m_presetName << '\n';
    282 			return -2;
    283 		}
    284 
    285 		ptrA += 3;
    286 		ptrB += 3;
    287 	}
    288 
    289 	std::cout << "Test succeeded, compared " << sampleCount << " samples, ROM " << m_romFile << ", preset " << m_presetName << '\n';
    290 	return 0;
    291 }
    292 
    293 int IntegrationTest::runCreate(const int _lengthSeconds)
    294 {
    295 	const auto sampleCount = m_app.getRom().getSamplerate() * _lengthSeconds;
    296 
    297 	File file;
    298 	return createAudioFile(file, "", sampleCount);
    299 }
    300 
    301 int IntegrationTest::createAudioFile(File& _dst, const std::string& _prefix, const uint32_t _sampleCount)
    302 {
    303 	const auto filename = m_outputFolder + _prefix + m_app.getSingleNameAsFilename();
    304 
    305 	auto* hFile = fopen(filename.c_str(), "wb");
    306 
    307 	if(!hFile)
    308 	{
    309 		std::cout << "Failed to create output file " << filename << '\n';
    310 		return -1;
    311 	}
    312 
    313 	fclose(hFile);
    314 
    315 	m_app.run(filename, _sampleCount);
    316 
    317 	if(!loadAudioFile(_dst, filename))
    318 	{
    319 		std::cout << "Failed to open written file " << filename << " for verification" << '\n';
    320 		return -1;
    321 	}
    322 
    323 	const auto sampleCount = _dst.data.dataByteSize * 8 / _dst.data.bitsPerSample / 2;
    324 
    325 	if(sampleCount != _sampleCount)
    326 	{
    327 		std::cout << "Verification of written file failed, expected " << _sampleCount << " samples but file only has " << sampleCount << " samples" << '\n';
    328 		return -1;
    329 	}
    330 	return 0;
    331 }