/* Test-only AquaSynth ZynAddSubFX PAD oracle. This file links against the pinned ZynAddSubFX reference source in an ignored artifact build. It is development/parity tooling, not AquaSynth runtime code. */ #include "../globals.h" #include "../DSP/FFTwrapper.h" #include "../Misc/Allocator.h" #include "../Misc/Time.h" #include "../Misc/Util.h" #include "../Misc/XMLwrapper.h" #include "../Params/Controller.h" #include "../Params/PADnoteParameters.h" #include "../Synth/OscilGen.h" #include "../Synth/PADnote.h" #include #include #include #include #include #include #include #include using namespace zyn; namespace { bool enterInstrument(XMLwrapper& xml) { if(xml.enterbranch("INSTRUMENT")) return true; if(xml.enterbranch("MASTER")) { if(xml.enterbranch("PART", 0) && xml.enterbranch("INSTRUMENT")) return true; } return false; } int parseInt(const char* text, const char* name) { char* end = nullptr; const long value = std::strtol(text, &end, 10); if(end == text || *end != '\0') { std::cerr << "Invalid " << name << ": " << text << "\n"; std::exit(2); } return static_cast(value); } void writeFloat32(const std::string& path, const float* samples, int count) { std::ofstream output(path, std::ios::binary); if(!output) { std::cerr << "Could not open output file: " << path << "\n"; std::exit(2); } output.write(reinterpret_cast(samples), sizeof(float) * count); if(!output) { std::cerr << "Could not write output file: " << path << "\n"; std::exit(2); } } void normalizeMaxLikePad(float* values, int count) { float max = 0.0f; for(int i = 0; i < count; ++i) max = std::max(max, std::fabs(values[i])); if(max > 0.000001f) for(int i = 0; i < count; ++i) values[i] /= max; } void writeHarmonics( PADnoteParameters& parameters, const SYNTH_T& synth, const std::string& path, float baseFrequency) { std::vector harmonics(synth.oscilsize, 0.0f); parameters.oscilgen->get(harmonics.data(), baseFrequency, true); normalizeMaxLikePad(harmonics.data(), synth.oscilsize / 2); writeFloat32(path, harmonics.data(), synth.oscilsize / 2); } int selectedSampleIndex(const PADnoteParameters& parameters, float frequency) { const float noteLog2Frequency = std::log2(frequency); const float detune = getdetune( parameters.PDetuneType, parameters.PCoarseDetune, parameters.PDetune); const float log2Frequency = noteLog2Frequency + detune / 1200.0f; float minDistance = std::fabs(log2Frequency - std::log2(parameters.sample[0].basefreq + 0.0001f)); int selected = 0; for(int i = 1; i < PAD_MAX_SAMPLES; ++i) { if(parameters.sample[i].smp == nullptr) break; const float distance = std::fabs(log2Frequency - std::log2(parameters.sample[i].basefreq + 0.0001f)); if(distance < minDistance) { selected = i; minDistance = distance; } } return selected; } void renderNote( const PADnoteParameters& parameters, const SYNTH_T& synth, const AbsTime& time, const std::string& path, float frequency, float seconds) { Alloc memory; Controller controller(synth, &time); const float log2Frequency = std::log2(frequency); SynthParams synthParams{memory, controller, synth, time, 120, nullptr, log2Frequency, false, prng()}; PADnote note(¶meters, synthParams, 0, nullptr, nullptr, false); const int total = std::max(1, static_cast(seconds * synth.samplerate)); std::vector rendered(total); std::vector left(synth.buffersize); std::vector right(synth.buffersize); int written = 0; while(written < total) { std::fill(left.begin(), left.end(), 0.0f); std::fill(right.begin(), right.end(), 0.0f); note.noteout(left.data(), right.data()); const int count = std::min(synth.buffersize, total - written); std::copy(left.begin(), left.begin() + count, rendered.begin() + written); written += count; } writeFloat32(path, rendered.data(), static_cast(rendered.size())); } } int main(int argc, char** argv) { if(argc < 3 || argc > 7) { std::cerr << "Usage: ZynPadReference [kit-index] [sample-index|selected|note|harmonics] [frequency-hz] [seconds]\n"; return 2; } const std::string inputPath = argv[1]; const std::string outputPath = argv[2]; const int kitIndex = argc >= 4 ? parseInt(argv[3], "kit index") : 0; const bool renderAudio = argc >= 5 && std::string(argv[4]) == "note"; const bool renderHarmonics = argc >= 5 && std::string(argv[4]) == "harmonics"; const bool renderSelectedSample = argc >= 5 && std::string(argv[4]) == "selected"; const int requestedSample = argc >= 5 && !renderAudio && !renderHarmonics && !renderSelectedSample ? parseInt(argv[4], "sample index") : 0; const float frequency = argc >= 6 ? std::strtof(argv[5], nullptr) : 261.6256f; const float seconds = argc >= 7 ? std::strtof(argv[6], nullptr) : 1.5f; SYNTH_T synth; synth.samplerate = 44100; synth.buffersize = 256; synth.oscilsize = 1024; synth.alias(false); AbsTime time(synth); FFTwrapper fft(synth.oscilsize); PADnoteParameters parameters(synth, &fft, &time); XMLwrapper xml; if(xml.loadXMLfile(inputPath) != 0) { std::cerr << "Could not load Zyn instrument: " << inputPath << "\n"; return 2; } if(!enterInstrument(xml) || !xml.enterbranch("INSTRUMENT_KIT") || !xml.enterbranch("INSTRUMENT_KIT_ITEM", kitIndex) || !xml.enterbranch("PAD_SYNTH_PARAMETERS")) { std::cerr << "Could not find PAD_SYNTH_PARAMETERS for kit item " << kitIndex << " in " << inputPath << "\n"; return 2; } parameters.getfromXML(xml); parameters.applyparameters([] { return false; }, 1); if(renderAudio) { renderNote(parameters, synth, time, outputPath, frequency, seconds); std::cout << "mode=note samples=" << static_cast(seconds * synth.samplerate) << " frequency=" << frequency << "\n"; FFT_cleanup(); return 0; } if(renderHarmonics) { writeHarmonics(parameters, synth, outputPath, frequency); std::cout << "mode=harmonics count=" << synth.oscilsize / 2 << " basefreq=" << frequency << "\n"; FFT_cleanup(); return 0; } int sampleIndex = renderSelectedSample ? selectedSampleIndex(parameters, frequency) : requestedSample; if(sampleIndex < 0 || sampleIndex >= PAD_MAX_SAMPLES || parameters.sample[sampleIndex].smp == nullptr) { sampleIndex = -1; for(int i = 0; i < PAD_MAX_SAMPLES; ++i) { if(parameters.sample[i].smp != nullptr) { sampleIndex = i; break; } } } if(sampleIndex < 0) { std::cerr << "Zyn generated no PAD samples for " << inputPath << "\n"; return 2; } const auto& sample = parameters.sample[sampleIndex]; writeFloat32(outputPath, sample.smp, sample.size); const int preview = std::min(sample.size, 8); std::cout << "sampleIndex=" << sampleIndex << " size=" << sample.size << " basefreq=" << sample.basefreq << " preview="; for(int i = 0; i < preview; ++i) { if(i) std::cout << ","; std::cout << sample.smp[i]; } std::cout << "\n"; FFT_cleanup(); return 0; }