From: <sag...@us...> - 2010-02-13 13:15:27
|
Revision: 491 http://modplug.svn.sourceforge.net/modplug/?rev=491&view=rev Author: saga-games Date: 2010-02-13 13:15:18 +0000 (Sat, 13 Feb 2010) Log Message: ----------- [Fix] Pattern Editor: Don't enable or disable "follow song" checkbox to its default setting all the time when updating other parts of the dialog [Fix] Mod Specs: Updated maximum IT pattern length (unhacked specs) [New] Added Sample Generator sourcecode. It's not finished yet so it's not included in the project. Modified Paths: -------------- trunk/OpenMPT/mptrack/Ctrl_pat.cpp trunk/OpenMPT/soundlib/mod_specifications.h Added Paths: ----------- trunk/OpenMPT/mptrack/SampleGenerator.cpp trunk/OpenMPT/mptrack/SampleGenerator.h Modified: trunk/OpenMPT/mptrack/Ctrl_pat.cpp =================================================================== --- trunk/OpenMPT/mptrack/Ctrl_pat.cpp 2010-02-10 18:21:42 UTC (rev 490) +++ trunk/OpenMPT/mptrack/Ctrl_pat.cpp 2010-02-13 13:15:18 UTC (rev 491) @@ -264,7 +264,7 @@ m_ToolBar.UpdateStyle(); // -> CODE#0007 // -> DESC="uncheck follow song checkbox by default" - CheckDlgButton(IDC_PATTERN_FOLLOWSONG, (CMainFrame::m_dwPatternSetup & PATTERN_FOLLOWSONGOFF) ? MF_UNCHECKED : MF_CHECKED); + //CheckDlgButton(IDC_PATTERN_FOLLOWSONG, (CMainFrame::m_dwPatternSetup & PATTERN_FOLLOWSONGOFF) ? MF_UNCHECKED : MF_CHECKED); m_ToolBar.SetState(ID_OVERFLOWPASTE, ((CMainFrame::m_dwPatternSetup & PATTERN_OVERFLOWPASTE) ? TBSTATE_CHECKED : 0) | TBSTATE_ENABLED); // -! BEHAVIOUR_CHANGE#0007 } Added: trunk/OpenMPT/mptrack/SampleGenerator.cpp =================================================================== --- trunk/OpenMPT/mptrack/SampleGenerator.cpp (rev 0) +++ trunk/OpenMPT/mptrack/SampleGenerator.cpp 2010-02-13 13:15:18 UTC (rev 491) @@ -0,0 +1,761 @@ +/* + * SampleGenerator.cpp + * ------------------- + * Purpose: Generate samples from math formulas using muParser + * Notes : (currently none) + * Authors: OpenMPT Devs + */ + + +#include "stdafx.h" +#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 formular 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() +//-------------------------------------- +{ + 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(abs(sample_buffer[i]) > minmax) minmax = 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<size_t>(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, OnSampleLengthChanged) + ON_EN_CHANGE(IDC_EDIT_SAMPLE_LENGTH_SEC, OnSampleSecondsChanged) + ON_EN_CHANGE(IDC_EDIT_SAMPLE_FREQ, OnSampleFreqChanged) + ON_EN_CHANGE(IDC_EDIT_FORMULA, OnExpressionChanged) + ON_COMMAND(IDC_BUTTON_SHOW_EXPRESSIONS, OnShowExpressions) + ON_COMMAND(IDC_BUTTON_SAMPLEGEN_PRESETS, OnShowPresets) + ON_COMMAND_RANGE(ID_SAMPLE_GENERATOR_MENU, ID_SAMPLE_GENERATOR_MENU + MAX_SAMPLEGEN_EXPRESSIONS - 1, OnInsertExpression) + ON_COMMAND_RANGE(ID_SAMPLE_GENERATOR_PRESET_MENU, ID_SAMPLE_GENERATOR_PRESET_MENU + MAX_SAMPLEGEN_PRESETS + 1, 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 = "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 = "Twinkle, Twinkle..."; + preset.expression = "sin(xp * _pi * 100 / xp) * 100 / sqrt(xp)"; + + 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); +} + + + +////////////////////////////////////////////////////////////////////////// +// Sample Generator Preset Dialog implementation + + +BEGIN_MESSAGE_MAP(CSmpGenPresetDlg, CDialog) + ON_COMMAND(IDC_BUTTON_ADD, OnAddPreset) + ON_COMMAND(IDC_BUTTON_REMOVE, OnRemovePreset) + ON_EN_CHANGE(IDC_EDIT_PRESET_NAME, OnTextChanged) + ON_EN_CHANGE(IDC_EDIT_PRESET_EXPR, OnExpressionChanged) + ON_LBN_SELCHANGE(IDC_LIST_SAMPLEGEN_PRESETS, 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(); +} Added: trunk/OpenMPT/mptrack/SampleGenerator.h =================================================================== --- trunk/OpenMPT/mptrack/SampleGenerator.h (rev 0) +++ trunk/OpenMPT/mptrack/SampleGenerator.h 2010-02-13 13:15:18 UTC (rev 491) @@ -0,0 +1,207 @@ +/* + * SampleGenerator.h + * ----------------- + * Purpose: Header file for sample generator + * Notes : (currently none) + * Authors: OpenMPT Devs + */ + +#pragma once +#include "mptrack.h" +#include "Mainfrm.h" +#include "Sndfile.h" +#include "../muParser/include/muParser.h" + +// sample length +#define SMPGEN_MINLENGTH 1 +#define SMPGEN_MAXLENGTH MAX_SAMPLE_LENGTH +// sample frequency +#define SMPGEN_MINFREQ 1 +#define SMPGEN_MAXFREQ 96000 +// 16-bit sample quality - when changing this, also change CSampleGenerator::sampling_type and 16-bit flags in SampleGenerator.cpp! +#define SMPGEN_MIXBYTES 2 + +enum smpgen_clip_methods +{ + smpgen_clip, + smpgen_overflow, + smpgen_normalize, +}; + +//==================== +class CSampleGenerator +//==================== +{ +protected: + + // sample parameters + static int sample_frequency; + static int sample_length; + static mu::string_type expression; + static smpgen_clip_methods sample_clipping; + + // rendering helper variables (they're here for the callback functions) + static mu::value_type *sample_buffer; + static size_t samples_written; + + typedef int16 sampling_type; // has to match SMPGEN_MIXBYTES! + static const sampling_type sample_maxvalue = (1 << ((SMPGEN_MIXBYTES << 3) - 1)) - 1; + + // muParser object for parsing the expression + mu::Parser muParser; + + // Rendering callback functions + // functions + static mu::value_type ClipCallback(mu::value_type val, mu::value_type min, mu::value_type max) { return CLAMP(val, min, max); }; + static mu::value_type PWMCallback(mu::value_type pos, mu::value_type duty, mu::value_type width) { if((int)width == 0) return 0; else return (((int)pos % (int)width) < ((duty / 100) * width)) ? 1 : -1; }; + static mu::value_type RndCallback(mu::value_type v) { return v*std::rand()/(mu::value_type)(RAND_MAX+1.0); } + static mu::value_type SampleDataCallback(mu::value_type v); + static mu::value_type TriangleCallback(mu::value_type pos, mu::value_type width) { if((int)width == 0) return 0; else return abs(((int)pos % (int)(width)) - width / 2) / (width / 4) - 1; }; + + // binary operators + static mu::value_type ModuloCallback(mu::value_type x, mu::value_type y) { if((int)y == 0) return 0; else return ((int)x) % ((int)y); }; + + void ShowError(mu::Parser::exception_type *e); + +public: + + bool ShowDialog(); + bool TestExpression(); + bool CanRenderSample(); + bool RenderSample(CSoundFile *pSndFile, SAMPLEINDEX nSample); + + CSampleGenerator(); + +}; + + +////////////////////////////////////////////////////////////////////////// +// Sample Generator Formula Preset implementation + + +struct samplegen_expression +{ + std::string description; // e.g. "Pulse" + mu::string_type expression; // e.g. "pwm(x,y,z)" - empty if this is a sub menu +}; +#define MAX_SAMPLEGEN_PRESETS 100 + + +//================== +class CSmpGenPresets +//================== +{ +protected: + vector<samplegen_expression> presets; + +public: + bool AddPreset(samplegen_expression new_preset) { if(GetNumPresets() >= MAX_SAMPLEGEN_PRESETS) return false; presets.push_back(new_preset); return true;}; + bool RemovePreset(size_t which) { if(which < GetNumPresets()) { presets.erase(presets.begin() + which); return true; } else return false; }; + samplegen_expression *GetPreset(size_t which) { if(which < GetNumPresets()) return &presets[which]; else return nullptr; }; + size_t GetNumPresets() { return presets.size(); }; + void Clear() { presets.clear(); }; + + CSmpGenPresets() { Clear(); } + ~CSmpGenPresets() { Clear(); } +}; + + +////////////////////////////////////////////////////////////////////////// +// Sample Generator Dialog implementation + + +//================================= +class CSmpGenDialog: public CDialog +//================================= +{ +protected: + + // sample parameters + int sample_frequency; + int sample_length; + double sample_seconds; + mu::string_type expression; + smpgen_clip_methods sample_clipping; + // pressed "OK"? + bool apply; + // preset slots + CSmpGenPresets presets; + + HFONT hButtonFont; // "Marlett" font for "dropdown" button + + void RecalcParameters(bool secondsChanged, bool forceRefresh = false); + + // function presets + void CreateDefaultPresets(); + +public: + + int GetFrequency() { return sample_frequency; }; + int GetLength() { return sample_length; }; + smpgen_clip_methods GetClipping() { return sample_clipping; } + mu::string_type GetExpression() { return expression; }; + bool CanApply() { return apply; }; + + CSmpGenDialog(int freq, int len, smpgen_clip_methods clipping, mu::string_type expr):CDialog(IDD_SAMPLE_GENERATOR, CMainFrame::GetMainFrame()) + { + sample_frequency = freq; + sample_length = len; + sample_clipping = clipping; + expression = expr; + apply = false; + } + +protected: + virtual BOOL OnInitDialog(); + virtual void OnOK(); + virtual void OnCancel(); + + afx_msg void OnSampleLengthChanged(); + afx_msg void OnSampleSecondsChanged(); + afx_msg void OnSampleFreqChanged(); + afx_msg void OnExpressionChanged(); + afx_msg void OnShowExpressions(); + afx_msg void OnShowPresets(); + afx_msg void OnInsertExpression(UINT nId); + afx_msg void OnSelectPreset(UINT nId); + + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +////////////////////////////////////////////////////////////////////////// +// Sample Generator Preset Dialog implementation + + +//==================================== +class CSmpGenPresetDlg: public CDialog +//==================================== +{ +protected: + CSmpGenPresets *presets; + size_t currentItem; // first item is actually 1! + + void RefreshList(); + +public: + CSmpGenPresetDlg(CSmpGenPresets *pPresets):CDialog(IDD_SAMPLE_GENERATOR_PRESETS, CMainFrame::GetMainFrame()) + { + presets = pPresets; + currentItem = 0; + } + +protected: + virtual BOOL OnInitDialog(); + virtual void OnOK(); + + afx_msg void OnListSelChange(); + + afx_msg void OnTextChanged(); + afx_msg void OnExpressionChanged(); + + afx_msg void OnAddPreset(); + afx_msg void OnRemovePreset(); + + DECLARE_MESSAGE_MAP() +}; Modified: trunk/OpenMPT/soundlib/mod_specifications.h =================================================================== --- trunk/OpenMPT/soundlib/mod_specifications.h 2010-02-10 18:21:42 UTC (rev 490) +++ trunk/OpenMPT/soundlib/mod_specifications.h 2010-02-13 13:15:18 UTC (rev 491) @@ -331,7 +331,7 @@ 32, //Min tempo 255, //Max tempo 1, //Min pattern rows - 256, //Max pattern rows + 200, //Max pattern rows 25, //Max mod name length 25, //Max sample name length 12, //Max sample filename length This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |