1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-19 17:40:15 +02:00
audacity/src/effects/nyquist/Nyquist.cpp
2013-10-09 05:03:42 +00:00

1519 lines
42 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
Nyquist.cpp
Dominic Mazzoni
******************************************************************//**
\class EffectNyquist
\brief An Effect that calls up a Nyquist (XLISP) plug in, i.e. many possible
effects from this one class.
*//****************************************************************//**
\class NyquistInputDialog
\brief Dialog used with EffectNyquist
*//****************************************************************//**
\class NyquistOutputDialog
\brief Dialog used with EffectNyquist
*//****************************************************************//**
\class NyquistDialog
\brief Base class for NyquistInputDialog and NyquistOutputDialog.
*//****************************************************************//**
\class NyqControl
\brief A control on a NyquistDialog.
*//*******************************************************************/
#include "../../Audacity.h"
#include <math.h>
#include <locale.h>
#include <wx/defs.h>
#include <wx/log.h>
#include <wx/intl.h>
#include <wx/msgdlg.h>
#include <wx/textdlg.h>
#include <wx/textfile.h>
#include <wx/choice.h>
#include "../../AudacityApp.h"
#include "../../LabelTrack.h"
#include "../../Internat.h"
#include "../../ShuttleGui.h"
#include "Nyquist.h"
#ifndef nyx_returns_start_and_end_time
#error You need to update lib-src/libnyquist
#endif
#include <locale.h>
#include <iostream>
#include <ostream>
#include <sstream>
#include <wx/arrimpl.cpp>
WX_DEFINE_OBJARRAY(NyqControlArray);
EffectNyquist::EffectNyquist(wxString fName)
{
mAction = _("Applying Nyquist Effect...");
mInputCmd = wxEmptyString;
mCmd = wxEmptyString;
SetEffectFlags(HIDDEN_EFFECT);
mInteractive = false;
mExternal = false;
mCompiler = false;
mDebug = false;
mIsSal = false;
mOK = false;
mStop = false;
mBreak = false;
mCont = false;
if (!SetXlispPath()) {
wxLogWarning(wxT("Critical Nyquist files could not be found. Nyquist effects will not work."));
return;
}
if (fName == wxT("")) {
// Interactive Nyquist
mOK = true;
mInteractive = true;
mName = _("Nyquist Prompt...");
SetEffectFlags(PROCESS_EFFECT | BUILTIN_EFFECT | ADVANCED_EFFECT);
return;
}
// wxLogNull dontLog; // Vaughan, 2010-08-27: Why turn off logging? Logging is good!
mName = wxFileName(fName).GetName();
mFileName = wxFileName(fName);
mFileModified = mFileName.GetModificationTime();
ParseFile();
}
EffectNyquist::~EffectNyquist()
{
}
wxString EffectNyquist::NyquistToWxString(const char *nyqString)
{
wxString str(nyqString, wxConvUTF8);
if (nyqString != NULL && nyqString[0] && str.IsEmpty()) {
// invalid UTF-8 string, convert as Latin-1
str = _("[Warning: Nyquist returned invalid UTF-8 string, converted here as Latin-1]");
str += LAT1CTOWX(nyqString);
}
return str;
}
void EffectNyquist::Break()
{
mBreak = true;
}
void EffectNyquist::Continue()
{
mCont = true;
}
void EffectNyquist::Stop()
{
mStop = true;
}
#define UNINITIALIZED_CONTROL ((double)99999999.99)
wxString EffectNyquist::UnQuote(wxString s)
{
wxString out;
int len = s.Length();
if (len >= 2 && s[0] == wxT('\"') && s[len - 1] == wxT('\"')) {
return s.Mid(1, len - 2);
}
return s;
}
double EffectNyquist::GetCtrlValue(wxString s)
{
if (s == wxT("rate")) {
TrackListOfKindIterator iter(Track::Wave, mTracks);
return ((WaveTrack *)iter.First())->GetRate();
}
return Internat::CompatibleToDouble(s);
}
void EffectNyquist::Parse(wxString line)
{
wxArrayString tokens;
int i;
int len = line.Length();
bool sl = false;
bool q = false;
wxString tok = wxT("");
for (i = 1; i < len; i++) {
wxChar c = line[i];
if (c == wxT('\\')) {
sl = true;
}
else if (c == wxT('"')) {
q = !q;
}
else {
if ((!q && !sl && c == wxT(' ')) || c == wxT('\t')) {
tokens.Add(tok);
tok = wxT("");
}
else if (sl && c == wxT('n')) {
tok += wxT('\n');
}
else {
tok += c;
}
sl = false;
}
}
if (tok != wxT("")) {
tokens.Add(tok);
}
len = tokens.GetCount();
if (len < 1) {
return;
}
if (len == 2 && tokens[0] == wxT("nyquist") && tokens[1] == wxT("plug-in")) {
mOK = true;
return;
}
if (len >= 2 && tokens[0] == wxT("type")) {
if (tokens[1] == wxT("process")) {
SetEffectFlags(PROCESS_EFFECT | PLUGIN_EFFECT);
}
else if (tokens[1] == wxT("generate")) {
SetEffectFlags(INSERT_EFFECT | PLUGIN_EFFECT);
}
else if (tokens[1] == wxT("analyze")) {
SetEffectFlags(ANALYZE_EFFECT | PLUGIN_EFFECT);
}
return;
}
if (len == 2 && tokens[0] == wxT("codetype")) {
if (tokens[1] == wxT("lisp")) {
mIsSal = false;
}
else if (tokens[1] == wxT("sal")) {
mIsSal = true;
}
return;
}
if (len >= 2 && tokens[0] == wxT("debugflags")) {
for (int i = 1; i < len; i++) {
// Note: "trace" and "notrace" are overridden by "Debug" and "OK"
// buttons if the plug-in generates a dialog box by using controls
if (tokens[i] == wxT("trace")) {
mDebug = true;
}
else if (tokens[i] == wxT("notrace")) {
mDebug = false;
}
else if (tokens[i] == wxT("compiler")) {
mCompiler = true;
}
else if (tokens[i] == wxT("nocompiler")) {
mCompiler = false;
}
}
return;
}
// We support versions 1, 2 and 3
// (Version 2 added support for string parameters.)
// (Version 3 added support for choice parameters.)
if (len >= 2 && tokens[0] == wxT("version")) {
if (tokens[1] != wxT("1") && tokens[1] != wxT("2") && tokens[1] != wxT("3")) {
// This is an unsupported plug-in version
mOK = false;
return;
}
}
if (len >= 2 && tokens[0] == wxT("name")) {
mName = UnQuote(tokens[1]);
return;
}
if (len >= 2 && tokens[0] == wxT("action")) {
mAction = UnQuote(tokens[1]);
return;
}
if (len >= 2 && tokens[0] == wxT("info")) {
mInfo = UnQuote(tokens[1]);
return;
}
if (len >= 6 && tokens[0] == wxT("control")) {
NyqControl ctrl;
ctrl.var = tokens[1];
ctrl.name = tokens[2];
ctrl.label = tokens[4];
ctrl.valStr = tokens[5];
if (tokens[3] == wxT("string")) {
ctrl.type = NYQ_CTRL_STRING;
}
else if (tokens[ 3 ] == wxT("choice")) {
ctrl.type = NYQ_CTRL_CHOICE;
}
else {
if (len < 8) {
return;
}
if ((tokens[3] == wxT("real")) ||
(tokens[3] == wxT("float"))) // undocumented, but useful, alternative
ctrl.type = NYQ_CTRL_REAL;
else if (tokens[3] == wxT("int"))
ctrl.type = NYQ_CTRL_INT;
else
{
wxString str;
str.Printf(_("Bad Nyquist 'control' type specification: '%s' in plugin file '%s'.\nControl not created."),
tokens[3].c_str(), mFileName.GetFullPath().c_str());
// Too disturbing to show alert before Audacity frame is up.
// wxMessageBox(str, wxT("Nyquist Warning"), wxOK | wxICON_EXCLAMATION);
// Note that the AudacityApp's mLogger has not yet been created,
// so this brings up an alert box, but after the Audacity frame is up.
wxLogWarning(str);
return;
}
ctrl.lowStr = tokens[6];
ctrl.highStr = tokens[7];
}
ctrl.val = UNINITIALIZED_CONTROL;
mControls.Add(ctrl);
}
if (len >= 2 && tokens[0] == wxT("categories")) {
for (size_t i = 1; i < tokens.GetCount(); ++i) {
mCategories.Add(tokens[i]);
}
}
}
void EffectNyquist::ParseFile()
{
wxTextFile f(mFileName.GetFullPath());
if (!f.Open())
return;
mCmd = wxT("");
SetEffectFlags(PROCESS_EFFECT | PLUGIN_EFFECT);
mOK = false;
mIsSal = false;
mControls.Clear();
mDebug = false;
int i;
int len = f.GetLineCount();
wxString line;
for (i = 0; i < len; i++) {
line = f[i];
if (line.Length() > 1 && line[0] == wxT(';')) {
Parse(line);
}
// preserve comments so that SAL effects compile with proper line numbers
mCmd += line + wxT("\n");
}
}
void EffectNyquist::SetCommand(wxString cmd)
{
mExternal = true;
mInteractive = false;
mCmd = wxT("");
SetEffectFlags(INSERT_EFFECT | HIDDEN_EFFECT);
mOK = false;
mIsSal = false;
mControls.Clear();
wxStringTokenizer lines(cmd, wxT("\n"));
while (lines.HasMoreTokens()) {
wxString line = lines.GetNextToken();
if (line.Length() > 1 && line[0] == wxT(';')) {
Parse(line);
}
else {
mCmd += line + wxT("\n");
}
}
}
bool EffectNyquist::SetXlispPath()
{
wxString fname;
fname = mXlispPath + wxFILE_SEP_PATH + wxT("nyinit.lsp");
if (!(::wxFileExists(fname))) {
mXlispPath = wxT("");
}
if (mXlispPath == wxT("")) {
wxArrayString audacityPathList = wxGetApp().audacityPathList;
wxArrayString pathList;
wxArrayString files;
unsigned int i;
for (i = 0; i < audacityPathList.GetCount(); i++) {
wxString prefix = audacityPathList[i] + wxFILE_SEP_PATH;
wxGetApp().AddUniquePathToPathList(prefix + wxT("nyquist"),
pathList);
}
wxGetApp().FindFilesInPathList(wxT("nyquist.lsp"), pathList, files);
if (files.GetCount() > 0) {
mXlispPath = ::wxPathOnly(files[0]);
}
}
/* set_xlisp_path doesn't handle fn_Str() in Unicode build. May or may not actually work. */
nyx_set_xlisp_path(mXlispPath.mb_str());
fname = mXlispPath + wxFILE_SEP_PATH + wxT("nyinit.lsp");
return ::wxFileExists(fname);
}
bool EffectNyquist::SupportsChains()
{
return (GetEffectFlags() & PROCESS_EFFECT) != 0;
}
bool EffectNyquist::TransferParameters( Shuttle & shuttle )
{
for (size_t i = 0; i < mControls.GetCount(); i++) {
NyqControl *ctrl = &mControls[i];
double d = ctrl->val;
bool good = false;
if (d == UNINITIALIZED_CONTROL) {
if (ctrl->type != NYQ_CTRL_STRING) {
if (!shuttle.mbStoreInClient) {
d = GetCtrlValue(ctrl->valStr);
}
}
}
if (ctrl->type == NYQ_CTRL_REAL) {
good = shuttle.TransferDouble(ctrl->var, d, 0.0);
}
else if (ctrl->type == NYQ_CTRL_INT) {
int val = (int) d;
good = shuttle.TransferInt(ctrl->var, val, 0);
d = (double) val;
}
else if (ctrl->type == NYQ_CTRL_CHOICE) {
//str is coma separated labels for each choice
wxString str = ctrl->label;
wxArrayString choices;
while (1) {
int ci = str.Find( ',' ); //coma index
if (ci == -1) {
choices.Add( str );
break;
}
else {
choices.Add(str.Left(ci));
}
str = str.Right(str.length() - ci - 1);
}
int cnt = choices.GetCount();
if (choices.GetCount() > 0) {
wxString *array = NULL;
array = new wxString[cnt];
for (int j = 0; j < cnt; j++ ) {
array[j] = choices[j];
}
int val = (int) d;
good = shuttle.TransferEnum(ctrl->var, val, cnt, array);
d = (double) val;
delete [] array;
}
}
else if (ctrl->type == NYQ_CTRL_STRING) {
good = shuttle.TransferString(ctrl->var, ctrl->valStr, wxEmptyString);
}
if (ctrl->type != NYQ_CTRL_STRING) {
if (shuttle.mbStoreInClient && good) {
ctrl->val = d;
}
}
}
return true;
}
bool EffectNyquist::PromptUser()
{
if (mInteractive) {
NyquistInputDialog dlog(wxGetTopLevelParent(NULL), -1,
_("Nyquist Prompt"),
_("Enter Nyquist Command: "),
mInputCmd);
dlog.CentreOnParent();
int result = dlog.ShowModal();
if (result == wxID_CANCEL) {
return false;
}
/*if (result == eDebugID) {
mDebug = true;
}*/
mDebug = (result == eDebugID);
// remember exact input in mInputCmd which will appear in the next
// NyquistInputDialog. Copy to mCmd for possible embedding in
// "function main() begin ... end":
mCmd = mInputCmd = dlog.GetCommand();
// Is this LISP or SAL? Both allow comments. After comments, LISP
// must begin with "(". Technically, a LISP expression could be a
// symbol or number or string, etc., but these are not really
// useful expressions. If the input begins with a symbol, number,
// or string, etc., it is more likely an erroneous attempt to type
// a SAL expression (which should probably begin with "return"),
// so we will treat it as SAL.
// this is a state machine to scan past LISP comments and white
// space to find the first real character of LISP or SAL. Note
// that #| ... |# style comments are not valid in SAL, so we do
// not skip these. Instead, "#|" indicates LISP if found.
//
unsigned int i = 0;
bool inComment = false; // handle "; ... \n" comments
while (i < mCmd.Len()) {
if (inComment) {
inComment = (mCmd[i] != wxT('\n'));
} else if (mCmd[i] == wxT(';')) {
inComment = true;
} else if (!wxIsspace(mCmd[i])) {
break; // found the first non-comment, non-space character
}
i++;
}
// invariant: i == mCmd.Len() |
// mCmd[i] is first non-comment, non-space character
mIsSal = false;
if (mCmd.Len() > i && mCmd[i] != wxT('(') &&
(mCmd[i] != wxT('#') || mCmd.Len() <= i + 1 ||
mCmd[i + 1] != wxT('|'))) {
mIsSal = true;
wxString cmdUp = mCmd.Upper();
int returnLoc = cmdUp.Find(wxT("RETURN"));
if (returnLoc == wxNOT_FOUND) {
wxMessageBox(_("Your code looks like SAL syntax, but there is no return statement. Either use a return statement such as\n\treturn s * 0.1\nfor SAL, or begin with an open parenthesis such as\n\t(mult s 0.1)\n for LISP."), _("Error in Nyquist code"), wxOK | wxCENTRE);
return false;
}
}
return true;
}
if (!mExternal) {
if (mFileName.GetModificationTime().IsLaterThan(mFileModified)) {
ParseFile();
mFileModified = mFileName.GetModificationTime();
}
}
if (mControls.GetCount() == 0) {
return true;
}
for (unsigned int i = 0; i < mControls.GetCount(); i++) {
NyqControl *ctrl = &mControls[i];
if (ctrl->type == NYQ_CTRL_STRING) {
continue;
}
if (ctrl->val == UNINITIALIZED_CONTROL) {
ctrl->val = GetCtrlValue(ctrl->valStr);
}
if (ctrl->type == NYQ_CTRL_CHOICE) {
continue;
}
ctrl->low = GetCtrlValue(ctrl->lowStr);
ctrl->high = GetCtrlValue(ctrl->highStr);
if (ctrl->high < ctrl->low) {
ctrl->high = ctrl->low + 1;
}
if (ctrl->val < ctrl->low) {
ctrl->val = ctrl->low;
}
if (ctrl->val > ctrl->high) {
ctrl->val = ctrl->high;
}
ctrl->ticks = 1000;
if (ctrl->type == NYQ_CTRL_INT &&
(ctrl->high - ctrl->low < ctrl->ticks)) {
ctrl->ticks = (int)(ctrl->high - ctrl->low);
}
}
NyquistDialog dlog(mParent, -1, mName, mInfo, &mControls);
dlog.CentreOnParent();
int result = dlog.ShowModal();
if (result == wxID_CANCEL) {
return false;
}
/* if (result == eDebugID) {
mDebug = true;
} */
mDebug = (result == eDebugID);
return true;
}
bool EffectNyquist::Process()
{
bool success = true;
if (mExternal) {
mProgress->Hide();
}
// We must copy all the tracks, because Paste needs label tracks to ensure
// correct sync-lock group behavior when the timeline is affected; then we just want
// to operate on the selected wave tracks
this->CopyInputTracks(Track::All);
SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks);
mCurTrack[0] = (WaveTrack *) iter.First();
mOutputTime = mT1 - mT0;
mCount = 0;
mProgressIn = 0;
mProgressOut = 0;
mProgressTot = 0;
mScale = (GetEffectFlags() & PROCESS_EFFECT ? 0.5 : 1.0) / GetNumWaveGroups();
mStop = false;
mBreak = false;
mCont = false;
mDebugOutput = "";
// Keep track of whether the current track is first selected in its sync-lock group
// (we have no idea what the length of the returned audio will be, so we have
// to handle sync-lock group behavior the "old" way).
mFirstInGroup = true;
Track *gtLast = NULL;
while (mCurTrack[0]) {
mCurNumChannels = 1;
if (mT1 >= mT0) {
if (mCurTrack[0]->GetLinked()) {
mCurNumChannels = 2;
mCurTrack[1] = (WaveTrack *)iter.Next();
if (mCurTrack[1]->GetRate() != mCurTrack[0]->GetRate()) {
wxMessageBox(_("Sorry, cannot apply effect on stereo tracks where the tracks don't match."),
wxT("Nyquist"),
wxOK | wxCENTRE, mParent);
success = false;
goto finish;
}
mCurStart[1] = mCurTrack[1]->TimeToLongSamples(mT0);
}
// Check whether we're in the same group as the last selected track
SyncLockedTracksIterator gIter(mOutputTracks);
Track *gt = gIter.First(mCurTrack[0]);
mFirstInGroup = !gtLast || (gtLast != gt);
gtLast = gt;
mCurStart[0] = mCurTrack[0]->TimeToLongSamples(mT0);
sampleCount end = mCurTrack[0]->TimeToLongSamples(mT1);
mCurLen = (sampleCount)(end - mCurStart[0]);
mProgressIn = 0.0;
mProgressOut = 0.0;
// libnyquist breaks except in LC_NUMERIC=="C".
//
// Note that we must set the locale to "C" even before calling
// nyx_init() because otherwise some effects will not work!
//
// MB: setlocale is not thread-safe. Should use uselocale()
// if available, or fix libnyquist to be locale-independent.
// See also http://bugzilla.audacityteam.org/show_bug.cgi?id=642#c9
// for further info about this thread safety question.
wxString prevlocale = wxSetlocale(LC_NUMERIC, NULL);
wxSetlocale(LC_NUMERIC, wxT("C"));
nyx_init();
nyx_set_os_callback(StaticOSCallback, (void *)this);
nyx_capture_output(StaticOutputCallback, (void *)this);
success = ProcessOne();
nyx_capture_output(NULL, (void *)NULL);
nyx_set_os_callback(NULL, (void *)NULL);
nyx_cleanup();
// Reset previous locale
wxSetlocale(LC_NUMERIC, prevlocale);
if (!success) {
goto finish;
}
mProgressTot += mProgressIn + mProgressOut;
}
mCurTrack[0] = (WaveTrack *) iter.Next();
mCount += mCurNumChannels;
}
mT1 = mT0 + mOutputTime;
finish:
if (mDebug && !mExternal) {
NyquistOutputDialog dlog(mParent, -1,
_("Nyquist"),
_("Nyquist Output: "),
NyquistToWxString(mDebugOutput.c_str()));
dlog.CentreOnParent();
dlog.ShowModal();
}
this->ReplaceProcessedTracks(success);
//mDebug = false;
return success;
}
bool EffectNyquist::ProcessOne()
{
nyx_rval rval;
if (GetEffectFlags() & INSERT_EFFECT) {
nyx_set_audio_params(mCurTrack[0]->GetRate(), 0);
}
else {
nyx_set_audio_params(mCurTrack[0]->GetRate(), mCurLen);
nyx_set_input_audio(StaticGetCallback, (void *)this,
mCurNumChannels,
mCurLen, mCurTrack[0]->GetRate());
}
wxString cmd;
if (mDebug) {
cmd += wxT("(setf *tracenable* T)\n");
if (mExternal) {
cmd += wxT("(setf *breakenable* T)\n");
}
}
for (unsigned int j = 0; j < mControls.GetCount(); j++) {
if (mControls[j].type == NYQ_CTRL_REAL) {
// We use Internat::ToString() rather than "%f" here because we
// always have to use the dot as decimal separator when giving
// numbers to Nyquist, whereas using "%f" will use the user's
// decimal separator which may be a comma in some countries.
cmd += wxString::Format(wxT("(setf %s %s)\n"),
mControls[j].var.c_str(),
Internat::ToString(mControls[j].val).c_str());
}
else if (mControls[j].type == NYQ_CTRL_INT ||
mControls[j].type == NYQ_CTRL_CHOICE) {
cmd += wxString::Format(wxT("(setf %s %d)\n"),
mControls[j].var.c_str(),
(int)(mControls[j].val));
}
else if (mControls[j].type == NYQ_CTRL_STRING) {
wxString str = mControls[j].valStr;
str.Replace(wxT("\\"), wxT("\\\\"));
str.Replace(wxT("\""), wxT("\\\""));
cmd += wxT("(setf ");
// restrict variable names to 7-bit ASCII:
cmd += mControls[j].var.c_str();
cmd += wxT(" \"");
cmd += str; // unrestricted value will become quoted UTF-8
cmd += wxT("\")\n");
}
}
if (mIsSal) {
wxString str = mCmd;
str.Replace(wxT("\\"), wxT("\\\\"));
str.Replace(wxT("\""), wxT("\\\""));
// this is tricky: we need SAL to call main so that we can get a
// SAL traceback in the event of an error (sal-compile catches the
// error and calls sal-error-output), but SAL does not return values.
// We will catch the value in a special global aud:result and if no
// error occurs, we will grab the value with a LISP expression
str += wxT("\nset aud:result = main()\n");
if (mDebug) {
// since we're about to evaluate SAL, remove LISP trace enable and
// break enable (which stops SAL processing) and turn on SAL stack
// trace
cmd += wxT("(setf *tracenable* nil)\n");
cmd += wxT("(setf *breakenable* nil)\n");
cmd += wxT("(setf *sal-traceback* t)\n");
}
if (mCompiler) {
cmd += wxT("(setf *sal-compiler-debug* t)\n");
}
cmd += wxT("(setf *sal-call-stack* nil)\n");
// if we do not set this here and an error occurs in main, another
// error will be raised when we try to return the value of aud:result
// which is unbound
cmd += wxT("(setf aud:result nil)\n");
cmd += wxT("(sal-compile-audacity \"") + str + wxT("\" t t nil)\n");
// Capture the value returned by main (saved in aud:result), but
// set aud:result to nil so sound results can be evaluated without
// retaining audio in memory
cmd += wxT("(prog1 aud:result (setf aud:result nil))\n");
}
else {
cmd += mCmd;
}
int i;
for (i = 0; i < mCurNumChannels; i++) {
mCurBuffer[i] = NULL;
}
rval = nyx_eval_expression(cmd.mb_str(wxConvUTF8));
if (rval == nyx_string) {
wxMessageBox(NyquistToWxString(nyx_get_string()),
wxT("Nyquist"),
wxOK | wxCENTRE, mParent);
return true;
}
if (rval == nyx_double) {
wxString str;
str.Printf(_("Nyquist returned the value:") + wxString(wxT(" %f")),
nyx_get_double());
wxMessageBox(str, wxT("Nyquist"),
wxOK | wxCENTRE, mParent);
return true;
}
if (rval == nyx_int) {
wxString str;
str.Printf(_("Nyquist returned the value:") + wxString(wxT(" %d")),
nyx_get_int());
wxMessageBox(str, wxT("Nyquist"),
wxOK | wxCENTRE, mParent);
return true;
}
if (rval == nyx_labels) {
unsigned int numLabels = nyx_get_num_labels();
unsigned int l;
LabelTrack *ltrack = NULL;
TrackListIterator iter(mOutputTracks);
for (Track *t = iter.First(); t; t = iter.Next()) {
if (t->GetKind() == Track::Label) {
ltrack = (LabelTrack *)t;
break;
}
}
if (!ltrack) {
ltrack = mFactory->NewLabelTrack();
this->AddToOutputTracks((Track *)ltrack);
}
for (l = 0; l < numLabels; l++) {
double t0, t1;
const char *str;
nyx_get_label(l, &t0, &t1, &str);
ltrack->AddLabel(t0 + mT0, t1 + mT0, UTF8CTOWX(str));
}
return true;
}
if (rval != nyx_audio) {
wxMessageBox(_("Nyquist did not return audio.\n"), wxT("Nyquist"),
wxOK | wxCENTRE, mParent);
return false;
}
int outChannels;
outChannels = nyx_get_audio_num_channels();
if (outChannels > mCurNumChannels) {
wxMessageBox(_("Nyquist returned too many audio channels.\n"),
wxT("Nyquist"),
wxOK | wxCENTRE, mParent);
return false;
}
if (outChannels == -1) {
wxMessageBox(_("Nyquist returned one audio channel as an array.\n"),
wxT("Nyquist"),
wxOK | wxCENTRE, mParent);
return false;
}
if (outChannels == 0) {
wxMessageBox(_("Nyquist returned an empty array.\n"),
wxT("Nyquist"),
wxOK | wxCENTRE, mParent);
return false;
}
double rate = mCurTrack[0]->GetRate();
for (i = 0; i < outChannels; i++) {
sampleFormat format = mCurTrack[i]->GetSampleFormat();
if (outChannels == mCurNumChannels) {
rate = mCurTrack[i]->GetRate();
}
mOutputTrack[i] = mFactory->NewWaveTrack(format, rate);
mCurBuffer[i] = NULL;
}
int success = nyx_get_audio(StaticPutCallback, (void *)this);
if (!success) {
for(i = 0; i < outChannels; i++) {
delete mOutputTrack[i];
mOutputTrack[i] = NULL;
}
return false;
}
for (i = 0; i < outChannels; i++) {
mOutputTrack[i]->Flush();
if (mCurBuffer[i]) {
DeleteSamples(mCurBuffer[i]);
}
mOutputTime = mOutputTrack[i]->GetEndTime();
}
for (i = 0; i < mCurNumChannels; i++) {
WaveTrack *out;
if (outChannels == mCurNumChannels) {
out = mOutputTrack[i];
}
else {
out = mOutputTrack[0];
}
mCurTrack[i]->ClearAndPaste(mT0, mT1, out, false, false);
// If we were first in the group adjust non-selected group tracks
if (mFirstInGroup) {
SyncLockedTracksIterator git(mOutputTracks);
Track *t;
for (t = git.First(mCurTrack[i]); t; t = git.Next())
{
if (!t->GetSelected() && t->IsSyncLockSelected()) {
t->SyncLockAdjust(mT1, mT0 + out->GetEndTime());
}
}
}
// Only the first channel can be first in its group
mFirstInGroup = false;
}
for (i = 0; i < outChannels; i++) {
delete mOutputTrack[i];
mOutputTrack[i] = NULL;
}
return true;
}
int EffectNyquist::StaticGetCallback(float *buffer, int channel,
long start, long len, long totlen,
void *userdata)
{
EffectNyquist *This = (EffectNyquist *)userdata;
return This->GetCallback(buffer, channel, start, len, totlen);
}
int EffectNyquist::GetCallback(float *buffer, int ch,
long start, long len, long WXUNUSED(totlen))
{
if (mCurBuffer[ch]) {
if ((mCurStart[ch] + start) < mCurBufferStart[ch] ||
(mCurStart[ch] + start)+len >
mCurBufferStart[ch]+mCurBufferLen[ch]) {
delete[] mCurBuffer[ch];
mCurBuffer[ch] = NULL;
}
}
if (!mCurBuffer[ch]) {
mCurBufferStart[ch] = (mCurStart[ch] + start);
mCurBufferLen[ch] = mCurTrack[ch]->GetBestBlockSize(mCurBufferStart[ch]);
if (mCurBufferLen[ch] < len) {
mCurBufferLen[ch] = mCurTrack[ch]->GetIdealBlockSize();
}
if (mCurBufferStart[ch] + mCurBufferLen[ch] > mCurStart[ch] + mCurLen) {
mCurBufferLen[ch] = mCurStart[ch] + mCurLen - mCurBufferStart[ch];
}
mCurBuffer[ch] = NewSamples(mCurBufferLen[ch], floatSample);
if (!mCurTrack[ch]->Get(mCurBuffer[ch], floatSample,
mCurBufferStart[ch], mCurBufferLen[ch])) {
wxPrintf(wxT("GET error\n"));
return -1;
}
}
long offset = (mCurStart[ch] + start) - mCurBufferStart[ch];
CopySamples(mCurBuffer[ch] + offset*SAMPLE_SIZE(floatSample), floatSample,
(samplePtr)buffer, floatSample,
len);
if (ch == 0) {
double progress = mScale*(((float)start+len)/mCurLen);
if (progress > mProgressIn) {
mProgressIn = progress;
}
if (TotalProgress(mProgressIn+mProgressOut+mProgressTot)) {
return -1;
}
}
return 0;
}
int EffectNyquist::StaticPutCallback(float *buffer, int channel,
long start, long len, long totlen,
void *userdata)
{
EffectNyquist *This = (EffectNyquist *)userdata;
return This->PutCallback(buffer, channel, start, len, totlen);
}
int EffectNyquist::PutCallback(float *buffer, int channel,
long start, long len, long totlen)
{
if (channel == 0) {
double progress = mScale*((float)(start+len)/totlen);
if (progress > mProgressOut) {
mProgressOut = progress;
}
if (TotalProgress(mProgressIn+mProgressOut+mProgressTot)) {
return -1;
}
}
if (mOutputTrack[channel]->Append((samplePtr)buffer, floatSample, len)) {
return 0; // success
}
return -1; // failure
}
void EffectNyquist::StaticOutputCallback(int c, void *This)
{
((EffectNyquist *)This)->OutputCallback(c);
}
void EffectNyquist::OutputCallback(int c)
{
if (mDebug && !mExternal) {
mDebugOutput += (char)c;
return;
}
std::cout << (char)c;
}
void EffectNyquist::StaticOSCallback(void *This)
{
((EffectNyquist *)This)->OSCallback();
}
void EffectNyquist::OSCallback()
{
if (mStop) {
mStop = false;
nyx_stop();
}
else if (mBreak) {
mBreak = false;
nyx_break();
}
else if (mCont) {
mCont = false;
nyx_continue();
}
wxYieldIfNeeded();
}
/**********************************************************/
#define ID_NYQ_SLIDER 2000
#define ID_NYQ_TEXT 3000
#define ID_NYQ_CHOICE 4000
BEGIN_EVENT_TABLE(NyquistDialog, wxDialog)
EVT_BUTTON(wxID_OK, NyquistDialog::OnOk)
EVT_BUTTON(wxID_CANCEL, NyquistDialog::OnCancel)
EVT_BUTTON(eDebugID, NyquistDialog::OnDebug)
EVT_COMMAND_RANGE(ID_NYQ_SLIDER, ID_NYQ_SLIDER+99,
wxEVT_COMMAND_SLIDER_UPDATED, NyquistDialog::OnSlider)
EVT_COMMAND_RANGE(ID_NYQ_TEXT, ID_NYQ_TEXT+99,
wxEVT_COMMAND_TEXT_UPDATED, NyquistDialog::OnText)
EVT_COMMAND_RANGE( ID_NYQ_CHOICE, ID_NYQ_CHOICE + 99,
wxEVT_COMMAND_CHOICE_SELECTED, NyquistDialog::OnChoice )
END_EVENT_TABLE()
NyquistDialog::NyquistDialog(wxWindow * parent, wxWindowID id,
const wxString & title,
wxString info,
NyqControlArray *controlArray)
: wxDialog(parent, id, title)
{
mControls = controlArray;
mInHandler = true; // prevents race condition on MSW
wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
if (info.Length() > 0) {
wxControl *item;
item = new wxStaticText(this, -1, info);
item->SetName(info); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
mainSizer->Add(item, 0, wxALIGN_LEFT | wxALL, 5);
}
wxFlexGridSizer *grid = new wxFlexGridSizer(4, 0, 0);
for (size_t i = 0; i < mControls->GetCount(); i++) {
wxControl *item;
NyqControl *ctrl = &((*mControls)[i]);
item = new wxStaticText(this, -1, ctrl->name + wxT(":"));
item->SetName(item->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
grid->Add(item, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxALL, 5);
if (ctrl->type == NYQ_CTRL_STRING) {
grid->Add(10, 10);
item = new wxTextCtrl(this, ID_NYQ_TEXT+i, ctrl->valStr,
wxDefaultPosition, wxSize(150, -1));
item->SetName(ctrl->name);
grid->Add(item, 0, wxALIGN_CENTRE | wxALIGN_CENTER_VERTICAL | wxALL, 5);
}
else if (ctrl->type == NYQ_CTRL_CHOICE) {
//str is coma separated labels for each choice
wxString str = ctrl->label;
wxArrayString choices;
while (1) {
int ci = str.Find( ',' ); //coma index
if (ci == -1) {
choices.Add( str );
break;
}
else {
choices.Add(str.Left(ci));
}
str = str.Right(str.length() - ci - 1);
}
wxChoice *choice = new wxChoice(this, ID_NYQ_CHOICE + i,
wxDefaultPosition, wxSize(150, -1), choices);
choice->SetName(ctrl->name);
int val = (int)ctrl->val;
if (val >= 0 && val < (int)choice->GetCount()) {
choice->SetSelection(val);
}
grid->Add(10, 10);
grid->Add(choice, 0, wxALIGN_CENTRE | wxALIGN_CENTER_VERTICAL | wxALL, 5);
}
else {
// Integer or Real
int val = (int)(0.5 + ctrl->ticks * (ctrl->val - ctrl->low) /
(ctrl->high - ctrl->low));
item = new wxTextCtrl(this, ID_NYQ_TEXT+i, wxT(""),
wxDefaultPosition, wxSize(60, -1));
item->SetName(ctrl->name);
wxTextValidator vld(wxFILTER_NUMERIC);
item->SetValidator(vld);
grid->Add(item, 0, wxALIGN_CENTRE | wxALIGN_CENTER_VERTICAL | wxALL, 5);
item = new wxSlider(this, ID_NYQ_SLIDER+i, val, 0, ctrl->ticks,
wxDefaultPosition, wxSize(150, -1));
item->SetName(ctrl->name);
grid->Add(item, 0, wxALIGN_CENTRE | wxALIGN_CENTER_VERTICAL | wxALL, 5);
}
if (ctrl->type == NYQ_CTRL_CHOICE) {
grid->Add( 10, 10 );
}
else {
item = new wxStaticText(this, -1, ctrl->label);
item->SetName(ctrl->label); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
grid->Add(item, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 5);
}
}
mainSizer->Add(grid, 0, wxALIGN_CENTRE | wxALL, 5);
mainSizer->Add(CreateStdButtonSizer(this, eDebugButton | eCancelButton | eOkButton),
0,
wxEXPAND);
mInHandler = false;
wxCommandEvent dummy;
OnSlider(dummy);
SetAutoLayout(true);
SetSizer(mainSizer);
mainSizer->Fit(this);
mainSizer->SetSizeHints(this);
}
void NyquistDialog::OnSlider(wxCommandEvent & /* event */)
{
if (mInHandler) {
return; // prevent recursing forever
}
mInHandler = true;
for (size_t i = 0; i < mControls->GetCount(); i++) {
NyqControl *ctrl = &((*mControls)[i]);
if (ctrl->type == NYQ_CTRL_STRING || ctrl->type == NYQ_CTRL_CHOICE) {
continue;
}
wxSlider *slider = (wxSlider *)FindWindow(ID_NYQ_SLIDER + i);
wxTextCtrl *text = (wxTextCtrl *)FindWindow(ID_NYQ_TEXT + i);
wxASSERT(slider && text);
int val = slider->GetValue();
double newVal = (val / (double)ctrl->ticks)*
(ctrl->high - ctrl->low) + ctrl->low;
// Determine precision for displayed number
int precision = ctrl->high - ctrl->low < 1 ? 3 :
ctrl->high - ctrl->low < 10 ? 2 :
ctrl->high - ctrl->low < 100 ? 1 :
0;
// If the value is at least one tick different from the current value
// change it (this prevents changes from manually entered values unless
// the slider actually moved)
if (fabs(newVal - ctrl->val) >= (1 / (double)ctrl->ticks) *
(ctrl->high - ctrl->low) &&
fabs(newVal - ctrl->val) >= pow(0.1, precision) / 2 )
{
// First round to the appropriate precision
newVal *= pow(10.0, precision);
newVal = floor(newVal + 0.5);
newVal /= pow(10.0, precision);
ctrl->val = newVal;
}
wxString valStr;
if (ctrl->type == NYQ_CTRL_REAL) {
// If this is a user-typed value, allow unlimited precision
if (ctrl->val != newVal)
{
valStr = Internat::ToDisplayString(ctrl->val);
}
else
{
if (precision == 0)
{
valStr.Printf(wxT("%d"), (int)floor(ctrl->val + 0.5));
}
else
{
valStr = Internat::ToDisplayString(ctrl->val, precision);
}
}
}
else if (ctrl->type == NYQ_CTRL_INT) {
valStr.Printf(wxT("%d"), (int)floor(ctrl->val + 0.5));
}
if (valStr != wxT("")) {
text->SetValue(valStr);
}
}
mInHandler = false;
}
void NyquistDialog::OnChoice( wxCommandEvent &event )
{
if (mInHandler) {
return; // prevent recursing forever
}
mInHandler = true;
unsigned int ctrlId = event.GetId() - ID_NYQ_CHOICE;
wxASSERT(ctrlId >= 0 && ctrlId < mControls->GetCount());
NyqControl *ctrl = &(mControls->Item(ctrlId));
wxChoice *choice = (wxChoice *)FindWindow(ID_NYQ_CHOICE + ctrlId);
wxASSERT(choice);
ctrl->val = choice->GetSelection();
mInHandler = false;
}
void NyquistDialog::OnText(wxCommandEvent &event)
{
if (mInHandler) {
return; // prevent recursing forever
}
mInHandler = true;
unsigned int ctrlId = event.GetId() - ID_NYQ_TEXT;
wxASSERT(ctrlId >= 0 && ctrlId < mControls->GetCount());
NyqControl *ctrl = &((*mControls)[ctrlId]);
wxTextCtrl *text = (wxTextCtrl *)FindWindow(ID_NYQ_TEXT + ctrlId);
wxASSERT(text);
ctrl->valStr = text->GetValue();
if (ctrl->type != NYQ_CTRL_STRING) {
wxSlider *slider = (wxSlider *)FindWindow(ID_NYQ_SLIDER + ctrlId);
wxASSERT(slider);
if (ctrl->valStr.ToDouble(&ctrl->val)) {
int pos = (int)floor((ctrl->val - ctrl->low) /
(ctrl->high - ctrl->low) * ctrl->ticks + 0.5);
if (pos < 0) {
pos = 0;
}
else if (pos > ctrl->ticks) {
pos = ctrl->ticks;
}
slider->SetValue(pos);
}
}
mInHandler = false;
}
void NyquistDialog::OnOk(wxCommandEvent & /* event */)
{
// Transfer data
EndModal(wxID_OK);
}
void NyquistDialog::OnCancel(wxCommandEvent & /* event */)
{
EndModal(wxID_CANCEL);
}
void NyquistDialog::OnDebug(wxCommandEvent & /* event */)
{
// Transfer data
EndModal(eDebugID);
}
/**********************************************************/
BEGIN_EVENT_TABLE(NyquistInputDialog, wxDialog)
EVT_BUTTON(wxID_OK, NyquistInputDialog::OnOk)
EVT_BUTTON(wxID_CANCEL, NyquistInputDialog::OnCancel)
EVT_BUTTON(eDebugID, NyquistInputDialog::OnDebug)
END_EVENT_TABLE()
NyquistInputDialog::NyquistInputDialog(wxWindow * parent, wxWindowID id,
const wxString & title,
const wxString & prompt,
wxString initialCommand)
: wxDialog(parent, id, title)
{
wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
wxControl *item;
item = new wxStaticText(this, -1, prompt);
item->SetName(prompt); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
mainSizer->Add(item, 0, wxALIGN_LEFT | wxLEFT | wxTOP | wxRIGHT, 10);
mCommandText = new wxTextCtrl(this, -1, initialCommand,
wxDefaultPosition, wxSize(400, 200),
wxTE_MULTILINE);
mainSizer->Add(mCommandText, 0, wxALIGN_LEFT | wxALL, 10);
// Debug, OK, & Cancel buttons
mainSizer->Add(CreateStdButtonSizer(this, eDebugButton|eCancelButton|eOkButton), 0, wxEXPAND);
SetAutoLayout(true);
SetSizer(mainSizer);
mainSizer->Fit(this);
mainSizer->SetSizeHints(this);
mCommandText->SetFocus();
}
wxString NyquistInputDialog::GetCommand()
{
return mCommandText->GetValue();
}
void NyquistInputDialog::OnOk(wxCommandEvent & /* event */)
{
EndModal(wxID_OK);
}
void NyquistInputDialog::OnCancel(wxCommandEvent & /* event */)
{
EndModal(wxID_CANCEL);
}
void NyquistInputDialog::OnDebug(wxCommandEvent & /* event */)
{
// Transfer data
EndModal(eDebugID);
}
/**********************************************************/
BEGIN_EVENT_TABLE(NyquistOutputDialog, wxDialog)
EVT_BUTTON(wxID_OK, NyquistOutputDialog::OnOk)
END_EVENT_TABLE()
NyquistOutputDialog::NyquistOutputDialog(wxWindow * parent, wxWindowID id,
const wxString & title,
const wxString & prompt,
wxString message)
: wxDialog(parent, id, title)
{
wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *hSizer;
wxButton *button;
wxControl *item;
item = new wxStaticText(this, -1, prompt);
item->SetName(prompt); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
mainSizer->Add(item, 0, wxALIGN_LEFT | wxLEFT | wxTOP | wxRIGHT, 10);
// TODO use ShowInfoDialog() instead.
// Beware this dialog MUST work with screen readers.
item = new wxTextCtrl(this, -1, message,
wxDefaultPosition, wxSize(400, 200),
wxTE_MULTILINE | wxTE_READONLY);
mainSizer->Add(item, 0, wxALIGN_LEFT | wxALL, 10);
hSizer = new wxBoxSizer(wxHORIZONTAL);
/* i18n-hint: In most languages OK is to be translated as OK. It appears on a button.*/
button = new wxButton(this, wxID_OK, _("OK"));
button->SetDefault();
hSizer->Add(button, 0, wxALIGN_CENTRE | wxALL, 5);
mainSizer->Add(hSizer, 0, wxALIGN_CENTRE | wxLEFT | wxBOTTOM | wxRIGHT, 5);
SetAutoLayout(true);
SetSizer(mainSizer);
mainSizer->Fit(this);
mainSizer->SetSizeHints(this);
}
void NyquistOutputDialog::OnOk(wxCommandEvent & /* event */)
{
EndModal(wxID_OK);
}