SampleGenerator.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. /*
  2. * SampleGenerator.cpp
  3. * -------------------
  4. * Purpose: Generate samples from math formulas using muParser
  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. #if MPT_DISABLED_CODE
  11. #include "SampleGenerator.h"
  12. #include "modsmp_ctrl.h"
  13. int CSampleGenerator::sample_frequency = 44100;
  14. int CSampleGenerator::sample_length = CSampleGenerator::sample_frequency;
  15. mu::string_type CSampleGenerator::expression = _T("sin(xp * _pi)");
  16. smpgen_clip_methods CSampleGenerator::sample_clipping = smpgen_normalize;
  17. mu::value_type *CSampleGenerator::sample_buffer = nullptr;
  18. size_t CSampleGenerator::samples_written = 0;
  19. CSampleGenerator::CSampleGenerator()
  20. {
  21. // Setup function callbacks
  22. muParser.DefineFun(_T("clip"), &ClipCallback, false);
  23. muParser.DefineFun(_T("pwm"), &PWMCallback, false);
  24. muParser.DefineFun(_T("rnd"), &RndCallback, false);
  25. muParser.DefineFun(_T("smp"), &SampleDataCallback, false);
  26. muParser.DefineFun(_T("tri"), &TriangleCallback, false);
  27. // Setup binary operator callbacks
  28. muParser.DefineOprt(_T("mod"), &ModuloCallback, 0);
  29. //muParser.DefineConst("pi", (mu::value_type)PARSER_CONST_PI);
  30. }
  31. // Open the smpgen dialog
  32. bool CSampleGenerator::ShowDialog()
  33. {
  34. bool isDone = false, result = false;
  35. while(!isDone)
  36. {
  37. CSmpGenDialog dlg(sample_frequency, sample_length, sample_clipping, expression);
  38. dlg.DoModal();
  39. // pressed "OK" button?
  40. if(dlg.CanApply())
  41. {
  42. sample_frequency = dlg.GetFrequency();
  43. sample_length = dlg.GetLength();
  44. sample_clipping = dlg.GetClipping();
  45. expression = dlg.GetExpression();
  46. isDone = CanRenderSample();
  47. if(isDone) isDone = TestExpression(); // show dialog again if the formula can't be parsed.
  48. result = true;
  49. } else
  50. {
  51. isDone = true; // just quit.
  52. result = false;
  53. }
  54. }
  55. return result;
  56. }
  57. // Check if the currently select expression can be parsed by muParser.
  58. bool CSampleGenerator::TestExpression()
  59. {
  60. // reset helper variables
  61. samples_written = 0;
  62. sample_buffer = nullptr;
  63. muParser.SetExpr(expression);
  64. mu::value_type x = 0;
  65. muParser.DefineVar(_T("x"), &x);
  66. muParser.DefineVar(_T("xp"), &x);
  67. muParser.DefineVar(_T("len"), &x);
  68. muParser.DefineVar(_T("lens"), &x);
  69. muParser.DefineVar(_T("freq"), &x);
  70. try
  71. {
  72. muParser.Eval();
  73. }
  74. catch (mu::Parser::exception_type &e)
  75. {
  76. ShowError(&e);
  77. return false;
  78. }
  79. return true;
  80. }
  81. // Check if sample parameters are valid.
  82. bool CSampleGenerator::CanRenderSample() const
  83. {
  84. if(sample_frequency < SMPGEN_MINFREQ || sample_frequency > SMPGEN_MAXFREQ || sample_length < SMPGEN_MINLENGTH || sample_length > SMPGEN_MAXLENGTH) return false;
  85. return true;
  86. }
  87. // Actual render loop.
  88. bool CSampleGenerator::RenderSample(CSoundFile *pSndFile, SAMPLEINDEX nSample)
  89. {
  90. if(!CanRenderSample() || !TestExpression() || (pSndFile == nullptr) || (nSample < 1) || (nSample > pSndFile->m_nSamples)) return false;
  91. // allocate a new buffer
  92. sample_buffer = (mu::value_type *)malloc(sample_length * sizeof(mu::value_type));
  93. if(sample_buffer == nullptr) return false;
  94. memset(sample_buffer, 0, sample_length * sizeof(mu::value_type));
  95. mu::value_type x = 0, xp = 0;
  96. mu::value_type v_len = sample_length, v_freq = sample_frequency, v_lens = v_len / v_freq;
  97. muParser.DefineVar(_T("x"), &x);
  98. muParser.DefineVar(_T("xp"), &xp);
  99. muParser.DefineVar(_T("len"), &v_len);
  100. muParser.DefineVar(_T("lens"), &v_lens);
  101. muParser.DefineVar(_T("freq"), &v_freq);
  102. bool success = true;
  103. mu::value_type minmax = 0;
  104. for(size_t i = 0; i < (size_t)sample_length; i++)
  105. {
  106. samples_written = i;
  107. x = (mu::value_type)i;
  108. xp = x * 100 / sample_length;
  109. try
  110. {
  111. sample_buffer[i] = muParser.Eval();
  112. }
  113. catch (mu::Parser::exception_type &e)
  114. {
  115. // let's just ignore div by zero errors (note: this error code is currently unused (muParser 1.30))
  116. if(e.GetCode() != mu::ecDIV_BY_ZERO)
  117. {
  118. ShowError(&e);
  119. success = false;
  120. break;
  121. }
  122. sample_buffer[i] = 0;
  123. }
  124. // new maximum value?
  125. if(std::abs(sample_buffer[i]) > minmax) minmax = std::abs(sample_buffer[i]);
  126. }
  127. if(success)
  128. {
  129. MODSAMPLE *pModSample = &pSndFile->Samples[nSample];
  130. BEGIN_CRITICAL();
  131. // first, save some memory... (leads to crashes)
  132. //CSoundFile::FreeSample(pModSample->pSample);
  133. //pModSample->pSample = nullptr;
  134. if(minmax == 0) minmax = 1; // avoid division by 0
  135. // convert sample to 16-bit (or whateve rhas been specified)
  136. int16 *pSample = (sampling_type *)CSoundFile::AllocateSample((sample_length + 4) * SMPGEN_MIXBYTES);
  137. for(size_t i = 0; i < (size_t)sample_length; i++)
  138. {
  139. switch(sample_clipping)
  140. {
  141. case smpgen_clip: sample_buffer[i] = CLAMP(sample_buffer[i], -1, 1); break; // option 1: clip
  142. case smpgen_normalize: sample_buffer[i] /= minmax; break; // option 3: normalize
  143. }
  144. pSample[i] = (sampling_type)(sample_buffer[i] * sample_maxvalue);
  145. }
  146. // set new sample proprerties
  147. pModSample->nC5Speed = sample_frequency;
  148. CSoundFile::FrequencyToTranspose(pModSample);
  149. pModSample->uFlags |= CHN_16BIT; // has to be adjusted if SMPGEN_MIXBYTES changes!
  150. pModSample->uFlags &= ~(CHN_STEREO|CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN);
  151. pModSample->nLoopStart = 0;
  152. pModSample->nLoopEnd = sample_length;
  153. pModSample->nSustainStart = pModSample->nSustainEnd = 0;
  154. if(sample_length / sample_frequency < 5) // arbitrary limit for automatic sample loop (5 seconds)
  155. pModSample->uFlags |= CHN_LOOP;
  156. else
  157. pModSample->uFlags &= ~(CHN_LOOP|CHN_PINGPONGLOOP);
  158. ctrlSmp::ReplaceSample(*pModSample, (LPSTR)pSample, sample_length, pSndFile);
  159. END_CRITICAL();
  160. }
  161. free(sample_buffer);
  162. sample_buffer = nullptr;
  163. return success;
  164. }
  165. // Callback function to access sample data
  166. mu::value_type CSampleGenerator::SampleDataCallback(mu::value_type v)
  167. {
  168. if(sample_buffer == nullptr) return 0;
  169. v = CLAMP(v, 0, samples_written);
  170. size_t pos = static_cast<size_t>(v);
  171. return sample_buffer[pos];
  172. }
  173. void CSampleGenerator::ShowError(mu::Parser::exception_type *e)
  174. {
  175. std::string errmsg;
  176. errmsg = "The expression\n " + e->GetExpr() + "\ncontains an error ";
  177. if(!e->GetToken().empty())
  178. errmsg += "in the token\n " + e->GetToken() + "\n";
  179. errmsg += "at position " + Stringify(e->GetPos()) + ".\nThe error message was: " + e->GetMsg();
  180. ::MessageBox(0, errmsg.c_str(), _T("muParser Sample Generator"), 0);
  181. }
  182. //////////////////////////////////////////////////////////////////////////
  183. // Sample Generator Dialog implementation
  184. #define MAX_SAMPLEGEN_EXPRESSIONS 61
  185. BEGIN_MESSAGE_MAP(CSmpGenDialog, CDialog)
  186. ON_EN_CHANGE(IDC_EDIT_SAMPLE_LENGTH, &CSmpGenDialog::OnSampleLengthChanged)
  187. ON_EN_CHANGE(IDC_EDIT_SAMPLE_LENGTH_SEC, &CSmpGenDialog::OnSampleSecondsChanged)
  188. ON_EN_CHANGE(IDC_EDIT_SAMPLE_FREQ, &CSmpGenDialog::OnSampleFreqChanged)
  189. ON_EN_CHANGE(IDC_EDIT_FORMULA, &CSmpGenDialog::OnExpressionChanged)
  190. ON_COMMAND(IDC_BUTTON_SHOW_EXPRESSIONS, &CSmpGenDialog::OnShowExpressions)
  191. ON_COMMAND(IDC_BUTTON_SAMPLEGEN_PRESETS, &CSmpGenDialog::OnShowPresets)
  192. ON_COMMAND_RANGE(ID_SAMPLE_GENERATOR_MENU, ID_SAMPLE_GENERATOR_MENU + MAX_SAMPLEGEN_EXPRESSIONS - 1, &CSmpGenDialog::OnInsertExpression)
  193. ON_COMMAND_RANGE(ID_SAMPLE_GENERATOR_PRESET_MENU, ID_SAMPLE_GENERATOR_PRESET_MENU + MAX_SAMPLEGEN_PRESETS + 1, &CSmpGenDialog::OnSelectPreset)
  194. END_MESSAGE_MAP()
  195. // List of all possible expression for expression menu
  196. const samplegen_expression menu_descriptions[MAX_SAMPLEGEN_EXPRESSIONS] =
  197. {
  198. {"Variables", ""},
  199. {"Current position (sampling point)", "x"},
  200. {"Current position (percentage)", "xp"},
  201. {"Sample length", "len"},
  202. {"Sample length (seconds)", "lens"},
  203. {"Sampling frequency", "freq"},
  204. {"Constants", ""},
  205. {"Pi", "_pi"},
  206. {"e", "_e"},
  207. {"Trigonometric functions", ""},
  208. {"Sine", "sin(x)"},
  209. {"Cosine", "cos(x)"},
  210. {"Tangens", "tan(x)"},
  211. {"Arcus Sine", "asin(x)"},
  212. {"Arcus Cosine", "acos(x)"},
  213. {"Arcus Tangens", "atan(x)"},
  214. {"Hyperbolic Sine", "sinh(x)"},
  215. {"Hyperbolic Cosine", "cosh(x)"},
  216. {"Hyperbolic Tangens", "tanh(x)"},
  217. {"Hyperbolic Arcus Sine", "asinh(x)"},
  218. {"Hyperbolic Arcus Cosine", "acosh(x)"},
  219. {"Hyperbolic Arcus Tangens", "atanh(x)"},
  220. {"Log, Exp, Root", ""},
  221. {"Logarithm (base 2)", "log2(x)"},
  222. {"Logarithm (base 10)", "log(x)"},
  223. {"Natural Logarithm (base e)", "ln(x)"},
  224. {"e^x", "exp(x)"},
  225. {"Square Root", "sqrt(x)"},
  226. {"Sign and rounding", ""},
  227. {"Sign", "sign(x)"},
  228. {"Absolute value", "abs(x)"},
  229. {"Round to nearest integer", "rint(x)"},
  230. {"Sets", ""},
  231. {"Minimum", "min(x, y, ...)"},
  232. {"Maximum", "max(x, y, ...)"},
  233. {"Sum", "sum(x, y, ...)"},
  234. {"Mean value", "avg(x, y, ...)"},
  235. {"Misc functions", ""},
  236. {"Pulse generator", "pwm(position, duty%, width)"},
  237. {"Triangle", "tri(position, width)"},
  238. {"Random value between 0 and x", "rnd(x)"},
  239. {"Access previous sampling point", "smp(position)"},
  240. {"Clip between values", "clip(value, minclip, maxclip)"},
  241. {"If...Then...Else", "if(condition, statement1, statement2)"},
  242. {"Operators", ""},
  243. {"Assignment", "x = y"},
  244. {"Logical And", "x abd y"},
  245. {"Logical Or", "x or y"},
  246. {"Logical Xor", "x xor y"},
  247. {"Less or equal", "x <= y"},
  248. {"Greater or equal", "x >= y"},
  249. {"Not equal", "x != y"},
  250. {"Equal", "x == y"},
  251. {"Greater than", "x > y"},
  252. {"Less than", "x < y"},
  253. {"Addition", "x + y"},
  254. {"Subtraction", "x - y"},
  255. {"Multiplication", "x * y"},
  256. {"Division", "x / y"},
  257. {"x^y", "x ^ y"},
  258. {"Modulo", "x mod y"},
  259. };
  260. BOOL CSmpGenDialog::OnInitDialog()
  261. {
  262. CDialog::OnInitDialog();
  263. RecalcParameters(false, true);
  264. SetDlgItemText(IDC_EDIT_FORMULA, expression.c_str());
  265. int check = IDC_RADIO_SMPCLIP1;
  266. switch(sample_clipping)
  267. {
  268. case smpgen_clip: check = IDC_RADIO_SMPCLIP1; break;
  269. case smpgen_overflow: check = IDC_RADIO_SMPCLIP2; break;
  270. case smpgen_normalize: check = IDC_RADIO_SMPCLIP3; break;
  271. }
  272. CheckRadioButton(IDC_RADIO_SMPCLIP1, IDC_RADIO_SMPCLIP3, check);
  273. if(presets.GetNumPresets() == 0)
  274. {
  275. CreateDefaultPresets();
  276. }
  277. // Create font for "dropdown" button (Marlett system font)
  278. hButtonFont = CreateFont(14, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, TEXT("Marlett"));
  279. ::SendMessage(GetDlgItem(IDC_BUTTON_SHOW_EXPRESSIONS)->m_hWnd, WM_SETFONT, (WPARAM)hButtonFont, MAKELPARAM(TRUE, 0));
  280. return TRUE;
  281. }
  282. void CSmpGenDialog::OnOK()
  283. {
  284. CDialog::OnOK();
  285. apply = true;
  286. int check = GetCheckedRadioButton(IDC_RADIO_SMPCLIP1, IDC_RADIO_SMPCLIP3);
  287. switch(check)
  288. {
  289. case IDC_RADIO_SMPCLIP1: sample_clipping = smpgen_clip; break;
  290. case IDC_RADIO_SMPCLIP2: sample_clipping = smpgen_overflow; break;
  291. case IDC_RADIO_SMPCLIP3: sample_clipping = smpgen_normalize; break;
  292. }
  293. DeleteObject(hButtonFont);
  294. }
  295. void CSmpGenDialog::OnCancel()
  296. {
  297. CDialog::OnCancel();
  298. apply = false;
  299. }
  300. // User changed formula
  301. void CSmpGenDialog::OnExpressionChanged()
  302. {
  303. CString result;
  304. GetDlgItemText(IDC_EDIT_FORMULA, result);
  305. expression = result;
  306. }
  307. // User changed sample length field
  308. void CSmpGenDialog::OnSampleLengthChanged()
  309. {
  310. int temp_length = GetDlgItemInt(IDC_EDIT_SAMPLE_LENGTH);
  311. if(temp_length >= SMPGEN_MINLENGTH && temp_length <= SMPGEN_MAXLENGTH)
  312. {
  313. sample_length = temp_length;
  314. RecalcParameters(false);
  315. }
  316. }
  317. // User changed sample length (seconds) field
  318. void CSmpGenDialog::OnSampleSecondsChanged()
  319. {
  320. CString str;
  321. GetDlgItemText(IDC_EDIT_SAMPLE_LENGTH_SEC, str);
  322. double temp_seconds = atof(str);
  323. if(temp_seconds > 0)
  324. {
  325. sample_seconds = temp_seconds;
  326. RecalcParameters(true);
  327. }
  328. }
  329. // User changed sample frequency field
  330. void CSmpGenDialog::OnSampleFreqChanged()
  331. {
  332. int temp_freq = GetDlgItemInt(IDC_EDIT_SAMPLE_FREQ);
  333. if(temp_freq >= SMPGEN_MINFREQ && temp_freq <= SMPGEN_MAXFREQ)
  334. {
  335. sample_frequency = temp_freq;
  336. RecalcParameters(false);
  337. }
  338. }
  339. // Show all expressions that can be input
  340. void CSmpGenDialog::OnShowExpressions()
  341. {
  342. HMENU hMenu = ::CreatePopupMenu(), hSubMenu = NULL;
  343. if(!hMenu) return;
  344. for(int i = 0; i < MAX_SAMPLEGEN_EXPRESSIONS; i++)
  345. {
  346. if(menu_descriptions[i].expression == "")
  347. {
  348. // add sub menu
  349. if(hSubMenu != NULL) ::DestroyMenu(hSubMenu);
  350. hSubMenu = ::CreatePopupMenu();
  351. AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hSubMenu, menu_descriptions[i].description.c_str());
  352. } else
  353. {
  354. // add sub menu entry (formula)
  355. AppendMenu(hSubMenu, MF_STRING, ID_SAMPLE_GENERATOR_MENU + i, menu_descriptions[i].description.c_str());
  356. }
  357. }
  358. // place popup menu below button
  359. RECT button;
  360. GetDlgItem(IDC_BUTTON_SHOW_EXPRESSIONS)->GetWindowRect(&button);
  361. ::TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, button.left, button.bottom, 0, m_hWnd, NULL);
  362. ::DestroyMenu(hMenu);
  363. ::DestroyMenu(hSubMenu);
  364. }
  365. // Show all expression presets
  366. void CSmpGenDialog::OnShowPresets()
  367. {
  368. HMENU hMenu = ::CreatePopupMenu();
  369. if(!hMenu) return;
  370. bool prestsExist = false;
  371. for(size_t i = 0; i < presets.GetNumPresets(); i++)
  372. {
  373. if(presets.GetPreset(i)->expression != "")
  374. {
  375. AppendMenu(hMenu, MF_STRING, ID_SAMPLE_GENERATOR_PRESET_MENU + i, presets.GetPreset(i)->description.c_str());
  376. prestsExist = true;
  377. }
  378. }
  379. if(prestsExist) AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);
  380. AppendMenu(hMenu, MF_STRING, ID_SAMPLE_GENERATOR_PRESET_MENU + MAX_SAMPLEGEN_PRESETS, _TEXT("Manage..."));
  381. CString result;
  382. GetDlgItemText(IDC_EDIT_FORMULA, result);
  383. if((!result.IsEmpty()) && (presets.GetNumPresets() < MAX_SAMPLEGEN_PRESETS))
  384. {
  385. AppendMenu(hMenu, MF_STRING, ID_SAMPLE_GENERATOR_PRESET_MENU + MAX_SAMPLEGEN_PRESETS + 1, _TEXT("Add current..."));
  386. }
  387. // place popup menu below button
  388. RECT button;
  389. GetDlgItem(IDC_BUTTON_SAMPLEGEN_PRESETS)->GetWindowRect(&button);
  390. ::TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, button.left, button.bottom, 0, m_hWnd, NULL);
  391. ::DestroyMenu(hMenu);
  392. }
  393. // Insert expression from context menu
  394. void CSmpGenDialog::OnInsertExpression(UINT nId)
  395. {
  396. if((nId < ID_SAMPLE_GENERATOR_MENU) || (nId >= ID_SAMPLE_GENERATOR_MENU + MAX_SAMPLEGEN_EXPRESSIONS)) return;
  397. expression += " " + menu_descriptions[nId - ID_SAMPLE_GENERATOR_MENU].expression;
  398. SetDlgItemText(IDC_EDIT_FORMULA, expression.c_str());
  399. }
  400. // Select a preset (or manage them, or add one)
  401. void CSmpGenDialog::OnSelectPreset(UINT nId)
  402. {
  403. if((nId < ID_SAMPLE_GENERATOR_PRESET_MENU) || (nId >= ID_SAMPLE_GENERATOR_PRESET_MENU + MAX_SAMPLEGEN_PRESETS + 2)) return;
  404. if(nId - ID_SAMPLE_GENERATOR_PRESET_MENU >= MAX_SAMPLEGEN_PRESETS)
  405. {
  406. // add...
  407. if((nId - ID_SAMPLE_GENERATOR_PRESET_MENU == MAX_SAMPLEGEN_PRESETS + 1))
  408. {
  409. samplegen_expression newPreset;
  410. newPreset.description = newPreset.expression = expression;
  411. presets.AddPreset(newPreset);
  412. // call preset manager now.
  413. }
  414. // manage...
  415. CSmpGenPresetDlg dlg(&presets);
  416. dlg.DoModal();
  417. } else
  418. {
  419. expression = presets.GetPreset(nId - ID_SAMPLE_GENERATOR_PRESET_MENU)->expression;
  420. SetDlgItemText(IDC_EDIT_FORMULA, expression.c_str());
  421. }
  422. }
  423. // Update input fields, depending on what has been chagned
  424. void CSmpGenDialog::RecalcParameters(bool secondsChanged, bool forceRefresh)
  425. {
  426. static bool isLocked = false;
  427. if(isLocked) return;
  428. isLocked = true; // avoid deadlock
  429. if(secondsChanged)
  430. {
  431. // seconds changed => recalc length
  432. sample_length = (int)(sample_seconds * sample_frequency);
  433. if(sample_length < SMPGEN_MINLENGTH || sample_length > SMPGEN_MAXLENGTH) sample_length = SMPGEN_MAXLENGTH;
  434. } else
  435. {
  436. // length/freq changed => recalc seconds
  437. sample_seconds = ((double)sample_length) / ((double)sample_frequency);
  438. }
  439. if(secondsChanged || forceRefresh) SetDlgItemInt(IDC_EDIT_SAMPLE_LENGTH, sample_length);
  440. if(secondsChanged || forceRefresh) SetDlgItemInt(IDC_EDIT_SAMPLE_FREQ, sample_frequency);
  441. CString str;
  442. str.Format("%.4f", sample_seconds);
  443. if(!secondsChanged || forceRefresh) SetDlgItemText(IDC_EDIT_SAMPLE_LENGTH_SEC, str);
  444. int smpsize = sample_length * SMPGEN_MIXBYTES;
  445. if(smpsize < 1024)
  446. {
  447. str.Format("Sample Size: %d Bytes", smpsize);
  448. } else if((smpsize >> 10) < 1024)
  449. {
  450. str.Format("Sample Size: %d KB", smpsize >> 10);
  451. } else
  452. {
  453. str.Format("Sample Size: %d MB", smpsize >> 20);
  454. }
  455. SetDlgItemText(IDC_STATIC_SMPSIZE_KB, str);
  456. isLocked = false;
  457. }
  458. // Create a set of default formla presets
  459. void CSmpGenDialog::CreateDefaultPresets()
  460. {
  461. samplegen_expression preset;
  462. preset.description = "A440";
  463. preset.expression = "sin(xp * _pi / 50 * 440 * len / freq)";
  464. presets.AddPreset(preset);
  465. preset.description = "Brown Noise (kind of)";
  466. preset.expression = "rnd(1) * 0.1 + smp(x - 1) * 0.9";
  467. presets.AddPreset(preset);
  468. preset.description = "Noisy Saw";
  469. preset.expression = "(x mod 800) / 800 - 0.5 + rnd(0.1)";
  470. presets.AddPreset(preset);
  471. preset.description = "PWM Filter";
  472. preset.expression = "pwm(x, 50 + sin(xp * _pi / 100) * 40, 100) + tri(x, 50)";
  473. presets.AddPreset(preset);
  474. preset.description = "Fat PWM Pad";
  475. preset.expression = "pwm(x, xp, 500) + pwm(x, abs(50 - xp), 1000)";
  476. presets.AddPreset(preset);
  477. preset.description = "Dual Square";
  478. preset.expression = "if((x mod 100) < 50, (x mod 200), -x mod 200)";
  479. presets.AddPreset(preset);
  480. preset.description = "Noise Hit";
  481. preset.expression = "exp(-xp) * (rnd(x) - x / 2)";
  482. presets.AddPreset(preset);
  483. preset.description = "Laser";
  484. preset.expression = "sin(xp * _pi * 100 /(xp ^ 2)) * 100 / sqrt(xp)";
  485. presets.AddPreset(preset);
  486. preset.description = "Noisy Laser Hit";
  487. preset.expression = "(sin(sqrt(xp) * 100) + rnd(1) - 0.5) * exp(-xp / 10)";
  488. presets.AddPreset(preset);
  489. preset.description = "Twinkle, Twinkle...";
  490. preset.expression = "sin(xp * _pi * 100 / xp) * 100 / sqrt(xp)";
  491. presets.AddPreset(preset);
  492. preset.description = "FM Tom";
  493. preset.expression = "sin(xp * _pi * 2 + (xp / 5 - 50) ^ 2) * exp(-xp / 10)";
  494. presets.AddPreset(preset);
  495. preset.description = "FM Warp";
  496. preset.expression = "sin(_pi * xp / 2 * (1 + (1 + sin(_pi * xp / 4 * 50)) / 4)) * exp(-(xp / 8) * .6)";
  497. presets.AddPreset(preset);
  498. preset.description = "Weird Noise";
  499. preset.expression = "rnd(1) * 0.1 + smp(x - rnd(xp)) * 0.9";
  500. presets.AddPreset(preset);
  501. }
  502. //////////////////////////////////////////////////////////////////////////
  503. // Sample Generator Preset Dialog implementation
  504. BEGIN_MESSAGE_MAP(CSmpGenPresetDlg, CDialog)
  505. ON_COMMAND(IDC_BUTTON_ADD, &CSmpGenPresetDlg::OnAddPreset)
  506. ON_COMMAND(IDC_BUTTON_REMOVE, &CSmpGenPresetDlg::OnRemovePreset)
  507. ON_EN_CHANGE(IDC_EDIT_PRESET_NAME, &CSmpGenPresetDlg::OnTextChanged)
  508. ON_EN_CHANGE(IDC_EDIT_PRESET_EXPR, &CSmpGenPresetDlg::OnExpressionChanged)
  509. ON_LBN_SELCHANGE(IDC_LIST_SAMPLEGEN_PRESETS, &CSmpGenPresetDlg::OnListSelChange)
  510. END_MESSAGE_MAP()
  511. BOOL CSmpGenPresetDlg::OnInitDialog()
  512. {
  513. CDialog::OnInitDialog();
  514. RefreshList();
  515. return TRUE;
  516. }
  517. void CSmpGenPresetDlg::OnOK()
  518. {
  519. // remove empty presets
  520. for(size_t i = 0; i < presets->GetNumPresets(); i++)
  521. {
  522. if(presets->GetPreset(i)->expression.empty())
  523. {
  524. presets->RemovePreset(i);
  525. }
  526. }
  527. CDialog::OnOK();
  528. }
  529. void CSmpGenPresetDlg::OnListSelChange()
  530. {
  531. currentItem = ((CListBox *)GetDlgItem(IDC_LIST_SAMPLEGEN_PRESETS))->GetCurSel() + 1;
  532. if(currentItem == 0 || currentItem > presets->GetNumPresets()) return;
  533. samplegen_expression *preset = presets->GetPreset(currentItem - 1);
  534. if(preset == nullptr) return;
  535. SetDlgItemText(IDC_EDIT_PRESET_NAME, preset->description.c_str());
  536. SetDlgItemText(IDC_EDIT_PRESET_EXPR, preset->expression.c_str());
  537. }
  538. void CSmpGenPresetDlg::OnTextChanged()
  539. {
  540. if(currentItem == 0 || currentItem > presets->GetNumPresets()) return;
  541. CString result;
  542. GetDlgItemText(IDC_EDIT_PRESET_NAME, result);
  543. samplegen_expression *preset = presets->GetPreset(currentItem - 1);
  544. if(preset == nullptr) return;
  545. preset->description = result;
  546. CListBox *clist = (CListBox *)GetDlgItem(IDC_LIST_SAMPLEGEN_PRESETS);
  547. clist->DeleteString(currentItem - 1);
  548. clist->InsertString(currentItem - 1, (preset->description).c_str());
  549. clist->SetCurSel(currentItem - 1);
  550. }
  551. void CSmpGenPresetDlg::OnExpressionChanged()
  552. {
  553. if(currentItem == 0 || currentItem > presets->GetNumPresets()) return;
  554. CString result;
  555. GetDlgItemText(IDC_EDIT_PRESET_EXPR, result);
  556. samplegen_expression *preset = presets->GetPreset(currentItem - 1);
  557. if(preset != nullptr) preset->expression = result;
  558. }
  559. void CSmpGenPresetDlg::OnAddPreset()
  560. {
  561. samplegen_expression newPreset;
  562. newPreset.description = "New Preset";
  563. newPreset.expression = "";
  564. if(presets->AddPreset(newPreset))
  565. {
  566. currentItem = presets->GetNumPresets();
  567. RefreshList();
  568. }
  569. }
  570. void CSmpGenPresetDlg::OnRemovePreset()
  571. {
  572. if(currentItem == 0 || currentItem > presets->GetNumPresets()) return;
  573. if(presets->RemovePreset(currentItem - 1))
  574. RefreshList();
  575. }
  576. void CSmpGenPresetDlg::RefreshList()
  577. {
  578. CListBox *clist = (CListBox *)GetDlgItem(IDC_LIST_SAMPLEGEN_PRESETS);
  579. clist->SetRedraw(FALSE); //disable lisbox refreshes during fill to avoid flicker
  580. clist->ResetContent();
  581. for(size_t i = 0; i < presets->GetNumPresets(); i++)
  582. {
  583. samplegen_expression *preset = presets->GetPreset(i);
  584. if(preset != nullptr)
  585. clist->AddString((preset->description).c_str());
  586. }
  587. clist->SetRedraw(TRUE); //re-enable lisbox refreshes
  588. if(currentItem == 0 || currentItem > presets->GetNumPresets())
  589. {
  590. currentItem = presets->GetNumPresets();
  591. }
  592. if(currentItem != 0) clist->SetCurSel(currentItem - 1);
  593. OnListSelChange();
  594. }
  595. #endif // MPT_DISABLED_CODE