SampleFormatSFZ.cpp 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270
  1. /*
  2. * SampleFormatSFZ.cpp
  3. * -------------------
  4. * Purpose: Loading and saving SFZ instruments.
  5. * Notes : (currently none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "Sndfile.h"
  11. #ifdef MODPLUG_TRACKER
  12. #include "../mptrack/TrackerSettings.h"
  13. #endif // MODPLUG_TRACKER
  14. #ifndef MODPLUG_NO_FILESAVE
  15. #include "../common/mptFileIO.h"
  16. #endif // !MODPLUG_NO_FILESAVE
  17. #include "modsmp_ctrl.h"
  18. #include "mpt/base/numbers.hpp"
  19. #include <functional>
  20. OPENMPT_NAMESPACE_BEGIN
  21. #ifdef MPT_EXTERNAL_SAMPLES
  22. template<size_t N>
  23. static bool SFZStartsWith(const std::string_view &l, const char(&r)[N])
  24. {
  25. return l.substr(0, N - 1) == r;
  26. }
  27. template <size_t N>
  28. static bool SFZEndsWith(const std::string_view &l, const char (&r)[N])
  29. {
  30. return l.size() >= (N - 1) && l.substr(l.size() - (N - 1), N - 1) == r;
  31. }
  32. static bool SFZIsNumeric(const std::string_view &str)
  33. {
  34. return std::find_if(str.begin(), str.end(), [](char c) { return c < '0' || c > '9'; }) == str.end();
  35. }
  36. struct SFZControl
  37. {
  38. std::string defaultPath;
  39. int8 octaveOffset = 0, noteOffset = 0;
  40. void Parse(const std::string_view key, const std::string &value)
  41. {
  42. if(key == "default_path")
  43. defaultPath = value;
  44. else if(key == "octave_offset")
  45. octaveOffset = ConvertStrTo<int8>(value);
  46. else if(key == "note_offset")
  47. noteOffset = ConvertStrTo<int8>(value);
  48. }
  49. };
  50. struct SFZFlexEG
  51. {
  52. using PointIndex = decltype(InstrumentEnvelope().nLoopStart);
  53. std::vector<std::pair<double, double>> points;
  54. double amplitude = 0; // percentage (100 = full volume range)
  55. double pan = 0; // percentage (100 = full pan range)
  56. double pitch = 0; // in cents
  57. double cutoff = 0; // in cents
  58. PointIndex sustain = 0;
  59. void Parse(std::string_view key, const std::string &value)
  60. {
  61. key = key.substr(key.find('_') + 1);
  62. const double v = ConvertStrTo<double>(value);
  63. const bool isTime = SFZStartsWith(key, "time"), isLevel = SFZStartsWith(key, "level");
  64. std::string_view pointStr;
  65. if(isTime)
  66. pointStr = key.substr(4);
  67. else if(isLevel)
  68. pointStr = key.substr(5);
  69. if(!pointStr.empty() && SFZIsNumeric(pointStr))
  70. {
  71. PointIndex point = ConvertStrTo<PointIndex>(std::string(pointStr));
  72. if(point >= points.size() && point < MAX_ENVPOINTS)
  73. points.resize(point + 1);
  74. if(point < points.size())
  75. {
  76. if(isTime)
  77. points[point].first = v;
  78. else
  79. points[point].second = v;
  80. }
  81. return;
  82. }
  83. if(key == "points")
  84. points.resize(std::min(static_cast<PointIndex>(v), static_cast<PointIndex>(MAX_ENVPOINTS)));
  85. else if(key == "sustain")
  86. sustain = mpt::saturate_round<PointIndex>(v);
  87. else if(key == "amplitude" || key == "ampeg")
  88. amplitude = v;
  89. else if(key == "pan")
  90. pan = v;
  91. else if(key == "pitch")
  92. pitch = v;
  93. else if(key == "cutoff")
  94. cutoff = v;
  95. }
  96. void ConvertToMPT(ModInstrument *ins, const CSoundFile &sndFile) const
  97. {
  98. if(amplitude)
  99. ConvertToMPT(ins, sndFile, ENV_VOLUME, amplitude / 100.0, 0.0, 1.0);
  100. if(pan)
  101. ConvertToMPT(ins, sndFile, ENV_PANNING, pan / 100.0, -1.0, 1.0);
  102. if(pitch)
  103. ConvertToMPT(ins, sndFile, ENV_PITCH, pitch / 1600.0, -1.0, 1.0);
  104. if(cutoff)
  105. ConvertToMPT(ins, sndFile, ENV_PITCH, cutoff, 0.0, 1.0, true);
  106. }
  107. void ConvertToMPT(ModInstrument *ins, const CSoundFile &sndFile, EnvelopeType envType, double scale, double minVal, double maxVal, bool forceFilter = false) const
  108. {
  109. const double tickDuration = sndFile.m_PlayState.m_nSamplesPerTick / static_cast<double>(sndFile.GetSampleRate());
  110. if(tickDuration <= 0 || points.empty() || scale == 0.0)
  111. return;
  112. auto &env = ins->GetEnvelope(envType);
  113. std::function<double(double)> conversionFunc = Identity;
  114. if(forceFilter && envType == ENV_PITCH)
  115. {
  116. env.dwFlags.set(ENV_FILTER);
  117. conversionFunc = FilterConversionFunc(*ins, sndFile);
  118. }
  119. env.clear();
  120. env.reserve(points.size());
  121. const auto ToValue = std::bind(SFZFlexEG::ToValue, std::placeholders::_1, scale, minVal, maxVal, conversionFunc);
  122. int32 prevTick = -1;
  123. // If the first envelope point's time is greater than 0, we fade in from a neutral value
  124. if(points.front().first > 0)
  125. {
  126. env.push_back({0, ToValue(0.0)});
  127. prevTick = 0;
  128. }
  129. for(const auto &point : points)
  130. {
  131. const auto tick = mpt::saturate_cast<EnvelopeNode::tick_t>(prevTick + ToTicks(point.first, tickDuration));
  132. const auto value = ToValue(point.second);
  133. env.push_back({tick, value});
  134. prevTick = tick;
  135. if(tick == Util::MaxValueOfType(tick))
  136. break;
  137. }
  138. if(sustain < env.size())
  139. {
  140. env.nSustainStart = env.nSustainEnd = sustain;
  141. env.dwFlags.set(ENV_SUSTAIN);
  142. } else
  143. {
  144. env.dwFlags.reset(ENV_SUSTAIN);
  145. }
  146. env.dwFlags.set(ENV_ENABLED);
  147. if(envType == ENV_VOLUME && env.nSustainEnd > 0)
  148. env.nReleaseNode = env.nSustainEnd;
  149. }
  150. protected:
  151. static EnvelopeNode::tick_t ToTicks(double duration, double tickDuration)
  152. {
  153. return std::max(EnvelopeNode::tick_t(1), mpt::saturate_round<EnvelopeNode::tick_t>(duration / tickDuration));
  154. }
  155. static EnvelopeNode::value_t ToValue(double value, double scale, double minVal, double maxVal, const std::function<double(double)> &conversionFunc)
  156. {
  157. value = conversionFunc((value * scale - minVal) / (maxVal - minVal)) * ENVELOPE_MAX + ENVELOPE_MIN;
  158. Limit<double, double>(value, ENVELOPE_MIN, ENVELOPE_MAX);
  159. return mpt::saturate_round<EnvelopeNode::value_t>(value);
  160. }
  161. static double Identity(double v) noexcept { return v; }
  162. static double CentsToFilterCutoff(double v, const CSoundFile &sndFile, int envBaseCutoff, uint32 envBaseFreq)
  163. {
  164. const auto freq = envBaseFreq * std::pow(2.0, v / 1200.0);
  165. return Util::muldivr(sndFile.FrequencyToCutOff(freq), 127, envBaseCutoff) / 127.0;
  166. }
  167. static std::function<double(double)> FilterConversionFunc(const ModInstrument &ins, const CSoundFile &sndFile)
  168. {
  169. const auto envBaseCutoff = ins.IsCutoffEnabled() ? ins.GetCutoff() : 127;
  170. const auto envBaseFreq = sndFile.CutOffToFrequency(envBaseCutoff);
  171. return std::bind(CentsToFilterCutoff, std::placeholders::_1, std::cref(sndFile), envBaseCutoff, envBaseFreq);
  172. }
  173. };
  174. struct SFZEnvelope
  175. {
  176. double startLevel = 0, delay = 0, attack = 0, hold = 0;
  177. double decay = 0, sustainLevel = 100, release = 0, depth = 0;
  178. void Parse(std::string_view key, const std::string &value)
  179. {
  180. key = key.substr(key.find('_') + 1);
  181. double v = ConvertStrTo<double>(value);
  182. if(key == "depth")
  183. Limit(v, -12000.0, 12000.0);
  184. else if(key == "start" || key == "sustain")
  185. Limit(v, -100.0, 100.0);
  186. else
  187. Limit(v, 0.0, 100.0);
  188. if(key == "start")
  189. startLevel = v;
  190. else if(key == "delay")
  191. delay = v;
  192. else if(key == "attack")
  193. attack = v;
  194. else if(key == "hold")
  195. hold = v;
  196. else if(key == "decay")
  197. decay = v;
  198. else if(key == "sustain")
  199. sustainLevel = v;
  200. else if(key == "release")
  201. release = v;
  202. else if(key == "depth")
  203. depth = v;
  204. }
  205. void ConvertToMPT(ModInstrument *ins, const CSoundFile &sndFile, EnvelopeType envType, bool forceFilter = false) const
  206. {
  207. SFZFlexEG eg;
  208. if(envType == ENV_VOLUME)
  209. eg.amplitude = 1.0;
  210. else if(envType == ENV_PITCH && !forceFilter)
  211. eg.pitch = depth / 100.0;
  212. else if(envType == ENV_PITCH && forceFilter)
  213. eg.cutoff = depth / 100.0;
  214. auto &env = eg.points;
  215. if(attack > 0 || delay > 0)
  216. {
  217. env.push_back({0.0, startLevel});
  218. if(delay > 0)
  219. env.push_back({delay, env.back().second});
  220. env.push_back({attack, 100.0});
  221. }
  222. if(hold > 0)
  223. {
  224. if(env.empty())
  225. env.push_back({0.0, 100.0});
  226. env.push_back({hold, env.back().second});
  227. }
  228. if(env.empty())
  229. env.push_back({0.0, 100.0});
  230. if(env.back().second != sustainLevel)
  231. env.push_back({decay, sustainLevel});
  232. if(sustainLevel != 0)
  233. {
  234. eg.sustain = static_cast<SFZFlexEG::PointIndex>(env.size() - 1);
  235. env.push_back({release, 0.0});
  236. } else
  237. {
  238. eg.sustain = std::numeric_limits<SFZFlexEG::PointIndex>::max();
  239. }
  240. eg.ConvertToMPT(ins, sndFile);
  241. }
  242. };
  243. struct SFZRegion
  244. {
  245. enum class LoopMode
  246. {
  247. kUnspecified,
  248. kContinuous,
  249. kOneShot,
  250. kSustain,
  251. kNoLoop
  252. };
  253. enum class LoopType
  254. {
  255. kUnspecified,
  256. kForward,
  257. kBackward,
  258. kAlternate,
  259. };
  260. size_t filenameOffset = 0;
  261. std::string filename, name;
  262. SFZEnvelope ampEnv, pitchEnv, filterEnv;
  263. std::vector<SFZFlexEG> flexEGs;
  264. SmpLength loopStart = 0, loopEnd = 0;
  265. SmpLength end = MAX_SAMPLE_LENGTH, offset = 0;
  266. LoopMode loopMode = LoopMode::kUnspecified;
  267. LoopType loopType = LoopType::kUnspecified;
  268. double loopCrossfade = 0.0;
  269. double cutoff = 0; // in Hz
  270. double resonance = 0; // 0...40dB
  271. double filterRandom = 0; // 0...9600 cents
  272. double volume = 0; // -144dB...+6dB
  273. double amplitude = 100.0; // 0...100
  274. double pitchBend = 200; // -9600...9600 cents
  275. double pitchLfoFade = 0; // 0...100 seconds
  276. double pitchLfoDepth = 0; // -1200...12000
  277. double pitchLfoFreq = 0; // 0...20 Hz
  278. double panning = -128; // -100...+100
  279. double finetune = 0; // in cents
  280. int8 transpose = 0;
  281. uint8 keyLo = 0, keyHi = 127, keyRoot = 60;
  282. FilterMode filterType = FilterMode::Unchanged;
  283. uint8 polyphony = 255;
  284. bool useSampleKeyRoot = false;
  285. bool invertPhase = false;
  286. template<typename T, typename Tc>
  287. static void Read(const std::string &valueStr, T &value, Tc valueMin = std::numeric_limits<T>::min(), Tc valueMax = std::numeric_limits<T>::max())
  288. {
  289. double valueF = ConvertStrTo<double>(valueStr);
  290. if constexpr(std::numeric_limits<T>::is_integer)
  291. {
  292. valueF = mpt::round(valueF);
  293. }
  294. Limit(valueF, static_cast<double>(valueMin), static_cast<double>(valueMax));
  295. value = static_cast<T>(valueF);
  296. }
  297. static uint8 ReadKey(const std::string &value, const SFZControl &control)
  298. {
  299. if(value.empty())
  300. return 0;
  301. int key = 0;
  302. if(value[0] >= '0' && value[0] <= '9')
  303. {
  304. // MIDI key
  305. key = ConvertStrTo<uint8>(value);
  306. } else if(value.length() < 2)
  307. {
  308. return 0;
  309. } else
  310. {
  311. // Scientific pitch
  312. static constexpr int8 keys[] = { 9, 11, 0, 2, 4, 5, 7 };
  313. static_assert(std::size(keys) == 'g' - 'a' + 1);
  314. auto keyC = value[0];
  315. if(keyC >= 'A' && keyC <= 'G')
  316. key = keys[keyC - 'A'];
  317. if(keyC >= 'a' && keyC <= 'g')
  318. key = keys[keyC - 'a'];
  319. else
  320. return 0;
  321. uint8 octaveOffset = 1;
  322. if(value[1] == '#')
  323. {
  324. key++;
  325. octaveOffset = 2;
  326. } else if(value[1] == 'b' || value[1] == 'B')
  327. {
  328. key--;
  329. octaveOffset = 2;
  330. }
  331. if(octaveOffset >= value.length())
  332. return 0;
  333. int8 octave = ConvertStrTo<int8>(value.c_str() + octaveOffset);
  334. key += (octave + 1) * 12;
  335. }
  336. key += control.octaveOffset * 12 + control.noteOffset;
  337. return static_cast<uint8>(Clamp(key, 0, 127));
  338. }
  339. void Parse(const std::string_view key, const std::string &value, const SFZControl &control)
  340. {
  341. if(key == "sample")
  342. {
  343. filename = control.defaultPath + value;
  344. filenameOffset = control.defaultPath.size();
  345. }
  346. else if(key == "region_label")
  347. name = value;
  348. else if(key == "lokey")
  349. keyLo = ReadKey(value, control);
  350. else if(key == "hikey")
  351. keyHi = ReadKey(value, control);
  352. else if(key == "pitch_keycenter")
  353. {
  354. keyRoot = ReadKey(value, control);
  355. useSampleKeyRoot = (value == "sample");
  356. }
  357. else if(key == "key")
  358. {
  359. keyLo = keyHi = keyRoot = ReadKey(value, control);
  360. useSampleKeyRoot = false;
  361. }
  362. else if(key == "bend_up" || key == "bendup")
  363. Read(value, pitchBend, -9600.0, 9600.0);
  364. else if(key == "pitchlfo_fade")
  365. Read(value, pitchLfoFade, 0.0, 100.0);
  366. else if(key == "pitchlfo_depth")
  367. Read(value, pitchLfoDepth, -12000.0, 12000.0);
  368. else if(key == "pitchlfo_freq")
  369. Read(value, pitchLfoFreq, 0.0, 20.0);
  370. else if(key == "volume")
  371. Read(value, volume, -144.0, 6.0);
  372. else if(key == "amplitude")
  373. Read(value, amplitude, 0.0, 100.0);
  374. else if(key == "pan")
  375. Read(value, panning, -100.0, 100.0);
  376. else if(key == "transpose")
  377. Read(value, transpose, -127, 127);
  378. else if(key == "tune")
  379. Read(value, finetune, -100.0, 100.0);
  380. else if(key == "end")
  381. Read(value, end, SmpLength(0), MAX_SAMPLE_LENGTH);
  382. else if(key == "offset")
  383. Read(value, offset, SmpLength(0), MAX_SAMPLE_LENGTH);
  384. else if(key == "loop_start" || key == "loopstart")
  385. Read(value, loopStart, SmpLength(0), MAX_SAMPLE_LENGTH);
  386. else if(key == "loop_end" || key == "loopend")
  387. Read(value, loopEnd, SmpLength(0), MAX_SAMPLE_LENGTH);
  388. else if(key == "loop_crossfade" || key == "loopcrossfade")
  389. Read(value, loopCrossfade, 0.0, DBL_MAX);
  390. else if(key == "loop_mode" || key == "loopmode")
  391. {
  392. if(value == "loop_continuous")
  393. loopMode = LoopMode::kContinuous;
  394. else if(value == "one_shot")
  395. loopMode = LoopMode::kOneShot;
  396. else if(value == "loop_sustain")
  397. loopMode = LoopMode::kSustain;
  398. else if(value == "no_loop")
  399. loopMode = LoopMode::kNoLoop;
  400. }
  401. else if(key == "loop_type" || key == "looptype")
  402. {
  403. if(value == "forward")
  404. loopType = LoopType::kForward;
  405. else if(value == "backward")
  406. loopType = LoopType::kBackward;
  407. else if(value == "alternate")
  408. loopType = LoopType::kAlternate;
  409. }
  410. else if(key == "cutoff")
  411. Read(value, cutoff, 0.0, 96000.0);
  412. else if(key == "fil_random")
  413. Read(value, filterRandom, 0.0, 9600.0);
  414. else if(key == "resonance")
  415. Read(value, resonance, 0.0, 40.0);
  416. else if(key == "polyphony")
  417. Read(value, polyphony, 0, 255);
  418. else if(key == "phase")
  419. invertPhase = (value == "invert");
  420. else if(key == "fil_type" || key == "filtype")
  421. {
  422. if(value == "lpf_1p" || value == "lpf_2p" || value == "lpf_4p" || value == "lpf_6p")
  423. filterType = FilterMode::LowPass;
  424. else if(value == "hpf_1p" || value == "hpf_2p" || value == "hpf_4p" || value == "hpf_6p")
  425. filterType = FilterMode::HighPass;
  426. // Alternatives: bpf_2p, brf_2p
  427. }
  428. else if(SFZStartsWith(key, "ampeg_"))
  429. ampEnv.Parse(key, value);
  430. else if(SFZStartsWith(key, "fileg_"))
  431. filterEnv.Parse(key, value);
  432. else if(SFZStartsWith(key, "pitcheg_"))
  433. pitchEnv.Parse(key, value);
  434. else if(SFZStartsWith(key, "eg") && SFZIsNumeric(key.substr(2, 2)) && key.substr(4, 1) == "_")
  435. {
  436. uint8 eg = ConvertStrTo<uint8>(std::string(key.substr(2, 2)));
  437. if(eg >= flexEGs.size())
  438. flexEGs.resize(eg + 1);
  439. flexEGs[eg].Parse(key, value);
  440. }
  441. }
  442. };
  443. struct SFZInputFile
  444. {
  445. FileReader file;
  446. std::unique_ptr<InputFile> inputFile; // FileReader has pointers into this so its address must not change
  447. std::string remain;
  448. SFZInputFile(FileReader f = {}, std::unique_ptr<InputFile> i = {}, std::string r = {})
  449. : file{std::move(f)}, inputFile{std::move(i)}, remain{std::move(r)} {}
  450. SFZInputFile(SFZInputFile &&) = default;
  451. };
  452. bool CSoundFile::ReadSFZInstrument(INSTRUMENTINDEX nInstr, FileReader &file)
  453. {
  454. file.Rewind();
  455. enum { kNone, kGlobal, kMaster, kGroup, kRegion, kControl, kCurve, kEffect, kUnknown } section = kNone;
  456. bool inMultiLineComment = false;
  457. SFZControl control;
  458. SFZRegion group, master, globals;
  459. std::vector<SFZRegion> regions;
  460. std::map<std::string, std::string> macros;
  461. std::vector<SFZInputFile> files;
  462. files.emplace_back(file);
  463. std::string s;
  464. while(!files.empty())
  465. {
  466. if(!files.back().file.ReadLine(s, 1024))
  467. {
  468. // Finished reading file, so back to remaining characters of the #include line from the previous file
  469. s = std::move(files.back().remain);
  470. files.pop_back();
  471. }
  472. if(inMultiLineComment)
  473. {
  474. if(auto commentEnd = s.find("*/"); commentEnd != std::string::npos)
  475. {
  476. s.erase(0, commentEnd + 2);
  477. inMultiLineComment = false;
  478. } else
  479. {
  480. continue;
  481. }
  482. }
  483. // First, terminate line at the start of a comment block
  484. if(auto commentPos = s.find("//"); commentPos != std::string::npos)
  485. {
  486. s.resize(commentPos);
  487. }
  488. // Now, read the tokens.
  489. // This format is so funky that no general tokenizer approach seems to work here...
  490. // Consider this jolly good example found at https://stackoverflow.com/questions/5923895/tokenizing-a-custom-text-file-format-file-using-c-sharp
  491. // <region>sample=piano C3.wav key=48 ampeg_release=0.7 // a comment here
  492. // <region>key = 49 sample = piano Db3.wav
  493. // <region>
  494. // group=1
  495. // key = 48
  496. // sample = piano D3.ogg
  497. // The original sfz specification claims that spaces around = are not allowed, but a quick look into the real world tells us otherwise.
  498. while(!s.empty())
  499. {
  500. s.erase(0, s.find_first_not_of(" \t"));
  501. const bool isDefine = SFZStartsWith(s, "#define ") || SFZStartsWith(s, "#define\t");
  502. // Replace macros (unless this is a #define statement, to allow for macro re-definition)
  503. if(!isDefine)
  504. {
  505. for(const auto &[oldStr, newStr] : macros)
  506. {
  507. std::string::size_type pos = 0;
  508. while((pos = s.find(oldStr, pos)) != std::string::npos)
  509. {
  510. s.replace(pos, oldStr.length(), newStr);
  511. pos += newStr.length();
  512. }
  513. }
  514. }
  515. if(s.empty())
  516. break;
  517. std::string::size_type charsRead = 0;
  518. if(s[0] == '<' && (charsRead = s.find('>')) != std::string::npos)
  519. {
  520. // Section header
  521. const auto sec = std::string_view(s).substr(1, charsRead - 1);
  522. section = kUnknown;
  523. if(sec == "global")
  524. {
  525. section = kGlobal;
  526. // Reset global parameters
  527. globals = SFZRegion();
  528. } else if(sec == "master")
  529. {
  530. section = kMaster;
  531. // Reset master parameters
  532. master = globals;
  533. } else if(sec == "group")
  534. {
  535. section = kGroup;
  536. // Reset group parameters
  537. group = master;
  538. } else if(sec == "region")
  539. {
  540. section = kRegion;
  541. regions.push_back(group);
  542. } else if(sec == "control")
  543. {
  544. section = kControl;
  545. } else if(sec == "curve")
  546. {
  547. section = kCurve;
  548. } else if(sec == "effect")
  549. {
  550. section = kEffect;
  551. }
  552. charsRead++;
  553. } else if(isDefine)
  554. {
  555. // Macro definition
  556. charsRead += 8;
  557. auto keyStart = s.find_first_not_of(" \t", 8);
  558. auto keyEnd = s.find_first_of(" \t", keyStart);
  559. auto valueStart = s.find_first_not_of(" \t", keyEnd);
  560. if(keyStart != std::string::npos && valueStart != std::string::npos)
  561. {
  562. charsRead = s.find_first_of(" \t", valueStart);
  563. const auto key = s.substr(keyStart, keyEnd - keyStart);
  564. if(key.length() > 1 && key[0] == '$')
  565. macros[std::move(key)] = s.substr(valueStart, charsRead - valueStart);
  566. } else
  567. {
  568. break;
  569. }
  570. } else if(SFZStartsWith(s, "#include ") || SFZStartsWith(s, "#include\t"))
  571. {
  572. // Include other sfz file
  573. auto fileStart = s.find("\"", 9); // Yes, there can be arbitrary characters before the opening quote, at least that's how sforzando does it.
  574. auto fileEnd = s.find("\"", fileStart + 1);
  575. if(fileStart != std::string::npos && fileEnd != std::string::npos)
  576. {
  577. charsRead = fileEnd + 1;
  578. fileStart++;
  579. } else
  580. {
  581. break;
  582. }
  583. std::string filenameU8 = s.substr(fileStart, fileEnd - fileStart);
  584. mpt::PathString filename = mpt::PathString::FromUTF8(filenameU8);
  585. if(!filename.empty())
  586. {
  587. if(filenameU8.find(':') == std::string::npos)
  588. filename = file.GetOptionalFileName().value_or(P_("")).GetPath() + filename;
  589. filename = filename.Simplify();
  590. // Avoid recursive #include
  591. if(std::find_if(files.begin(), files.end(), [&filename](const SFZInputFile &f) { return f.file.GetOptionalFileName().value_or(P_("")) == filename; }) == files.end())
  592. {
  593. auto f = std::make_unique<InputFile>(filename);
  594. if(f->IsValid())
  595. {
  596. s.erase(0, charsRead);
  597. files.emplace_back(GetFileReader(*f), std::move(f), std::move(s));
  598. break;
  599. } else
  600. {
  601. AddToLog(LogWarning, U_("Unable to load include file: ") + filename.ToUnicode());
  602. }
  603. } else
  604. {
  605. AddToLog(LogWarning, U_("Recursive include file ignored: ") + filename.ToUnicode());
  606. }
  607. }
  608. } else if(SFZStartsWith(s, "/*"))
  609. {
  610. // Multi-line comment
  611. if(auto commentEnd = s.find("*/", charsRead + 2); commentEnd != std::string::npos)
  612. {
  613. charsRead = commentEnd;
  614. } else
  615. {
  616. inMultiLineComment = true;
  617. charsRead = s.length();
  618. }
  619. } else if(section == kNone)
  620. {
  621. // Garbage before any section, probably not an sfz file
  622. return false;
  623. } else if(s.find('=') != std::string::npos)
  624. {
  625. // Read key=value pair
  626. auto keyEnd = s.find_first_of(" \t=");
  627. auto valueStart = s.find_first_not_of(" \t=", keyEnd);
  628. if(valueStart == std::string::npos)
  629. {
  630. break;
  631. }
  632. const std::string key = mpt::ToLowerCaseAscii(s.substr(0, keyEnd));
  633. // Currently defined *_label opcodes are global_label, group_label, master_label, region_label, sw_label
  634. if(key == "sample" || key == "default_path" || SFZStartsWith(key, "label_cc") || SFZStartsWith(key, "label_key") || SFZEndsWith(key, "_label"))
  635. {
  636. // Sample / CC name may contain spaces...
  637. charsRead = s.find_first_of("=\t<", valueStart);
  638. if(charsRead != std::string::npos && s[charsRead] == '=')
  639. {
  640. // Backtrack to end of key
  641. while(charsRead > valueStart && s[charsRead] == ' ')
  642. charsRead--;
  643. // Backtrack to start of key
  644. while(charsRead > valueStart && s[charsRead] != ' ')
  645. charsRead--;
  646. }
  647. } else
  648. {
  649. charsRead = s.find_first_of(" \t<", valueStart);
  650. }
  651. const std::string value = s.substr(valueStart, charsRead - valueStart);
  652. switch(section)
  653. {
  654. case kGlobal:
  655. globals.Parse(key, value, control);
  656. [[fallthrough]];
  657. case kMaster:
  658. master.Parse(key, value, control);
  659. [[fallthrough]];
  660. case kGroup:
  661. group.Parse(key, value, control);
  662. break;
  663. case kRegion:
  664. regions.back().Parse(key, value, control);
  665. break;
  666. case kControl:
  667. control.Parse(key, value);
  668. break;
  669. }
  670. } else
  671. {
  672. // Garbage, probably not an sfz file
  673. return false;
  674. }
  675. // Remove the token(s) we just read
  676. s.erase(0, charsRead);
  677. }
  678. }
  679. if(regions.empty())
  680. return false;
  681. ModInstrument *pIns = new (std::nothrow) ModInstrument();
  682. if(pIns == nullptr)
  683. return false;
  684. RecalculateSamplesPerTick();
  685. DestroyInstrument(nInstr, deleteAssociatedSamples);
  686. if(nInstr > m_nInstruments) m_nInstruments = nInstr;
  687. Instruments[nInstr] = pIns;
  688. SAMPLEINDEX prevSmp = 0;
  689. for(auto &region : regions)
  690. {
  691. uint8 keyLo = region.keyLo, keyHi = region.keyHi;
  692. if(keyLo > keyHi)
  693. continue;
  694. Clamp<uint8, uint8>(keyLo, 0, NOTE_MAX - NOTE_MIN);
  695. Clamp<uint8, uint8>(keyHi, 0, NOTE_MAX - NOTE_MIN);
  696. SAMPLEINDEX smp = GetNextFreeSample(nInstr, prevSmp + 1);
  697. if(smp == SAMPLEINDEX_INVALID)
  698. break;
  699. prevSmp = smp;
  700. ModSample &sample = Samples[smp];
  701. sample.Initialize(MOD_TYPE_MPT);
  702. if(const auto synthSample = std::string_view(region.filename).substr(region.filenameOffset); SFZStartsWith(synthSample, "*"))
  703. {
  704. sample.nLength = 256;
  705. sample.nC5Speed = mpt::saturate_round<uint32>(sample.nLength * 261.6255653);
  706. sample.uFlags.set(CHN_16BIT);
  707. std::function<uint16(int32)> generator;
  708. if(synthSample == "*sine")
  709. generator = [](int32 i) { return mpt::saturate_round<int16>(std::sin(i * ((2.0 * mpt::numbers::pi) / 256.0)) * int16_max); };
  710. else if(synthSample == "*square")
  711. generator = [](int32 i) { return i < 128 ? int16_max : int16_min; };
  712. else if(synthSample == "*triangle" || synthSample == "*tri")
  713. generator = [](int32 i) { return static_cast<int16>(i < 128 ? ((63 - i) * 512) : ((i - 192) * 512)); };
  714. else if(synthSample == "*saw")
  715. generator = [](int32 i) { return static_cast<int16>((i - 128) * 256); };
  716. else if(synthSample == "*silence")
  717. generator = [](int32) { return int16(0); };
  718. else if(synthSample == "*noise")
  719. {
  720. sample.nLength = sample.nC5Speed;
  721. generator = [this](int32) { return mpt::random<int16>(AccessPRNG()); };
  722. } else
  723. {
  724. AddToLog(LogWarning, U_("Unknown sample type: ") + mpt::ToUnicode(mpt::Charset::UTF8, std::string(synthSample)));
  725. prevSmp--;
  726. continue;
  727. }
  728. if(sample.AllocateSample())
  729. {
  730. for(SmpLength i = 0; i < sample.nLength; i++)
  731. {
  732. sample.sample16()[i] = generator(static_cast<int32>(i));
  733. }
  734. if(smp > m_nSamples)
  735. m_nSamples = smp;
  736. region.offset = 0;
  737. region.loopMode = SFZRegion::LoopMode::kContinuous;
  738. region.loopStart = 0;
  739. region.loopEnd = sample.nLength - 1;
  740. region.loopCrossfade = 0;
  741. region.keyRoot = 60;
  742. }
  743. } else if(auto filename = mpt::PathString::FromUTF8(region.filename); !filename.empty())
  744. {
  745. if(region.filename.find(':') == std::string::npos)
  746. {
  747. filename = file.GetOptionalFileName().value_or(P_("")).GetPath() + filename;
  748. }
  749. filename = filename.Simplify();
  750. SetSamplePath(smp, filename);
  751. InputFile f(filename, SettingCacheCompleteFileBeforeLoading());
  752. FileReader smpFile = GetFileReader(f);
  753. if(!ReadSampleFromFile(smp, smpFile, false))
  754. {
  755. AddToLog(LogWarning, U_("Unable to load sample: ") + filename.ToUnicode());
  756. prevSmp--;
  757. continue;
  758. }
  759. if(UseFinetuneAndTranspose())
  760. sample.TransposeToFrequency();
  761. sample.uFlags.set(SMP_KEEPONDISK, sample.HasSampleData());
  762. }
  763. if(!region.name.empty())
  764. m_szNames[smp] = mpt::ToCharset(GetCharsetInternal(), mpt::Charset::UTF8, region.name);
  765. if(!m_szNames[smp][0])
  766. m_szNames[smp] = mpt::ToCharset(GetCharsetInternal(), mpt::PathString::FromUTF8(region.filename).GetFileName().ToUnicode());
  767. if(region.useSampleKeyRoot)
  768. {
  769. if(sample.rootNote != NOTE_NONE)
  770. region.keyRoot = sample.rootNote - NOTE_MIN;
  771. else
  772. region.keyRoot = 60;
  773. }
  774. const auto origSampleRate = sample.GetSampleRate(GetType());
  775. int8 transp = region.transpose + (60 - region.keyRoot);
  776. for(uint8 i = keyLo; i <= keyHi; i++)
  777. {
  778. pIns->Keyboard[i] = smp;
  779. if(GetType() != MOD_TYPE_XM)
  780. pIns->NoteMap[i] = NOTE_MIN + i + transp;
  781. }
  782. if(GetType() == MOD_TYPE_XM)
  783. sample.Transpose(transp / 12.0);
  784. pIns->filterMode = region.filterType;
  785. if(region.cutoff != 0)
  786. pIns->SetCutoff(FrequencyToCutOff(region.cutoff), true);
  787. if(region.resonance != 0)
  788. pIns->SetResonance(mpt::saturate_round<uint8>(region.resonance * 128.0 / 24.0), true);
  789. pIns->nCutSwing = mpt::saturate_round<uint8>(region.filterRandom * (m_SongFlags[SONG_EXFILTERRANGE] ? 20 : 24) / 1200.0);
  790. pIns->midiPWD = mpt::saturate_round<int8>(region.pitchBend / 100.0);
  791. pIns->nNNA = NewNoteAction::NoteOff;
  792. if(region.polyphony == 1)
  793. {
  794. pIns->nDNA = DuplicateNoteAction::NoteCut;
  795. pIns->nDCT = DuplicateCheckType::Sample;
  796. }
  797. region.ampEnv.ConvertToMPT(pIns, *this, ENV_VOLUME);
  798. if(region.pitchEnv.depth)
  799. region.pitchEnv.ConvertToMPT(pIns, *this, ENV_PITCH);
  800. else if(region.filterEnv.depth)
  801. region.filterEnv.ConvertToMPT(pIns, *this, ENV_PITCH, true);
  802. for(const auto &flexEG : region.flexEGs)
  803. {
  804. flexEG.ConvertToMPT(pIns, *this);
  805. }
  806. if(region.ampEnv.release > 0)
  807. {
  808. const double tickDuration = m_PlayState.m_nSamplesPerTick / static_cast<double>(GetSampleRate());
  809. pIns->nFadeOut = std::min(mpt::saturate_cast<uint32>(32768.0 * tickDuration / region.ampEnv.release), uint32(32767));
  810. if(GetType() == MOD_TYPE_IT)
  811. pIns->nFadeOut = std::min((pIns->nFadeOut + 16u) & ~31u, uint32(8192));
  812. }
  813. sample.rootNote = region.keyRoot + NOTE_MIN;
  814. sample.nGlobalVol = mpt::saturate_round<decltype(sample.nGlobalVol)>(64.0 * Clamp(std::pow(10.0, region.volume / 20.0) * region.amplitude / 100.0, 0.0, 1.0));
  815. if(region.panning != -128)
  816. {
  817. sample.nPan = mpt::saturate_round<decltype(sample.nPan)>((region.panning + 100) * 256.0 / 200.0);
  818. sample.uFlags.set(CHN_PANNING);
  819. }
  820. sample.Transpose(region.finetune / 1200.0);
  821. if(region.pitchLfoDepth && region.pitchLfoFreq)
  822. {
  823. sample.nVibSweep = 255;
  824. if(region.pitchLfoFade > 0)
  825. sample.nVibSweep = mpt::saturate_round<uint8>(255.0 / region.pitchLfoFade);
  826. sample.nVibDepth = mpt::saturate_round<uint8>(region.pitchLfoDepth * 32.0 / 100.0);
  827. sample.nVibRate = mpt::saturate_round<uint8>(region.pitchLfoFreq * 4.0);
  828. }
  829. if(region.loopMode != SFZRegion::LoopMode::kUnspecified)
  830. {
  831. switch(region.loopMode)
  832. {
  833. case SFZRegion::LoopMode::kContinuous:
  834. sample.uFlags.set(CHN_LOOP);
  835. break;
  836. case SFZRegion::LoopMode::kSustain:
  837. sample.uFlags.set(CHN_SUSTAINLOOP);
  838. break;
  839. case SFZRegion::LoopMode::kNoLoop:
  840. case SFZRegion::LoopMode::kOneShot:
  841. sample.uFlags.reset(CHN_LOOP | CHN_SUSTAINLOOP);
  842. }
  843. }
  844. if(region.loopEnd > region.loopStart)
  845. {
  846. // Loop may also be defined in file, in which case loopStart and loopEnd are unset.
  847. if(region.loopMode == SFZRegion::LoopMode::kSustain)
  848. {
  849. sample.nSustainStart = region.loopStart;
  850. sample.nSustainEnd = region.loopEnd + 1;
  851. } else if(region.loopMode == SFZRegion::LoopMode::kContinuous || region.loopMode == SFZRegion::LoopMode::kOneShot)
  852. {
  853. sample.nLoopStart = region.loopStart;
  854. sample.nLoopEnd = region.loopEnd + 1;
  855. }
  856. } else if(sample.nLoopEnd <= sample.nLoopStart && region.loopMode != SFZRegion::LoopMode::kUnspecified && region.loopMode != SFZRegion::LoopMode::kNoLoop)
  857. {
  858. sample.nLoopEnd = sample.nLength;
  859. }
  860. switch(region.loopType)
  861. {
  862. case SFZRegion::LoopType::kUnspecified:
  863. break;
  864. case SFZRegion::LoopType::kForward:
  865. sample.uFlags.reset(CHN_PINGPONGLOOP | CHN_PINGPONGSUSTAIN | CHN_REVERSE);
  866. break;
  867. case SFZRegion::LoopType::kBackward:
  868. sample.uFlags.set(CHN_REVERSE);
  869. break;
  870. case SFZRegion::LoopType::kAlternate:
  871. sample.uFlags.set(CHN_PINGPONGLOOP | CHN_PINGPONGSUSTAIN);
  872. break;
  873. default:
  874. break;
  875. }
  876. if(sample.nSustainEnd <= sample.nSustainStart && sample.nLoopEnd > sample.nLoopStart && region.loopMode == SFZRegion::LoopMode::kSustain)
  877. {
  878. // Turn normal loop (imported from sample) into sustain loop
  879. std::swap(sample.nSustainStart, sample.nLoopStart);
  880. std::swap(sample.nSustainEnd, sample.nLoopEnd);
  881. sample.uFlags.set(CHN_SUSTAINLOOP);
  882. sample.uFlags.set(CHN_PINGPONGSUSTAIN, sample.uFlags[CHN_PINGPONGLOOP]);
  883. sample.uFlags.reset(CHN_LOOP | CHN_PINGPONGLOOP);
  884. }
  885. mpt::PathString filenameModifier;
  886. // Loop cross-fade
  887. SmpLength fadeSamples = mpt::saturate_round<SmpLength>(region.loopCrossfade * origSampleRate);
  888. LimitMax(fadeSamples, sample.uFlags[CHN_SUSTAINLOOP] ? sample.nSustainStart : sample.nLoopStart);
  889. if(fadeSamples > 0)
  890. {
  891. ctrlSmp::XFadeSample(sample, fadeSamples, 50000, true, sample.uFlags[CHN_SUSTAINLOOP], *this);
  892. sample.uFlags.set(SMP_MODIFIED);
  893. filenameModifier += P_(" (cross-fade)");
  894. }
  895. // Sample offset
  896. if(region.offset && region.offset < sample.nLength)
  897. {
  898. auto offset = region.offset * sample.GetBytesPerSample();
  899. memmove(sample.sampleb(), sample.sampleb() + offset, sample.nLength * sample.GetBytesPerSample() - offset);
  900. if(region.end > region.offset)
  901. region.end -= region.offset;
  902. sample.nLength -= region.offset;
  903. sample.nLoopStart -= region.offset;
  904. sample.nLoopEnd -= region.offset;
  905. sample.uFlags.set(SMP_MODIFIED);
  906. filenameModifier += P_(" (offset)");
  907. }
  908. LimitMax(sample.nLength, region.end);
  909. if(region.invertPhase)
  910. {
  911. ctrlSmp::InvertSample(sample, 0, sample.nLength, *this);
  912. sample.uFlags.set(SMP_MODIFIED);
  913. filenameModifier += P_(" (inverted)");
  914. }
  915. if(sample.uFlags.test_all(SMP_KEEPONDISK | SMP_MODIFIED))
  916. {
  917. // Avoid ruining the original samples
  918. if(auto filename = GetSamplePath(smp); !filename.empty())
  919. {
  920. filename = filename.GetPath() + filename.GetFileName() + filenameModifier + filename.GetFileExt();
  921. SetSamplePath(smp, filename);
  922. }
  923. }
  924. sample.PrecomputeLoops(*this, false);
  925. sample.Convert(MOD_TYPE_MPT, GetType());
  926. }
  927. pIns->Sanitize(MOD_TYPE_MPT);
  928. pIns->Convert(MOD_TYPE_MPT, GetType());
  929. return true;
  930. }
  931. #ifndef MODPLUG_NO_FILESAVE
  932. static double SFZLinear2dB(double volume)
  933. {
  934. return (volume > 0.0 ? 20.0 * std::log10(volume) : -144.0);
  935. }
  936. static void WriteSFZEnvelope(std::ostream &f, double tickDuration, int index, const InstrumentEnvelope &env, const char *type, double scale, std::function<double(int32)> convFunc)
  937. {
  938. if(!env.dwFlags[ENV_ENABLED] || env.empty())
  939. return;
  940. const bool sustainAtEnd = (!env.dwFlags[ENV_SUSTAIN] || env.nSustainStart == (env.size() - 1)) && convFunc(env.back().value) != 0.0;
  941. const auto prefix = MPT_AFORMAT("\neg{}_")(mpt::afmt::dec0<2>(index));
  942. f << "\n" << prefix << type << "=" << scale;
  943. f << prefix << "points=" << (env.size() + (sustainAtEnd ? 1 : 0));
  944. EnvelopeNode::tick_t lastTick = 0;
  945. int nodeIndex = 0;
  946. for(const auto &node : env)
  947. {
  948. const double time = (node.tick - lastTick) * tickDuration;
  949. lastTick = node.tick;
  950. f << prefix << "time" << nodeIndex << "=" << time;
  951. f << prefix << "level" << nodeIndex << "=" << convFunc(node.value);
  952. nodeIndex++;
  953. }
  954. if(sustainAtEnd)
  955. {
  956. // Prevent envelope from going back to neutral
  957. f << prefix << "time" << nodeIndex << "=0";
  958. f << prefix << "level" << nodeIndex << "=" << convFunc(env.back().value);
  959. }
  960. // We always must write a sustain point, or the envelope will be sustained on the first point of the envelope
  961. f << prefix << "sustain=" << (env.dwFlags[ENV_SUSTAIN] ? env.nSustainStart : (env.size() - 1));
  962. if(env.dwFlags[ENV_LOOP])
  963. f << "\n// Loop: " << static_cast<uint32>(env.nLoopStart) << "-" << static_cast<uint32>(env.nLoopEnd);
  964. if(env.dwFlags[ENV_SUSTAIN] && env.nSustainEnd > env.nSustainStart)
  965. f << "\n// Sustain Loop: " << static_cast<uint32>(env.nSustainStart) << "-" << static_cast<uint32>(env.nSustainEnd);
  966. if(env.nReleaseNode != ENV_RELEASE_NODE_UNSET)
  967. f << "\n// Release Node: " << static_cast<uint32>(env.nReleaseNode);
  968. }
  969. bool CSoundFile::SaveSFZInstrument(INSTRUMENTINDEX nInstr, std::ostream &f, const mpt::PathString &filename, bool useFLACsamples) const
  970. {
  971. #ifdef MODPLUG_TRACKER
  972. const mpt::FlushMode flushMode = mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave);
  973. #else
  974. const mpt::FlushMode flushMode = mpt::FlushMode::Full;
  975. #endif
  976. const ModInstrument *ins = Instruments[nInstr];
  977. if(ins == nullptr)
  978. return false;
  979. // Creating directory names with trailing spaces or dots is a bad idea, as they are difficult to remove in Windows.
  980. const mpt::RawPathString whitespaceDirName = PL_(" \n\r\t.");
  981. const mpt::PathString sampleBaseName = mpt::PathString::FromNative(mpt::trim(filename.GetFileName().AsNative(), whitespaceDirName));
  982. const mpt::PathString sampleDirName = (sampleBaseName.empty() ? P_("Samples") : sampleBaseName) + P_("/");
  983. const mpt::PathString sampleBasePath = filename.GetPath() + sampleDirName;
  984. if(!sampleBasePath.IsDirectory() && !::CreateDirectory(sampleBasePath.AsNative().c_str(), nullptr))
  985. return false;
  986. const double tickDuration = m_PlayState.m_nSamplesPerTick / static_cast<double>(m_MixerSettings.gdwMixingFreq);
  987. f << std::setprecision(10);
  988. if(!ins->name.empty())
  989. {
  990. f << "// Name: " << mpt::ToCharset(mpt::Charset::UTF8, GetCharsetInternal(), ins->name) << "\n";
  991. }
  992. f << "// Created with " << mpt::ToCharset(mpt::Charset::UTF8, Version::Current().GetOpenMPTVersionString()) << "\n";
  993. f << "// Envelope tempo base: tempo " << m_PlayState.m_nMusicTempo.ToDouble();
  994. switch(m_nTempoMode)
  995. {
  996. case TempoMode::Classic:
  997. f << " (classic tempo mode)";
  998. break;
  999. case TempoMode::Alternative:
  1000. f << " (alternative tempo mode)";
  1001. break;
  1002. case TempoMode::Modern:
  1003. f << ", " << m_PlayState.m_nMusicSpeed << " ticks per row, " << m_PlayState.m_nCurrentRowsPerBeat << " rows per beat (modern tempo mode)";
  1004. break;
  1005. default:
  1006. MPT_ASSERT_NOTREACHED();
  1007. break;
  1008. }
  1009. f << "\n\n<control>\ndefault_path=" << sampleDirName.ToUTF8() << "\n\n";
  1010. f << "<group>";
  1011. f << "\nbend_up=" << ins->midiPWD * 100;
  1012. f << "\nbend_down=" << -ins->midiPWD * 100;
  1013. const uint32 cutoff = ins->IsCutoffEnabled() ? ins->GetCutoff() : 127;
  1014. // If filter envelope is active but cutoff is not set, we still need to set the base cutoff frequency to be modulated by the envelope.
  1015. if(ins->IsCutoffEnabled() || ins->PitchEnv.dwFlags[ENV_FILTER])
  1016. f << "\ncutoff=" << CSoundFile::CutOffToFrequency(cutoff) << " // " << cutoff;
  1017. if(ins->IsResonanceEnabled())
  1018. f << "\nresonance=" << Util::muldivr_unsigned(ins->GetResonance(), 24, 128) << " // " << static_cast<int>(ins->GetResonance());
  1019. if(ins->IsCutoffEnabled() || ins->IsResonanceEnabled())
  1020. f << "\nfil_type=" << (ins->filterMode == FilterMode::HighPass ? "hpf_2p" : "lpf_2p");
  1021. if(ins->dwFlags[INS_SETPANNING])
  1022. f << "\npan=" << (Util::muldivr_unsigned(ins->nPan, 200, 256) - 100) << " // " << ins->nPan;
  1023. if(ins->nGlobalVol != 64)
  1024. f << "\nvolume=" << SFZLinear2dB(ins->nGlobalVol / 64.0) << " // " << ins->nGlobalVol;
  1025. if(ins->nFadeOut)
  1026. {
  1027. f << "\nampeg_release=" << (32768.0 * tickDuration / ins->nFadeOut) << " // " << ins->nFadeOut;
  1028. f << "\nampeg_release_shape=0";
  1029. }
  1030. if(ins->nDNA == DuplicateNoteAction::NoteCut && ins->nDCT != DuplicateCheckType::None)
  1031. f << "\npolyphony=1";
  1032. WriteSFZEnvelope(f, tickDuration, 1, ins->VolEnv, "amplitude", 100.0, [](int32 val) { return val / static_cast<double>(ENVELOPE_MAX); });
  1033. WriteSFZEnvelope(f, tickDuration, 2, ins->PanEnv, "pan", 100.0, [](int32 val) { return 2.0 * (val - ENVELOPE_MID) / (ENVELOPE_MAX - ENVELOPE_MIN); });
  1034. if(ins->PitchEnv.dwFlags[ENV_FILTER])
  1035. {
  1036. const auto envScale = 1200.0 * std::log(CutOffToFrequency(127, 256) / static_cast<double>(CutOffToFrequency(0, -256))) / mpt::numbers::ln2;
  1037. const auto cutoffNormal = CutOffToFrequency(cutoff);
  1038. WriteSFZEnvelope(f, tickDuration, 3, ins->PitchEnv, "cutoff", envScale, [this, cutoff, cutoffNormal, envScale](int32 val) {
  1039. // Convert interval between center frequency and envelope into cents
  1040. const auto freq = CutOffToFrequency(cutoff, (val - ENVELOPE_MID) * 256 / (ENVELOPE_MAX - ENVELOPE_MID));
  1041. return 1200.0 * std::log(freq / static_cast<double>(cutoffNormal)) / mpt::numbers::ln2 / envScale;
  1042. });
  1043. } else
  1044. {
  1045. WriteSFZEnvelope(f, tickDuration, 3, ins->PitchEnv, "pitch", 1600.0, [](int32 val) { return 2.0 * (val - ENVELOPE_MID) / (ENVELOPE_MAX - ENVELOPE_MIN); });
  1046. }
  1047. size_t numSamples = 0;
  1048. for(size_t i = 0; i < std::size(ins->Keyboard); i++)
  1049. {
  1050. if(ins->Keyboard[i] < 1 || ins->Keyboard[i] > GetNumSamples())
  1051. continue;
  1052. size_t endOfRegion = i + 1;
  1053. while(endOfRegion < std::size(ins->Keyboard))
  1054. {
  1055. if(ins->Keyboard[endOfRegion] != ins->Keyboard[i] || ins->NoteMap[endOfRegion] != (ins->NoteMap[i] + endOfRegion - i))
  1056. break;
  1057. endOfRegion++;
  1058. }
  1059. endOfRegion--;
  1060. const ModSample &sample = Samples[ins->Keyboard[i]];
  1061. const bool isAdlib = sample.uFlags[CHN_ADLIB];
  1062. if(!sample.HasSampleData())
  1063. {
  1064. i = endOfRegion;
  1065. continue;
  1066. }
  1067. numSamples++;
  1068. mpt::PathString sampleName = sampleBasePath + (sampleBaseName.empty() ? P_("Sample") : sampleBaseName) + P_(" ") + mpt::PathString::FromUnicode(mpt::ufmt::val(numSamples));
  1069. if(isAdlib)
  1070. sampleName += P_(".s3i");
  1071. else if(useFLACsamples)
  1072. sampleName += P_(".flac");
  1073. else
  1074. sampleName += P_(".wav");
  1075. bool success = false;
  1076. try
  1077. {
  1078. mpt::SafeOutputFile sfSmp(sampleName, std::ios::binary, flushMode);
  1079. if(sfSmp)
  1080. {
  1081. mpt::ofstream &fSmp = sfSmp;
  1082. fSmp.exceptions(fSmp.exceptions() | std::ios::badbit | std::ios::failbit);
  1083. if(isAdlib)
  1084. success = SaveS3ISample(ins->Keyboard[i], fSmp);
  1085. else if(useFLACsamples)
  1086. success = SaveFLACSample(ins->Keyboard[i], fSmp);
  1087. else
  1088. success = SaveWAVSample(ins->Keyboard[i], fSmp);
  1089. }
  1090. } catch(const std::exception &)
  1091. {
  1092. success = false;
  1093. }
  1094. if(!success)
  1095. {
  1096. AddToLog(LogError, MPT_USTRING("Unable to save sample: ") + sampleName.ToUnicode());
  1097. }
  1098. f << "\n\n<region>";
  1099. if(!m_szNames[ins->Keyboard[i]].empty())
  1100. {
  1101. f << "\nregion_label=" << mpt::ToCharset(mpt::Charset::UTF8, GetCharsetInternal(), m_szNames[ins->Keyboard[i]]);
  1102. }
  1103. f << "\nsample=" << sampleName.GetFullFileName().ToUTF8();
  1104. f << "\nlokey=" << i;
  1105. f << "\nhikey=" << endOfRegion;
  1106. if(sample.rootNote != NOTE_NONE)
  1107. f << "\npitch_keycenter=" << sample.rootNote - NOTE_MIN;
  1108. else
  1109. f << "\npitch_keycenter=" << NOTE_MIDDLEC + i - ins->NoteMap[i];
  1110. if(sample.uFlags[CHN_PANNING])
  1111. f << "\npan=" << (Util::muldivr_unsigned(sample.nPan, 200, 256) - 100) << " // " << sample.nPan;
  1112. if(sample.nGlobalVol != 64)
  1113. f << "\nvolume=" << SFZLinear2dB((ins->nGlobalVol * sample.nGlobalVol) / 4096.0) << " // " << sample.nGlobalVol;
  1114. const char *loopMode = "no_loop", *loopType = "forward";
  1115. SmpLength loopStart = 0, loopEnd = 0;
  1116. if(sample.uFlags[CHN_SUSTAINLOOP])
  1117. {
  1118. loopMode = "loop_sustain";
  1119. loopStart = sample.nSustainStart;
  1120. loopEnd = sample.nSustainEnd;
  1121. if(sample.uFlags[CHN_PINGPONGSUSTAIN])
  1122. loopType = "alternate";
  1123. } else if(sample.uFlags[CHN_LOOP])
  1124. {
  1125. loopMode = "loop_continuous";
  1126. loopStart = sample.nLoopStart;
  1127. loopEnd = sample.nLoopEnd;
  1128. if(sample.uFlags[CHN_PINGPONGLOOP])
  1129. loopType = "alternate";
  1130. else if(sample.uFlags[CHN_REVERSE])
  1131. loopType = "backward";
  1132. }
  1133. f << "\nloop_mode=" << loopMode;
  1134. if(loopStart < loopEnd)
  1135. {
  1136. f << "\nloop_start=" << loopStart;
  1137. f << "\nloop_end=" << (loopEnd - 1);
  1138. f << "\nloop_type=" << loopType;
  1139. }
  1140. if(sample.uFlags.test_all(CHN_SUSTAINLOOP | CHN_LOOP))
  1141. {
  1142. f << "\n// Warning: Only sustain loop was exported!";
  1143. }
  1144. i = endOfRegion;
  1145. }
  1146. return true;
  1147. }
  1148. #endif // MODPLUG_NO_FILESAVE
  1149. #else
  1150. bool CSoundFile::ReadSFZInstrument(INSTRUMENTINDEX, FileReader &)
  1151. {
  1152. return false;
  1153. }
  1154. #endif // MPT_EXTERNAL_SAMPLES
  1155. OPENMPT_NAMESPACE_END