/* * SampleGenerator.cpp * ------------------- * Purpose: Generate samples from math formulas using muParser * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #if MPT_DISABLED_CODE #include "SampleGenerator.h" #include "modsmp_ctrl.h" int CSampleGenerator::sample_frequency = 44100; int CSampleGenerator::sample_length = CSampleGenerator::sample_frequency; mu::string_type CSampleGenerator::expression = _T("sin(xp * _pi)"); smpgen_clip_methods CSampleGenerator::sample_clipping = smpgen_normalize; mu::value_type *CSampleGenerator::sample_buffer = nullptr; size_t CSampleGenerator::samples_written = 0; CSampleGenerator::CSampleGenerator() { // Setup function callbacks muParser.DefineFun(_T("clip"), &ClipCallback, false); muParser.DefineFun(_T("pwm"), &PWMCallback, false); muParser.DefineFun(_T("rnd"), &RndCallback, false); muParser.DefineFun(_T("smp"), &SampleDataCallback, false); muParser.DefineFun(_T("tri"), &TriangleCallback, false); // Setup binary operator callbacks muParser.DefineOprt(_T("mod"), &ModuloCallback, 0); //muParser.DefineConst("pi", (mu::value_type)PARSER_CONST_PI); } // Open the smpgen dialog bool CSampleGenerator::ShowDialog() { bool isDone = false, result = false; while(!isDone) { CSmpGenDialog dlg(sample_frequency, sample_length, sample_clipping, expression); dlg.DoModal(); // pressed "OK" button? if(dlg.CanApply()) { sample_frequency = dlg.GetFrequency(); sample_length = dlg.GetLength(); sample_clipping = dlg.GetClipping(); expression = dlg.GetExpression(); isDone = CanRenderSample(); if(isDone) isDone = TestExpression(); // show dialog again if the formula can't be parsed. result = true; } else { isDone = true; // just quit. result = false; } } return result; } // Check if the currently select expression can be parsed by muParser. bool CSampleGenerator::TestExpression() { // reset helper variables samples_written = 0; sample_buffer = nullptr; muParser.SetExpr(expression); mu::value_type x = 0; muParser.DefineVar(_T("x"), &x); muParser.DefineVar(_T("xp"), &x); muParser.DefineVar(_T("len"), &x); muParser.DefineVar(_T("lens"), &x); muParser.DefineVar(_T("freq"), &x); try { muParser.Eval(); } catch (mu::Parser::exception_type &e) { ShowError(&e); return false; } return true; } // Check if sample parameters are valid. bool CSampleGenerator::CanRenderSample() const { if(sample_frequency < SMPGEN_MINFREQ || sample_frequency > SMPGEN_MAXFREQ || sample_length < SMPGEN_MINLENGTH || sample_length > SMPGEN_MAXLENGTH) return false; return true; } // Actual render loop. bool CSampleGenerator::RenderSample(CSoundFile *pSndFile, SAMPLEINDEX nSample) { if(!CanRenderSample() || !TestExpression() || (pSndFile == nullptr) || (nSample < 1) || (nSample > pSndFile->m_nSamples)) return false; // allocate a new buffer sample_buffer = (mu::value_type *)malloc(sample_length * sizeof(mu::value_type)); if(sample_buffer == nullptr) return false; memset(sample_buffer, 0, sample_length * sizeof(mu::value_type)); mu::value_type x = 0, xp = 0; mu::value_type v_len = sample_length, v_freq = sample_frequency, v_lens = v_len / v_freq; muParser.DefineVar(_T("x"), &x); muParser.DefineVar(_T("xp"), &xp); muParser.DefineVar(_T("len"), &v_len); muParser.DefineVar(_T("lens"), &v_lens); muParser.DefineVar(_T("freq"), &v_freq); bool success = true; mu::value_type minmax = 0; for(size_t i = 0; i < (size_t)sample_length; i++) { samples_written = i; x = (mu::value_type)i; xp = x * 100 / sample_length; try { sample_buffer[i] = muParser.Eval(); } catch (mu::Parser::exception_type &e) { // let's just ignore div by zero errors (note: this error code is currently unused (muParser 1.30)) if(e.GetCode() != mu::ecDIV_BY_ZERO) { ShowError(&e); success = false; break; } sample_buffer[i] = 0; } // new maximum value? if(std::abs(sample_buffer[i]) > minmax) minmax = std::abs(sample_buffer[i]); } if(success) { MODSAMPLE *pModSample = &pSndFile->Samples[nSample]; BEGIN_CRITICAL(); // first, save some memory... (leads to crashes) //CSoundFile::FreeSample(pModSample->pSample); //pModSample->pSample = nullptr; if(minmax == 0) minmax = 1; // avoid division by 0 // convert sample to 16-bit (or whateve rhas been specified) int16 *pSample = (sampling_type *)CSoundFile::AllocateSample((sample_length + 4) * SMPGEN_MIXBYTES); for(size_t i = 0; i < (size_t)sample_length; i++) { switch(sample_clipping) { case smpgen_clip: sample_buffer[i] = CLAMP(sample_buffer[i], -1, 1); break; // option 1: clip case smpgen_normalize: sample_buffer[i] /= minmax; break; // option 3: normalize } pSample[i] = (sampling_type)(sample_buffer[i] * sample_maxvalue); } // set new sample proprerties pModSample->nC5Speed = sample_frequency; CSoundFile::FrequencyToTranspose(pModSample); pModSample->uFlags |= CHN_16BIT; // has to be adjusted if SMPGEN_MIXBYTES changes! pModSample->uFlags &= ~(CHN_STEREO|CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN); pModSample->nLoopStart = 0; pModSample->nLoopEnd = sample_length; pModSample->nSustainStart = pModSample->nSustainEnd = 0; if(sample_length / sample_frequency < 5) // arbitrary limit for automatic sample loop (5 seconds) pModSample->uFlags |= CHN_LOOP; else pModSample->uFlags &= ~(CHN_LOOP|CHN_PINGPONGLOOP); ctrlSmp::ReplaceSample(*pModSample, (LPSTR)pSample, sample_length, pSndFile); END_CRITICAL(); } free(sample_buffer); sample_buffer = nullptr; return success; } // Callback function to access sample data mu::value_type CSampleGenerator::SampleDataCallback(mu::value_type v) { if(sample_buffer == nullptr) return 0; v = CLAMP(v, 0, samples_written); size_t pos = static_cast(v); return sample_buffer[pos]; } void CSampleGenerator::ShowError(mu::Parser::exception_type *e) { std::string errmsg; errmsg = "The expression\n " + e->GetExpr() + "\ncontains an error "; if(!e->GetToken().empty()) errmsg += "in the token\n " + e->GetToken() + "\n"; errmsg += "at position " + Stringify(e->GetPos()) + ".\nThe error message was: " + e->GetMsg(); ::MessageBox(0, errmsg.c_str(), _T("muParser Sample Generator"), 0); } ////////////////////////////////////////////////////////////////////////// // Sample Generator Dialog implementation #define MAX_SAMPLEGEN_EXPRESSIONS 61 BEGIN_MESSAGE_MAP(CSmpGenDialog, CDialog) ON_EN_CHANGE(IDC_EDIT_SAMPLE_LENGTH, &CSmpGenDialog::OnSampleLengthChanged) ON_EN_CHANGE(IDC_EDIT_SAMPLE_LENGTH_SEC, &CSmpGenDialog::OnSampleSecondsChanged) ON_EN_CHANGE(IDC_EDIT_SAMPLE_FREQ, &CSmpGenDialog::OnSampleFreqChanged) ON_EN_CHANGE(IDC_EDIT_FORMULA, &CSmpGenDialog::OnExpressionChanged) ON_COMMAND(IDC_BUTTON_SHOW_EXPRESSIONS, &CSmpGenDialog::OnShowExpressions) ON_COMMAND(IDC_BUTTON_SAMPLEGEN_PRESETS, &CSmpGenDialog::OnShowPresets) ON_COMMAND_RANGE(ID_SAMPLE_GENERATOR_MENU, ID_SAMPLE_GENERATOR_MENU + MAX_SAMPLEGEN_EXPRESSIONS - 1, &CSmpGenDialog::OnInsertExpression) ON_COMMAND_RANGE(ID_SAMPLE_GENERATOR_PRESET_MENU, ID_SAMPLE_GENERATOR_PRESET_MENU + MAX_SAMPLEGEN_PRESETS + 1, &CSmpGenDialog::OnSelectPreset) END_MESSAGE_MAP() // List of all possible expression for expression menu const samplegen_expression menu_descriptions[MAX_SAMPLEGEN_EXPRESSIONS] = { {"Variables", ""}, {"Current position (sampling point)", "x"}, {"Current position (percentage)", "xp"}, {"Sample length", "len"}, {"Sample length (seconds)", "lens"}, {"Sampling frequency", "freq"}, {"Constants", ""}, {"Pi", "_pi"}, {"e", "_e"}, {"Trigonometric functions", ""}, {"Sine", "sin(x)"}, {"Cosine", "cos(x)"}, {"Tangens", "tan(x)"}, {"Arcus Sine", "asin(x)"}, {"Arcus Cosine", "acos(x)"}, {"Arcus Tangens", "atan(x)"}, {"Hyperbolic Sine", "sinh(x)"}, {"Hyperbolic Cosine", "cosh(x)"}, {"Hyperbolic Tangens", "tanh(x)"}, {"Hyperbolic Arcus Sine", "asinh(x)"}, {"Hyperbolic Arcus Cosine", "acosh(x)"}, {"Hyperbolic Arcus Tangens", "atanh(x)"}, {"Log, Exp, Root", ""}, {"Logarithm (base 2)", "log2(x)"}, {"Logarithm (base 10)", "log(x)"}, {"Natural Logarithm (base e)", "ln(x)"}, {"e^x", "exp(x)"}, {"Square Root", "sqrt(x)"}, {"Sign and rounding", ""}, {"Sign", "sign(x)"}, {"Absolute value", "abs(x)"}, {"Round to nearest integer", "rint(x)"}, {"Sets", ""}, {"Minimum", "min(x, y, ...)"}, {"Maximum", "max(x, y, ...)"}, {"Sum", "sum(x, y, ...)"}, {"Mean value", "avg(x, y, ...)"}, {"Misc functions", ""}, {"Pulse generator", "pwm(position, duty%, width)"}, {"Triangle", "tri(position, width)"}, {"Random value between 0 and x", "rnd(x)"}, {"Access previous sampling point", "smp(position)"}, {"Clip between values", "clip(value, minclip, maxclip)"}, {"If...Then...Else", "if(condition, statement1, statement2)"}, {"Operators", ""}, {"Assignment", "x = y"}, {"Logical And", "x abd y"}, {"Logical Or", "x or y"}, {"Logical Xor", "x xor y"}, {"Less or equal", "x <= y"}, {"Greater or equal", "x >= y"}, {"Not equal", "x != y"}, {"Equal", "x == y"}, {"Greater than", "x > y"}, {"Less than", "x < y"}, {"Addition", "x + y"}, {"Subtraction", "x - y"}, {"Multiplication", "x * y"}, {"Division", "x / y"}, {"x^y", "x ^ y"}, {"Modulo", "x mod y"}, }; BOOL CSmpGenDialog::OnInitDialog() { CDialog::OnInitDialog(); RecalcParameters(false, true); SetDlgItemText(IDC_EDIT_FORMULA, expression.c_str()); int check = IDC_RADIO_SMPCLIP1; switch(sample_clipping) { case smpgen_clip: check = IDC_RADIO_SMPCLIP1; break; case smpgen_overflow: check = IDC_RADIO_SMPCLIP2; break; case smpgen_normalize: check = IDC_RADIO_SMPCLIP3; break; } CheckRadioButton(IDC_RADIO_SMPCLIP1, IDC_RADIO_SMPCLIP3, check); if(presets.GetNumPresets() == 0) { CreateDefaultPresets(); } // Create font for "dropdown" button (Marlett system font) 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")); ::SendMessage(GetDlgItem(IDC_BUTTON_SHOW_EXPRESSIONS)->m_hWnd, WM_SETFONT, (WPARAM)hButtonFont, MAKELPARAM(TRUE, 0)); return TRUE; } void CSmpGenDialog::OnOK() { CDialog::OnOK(); apply = true; int check = GetCheckedRadioButton(IDC_RADIO_SMPCLIP1, IDC_RADIO_SMPCLIP3); switch(check) { case IDC_RADIO_SMPCLIP1: sample_clipping = smpgen_clip; break; case IDC_RADIO_SMPCLIP2: sample_clipping = smpgen_overflow; break; case IDC_RADIO_SMPCLIP3: sample_clipping = smpgen_normalize; break; } DeleteObject(hButtonFont); } void CSmpGenDialog::OnCancel() { CDialog::OnCancel(); apply = false; } // User changed formula void CSmpGenDialog::OnExpressionChanged() { CString result; GetDlgItemText(IDC_EDIT_FORMULA, result); expression = result; } // User changed sample length field void CSmpGenDialog::OnSampleLengthChanged() { int temp_length = GetDlgItemInt(IDC_EDIT_SAMPLE_LENGTH); if(temp_length >= SMPGEN_MINLENGTH && temp_length <= SMPGEN_MAXLENGTH) { sample_length = temp_length; RecalcParameters(false); } } // User changed sample length (seconds) field void CSmpGenDialog::OnSampleSecondsChanged() { CString str; GetDlgItemText(IDC_EDIT_SAMPLE_LENGTH_SEC, str); double temp_seconds = atof(str); if(temp_seconds > 0) { sample_seconds = temp_seconds; RecalcParameters(true); } } // User changed sample frequency field void CSmpGenDialog::OnSampleFreqChanged() { int temp_freq = GetDlgItemInt(IDC_EDIT_SAMPLE_FREQ); if(temp_freq >= SMPGEN_MINFREQ && temp_freq <= SMPGEN_MAXFREQ) { sample_frequency = temp_freq; RecalcParameters(false); } } // Show all expressions that can be input void CSmpGenDialog::OnShowExpressions() { HMENU hMenu = ::CreatePopupMenu(), hSubMenu = NULL; if(!hMenu) return; for(int i = 0; i < MAX_SAMPLEGEN_EXPRESSIONS; i++) { if(menu_descriptions[i].expression == "") { // add sub menu if(hSubMenu != NULL) ::DestroyMenu(hSubMenu); hSubMenu = ::CreatePopupMenu(); AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hSubMenu, menu_descriptions[i].description.c_str()); } else { // add sub menu entry (formula) AppendMenu(hSubMenu, MF_STRING, ID_SAMPLE_GENERATOR_MENU + i, menu_descriptions[i].description.c_str()); } } // place popup menu below button RECT button; GetDlgItem(IDC_BUTTON_SHOW_EXPRESSIONS)->GetWindowRect(&button); ::TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, button.left, button.bottom, 0, m_hWnd, NULL); ::DestroyMenu(hMenu); ::DestroyMenu(hSubMenu); } // Show all expression presets void CSmpGenDialog::OnShowPresets() { HMENU hMenu = ::CreatePopupMenu(); if(!hMenu) return; bool prestsExist = false; for(size_t i = 0; i < presets.GetNumPresets(); i++) { if(presets.GetPreset(i)->expression != "") { AppendMenu(hMenu, MF_STRING, ID_SAMPLE_GENERATOR_PRESET_MENU + i, presets.GetPreset(i)->description.c_str()); prestsExist = true; } } if(prestsExist) AppendMenu(hMenu, MF_SEPARATOR, 0, NULL); AppendMenu(hMenu, MF_STRING, ID_SAMPLE_GENERATOR_PRESET_MENU + MAX_SAMPLEGEN_PRESETS, _TEXT("Manage...")); CString result; GetDlgItemText(IDC_EDIT_FORMULA, result); if((!result.IsEmpty()) && (presets.GetNumPresets() < MAX_SAMPLEGEN_PRESETS)) { AppendMenu(hMenu, MF_STRING, ID_SAMPLE_GENERATOR_PRESET_MENU + MAX_SAMPLEGEN_PRESETS + 1, _TEXT("Add current...")); } // place popup menu below button RECT button; GetDlgItem(IDC_BUTTON_SAMPLEGEN_PRESETS)->GetWindowRect(&button); ::TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, button.left, button.bottom, 0, m_hWnd, NULL); ::DestroyMenu(hMenu); } // Insert expression from context menu void CSmpGenDialog::OnInsertExpression(UINT nId) { if((nId < ID_SAMPLE_GENERATOR_MENU) || (nId >= ID_SAMPLE_GENERATOR_MENU + MAX_SAMPLEGEN_EXPRESSIONS)) return; expression += " " + menu_descriptions[nId - ID_SAMPLE_GENERATOR_MENU].expression; SetDlgItemText(IDC_EDIT_FORMULA, expression.c_str()); } // Select a preset (or manage them, or add one) void CSmpGenDialog::OnSelectPreset(UINT nId) { if((nId < ID_SAMPLE_GENERATOR_PRESET_MENU) || (nId >= ID_SAMPLE_GENERATOR_PRESET_MENU + MAX_SAMPLEGEN_PRESETS + 2)) return; if(nId - ID_SAMPLE_GENERATOR_PRESET_MENU >= MAX_SAMPLEGEN_PRESETS) { // add... if((nId - ID_SAMPLE_GENERATOR_PRESET_MENU == MAX_SAMPLEGEN_PRESETS + 1)) { samplegen_expression newPreset; newPreset.description = newPreset.expression = expression; presets.AddPreset(newPreset); // call preset manager now. } // manage... CSmpGenPresetDlg dlg(&presets); dlg.DoModal(); } else { expression = presets.GetPreset(nId - ID_SAMPLE_GENERATOR_PRESET_MENU)->expression; SetDlgItemText(IDC_EDIT_FORMULA, expression.c_str()); } } // Update input fields, depending on what has been chagned void CSmpGenDialog::RecalcParameters(bool secondsChanged, bool forceRefresh) { static bool isLocked = false; if(isLocked) return; isLocked = true; // avoid deadlock if(secondsChanged) { // seconds changed => recalc length sample_length = (int)(sample_seconds * sample_frequency); if(sample_length < SMPGEN_MINLENGTH || sample_length > SMPGEN_MAXLENGTH) sample_length = SMPGEN_MAXLENGTH; } else { // length/freq changed => recalc seconds sample_seconds = ((double)sample_length) / ((double)sample_frequency); } if(secondsChanged || forceRefresh) SetDlgItemInt(IDC_EDIT_SAMPLE_LENGTH, sample_length); if(secondsChanged || forceRefresh) SetDlgItemInt(IDC_EDIT_SAMPLE_FREQ, sample_frequency); CString str; str.Format("%.4f", sample_seconds); if(!secondsChanged || forceRefresh) SetDlgItemText(IDC_EDIT_SAMPLE_LENGTH_SEC, str); int smpsize = sample_length * SMPGEN_MIXBYTES; if(smpsize < 1024) { str.Format("Sample Size: %d Bytes", smpsize); } else if((smpsize >> 10) < 1024) { str.Format("Sample Size: %d KB", smpsize >> 10); } else { str.Format("Sample Size: %d MB", smpsize >> 20); } SetDlgItemText(IDC_STATIC_SMPSIZE_KB, str); isLocked = false; } // Create a set of default formla presets void CSmpGenDialog::CreateDefaultPresets() { samplegen_expression preset; preset.description = "A440"; preset.expression = "sin(xp * _pi / 50 * 440 * len / freq)"; presets.AddPreset(preset); preset.description = "Brown Noise (kind of)"; preset.expression = "rnd(1) * 0.1 + smp(x - 1) * 0.9"; presets.AddPreset(preset); preset.description = "Noisy Saw"; preset.expression = "(x mod 800) / 800 - 0.5 + rnd(0.1)"; presets.AddPreset(preset); preset.description = "PWM Filter"; preset.expression = "pwm(x, 50 + sin(xp * _pi / 100) * 40, 100) + tri(x, 50)"; presets.AddPreset(preset); preset.description = "Fat PWM Pad"; preset.expression = "pwm(x, xp, 500) + pwm(x, abs(50 - xp), 1000)"; presets.AddPreset(preset); preset.description = "Dual Square"; preset.expression = "if((x mod 100) < 50, (x mod 200), -x mod 200)"; presets.AddPreset(preset); preset.description = "Noise Hit"; preset.expression = "exp(-xp) * (rnd(x) - x / 2)"; presets.AddPreset(preset); preset.description = "Laser"; preset.expression = "sin(xp * _pi * 100 /(xp ^ 2)) * 100 / sqrt(xp)"; presets.AddPreset(preset); preset.description = "Noisy Laser Hit"; preset.expression = "(sin(sqrt(xp) * 100) + rnd(1) - 0.5) * exp(-xp / 10)"; presets.AddPreset(preset); preset.description = "Twinkle, Twinkle..."; preset.expression = "sin(xp * _pi * 100 / xp) * 100 / sqrt(xp)"; presets.AddPreset(preset); preset.description = "FM Tom"; preset.expression = "sin(xp * _pi * 2 + (xp / 5 - 50) ^ 2) * exp(-xp / 10)"; presets.AddPreset(preset); preset.description = "FM Warp"; preset.expression = "sin(_pi * xp / 2 * (1 + (1 + sin(_pi * xp / 4 * 50)) / 4)) * exp(-(xp / 8) * .6)"; presets.AddPreset(preset); preset.description = "Weird Noise"; preset.expression = "rnd(1) * 0.1 + smp(x - rnd(xp)) * 0.9"; presets.AddPreset(preset); } ////////////////////////////////////////////////////////////////////////// // Sample Generator Preset Dialog implementation BEGIN_MESSAGE_MAP(CSmpGenPresetDlg, CDialog) ON_COMMAND(IDC_BUTTON_ADD, &CSmpGenPresetDlg::OnAddPreset) ON_COMMAND(IDC_BUTTON_REMOVE, &CSmpGenPresetDlg::OnRemovePreset) ON_EN_CHANGE(IDC_EDIT_PRESET_NAME, &CSmpGenPresetDlg::OnTextChanged) ON_EN_CHANGE(IDC_EDIT_PRESET_EXPR, &CSmpGenPresetDlg::OnExpressionChanged) ON_LBN_SELCHANGE(IDC_LIST_SAMPLEGEN_PRESETS, &CSmpGenPresetDlg::OnListSelChange) END_MESSAGE_MAP() BOOL CSmpGenPresetDlg::OnInitDialog() { CDialog::OnInitDialog(); RefreshList(); return TRUE; } void CSmpGenPresetDlg::OnOK() { // remove empty presets for(size_t i = 0; i < presets->GetNumPresets(); i++) { if(presets->GetPreset(i)->expression.empty()) { presets->RemovePreset(i); } } CDialog::OnOK(); } void CSmpGenPresetDlg::OnListSelChange() { currentItem = ((CListBox *)GetDlgItem(IDC_LIST_SAMPLEGEN_PRESETS))->GetCurSel() + 1; if(currentItem == 0 || currentItem > presets->GetNumPresets()) return; samplegen_expression *preset = presets->GetPreset(currentItem - 1); if(preset == nullptr) return; SetDlgItemText(IDC_EDIT_PRESET_NAME, preset->description.c_str()); SetDlgItemText(IDC_EDIT_PRESET_EXPR, preset->expression.c_str()); } void CSmpGenPresetDlg::OnTextChanged() { if(currentItem == 0 || currentItem > presets->GetNumPresets()) return; CString result; GetDlgItemText(IDC_EDIT_PRESET_NAME, result); samplegen_expression *preset = presets->GetPreset(currentItem - 1); if(preset == nullptr) return; preset->description = result; CListBox *clist = (CListBox *)GetDlgItem(IDC_LIST_SAMPLEGEN_PRESETS); clist->DeleteString(currentItem - 1); clist->InsertString(currentItem - 1, (preset->description).c_str()); clist->SetCurSel(currentItem - 1); } void CSmpGenPresetDlg::OnExpressionChanged() { if(currentItem == 0 || currentItem > presets->GetNumPresets()) return; CString result; GetDlgItemText(IDC_EDIT_PRESET_EXPR, result); samplegen_expression *preset = presets->GetPreset(currentItem - 1); if(preset != nullptr) preset->expression = result; } void CSmpGenPresetDlg::OnAddPreset() { samplegen_expression newPreset; newPreset.description = "New Preset"; newPreset.expression = ""; if(presets->AddPreset(newPreset)) { currentItem = presets->GetNumPresets(); RefreshList(); } } void CSmpGenPresetDlg::OnRemovePreset() { if(currentItem == 0 || currentItem > presets->GetNumPresets()) return; if(presets->RemovePreset(currentItem - 1)) RefreshList(); } void CSmpGenPresetDlg::RefreshList() { CListBox *clist = (CListBox *)GetDlgItem(IDC_LIST_SAMPLEGEN_PRESETS); clist->SetRedraw(FALSE); //disable lisbox refreshes during fill to avoid flicker clist->ResetContent(); for(size_t i = 0; i < presets->GetNumPresets(); i++) { samplegen_expression *preset = presets->GetPreset(i); if(preset != nullptr) clist->AddString((preset->description).c_str()); } clist->SetRedraw(TRUE); //re-enable lisbox refreshes if(currentItem == 0 || currentItem > presets->GetNumPresets()) { currentItem = presets->GetNumPresets(); } if(currentItem != 0) clist->SetCurSel(currentItem - 1); OnListSelChange(); } #endif // MPT_DISABLED_CODE