1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-04-29 15:19:44 +02:00

These changes add a Tool:Nyquist to Audacity. The tool loads (executes) a Nyquist program. Nyquist is extended with aud-{get,put}-{audio,label,notes} functions so that Nyquist can read/write track data.

This commit is contained in:
rbdannenberg 2021-06-12 21:32:30 -04:00
parent bbc3ef44ce
commit 596be2b50e
24 changed files with 1623 additions and 630 deletions

View File

@ -274,6 +274,7 @@ list( APPEND INCLUDES
${TARGET_ROOT}/nyquist/xlisp
$<$<BOOL:${UNIX}>:${TARGET_ROOT}/nyquist/sys/unix>
$<$<NOT:$<BOOL:${UNIX}>>:${TARGET_ROOT}/nyquist/sys/win/msvc>
${TARGET_ROOT}/../../src/effects/nyquist
PUBLIC
${TARGET_ROOT}
)

View File

@ -5,6 +5,11 @@
#include "sndfnintdefs.h"
#include "seqfnintdefs.h"
/* from Nyquist.cpp */
#include "audacityintdefs.h"
#include "nyquistapiintdefs.h"
/* from sndsliders.c */
LVAL xslider_read(void);

View File

@ -7,3 +7,9 @@
/* extension to xlisp */
#include "sndfnintptrs.h"
#include "seqfnintptrs.h"
{ "_", SUBR, xlc_gettext },
{ "_C", SUBR, xlc_gettextc },
{ "NGETTEXT", SUBR, xlc_ngettext },
{ "NGETTEXTC", SUBR, xlc_ngettextc },
{ "AUD-DO", SUBR, xlc_aud_do },
#include "nyquistapiintptrs.h"

View File

@ -34,7 +34,7 @@ LVAL xstoprecordio(void);
#endif
/* the function table */
FUNDEF init_funtab[] = {
FUNDEF funtab[] = {
/* read macro functions */
{ NULL, S, rmhash }, /* 0 */
@ -421,6 +421,12 @@ FUNDEF init_funtab[] = {
};
#ifdef RUNTIME_REDEFINE_PRIMITIVES
/* NYQUIST XLisp already has an extension mechanism -- functions that extend
* Xlisp are defined in localptrs.h. There is no reason to keep multiple
* copies of the function table or extend it at runtime.
*/
FUNDEF *funtab = init_funtab;
static size_t szfuntab = sizeof(init_funtab) / sizeof(*init_funtab);
@ -445,6 +451,8 @@ int xlbindfunctions(const FUNDEF *functions, size_t nfunctions)
/* To do: deallocate funtab when XLisp runtime shuts down */
}
#endif
/* xnotimp does not return anything on purpose, so disable
* "no return value" warning

View File

@ -182,7 +182,7 @@ int xlisave(const char *fname)
/* xlirestore - restore a saved memory image */
int xlirestore(const char *fname)
{
extern FUNDEF *funtab;
extern FUNDEF funtab[];
char fullname[STRMAX+1];
unsigned char *cp;
int n,i,max,type;

View File

@ -41,7 +41,7 @@ extern LVAL a_fixnum,a_flonum,a_string,a_stream,a_object;
extern LVAL a_vector,a_closure,a_char,a_ustream,a_extern;
extern LVAL s_gcflag,s_gchook;
extern LVAL s_search_path;
extern FUNDEF *funtab;
extern FUNDEF funtab[];
/* forward declarations */
FORWARD LOCAL void initwks(void);

View File

@ -88,7 +88,7 @@ void xladdivar(LVAL cls, const char *var)
/* xladdmsg - add a message to a class */
void xladdmsg(LVAL cls, const char *msg, int offset)
{
extern FUNDEF *funtab;
extern FUNDEF funtab[];
LVAL mptr;
/* enter the message selector */

View File

@ -21,7 +21,7 @@
/* external variables */
extern LVAL s_printcase,k_downcase,k_const,k_nmacro;
extern LVAL s_ifmt,s_ffmt;
extern FUNDEF *funtab;
extern FUNDEF funtab[];
extern char buf[];
LOCAL void putsymbol(LVAL fptr, char *str, int escflag);
@ -209,7 +209,7 @@ LOCAL void putqstring(LVAL fptr, LVAL str)
for (p = getstring(str); (ch = *p) != '\0'; ++p)
/* check for a control character */
if (ch < 040 || ch == '\\' || ch > 0176 /* || ch == '"' */) {
if (ch < 040 || ch == '\\' || ch > 0176 || ch == '"') {
xlputc(fptr,'\\');
switch (ch) {
case '\011':

View File

@ -932,7 +932,7 @@ int xlisnumber(char *str, LVAL *pval)
/* defmacro - define a read macro */
void defmacro(int ch, LVAL type, int offset)
{
extern FUNDEF *funtab;
extern FUNDEF funtab[];
LVAL subr;
subr = cvsubr(funtab[offset].fd_subr,funtab[offset].fd_type,offset);
setelement(getvalue(s_rtable),ch,cons(type,subr));

View File

@ -538,7 +538,7 @@ void nyx_cleanup()
free(nyx_audio_name);
nyx_audio_name = NULL;
}
NyquistAPICleanup();
nyquistAPICleanup();
#if defined(NYX_MEMORY_STATS) && NYX_MEMORY_STATS
printf("\nnyx_cleanup\n");
xmem();
@ -851,6 +851,11 @@ nyx_rval nyx_compute_type(LVAL expr)
return ntype;
}
int nyx_load(const char* filename)
{
return xlload(filename, false, false);
}
nyx_rval nyx_eval_expression(const char *expr_string)
{
LVAL expr = NULL;
@ -1035,8 +1040,8 @@ finish:
}
// assumes sound is protected from garbage collection
int nyx_pump_audio(LVAL sound, int64_t input_len,
// assumes input_len >= 0
int nyx_pump_audio(LVAL sound, double maxdur,
nyx_audio_callback callback, void *userdata)
{
#define nyx_result do not access this global in nyx_pump_audio
@ -1047,6 +1052,7 @@ int nyx_pump_audio(LVAL sound, int64_t input_len,
int result = 0;
int num_channels;
int ch;
int64_t input_len; // in samples
// Any variable whose value is set between the _setjmp() and the "finish" label
// and that is used after the "finish" label, must be marked volatile since
@ -1082,22 +1088,6 @@ int nyx_pump_audio(LVAL sound, int64_t input_len,
}
// if LEN is set, we will return LEN samples per channel. If LEN is
// unbound, we will compute samples until every channel has terminated
// that the samples per channel will match the last termination time,
// i.e. it could result in a partial block at the end.
if (input_len == 0) {
LVAL val = getvalue(xlenter("LEN"));
if (val != s_unbound) {
if (ntype(val) == FLONUM) {
input_len = (int64_t) getflonum(val);
}
else if (ntype(val) == FIXNUM) {
nyx_input_length = (int64_t) getfixnum(val);
}
}
}
// at this point, input sounds which were referenced by symbol S
// (or nyx_get_audio_name()) could be referenced by sound, but
// S is now bound to NIL. sound is a protected (garbage
@ -1116,11 +1106,21 @@ int nyx_pump_audio(LVAL sound, int64_t input_len,
// To unify single and multi-channel sounds, we'll create an array
// of one element for single-channel sounds.
// the value of sound is already protected, but sound itself is not
// we might reassign it, so we need to protect sound:
xlprot1(sound);
if (num_channels == 1) {
LVAL array = newvector(1);
setelement(array, 0, sound);
sound = array;
}
maxdur = maxdur * getsound(getelement(sound, 0))->sr; // convert to samples
if (maxdur > MAX_SND_LEN) {
maxdur = MAX_SND_LEN; // avoid integer overflow
}
input_len = maxdur + 0.5;
for (ch = 0; ch < num_channels; ch++) {
if (ch > 0) { // no need to copy first channel
setelement(sound, ch,
@ -1140,7 +1140,7 @@ int nyx_pump_audio(LVAL sound, int64_t input_len,
bool terminated = true;
// how many samples to compute before calling callback:
int64_t togo = max_sample_block_len;
if (input_len > 0 && total + togo > input_len) {
if (total + togo > input_len) {
togo = input_len - total;
}
for (ch = 0; ch < num_channels; ch++) {
@ -1188,6 +1188,7 @@ int nyx_pump_audio(LVAL sound, int64_t input_len,
}
sound = NULL; // unreference sound array so GC can free it
xlpop(); // unprotect sound
finish:
@ -1330,6 +1331,11 @@ void nyx_continue()
xlcontinue();
}
void nyx_xlerror(const char *emsg)
{
xlerror(emsg, s_unbound);
}
int ostgetc()
{
if (nyx_expr_pos < nyx_expr_len) {

View File

@ -34,7 +34,7 @@ extern "C"
// and all the XLISP declarations, whereas NyquistAPICleanup() is only
// needed by Nyquist.cpp for Audacity effects as are other functions
// here in nyx.h.
void NyquistAPICleanup();
void nyquistAPICleanup(void);
void nyx_set_xlisp_path(const char *path);
/* should return return 0 for success, -1 for error */
@ -60,6 +60,7 @@ extern "C"
void nyx_stop();
void nyx_break();
void nyx_continue();
void nyx_xlerror(const char* emsg);
void nyx_set_audio_params(double rate, int64_t len);
@ -68,9 +69,15 @@ extern "C"
int num_channels,
int64_t len, double rate);
LVAL nyx_make_input_audio(nyx_audio_callback callback,
void* userdata,
int num_channels,
int64_t len, double rate);
char *nyx_get_audio_name();
void nyx_set_audio_name(const char *name);
int nyx_load(const char* filename);
nyx_rval nyx_eval_expression(const char *expr);
/** @brief Get the number of channels in the Nyquist audio object
@ -84,7 +91,7 @@ extern "C"
int nyx_get_num_channels(LVAL snd);
int nyx_get_audio(nyx_audio_callback callback,
void* userdata);
int nyx_pump_audio(LVAL sound, int64_t len,
int nyx_pump_audio(LVAL sound, double maxdur,
nyx_audio_callback callback, void* userdata);
int nyx_get_int();

View File

@ -186,6 +186,10 @@ list( APPEND SOURCES
NoteTrack.cpp
NoteTrack.h
NumberScale.h
NyquistDialog.cpp
NyquistDialog.h
NyquistAPI.cpp
NyquistAPI.h
PitchName.cpp
PitchName.h
PlatformCompatibility.cpp
@ -569,8 +573,6 @@ list( APPEND SOURCES
effects/nyquist/LoadNyquist.h
effects/nyquist/Nyquist.cpp
effects/nyquist/Nyquist.h
effects/nyquist/NyquistAPI.cpp
effects/nyquist/NyquistAPI.h
effects/nyquist/nyquistapiint.cpp
effects/nyquist/nyquistapiintdefs.h
effects/nyquist/nyquistapiintptrs.h

View File

@ -37,6 +37,7 @@ public:
/// 'source' variable, the destination sample in the 'dest' variable,
/// and hints to the formats of the samples. Even if the sample formats
/// are the same, samples are clipped, if necessary.
/// (see definition for parameter descriptions)
void Apply(DitherType ditherType,
constSamplePtr source, sampleFormat sourceFormat,
samplePtr dest, sampleFormat destFormat,

926
src/NyquistAPI.cpp Normal file
View File

@ -0,0 +1,926 @@
/**********************************************************************
Audacity: A Digital Audio Editor
NyquistAPI.cpp
Roger B. Dannenberg
Apr 2021
Interface for Nyquist access to tracks and more
**********************************************************************/
#include "../lib-src/libnyquist/nyquist/xlisp/xlisp.h"
#include "NyquistAPI.h"
#include "WaveClip.h"
#include "LabelTrack.h"
#include "nyx.h"
#include "../lib-src/portsmf/allegro.h"
// To access project information, effect runner finds and shares project here:
const AudacityProject *audacityProject = NULL;
void setNyquistProject(const AudacityProject *p)
{
audacityProject = p;
}
LVAL properties(const Track *track)
{
LVAL props, last, allClips, clips, lastClips, cl;
xlstkcheck(4);
xlsave(props);
xlsave(allClips);
xlsave(clips);
xlsave(lastClips);
if (track) {
wxString wname = track->GetName();
const char *name = wname.mb_str();
last = props = consa(xlenter("NAME"));
last = rplacd(last, consa(name ? cvstring(name) : NULL));
last = rplacd(last, consa(xlenter("TYPE")));
const char* kindString = NULL;
track->TypeSwitch([&](const WaveTrack* t) { kindString = "WAVE"; },
[&](const NoteTrack* t) { kindString = "NOTE"; },
[&](const LabelTrack* t) { kindString = "LABEL"; });
last = rplacd(last, consa(kindString ? xlenter(kindString) : NULL));
track->TypeSwitch([&](const WaveTrack* waveTrack) {
auto channels = TrackList::Channels(waveTrack); // track->GetOwner();
int nchans = channels.size(); // channels->size();
last = rplacd(last, consa(xlenter("CHANNELS")));
last = rplacd(last, consa(cvfixnum(nchans)));
// compute the clips list as property "CLIPS":
if (nchans > 1) {
allClips = newvector(nchans);
}
int channel_index = 0;
for (auto channel : channels) {
clips = NULL;
lastClips = NULL;
auto ca = channel->SortedClipArray();
for (size_t j = 0; j < ca.size(); j++) {
if (j < 1000) {
auto caj = ca[j]->GetStartTime();
cl = cons(cvflonum(ca[j]->GetStartTime()),
consa(cvflonum(ca[j]->GetEndTime())));
cl = consa(cl); // ((start end))
if (!clips) {
clips = cl;
}
}
else if (j == 1000) {
cl = consa(NULL); // (nil)
}
else {
break;
}
if (lastClips) {
lastClips = rplacd(lastClips, cl);
}
else {
lastClips = clips = cl;
}
}
if (allClips) {
setelement(allClips, channel_index++, clips);
}
}
if (!allClips) { // only one channel
allClips = clips;
}
last = rplacd(last, consa(xlenter("CLIPS")));
last = rplacd(last, consa(allClips));
});
}
xlpopn(4);
return props;
}
const Track *getTrackByNumber(FIXTYPE n)
{
const TrackList &trackList = TrackList::Get(*audacityProject);
auto range = trackList.Leaders();
if (n < 0 || n >= trackList.size()) {
return NULL;
}
TrackList::const_iterator iter = range.begin();
for (int i = 0; i < n; i++) iter++; // advance to nth track
return *iter;
}
const Track *getTrackByName(const char *name)
{
const TrackList& trackList = TrackList::Get(*audacityProject);
auto range = trackList.Leaders();
for (auto ptrack : range) {
wxString wname = ptrack->GetName();
const char* trackName = wname.mb_str();
if (strcmp(name, trackName) == 0) {
return ptrack;
}
}
return NULL;
}
const Track *getTrack(LVAL nameOrNumber)
{
if (fixp(nameOrNumber)) {
return getTrackByNumber(getfixnum(nameOrNumber));
} else if (stringp(nameOrNumber)) {
return getTrackByName((const char *)getstring(nameOrNumber));
}
return NULL;
}
LVAL getTrackInfo(LVAL nameOrNumber)
{
const Track* track = getTrack(nameOrNumber);
return properties(track);
}
// getAudio() creates a nyx_susp object to a source of samples
// This object only has a pointer to denote the state, but we
// need a buffer and other state, so we create a new class.
//
// A WaveTrackReader is allocated for each channel. When the effect
// completes, we need to delete the WaveTrackReaders, so keep them
// on a vector. If a sound terminates, we'll keep the WaveTrackReader
// anyway until the end of the effect.
//
// TODO: I think Nyquist effects can retain state and therefore sounds
// from one run to the next. Nyx should tie into the GC and keep
// backpointers to nyx_susp objects, changing their callbacks to
// return zero if they are invoked after the end of an effect. There is
// no problem retaining a sound as long as it is no longer "lazy" and
// will not call back into Audacity looking for samples.
class WaveTrackReader;
static std::vector<WaveTrackReader*> waveTrackReaders;
class WaveTrackReader
{
public:
sampleCount mCurBufferOffset; // offset where reading starts
// track samples are copied to this intermediate buffer
SampleBuffer mCurBuffer;
sampleCount mCurBufferStart; // sample count of first sample
size_t mCurBufferLen; // length in samples of mCurBuffer
const WaveTrack *mCurTrack;
WaveTrackReader(const WaveTrack *track, double start) {
mCurTrack = track;
double srate = track->GetRate();
mCurBufferOffset = (sampleCount)(start * srate + 0.5);
mCurBufferLen = 0;
// save all created objects and free them with NyquistAPICleanup()
// after the effect has completed:
waveTrackReaders.push_back(this);
}
// called from Nyquist sound object to implement lazy computation of
// samples
// buffer - where to put samples
// channel - channel number (passed on, but ultimately ignored)
// start - sample count for sound (starts from zero)
// len - number of samples to be read and returned
// (caller assumes len is fulfilled, so zero fill if needed)
// totlen - total sound length in samples (passed on, but ultimately
// ignored); caller may pass zero for "unknown"
//
// TODO: remove channel and totlen parameters (affects Nyquist effect)
static int StaticGetCallback(float* buffer, int channel,
int64_t start, int64_t len, int64_t totlen, void *userdata) {
WaveTrackReader *wtr = (WaveTrackReader *)userdata;
return wtr->GetCallback(buffer, channel, start, len, totlen);
}
// note: near duplicate code from Nyquist.cpp (NyquistEffect)
//
// see StaticGetCallback for parameter descriptions
int GetCallback(float *buffer, int WXUNUSED(ch),
int64_t start, int64_t len, int64_t WXUNUSED(totlen))
{
// Algorithm: either copy already-read samples out of mCurBuffer or
// start fresh: allocate buffer, fill it completely and copy from it
if (mCurBuffer.ptr()) {
// the first compare only happens if we do not read sequentially,
// so it should never happen:
if (mCurBufferOffset + start < mCurBufferStart ||
mCurBufferOffset + start + len >
mCurBufferStart + mCurBufferLen) {
mCurBuffer.Free();
}
}
if (!mCurBuffer.ptr()) {
mCurBufferStart = mCurBufferOffset + start;
mCurBufferLen = mCurTrack->GetBestBlockSize(mCurBufferStart);
if (mCurBufferLen < (size_t)len) {
mCurBufferLen = ((WaveTrack *)mCurTrack)->GetIdealBlockSize();
}
// We don't have an end time (length) for the track
// mCurBufferLen =
// limitSampleBufferSize(mCurBufferLen, mCurLen - mCurBufferStart);
mCurBuffer.Allocate(mCurBufferLen, floatSample);
// We're inside the Effects's try block, so none here?
// here's the action: pull samples from track
// try {
mCurTrack->Get(
mCurBuffer.ptr(), floatSample,
mCurBufferStart, mCurBufferLen);
/* }
catch (...) {
// Save the exception object for re-throw when out of the library
mpException = std::current_exception();
return -1;
} */
}
// We have guaranteed above that this is nonnegative and bounded by
// mCurBufferLen:
auto offset = (mCurBufferOffset + start - mCurBufferStart).as_size_t();
CopySamples(mCurBuffer.ptr() + offset * SAMPLE_SIZE(floatSample), floatSample,
(samplePtr)buffer, floatSample, len);
return 0;
}
};
int64_t getTrackLen(const WaveTrack *waveTrack)
{
auto channels = TrackList::Channels(waveTrack); // track->GetOwner();
double srate = waveTrack->GetRate();
int64_t len = 0;
for (auto channel : channels) {
auto ca = channel->SortedClipArray();
int n = ca.size();
if (n > 0) {
auto clip = ca[n - 1]; // last clip
int64_t clipLen = (int64_t)(clip->GetEndTime() * srate + 0.5);
if (clipLen > len) len = clipLen;
}
}
return len;
}
// XLISP primitive implements AUD-GET-AUDIO. start is the track time
// returns a sound with a start time of zero, i.e. the source is
// shifted by -start to to start at time 0.
LVAL getAudio(LVAL nameOrNumber, double start, double dur)
{
LVAL snd;
xlsave1(snd);
const Track* track = getTrack(nameOrNumber);
if (track) {
track->TypeSwitch([&](const WaveTrack* waveTrack) {
auto channels = TrackList::Channels(waveTrack); // track->GetOwner();
int nchans = channels.size(); // channels->size();
double srate = waveTrack->GetRate();
int64_t len = (int64_t)(dur * srate + 0.5);
if (nchans > 1) {
snd = newvector(nchans);
int ch = 0;
for (auto channel : channels) {
WaveTrackReader* wtr = new WaveTrackReader(channel, start);
setelement(snd, ch, nyx_make_input_audio(
WaveTrackReader::StaticGetCallback, wtr, 1,
len, waveTrack->GetRate()));
ch++;
}
}
else {
WaveTrackReader* wtr = new WaveTrackReader(waveTrack, start);
snd = nyx_make_input_audio(
WaveTrackReader::StaticGetCallback, wtr, 1,
len, waveTrack->GetRate());
}
});
}
// if not a WaveTrack, fall through, return NULL
xlpop();
return snd;
}
class WaveTrackWriter
{
public:
const char *error;
WaveTrack* mCurTrack[2];
WaveTrack* mOutputTrack[2];
double mOutputTime;
double srate; // all channels must match
// trkchans is the number of channels in the output track
// chans in the number of channels in snd
WaveTrackWriter(TrackIterRange<const WaveTrack>& channels, LVAL snd,
double rate, int trkchans, int chans,
double start, double maxdur) {
error = NULL;
mCurTrack[0] = mCurTrack[1] = nullptr;
mOutputTrack[0] = mOutputTrack[1] = nullptr;
mOutputTime = 0;
srate = rate;
std::shared_ptr<WaveTrack> outputTrack[2];
if (maxdur < 0) {
error = "Error: specified max duration < 0,";
return;
}
// for now, require every output channel to have the same sample rate
// the first channel already matches the Nyquist sound sample rate
for (auto track : channels) {
if (track->GetRate() != srate) {
error = "Error: mismatched sample rates.";
return;
}
}
int i = 0;
for (auto track : channels) {
// this must be wrong. Also, I'm not sure how to write to a track.
// I generally copied code from Nyquist.cpp (nyquist effects), but
// I don't understand why it makes copies or how deep the copies
// go (are these virtual copies of whole tracks, or just empty
// containers for new audio?)
mCurTrack[i] = (WaveTrack *) (const WaveTrack *) track;
outputTrack[i] = track->EmptyCopy();
outputTrack[i]->SetRate(srate);
i++;
}
{ // I suppose this is some kind of trick to clean up and restore
// tracks if track writing raises an exception.
auto vr0 = valueRestorer(mOutputTrack[0], outputTrack[0].get());
auto vr1 = valueRestorer(mOutputTrack[1], outputTrack[1].get());
if (!nyx_pump_audio(snd, maxdur,
&StaticPutCallback, (void*)this)) {
error = "Error: Something went wrong in evaluating expression.";
}
}
for (int i = 0; i < trkchans; i++) {
outputTrack[i]->Flush();
mOutputTime = outputTrack[i]->GetEndTime();
if (mOutputTime <= 0) {
error = "Nyquist returned nil audio.\n";
return;
}
}
for (size_t i = 0; i < trkchans; i++) {
WaveTrack* out;
if (chans == trkchans) {
out = outputTrack[i].get();
} else {
out = outputTrack[0].get();
}
// See Nyquist.cpp for some code to compute mRestoreSplits and
// bMergeClips options. For now, we ignore options. Let's see
// if the behavior is reasonable, and if not we'll try something
// else.
mCurTrack[i]->ClearAndPaste(start, start + mOutputTime, out,
true, true); // mRestoreSplits, bMergeClips
// See Nyquist.cpp for code dealing with SyncLockGroups here
}
// Well, we wrote to the project, so probably something should be
// notified. Nyquist.cpp sets mProjectChanged = true; and we're in
// an effect, but how can we find it? Should this go through globals
// in nyx.c? Which already uses some globals to negotiate between
// Audacity and Nyquist?
// mProjectChanged = true;
}
static int StaticPutCallback(float* buffer, int channel,
int64_t start, int64_t len, int64_t totlen, void* userdata) {
WaveTrackWriter* wtw = (WaveTrackWriter*)userdata;
return wtw->PutCallback(buffer, channel, start, len, totlen);
}
int PutCallback(float* buffer, int channel,
int64_t start, int64_t len, int64_t totlen) {
// Don't let C++ exceptions propagate through the Nyquist library
return GuardedCall<int>([&] {
mOutputTrack[channel]->Append((samplePtr)buffer, floatSample, len);
return 0; // success
}, MakeSimpleGuard(-1)); // translate all exceptions into failure
}
};
LVAL putAudio(LVAL nameOrNumber, LVAL expr, double start, double maxdur)
{
LVAL snd;
double srate;
const Track* track;
xlsave1(snd);
snd = xleval(expr);
int nchans = nyx_get_num_channels(snd);
if (nchans > 2) {
nyx_xlerror("AUD_PUT_AUDIO got more than 2 channels.");
goto exit;
} else if (nchans < 1) {
nyx_xlerror("AUD_PUT_AUDIO did not get a sound to put.");
goto exit;
}
srate = nyx_get_sample_rate(snd);
track = getTrack(nameOrNumber);
if (track) {
track->TypeSwitch([&](const WaveTrack* waveTrack) {
auto channels = TrackList::Channels(waveTrack);
int trkchans = channels.size(); // channels->size();
double trkrate = waveTrack->GetRate();
// int64_t len = (int64_t)(maxdur * trkrate + 0.5);
// allow mono expanded to N trkchans, otherwise require a match
if (trkchans != nchans) {
nyx_xlerror("Error: channel count mismatch.");
} else if (nchans == 0) {
nyx_xlerror(
"Error: sound expression did not return a sound.");
} else if (nchans == -1) {
nyx_xlerror(
"Error: sound expression returned array with one sound.");
} else if (trkrate != srate) {
nyx_xlerror("Error: sample rate mismatch.");
} else {
WaveTrackWriter waveTrackWriter(channels, snd, trkrate,
trkchans, nchans, start, maxdur);
if (waveTrackWriter.error) {
nyx_xlerror(waveTrackWriter.error);
} else {
snd = s_true;
}
}
});
}
exit:
xlpop();
return snd;
}
LVAL getLabels(LVAL nameOrNumber, double start, double dur)
{
LVAL labelList, elem;
xlsave1(labelList);
xlsave1(elem); // each sublist ((T0 T1 title))
LVAL last = NULL; // last element in labelList
const Track* track = getTrack(nameOrNumber);
if (track) {
track->TypeSwitch([&](const LabelTrack* labelTrack) {
// int n = labelTrack->GetNumLabels();
for (const auto& label : labelTrack->GetLabels()) {
if (label.getT0() >= start &&
label.getT0() < start + dur) {
elem = consa(cvstring(label.title));
elem = cons(cvflonum(label.getT1()), elem);
elem = cons(cvflonum(label.getT0()), elem);
elem = consa(elem); // ((T0 T1 title))
if (last) {
rplacd(last, elem);
last = elem;
}
else {
labelList = last = elem;
}
}
}
});
}
// if not a LabelTrack, fall through, return NULL
xlpopn(2);
return labelList;
}
// get a double from either a fixnum or flonum, o.w. return false
bool lvalNum(LVAL lval, double *time)
{
if (fixp(lval)) {
*time = getfixnum(lval);
} else if (floatp(lval)) {
*time = getflonum(lval);
} else return false;
return true;
}
LVAL putLabels(LVAL nameOrNumber, LVAL labels, LVAL merge_flag)
{
const Track* track = getTrack(nameOrNumber);
if (track) {
track->TypeSwitch([&](const LabelTrack* labelTrack) {
if (!merge_flag) {
double start = labelTrack->GetStartTime();
double end = labelTrack->GetEndTime();
((LabelTrack *) labelTrack)->Clear(start - 1, end + 1);
}
while (consp(labels)) {
LVAL triple = car(labels);
double t0, t1;
char* label;
if (!consp(triple) || !lvalNum(car(triple), &t0) ||
!consp(cdr(triple)) || !lvalNum(car(cdr(triple)), &t1)) {
break;
}
triple = cdr(cdr(triple)); // move to string
if (!consp(triple) || !stringp(car(triple))) {
break;
}
label = (char*)getstring(car(triple));
SelectedRegion region(t0, t1);
// insert the label into the track
wxString labelString(label);
((LabelTrack *) labelTrack)->AddLabel(region, labelString);
labels = cdr(labels);
}
// if we encounter an error, we exit the loop, leaving labels
// pointing near the error we encountered.
if (labels) {
xlerror("not a valid label list", labels);
}
});
}
return labels;
}
// appends attribute and value to list, which is the last
// cons cell in a NOTE or UPDATE so far. The
// new last cons cell containing the value is returned.
LVAL appendParameter(LVAL list, Alg_parameter_ptr param, LVAL STRUE)
{
char name[64];
// allow attributes and atom values to have length up to 60:
rplacd(list, consa(NIL)); // append attr to list
list = cdr(list);
if (strlen(param->attr_name()) <= 60) {
// convert to upper case:
strcpy(name, param->attr_name());
for (char* ptr = name; *ptr; ptr++) {
*ptr = toupper(*ptr);
}
rplaca(list, xlenter(name));
} // otherwise attribute is left as NIL
rplacd(list, consa(NIL)); // append value to list
list = cdr(list);
switch (param->attr_type()) {
case 'i':
rplaca(list, cvfixnum(param->i));
break;
case 'r':
rplaca(list, cvflonum(param->r));
break;
case 's':
rplaca(list, cvstring(param->s));
break;
case 'l':
rplaca(list, param->l ? STRUE : NIL);
break;
case 'a':
// atoms are stored like attributes as if the last character is a
// type character which is at param->a[0]. Skip the type character:
rplaca(list, xlenter(param->a + 1));
break;
default: /// perhaps we should report an error. This should never happen.
break;
}
return list;
}
// return the last cons cell in noteList after appending an update
LVAL appendUpdate(LVAL *noteList, LVAL last, Alg_event_ptr ev,
LVAL SUPDATE, LVAL STRUE)
{
Alg_update_ptr update = (Alg_update_ptr)ev;
// append a cons at the top-level
if (*noteList) {
rplacd(last, consa(NIL));
last = cdr(last);
} else {
*noteList = consa(NIL);
last = *noteList;
}
// last is now a cons where the update goes
LVAL list = consa(SUPDATE);
rplaca(last, list);
rplacd(list, consa(NIL)); // append key
list = cdr(list);
rplaca(list, cvfixnum(update->get_identifier()));
rplacd(list, consa(NIL)); // append time
list = cdr(list);
rplaca(list, cvflonum(update->time));
rplacd(list, consa(NIL)); // append channel
list = cdr(list);
rplaca(list, cvfixnum(update->chan));
// append attribute and value:
appendParameter(list, &update->parameter, STRUE);
return last;
}
LVAL appendNote(LVAL *noteList, LVAL last, Alg_event_ptr ev,
LVAL SNOTE, LVAL STRUE)
{
Alg_note_ptr note = (Alg_note_ptr) ev;
// append a cons at the top-level
if (*noteList) {
rplacd(last, consa(NIL));
last = cdr(last);
} else {
*noteList = consa(NIL);
last = *noteList;
}
// last is now a cons where the note goes
LVAL list = consa(SNOTE);
rplaca(last, list);
rplacd(list, consa(NIL)); // append key
list = cdr(list);
rplaca(list, cvfixnum(note->get_identifier()));
rplacd(list, consa(NIL)); // append time
list = cdr(list);
rplaca(list, cvflonum(note->time));
rplacd(list, consa(NIL)); // append channel
list = cdr(list);
rplaca(list, cvfixnum(note->chan));
rplacd(list, consa(NIL)); // append pitch
list = cdr(list);
rplaca(list, cvflonum(note->get_pitch()));
rplacd(list, consa(NIL)); // append loudness
list = cdr(list);
rplaca(list, cvflonum(note->get_loud()));
rplacd(list, consa(NIL)); // append duration
list = cdr(list);
rplaca(list, cvflonum(note->get_duration()));
for (Alg_parameters_ptr params = note->parameters; params; params->next) {
list = appendParameter(list, &params->parm, STRUE);
}
return last;
}
LVAL getNotes(LVAL nameOrNumber, double start, double dur, LVAL inbeats)
{
LVAL noteList;
xlsave1(noteList);
LVAL last = NULL; // last element in noteList
const Track* track = getTrack(nameOrNumber);
LVAL SNOTE = xlenter("NOTE");
LVAL SUPDATE = xlenter("UPDATE");
LVAL STRUE = xlenter("T");
if (track) {
track->TypeSwitch([&](const NoteTrack* noteTrack) {
Alg_seq& seq = noteTrack->GetSeq();
Alg_iterator iter(&seq, false);
iter.begin();
Alg_event_ptr event;
while (event = iter.next(NULL, NULL, NULL, dur)) {
if (event->time < start) {
continue; // skip to start time
}
if (event->is_note()) {
last = appendNote(&noteList, last, event, SNOTE, STRUE);
} else if (event->is_update()) {
last = appendUpdate(&noteList, last, event, SUPDATE, STRUE);
}
}
iter.end();
});
}
xlpop();
return noteList;
}
// extract an attribute and value from plist, set attribute in event,
// which may be an Alg_note or Alg_update.
// prerequisite: consp(plist)
// return cdr(cdr(plist)) if successful; otherwise return plist
// note that return value NULL indicates success and there are
// no more properties to parse.
//
LVAL getParameter(Alg_event_ptr event, LVAL plist, LVAL STRUE)
{
if (!symbolp(car(plist))) {
return NULL;
}
char* symname = (char *) getstring(getpname(car(plist)));
if (symname[0] == ':') {
symname++; // trim the colon if it is there
}
if (strlen(symname) > 60) {
return plist;
}
char attr[64];
strcpy(attr, symname);
// to lower case:
for (char* ptr = attr; *ptr; ptr++) {
*ptr = tolower(*ptr);
}
// Allegro attributes have a type character at the end,
// e.g. bendi is an integer attribute.
char typechar = attr[strlen(attr) - 1];
if (!consp(cdr(plist))) {
return NULL;
}
LVAL val = car(cdr(plist));
if (fixp(val) && typechar == 'i') {
event->set_integer_value(attr, getfixnum(val));
} else if (floatp(val) && typechar == 'r') {
event->set_real_value(attr, getflonum(val));
} else if (stringp(val) && typechar == 's') {
event->set_string_value(attr,
(char*)getstring(val));
} else if (symbolp(val) && typechar == 'a') {
// note: atom names are not required to end in a type character
// atoms are stored without case conversion, so typically
// upper case as in XLisp symbols.
Alg_attribute a = symbol_table.insert_string(
(const char *) getstring(getpname(val)));
event->set_atom_value(attr, a);
} else if (val == NULL ||
val == STRUE && typechar == 'l') {
event->set_logical_value(attr, (val == STRUE));
} else {
return NULL;
}
return cdr(cdr(plist));
}
LVAL putNotes(LVAL nameOrNumber, LVAL notes, LVAL inbeats, LVAL merge_flag)
{
const Track* track = getTrack(nameOrNumber);
if (track) {
track->TypeSwitch([&](const NoteTrack* noteTrack) {
Alg_seq& seq = noteTrack->GetSeq();
if (inbeats) {
seq.convert_to_beats();
} else {
seq.convert_to_seconds();
}
if (!merge_flag) { // free notes in track
// we could delete the whole seq, but we will keep tempo
// information and just delete all the events:
for (int j = 0; j < seq.track_list.length(); j++) {
Alg_track& notes = seq.track_list[j];
// Alg_events does not delete notes
for (int i = 0; i < notes.length(); i++) {
Alg_event_ptr event = notes[i];
delete event;
}
}
seq.track_list.reset();
seq.track_list.add_track(0, seq.get_time_map(),
seq.get_units_are_seconds());
}
LVAL SNOTE = xlenter("NOTE");
LVAL SUPDATE = xlenter("UPDATE");
LVAL STRUE = xlenter("T");
// insert notes from list
Alg_note_ptr anote = NULL;
Alg_update_ptr update = NULL;
while (consp(notes)) {
LVAL note = car(notes);
if (!consp(note)) {
break;
}
if (car(note) == SNOTE) {
note = cdr(note); // get the key (id)
if (!consp(note) || !fixp(car(note))) {
break;
}
anote = new Alg_note();
anote->set_identifier(getfixnum(car(note)));
note = cdr(note); // get the time
if (!consp(note) || !lvalNum(car(note), &anote->time)) {
break;
}
note = cdr(note); // get the channel
if (!consp(note) || !fixp(car(note))) {
break;
}
anote->chan = getfixnum(car(note));
note = cdr(note); // get the pitch
double pitch;
if (!consp(note) || !lvalNum(car(note), &pitch)) {
break;
}
anote->set_pitch(pitch);
note = cdr(note); // get the loudness
double loudness;
if (!consp(note) || !lvalNum(car(note), &loudness)) {
break;
}
anote->set_loud(loudness);
note = cdr(note); // get duration
if (!consp(note) || !lvalNum(car(note), &anote->dur)) {
break;
}
note = cdr(note);
while (consp(note)) { // get attribute/value pairs
LVAL next = getParameter(anote, note, STRUE);
if (next == note) { // error encountered
break;
}
note = next; // skip to next attr/value pair
}
} else if (car(note) == SUPDATE) {
note = cdr(note);
if (!consp(note) || !fixp(car(note))) {
break;
}
update = new Alg_update(); // get key (id)
update->set_identifier(getfixnum(car(note)));
note = cdr(note); // get the time
if (!consp(note) || !lvalNum(car(note), &anote->time)) {
break;
}
note = cdr(note); // get the channel
if (!consp(note) || !fixp(car(note))) {
break;
}
anote->chan = getfixnum(car(note));
note = cdr(note); // get parameter
if (!consp(note)) {
break;
}
LVAL next = getParameter(update, note, STRUE);
if (next) { // there should be 1 attr/val pair, so next
break; // should be NULL. Otherwise, error.
}
}
if (note) {
break;
} else { // good, used all list values
seq.add_event(anote, 0);
anote = NULL;
}
notes = cdr(notes);
}
if (notes) { // something went wrong
if (anote) {
delete anote; // didn't use it in note track
}
if (update) {
delete update; // didn't use it
}
xlerror("Invalid list of notes", notes);
}
// DEBUG: Write the track
seq.write("seq-after-aud-put.gio");
});
}
return notes;
}
LVAL getTimes(LVAL nameOrNumber, double start, double dur)
{
return NULL;
}
LVAL putTimes(LVAL nameOrNumber, LVAL breakpoints)
{
return NULL;
}
// call this when effect has completed
void nyquistAPICleanup(void)
{
waveTrackReaders.clear();
}

View File

@ -11,6 +11,7 @@
**********************************************************************/
// (AUD-GET-TRACK-INFO id) -- get a property list from track by name or index
// track index starts at 0, incremented by 1 for each multi-channel track
// E.g. if there are two stereo tracks, they are tracks 0 and 1.
@ -60,6 +61,7 @@
// id is name or number
// start is the project time for the start of a region (beats or seconds)
// dur is the duration for a region (beats or seconds)
// inbeats is NULL if times and durations are in seconds; o.w. beats
// returns a list of events with time >= start and time < start + dur
// each event in the returned list has one of two formats:
// (NOTE key time channel pitch loudness duration
@ -72,20 +74,26 @@
// duration is event duration in beats or seconds (FLONUM)
// each parameter (optional) is a keyword followed by a value.
// The keyword prepends a colon (":") to the Allegro attribute
// name and retains the type code suffix:
// name, retains the type code suffix, and converts all to
// upper case. Type code suffixes are:
// s[tring], r[eal], i[nteger], or l[ogical].
// Example: (NOTE 60 4.0 3 100.0 1.0 :colors "blue")
// Example: (NOTE 60 4.0 3 100.0 1.0 :COLORS "blue")
// (UPDATE key time channel parameter)
// key is an event identifier, normally MIDI key number (FIXNUM)
// time is the start time (beats or seconds) (FLONUM)
// channel is the (usually MIDI) channel number (FIXNUM)
// parameter is an attribute keyword followed by value as in NOTE.
//
// (AUD-PUT-NOTES id notes merge)
// (AUD-PUT-NOTES id notes inbeats merge)
// id is name or number
// notes is an event list in the format returned by AUD-GET-NOTES
// any symbol may denote an attribute for a parameter. If the symbol
// name starts with ":", the colon is removed. The name is converted
// to lower case, and the last character must match the type of the
// parameter. For logical attributes, the value must be T or NIL.
// inbeats is NULL if times and durations are in seconds; o.w. beats
// if merge is NULL, replace all events in the track. Otherwise, merge.
//
//
// (AUD-GET-TIMES id start dur)
// id is name or number
// start is project time for start of region (FLONUM)
@ -103,7 +111,7 @@
#ifndef __AUDACITY_NYQUISTAPI__
#define __AUDACITY_NYQUISTAPI__
void NyquistAPICleanup();
void setNyquistProject(const AudacityProject* p);
LVAL getTrackInfo(LVAL nameOrNumber);
/* LISP: (AUD-GET-TRACK-INFO ANY) */
@ -120,11 +128,11 @@ LVAL getLabels(LVAL nameOrNumber, double start, double dur);
LVAL putLabels(LVAL nameOrNumber, LVAL labels, LVAL merge_flag);
/* LISP: (AUD-PUT-LABELS ANY ANY ANY) */
LVAL getNotes(LVAL nameOrNumber, double start, double dur);
/* LISP: (AUD-GET-NOTES ANY ANYNUM ANYNUM) */
LVAL getNotes(LVAL nameOrNumber, double start, double dur, LVAL inbeats);
/* LISP: (AUD-GET-NOTES ANY ANYNUM ANYNUM ANY) */
LVAL putNotes(LVAL nameOrNumber, LVAL notes, LVAL merge_flag);
/* LISP: (AUD-PUT-NOTES ANY ANY ANY) */
LVAL putNotes(LVAL nameOrNumber, LVAL notes, LVAL inbeats, LVAL merge_flag);
/* LISP: (AUD-PUT-NOTES ANY ANY ANY ANY) */
LVAL getTimes(LVAL nameOrNumber, double start, double dur);
/* LISP: (AUD-GET-TIMES ANY ANYNUM ANYNUM) */

389
src/NyquistDialog.cpp Normal file
View File

@ -0,0 +1,389 @@
/**********************************************************************
Audacity: A Digital Audio Editor
NyquistDialog.cpp
Roger B. Dannenberg
*******************************************************************//*!
\class NyquistDialog
\brief Get nyquist command and execute
*//*******************************************************************/
#include "Audacity.h"
#include "NyquistDialog.h"
#include "../../../lib-src/libnyquist/nyquist/xlisp/xlisp.h"
#include "nyx.h"
#include <wx/setup.h> // for wxUSE_* macros
#ifdef __WXMSW__
#include <wx/ownerdrw.h>
#endif
#include <wx/defs.h>
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <wx/intl.h>
#include <wx/sizer.h>
#include <wx/statbox.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/listctrl.h>
#include <wx/radiobut.h>
#include <wx/button.h>
#include <wx/imaglist.h>
#include <wx/settings.h>
#include "Clipboard.h"
#include "Shuttle.h"
#include "ShuttleGui.h"
#include "Menus.h"
#include "Prefs.h"
#include "Project.h"
#include "ProjectFileManager.h"
#include "ProjectHistory.h"
#include "ProjectManager.h"
#include "ProjectWindow.h"
#include "SelectUtilities.h"
#include "commands/CommandManager.h"
#include "PluginManager.h"
#include "effects/Effect.h"
#include "../images/Arrow.xpm"
#include "../images/Empty9x16.xpm"
#include "UndoManager.h"
#include "AllThemeResources.h"
#include "widgets/FileDialog/FileDialog.h"
#include "FileNames.h"
#include "import/Import.h"
#include "widgets/ErrorDialog.h"
#include "widgets/AudacityMessageBox.h"
#include "widgets/HelpSystem.h"
#include "NyquistAPI.h"
#if wxUSE_ACCESSIBILITY
#include "widgets/WindowAccessible.h"
#endif
#define NyquistTitle XO("Nyquist Command")
//#define ManageMacrosTitle XO("Manage Macros")
static const wxChar* KEY_File = wxT("NyquistFile");
// Separate numerical range from the additional buttons
// in the expanded view (which start at 10,000).
#define MacrosListID 7001
#define CommandsListID 7002
#define ApplyToProjectID 7003
#define NyquistCmdID 7004
#define NyquistBrowseID 7005
#define NyquistRunID 7006
BEGIN_EVENT_TABLE(NyquistDialog, wxDialogWrapper)
EVT_BUTTON(NyquistBrowseID, NyquistDialog::OnBrowse)
EVT_BUTTON(wxID_CANCEL, NyquistDialog::OnCancel)
EVT_BUTTON(NyquistRunID, NyquistDialog::OnRunCommand)
END_EVENT_TABLE()
NyquistDialog::NyquistDialog(
wxWindow * parent, AudacityProject &project, bool bInherited):
wxDialogWrapper(parent, wxID_ANY, NyquistTitle,
wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, mProject{ project }
{
mStop = false;
mBreak = false;
mCont = false;
if( bInherited )
return;
SetLabel(NyquistTitle); // Provide visual label
SetName(NyquistTitle); // Provide audible label
Populate();
}
NyquistDialog::~NyquistDialog()
{
}
void NyquistDialog::Populate()
{
//------------------------- Main section --------------------
ShuttleGui S(this, eIsCreating);
PopulateOrExchange(S);
// ----------------------- End of main section --------------
Layout();
Fit();
wxSize sz = GetSize();
SetSizeHints( sz );
// Size and place window
SetSize(std::min(wxSystemSettings::GetMetric(wxSYS_SCREEN_X) * 3 / 4, sz.GetWidth()),
std::min(wxSystemSettings::GetMetric(wxSYS_SCREEN_Y) * 4 / 5, 400));
Center();
}
/// Defines the dialog and does data exchange with it.
void NyquistDialog::PopulateOrExchange(ShuttleGui &S)
{
PluginID id(wxT("Nyquist_Command"));
RegistryPath path(wxT("CurrentSettings"));
wxString filePath;
PluginManager::Get().GetPrivateConfig(id, path, KEY_File, filePath);
S.SetBorder(2);
S.StartStatic(XO("Nyquist Command File"));
{
S.AddSpace(1);
S.StartMultiColumn(3, wxEXPAND);
{
S.SetStretchyCol(1);
// I tried to use a validator here to transfer command file name
// to the text box, but I could not find any examples of file
// names with validators and there is no documentation on
// shuttleGui to speak of, so we'll try to recover saved parameter
// values manually.
mNyquistFile = S.AddTextBox(XXO("Location"), wxT(""), 30);
mNyquistFile->WriteText(filePath);
S.Id(NyquistBrowseID).AddButton(XXO("&Browse..."));
}
S.EndMultiColumn();
S.SetBorder(10);
S.StartHorizontalLay(wxALIGN_LEFT | wxEXPAND, false);
{
S.StartHorizontalLay(wxALIGN_LEFT, false);
{
S.Id(NyquistRunID).AddButton(XXO("Run"), wxALIGN_CENTRE, true);
/* i18n-hint verb */
S.Id(wxID_CANCEL).AddButton(XXO("Close"));
}
S.EndHorizontalLay();
}
S.EndHorizontalLay();
mNyquistOut = S.Prop(1)
.Position(wxEXPAND | wxALL)
.MinSize({ 480, 250 })
.Style(wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH)
.AddTextWindow("");
}
S.EndStatic();
}
void NyquistDialog::ApplyNyquist()
{
ShowModal();
}
/*
void NyquistDialog::OnHelp(wxCommandEvent & WXUNUSED(event))
{
wxString page = GetHelpPageName();
HelpSystem::ShowHelp(this, page, true);
}
*/
void NyquistDialog::StaticOSCallback(void* This)
{
((NyquistDialog*)This)->OSCallback();
}
void NyquistDialog::OSCallback()
{
if (mStop) {
mStop = false;
nyx_stop();
} else if (mBreak) {
mBreak = false;
nyx_break();
} else if (mCont) {
mCont = false;
nyx_continue();
}
}
void NyquistDialog::StaticOutputCallback(int c, void* This)
{
((NyquistDialog*)This)->OutputCallback(c);
}
void NyquistDialog::OutputCallback(int c)
{
// Always collect Nyquist error messages for normal plug-ins
mNyquistOut->WriteText(wxString((char) c));
std::cout << (char)c;
}
// DEBUG
const Track* getTrack0(AudacityProject &project)
{
const TrackList& trackList = TrackList::Get(project);
auto range = trackList.Leaders();
if (trackList.size() == 0) {
return NULL;
}
TrackList::const_iterator iter = range.begin();
return *iter;
}
void NyquistDialog::OnRunCommand(wxCommandEvent & WXUNUSED(event))
{
PluginID id(wxT("Nyquist_Command"));
RegistryPath path(wxT("CurrentSettings"));
wxString fileName = mNyquistFile->GetValue();
PluginManager::Get().SetPrivateConfig(id, path, KEY_File, fileName);
#ifdef WRITE_ONE_CHANNEL
// simple test: let's generate a 1kHz sine tone and write it at time 5 to 6 in mono audio Track 0
const Track* track = getTrack0(mProject);
if (track) {
track->TypeSwitch([&](const WaveTrack* waveTrack) {
auto channels = TrackList::Channels(waveTrack);
int trkchans = channels.size(); // channels->size();
double trkrate = waveTrack->GetRate();
assert(trkchans == 1);
assert(trkrate == 44100);
WaveTrack* curTrack = nullptr;
WaveTrack::Holder outputTrack;
double outputTime = 0;
int i = 0;
for (auto track : channels) {
// this must be wrong. Also, I'm not sure how to write to a track.
// I generally copied code from Nyquist.cpp (nyquist effects), but
// I don't understand why it makes copies or how deep the copies
// go (are these virtual copies of whole tracks, or just empty
// containers for new audio?)
curTrack = (WaveTrack*)(const WaveTrack*)track;
outputTrack = track->EmptyCopy();
outputTrack->SetRate(trkrate);
i++;
}
float tone[44100];
for (int i = 0; i < trkrate; i++) {
tone[i] = sin(3.14159 * 2 * 1000 * i / trkrate) * 0.1;
}
// write the audio
outputTrack->Append((samplePtr)tone, floatSample, 44100);
outputTrack->Flush();
double end_time = outputTrack->GetEndTime();
curTrack->ClearAndPaste(5, 6, outputTrack.get(),
true, true); // mRestoreSplits, bMergeClips
});
/* for (size_t i = 0; i < trkchans; i++) {
WaveTrack* out;
if (chans == trkchans) {
out = outputTrack[i].get();
} else {
out = outputTrack[0].get();
}
// See Nyquist.cpp for some code to compute mRestoreSplits and
// bMergeClips options. For now, we ignore options. Let's see
// if the behavior is reasonable, and if not we'll try something
// else.
mCurTrack[i]->ClearAndPaste(start, start + maxdur, out,
true, true); // mRestoreSplits, bMergeClips
// See Nyquist.cpp for code dealing with SyncLockGroups here
}
*/
// Well, we wrote to the project, so probably something should be
// notified. Nyquist.cpp sets mProjectChanged = true; and we're in
// an effect, but how can we find it? Should this go through globals
// in nyx.c? Which already uses some globals to negotiate between
// Audacity and Nyquist?
// mProjectChanged = true;
}
#endif
// test to see if we can just execute some Nyquist code and display text output
nyx_init();
nyx_set_os_callback(StaticOSCallback, (void*)this);
nyx_capture_output(StaticOutputCallback, (void*)this);
auto cleanup = finally([&] {
nyx_capture_output(NULL, (void*)NULL);
nyx_set_os_callback(NULL, (void*)NULL);
nyx_cleanup();
});
// reference needed when Nyquist calls into Audacity:
setNyquistProject(&mProject);
// ProjectHistory::Get(project).PushState(longDesc, shortDesc);
writeText(XO("loading "));
writeText(fileName);
writeText(" ...\n");
int status = nyx_load(fileName);
writeText(status ? XO("... load successful.") : XO("... load failed."));
//auto &window = ProjectWindow::Get( project );
// window.RedrawProject();
return;
}
void NyquistDialog::OnBrowse(wxCommandEvent& WXUNUSED(event))
{
FileNames::FileTypes fileTypes = FileNames::FileTypes({ { XO("XLisp file"), { "*.lsp"}},
{ XO("SAL file"), { "*.sal" }} });
FileDialogWrapper filePicker(
this,
XO("Choose a Nyquist program to run"), FileNames::DataDir(), wxT(""),
fileTypes);
if (filePicker.ShowModal() == wxID_CANCEL) {
return;
} else {
mNyquistFile->WriteText(filePicker.GetPath());
}
}
void NyquistDialog::OnCancel(wxCommandEvent & WXUNUSED(event))
{
Hide();
}
/////////////////////////////////////////////////////////////////////
#include <wx/textdlg.h>
#include "BatchCommandDialog.h"
enum {
AddButtonID = 10000,
RemoveButtonID,
RenameButtonID,
RestoreButtonID,
ImportButtonID,
ExportButtonID,
SaveButtonID,
DefaultsButtonID,
InsertButtonID,
EditButtonID,
DeleteButtonID,
UpButtonID,
DownButtonID,
// MacrosListID 7005
// CommandsListID, 7002
// Re-Use IDs from NyquistDialog.
ApplyToProjectButtonID = ApplyToProjectID,
};

93
src/NyquistDialog.h Normal file
View File

@ -0,0 +1,93 @@
/**********************************************************************
Audacity: A Digital Audio Editor
NyquistDialog.h
Roger B. Dannenberg
**********************************************************************/
#ifndef __AUDACITY_NYQUIST_WINDOW__
#define __AUDACITY_NYQUIST_WINDOW__
#include <wx/defs.h>
#include "BatchCommands.h"
#include "Prefs.h"
class wxWindow;
class wxTextCtrl;
class wxListCtrl;
class wxListEvent;
class wxButton;
class wxTextCtrl;
class AudacityProject;
class ShuttleGui;
class NyquistDialog : public wxDialogWrapper {
public:
// constructors and destructors
NyquistDialog(
wxWindow * parent, AudacityProject &project, bool bInherited=false);
virtual ~NyquistDialog();
public:
// Populate methods NOT virtual.
void Populate();
void PopulateOrExchange( ShuttleGui & S );
virtual void OnRunCommand(wxCommandEvent& event);
virtual void OnBrowse(wxCommandEvent& event);
virtual void OnCancel(wxCommandEvent & event);
//virtual void OnHelp(wxCommandEvent & event);
virtual wxString GetHelpPageName() {return "Apply_Macro";};
void PopulateMacros();
static CommandID MacroIdOfName( const wxString & MacroName );
void ApplyNyquist();
// Nyquist-to-Host interface
// same (for now) as Nyquist.cpp StaticOSCallback
static void StaticOSCallback(void* userdata);
void OSCallback();
bool mStop;
bool mBreak;
bool mCont;
static void StaticOutputCallback(int c, void* userdata);
void OutputCallback(int c);
wxButton *mOK;
wxButton *mCancel;
wxTextCtrl *mResults;
wxTextCtrl *mNyquistFile;
wxTextCtrl* mNyquistOut;
// It seems that Audacity always calls wxWidgets through a translation
// layer. Passing TranslatableString to a wxWidgets method is explicitly
// blocked (see comments on TranslatableString). Do we know why? I don't.
// We can't pass .Translate() to a &wxString, so this translation function
// is called to do it...
void writeText(TranslatableString ts) {
wxString s(ts.Translation());
mNyquistOut->WriteText(s);
}
void writeText(const char* ts) {
mNyquistOut->WriteText(ts);
}
/*
bool mAbort;
bool mbExpanded;
wxString mActiveMacro;
wxString mMacroBeingRenamed;
*/
protected:
const AudacityProject &mProject;
//const MacroCommandsCatalog mCatalog;
DECLARE_EVENT_TABLE()
};
#endif

View File

@ -99,6 +99,15 @@ void ReverseSamples(samplePtr dst, sampleFormat format,
}
}
// Copy samples with dithering:
// src - copy from here
// srcFormat - int16Sample, int24Sample, floatSample
// dst - copy to here
// dstFormat - int16Sample, int24Sample, floatSample
// len - number of samples to copy
// srcStride - defaults to 1 sample
// dstStride - defaults to 1 sample
void CopySamples(constSamplePtr src, sampleFormat srcFormat,
samplePtr dst, sampleFormat dstFormat,
unsigned int len,

View File

@ -26,6 +26,7 @@ effects from this one class.
#include <limits>
long mmmm = std::numeric_limits<long>::max();
#include "../../Audacity.h" // for USE_* macros
#include "../../../lib-src/libnyquist/nyquist/xlisp/xlisp.h"
#include "Nyquist.h"
@ -82,6 +83,7 @@ effects from this one class.
#include "../../widgets/ProgressDialog.h"
#include "../../widgets/FileDialog/FileDialog.h"
#include "NyquistAPI.h"
// DEBUG TODO: REMOVE THIS
#ifndef nyx_returns_start_and_end_time
@ -94,6 +96,16 @@ effects from this one class.
#include <sstream>
#include <float.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "audacityintdefs.h"
#ifdef __cplusplus
}
#endif
// DEBUG TODO: REMOVE THIS
#include <stdio.h>
#include <stdlib.h>
@ -117,7 +129,7 @@ void dprintf(const wchar_t *format, ...)
}
#endif
const AudacityProject* theNyquistProject;
int NyquistEffect::mReentryCount = 0;
enum
@ -775,7 +787,7 @@ bool NyquistEffect::Process()
(int) AllProjects{}.size());
mProps += wxString::Format(wxT("(putprop '*PROJECT* \"%s\" 'NAME)\n"), EscapeString(project->GetProjectName()));
theNyquistProject = project;
setNyquistProject(project);
int numTracks = 0;
int numWave = 0;
@ -3377,7 +3389,7 @@ void NyquistOutputDialog::OnOk(wxCommandEvent & /* event */)
// Registration of extra functions in XLisp.
#include "../../../lib-src/libnyquist/nyquist/xlisp/xlisp.h"
static LVAL gettext()
LVAL xlc_gettext()
{
auto string = UTF8CTOWX(getstring(xlgastring()));
#if !HAS_I18N_CONTEXTS
@ -3389,7 +3401,7 @@ static LVAL gettext()
return cvstring(GetCustomTranslation(string).mb_str(wxConvUTF8));
}
static LVAL gettextc()
LVAL xlc_gettextc()
{
#if HAS_I18N_CONTEXTS
auto string = UTF8CTOWX(getstring(xlgastring()));
@ -3402,7 +3414,7 @@ static LVAL gettextc()
#endif
}
static LVAL ngettext()
LVAL xlc_ngettext()
{
auto string1 = UTF8CTOWX(getstring(xlgastring()));
auto string2 = UTF8CTOWX(getstring(xlgastring()));
@ -3417,7 +3429,7 @@ static LVAL ngettext()
wxGetTranslation(string1, string2, number).mb_str(wxConvUTF8));
}
static LVAL ngettextc()
LVAL xlc_ngettextc()
{
#if HAS_I18N_CONTEXTS
auto string1 = UTF8CTOWX(getstring(xlgastring()));
@ -3484,10 +3496,10 @@ LVAL xlc_aud_do(void)
return (dst);
}
#include "nyquistapiintdefs.h"
static void RegisterFunctions()
{
#ifdef RUNTIME_DEFINE_PRIMITIVES
// Add functions to XLisp. Do this only once,
// before the first call to nyx_init.
static bool firstTime = true;
@ -3506,4 +3518,5 @@ static void RegisterFunctions()
xlbindfunctions( functions, WXSIZEOF( functions ) );
}
#endif
}

View File

@ -62,9 +62,6 @@ public:
int ticks;
};
// Here is where Nyquist gets access to the current project.
// This is set each time a Nyquist Effect is invoked.
extern const AudacityProject *theNyquistProject;
class AUDACITY_DLL_API NyquistEffect final : public Effect

View File

@ -1,518 +0,0 @@
/**********************************************************************
Audacity: A Digital Audio Editor
NyquistAPI.cpp
Roger B. Dannenberg
Apr 2021
Interface for Nyquist access to tracks and more
**********************************************************************/
#include "../../../lib-src/libnyquist/nyquist/xlisp/xlisp.h"
#include "Nyquist.h"
#include "NyquistAPI.h"
#include "../../WaveClip.h"
#include "../../LabelTrack.h"
// To access project information, effect runner finds and shares project here:
AudacityProject *audacityProject = NULL;
void setNyquistProject(AudacityProject *p)
{
audacityProject = p;
}
LVAL properties(const Track *track)
{
LVAL props, last, allClips, clips, lastClips, cl;
xlstkcheck(4);
xlsave(props);
xlsave(allClips);
xlsave(clips);
xlsave(lastClips);
if (track) {
wxString wname = track->GetName();
const char *name = wname.mb_str();
last = props = consa(xlenter("NAME"));
last = rplacd(last, consa(name ? cvstring(name) : NULL));
last = rplacd(last, consa(xlenter("TYPE")));
const char* kindString = NULL;
track->TypeSwitch([&](const WaveTrack* t) { kindString = "WAVE"; },
[&](const NoteTrack* t) { kindString = "NOTE"; },
[&](const LabelTrack* t) { kindString = "LABEL"; });
last = rplacd(last, consa(kindString ? xlenter(kindString) : NULL));
track->TypeSwitch([&](const WaveTrack* waveTrack) {
auto channels = TrackList::Channels(waveTrack); // track->GetOwner();
int nchans = channels.size(); // channels->size();
last = rplacd(last, consa(xlenter("CHANNELS")));
last = rplacd(last, consa(cvfixnum(nchans)));
// compute the clips list as property "CLIPS":
if (nchans > 1) {
allClips = newvector(nchans);
}
int channel_index = 0;
for (auto channel : channels) {
clips = NULL;
lastClips = NULL;
auto ca = channel->SortedClipArray();
for (size_t j = 0; j < ca.size(); j++) {
if (j < 1000) {
auto caj = ca[j]->GetStartTime();
cl = cons(cvflonum(ca[j]->GetStartTime()),
consa(cvflonum(ca[j]->GetEndTime())));
cl = consa(cl); // ((start end))
if (!clips) {
clips = cl;
}
}
else if (j == 1000) {
cl = consa(NULL); // (nil)
}
else {
break;
}
if (lastClips) {
lastClips = rplacd(lastClips, cl);
}
else {
lastClips = clips = cl;
}
}
if (allClips) {
setelement(allClips, channel_index++, clips);
}
}
if (!allClips) { // only one channel
allClips = clips;
}
last = rplacd(last, consa(xlenter("CLIPS")));
last = rplacd(last, consa(allClips));
});
}
xlpopn(4);
return props;
}
const Track *getTrackByNumber(FIXTYPE n)
{
const TrackList &trackList = TrackList::Get(*theNyquistProject);
auto range = trackList.Leaders();
if (n < 0 || n >= trackList.size()) {
return NULL;
}
TrackList::const_iterator iter = range.begin();
for (int i = 0; i < n; i++) iter++; // advance to nth track
return *iter;
}
const Track *getTrackByName(const char *name)
{
const TrackList& trackList = TrackList::Get(*theNyquistProject);
auto range = trackList.Leaders();
for (auto ptrack : range) {
wxString wname = ptrack->GetName();
const char* trackName = wname.mb_str();
if (strcmp(name, trackName) == 0) {
return ptrack;
}
}
return NULL;
}
const Track *getTrack(LVAL nameOrNumber)
{
if (fixp(nameOrNumber)) {
return getTrackByNumber(getfixnum(nameOrNumber));
} else if (stringp(nameOrNumber)) {
return getTrackByName((const char *)getstring(nameOrNumber));
}
return NULL;
}
LVAL getTrackInfo(LVAL nameOrNumber)
{
const Track* track = getTrack(nameOrNumber);
return properties(track);
}
// getAudio() creates a nyx_susp object to a source of samples
// This object only has a pointer to denote the state, but we
// need a buffer and other state, so we create a new class.
//
// A WaveTrackReader is allocated for each channel. When the effect
// completes, we need to delete the WaveTrackReaders, so keep them
// on a vector. If a sound terminates, we'll keep the WaveTrackReader
// anyway until the end of the effect.
//
// TODO: I think Nyquist effects can retain state and therefore sounds
// from one run to the next. Nyx should tie into the GC and keep
// backpointers to nyx_susp objects, changing their callbacks to
// return zero if they are invoked after the end of an effect. There is
// no problem retaining a sound as long as it is no longer "lazy" and
// will not call back into Audacity looking for samples.
class WaveTrackReader;
static std::vector<WaveTrackReader*> waveTrackReaders;
class WaveTrackReader
{
public:
SampleBuffer mCurBuffer;
sampleCount mCurBufferStart;
size_t mCurBufferLen;
const WaveTrack *mCurTrack;
WaveTrackReader(const WaveTrack *track) {
mCurTrack = track;
mCurBufferStart = 0;
mCurBufferLen = 0;
// save all created objects and free them with NyquistAPICleanup()
// after the effect has completed:
waveTrackReaders.push_back(this);
}
static int StaticGetCallback(float* buffer, int channel,
int64_t start, int64_t len, int64_t totlen, void *userdata) {
WaveTrackReader *wtr = (WaveTrackReader *)userdata;
return wtr->GetCallback(buffer, channel, start, len, totlen);
}
// note: duplicate code from Nyquist.cpp (NyquistEffect)
int GetCallback(float *buffer, int WXUNUSED(ch),
int64_t start, int64_t len, int64_t WXUNUSED(totlen))
{
if (mCurBuffer.ptr()) {
if (start < mCurBufferStart ||
start + len > mCurBufferStart + mCurBufferLen) {
mCurBuffer.Free();
}
}
if (!mCurBuffer.ptr()) {
mCurBufferStart = start;
mCurBufferLen = mCurTrack->GetBestBlockSize(mCurBufferStart);
if (mCurBufferLen < (size_t)len) {
mCurBufferLen = ((WaveTrack *)mCurTrack)->GetIdealBlockSize();
}
// We don't have an end time (length) for the track
// mCurBufferLen =
// limitSampleBufferSize(mCurBufferLen, mCurLen - mCurBufferStart);
mCurBuffer.Allocate(mCurBufferLen, floatSample);
// We're inside the Effects's try block, so none here?
// here's the action: pull samples from track
// try {
mCurTrack->Get(
mCurBuffer.ptr(), floatSample,
mCurBufferStart, mCurBufferLen);
/* }
catch (...) {
// Save the exception object for re-throw when out of the library
mpException = std::current_exception();
return -1;
} */
}
// We have guaranteed above that this is nonnegative and bounded by
// mCurBufferLen:
auto offset = (start - mCurBufferStart).as_size_t();
CopySamples(mCurBuffer.ptr() + offset * SAMPLE_SIZE(floatSample), floatSample,
(samplePtr)buffer, floatSample,
len);
return 0;
}
};
int64_t getTrackLen(const WaveTrack *waveTrack)
{
auto channels = TrackList::Channels(waveTrack); // track->GetOwner();
double srate = waveTrack->GetRate();
int64_t len = 0;
for (auto channel : channels) {
auto ca = channel->SortedClipArray();
int n = ca.size();
if (n > 0) {
auto clip = ca[n - 1]; // last clip
int64_t clipLen = (int64_t)(clip->GetEndTime() * srate + 0.5);
if (clipLen > len) len = clipLen;
}
}
return len;
}
LVAL getAudio(LVAL nameOrNumber, double start, double dur)
{
LVAL snd;
xlsave1(snd);
const Track* track = getTrack(nameOrNumber);
if (track) {
track->TypeSwitch([&](const WaveTrack* waveTrack) {
auto channels = TrackList::Channels(waveTrack); // track->GetOwner();
int nchans = channels.size(); // channels->size();
double srate = waveTrack->GetRate();
int64_t len = (int64_t)(dur * srate + 0.5);
if (nchans > 1) {
snd = newvector(nchans);
int ch = 0;
for (auto channel : channels) {
WaveTrackReader* wtr = new WaveTrackReader(channel);
setelement(snd, ch, nyx_make_input_audio(
WaveTrackReader::StaticGetCallback, wtr, 1,
len, waveTrack->GetRate()));
}
}
else {
WaveTrackReader* wtr = new WaveTrackReader(waveTrack);
snd = nyx_make_input_audio(
WaveTrackReader::StaticGetCallback, wtr, 1,
len, waveTrack->GetRate());
}
});
}
// if not a WaveTrack, fall through, return NULL
xlpop();
return snd;
}
class WaveTrackWriter
{
public:
const char *error;
WaveTrack* mCurTrack[2];
WaveTrack* mOutputTrack[2];
double mOutputTime;
double srate; // all channels must match
// trkchans is the number of channels in the output track
// chans in the number of channels in snd
WaveTrackWriter(TrackIterRange<const WaveTrack>& channels, LVAL snd,
double rate, int trkchans, int chans,
double start, double maxdur) {
error = NULL;
mCurTrack[0] = mCurTrack[1] = nullptr;
mOutputTrack[0] = mOutputTrack[1] = nullptr;
mOutputTime = 0;
srate = rate;
std::shared_ptr<WaveTrack> outputTrack[2];
// for now, require every output channel to have the same sample rate
// the first channel already matches the Nyquist sound sample rate
for (auto track : channels) {
if (track->GetRate() != srate) {
error = "Error: mismatched sample rates.";
return;
}
}
int i = 0;
for (auto track : channels) {
// this must be wrong. Also, I'm not sure how to write to a track.
// I generally copied code from Nyquist.cpp (nyquist effects), but
// I don't understand why it makes copies or how deep the copies
// go (are these virtual copies of whole tracks, or just empty
// containers for new audio?)
mCurTrack[i] = (WaveTrack *) (const WaveTrack *) track;
outputTrack[i] = track->EmptyCopy();
outputTrack[i]->SetRate(srate);
i++;
}
{ // I suppose this is some kind of trick to clean up and restore
// tracks if track writing raises an exception.
auto vr0 = valueRestorer(mOutputTrack[0], outputTrack[0].get());
auto vr1 = valueRestorer(mOutputTrack[1], outputTrack[1].get());
if (!nyx_pump_audio(snd, (int64_t)(maxdur / srate + 0.5),
&StaticPutCallback, (void*)this)) {
error = "Error: Something went wrong in evaluating expression.";
}
}
for (int i = 0; i < trkchans; i++) {
outputTrack[i]->Flush();
mOutputTime = outputTrack[i]->GetEndTime();
if (mOutputTime <= 0) {
error = "Nyquist returned nil audio.\n";
return;
}
}
for (size_t i = 0; i < trkchans; i++) {
WaveTrack* out;
if (chans == trkchans) {
out = outputTrack[i].get();
} else {
out = outputTrack[0].get();
}
// See Nyquist.cpp for some code to compute mRestoreSplits and
// bMergeClips options. For now, we ignore options. Let's see
// if the behavior is reasonable, and if not we'll try something
// else.
mCurTrack[i]->ClearAndPaste(start, start + maxdur, out,
true, true); // mRestoreSplits, bMergeClips
// See Nyquist.cpp for code dealing with SyncLockGroups here
}
// Well, we wrote to the project, so probably something should be
// notified. Nyquist.cpp sets mProjectChanged = true; and we're in
// an effect, but how can we find it? Should this go through globals
// in nyx.c? Which already uses some globals to negotiate between
// Audacity and Nyquist?
// mProjectChanged = true;
}
static int StaticPutCallback(float* buffer, int channel,
int64_t start, int64_t len, int64_t totlen, void* userdata) {
WaveTrackWriter* wtw = (WaveTrackWriter*)userdata;
return wtw->PutCallback(buffer, channel, start, len, totlen);
}
int PutCallback(float* buffer, int channel,
int64_t start, int64_t len, int64_t totlen) {
// Don't let C++ exceptions propagate through the Nyquist library
return GuardedCall<int>([&] {
mOutputTrack[channel]->Append((samplePtr)buffer, floatSample, len);
return 0; // success
}, MakeSimpleGuard(-1)); // translate all exceptions into failure
}
};
LVAL putAudio(LVAL nameOrNumber, LVAL expr, double start, double maxdur)
{
LVAL snd;
double srate;
const Track* track;
xlsave1(snd);
snd = xleval(expr);
int nchans = nyx_get_num_channels(snd);
if (nchans < 1) {
snd = cvstring("AUD_PUT_AUDIO did not get a sound.");
goto exit;
}
srate = nyx_get_sample_rate(snd);
track = getTrack(nameOrNumber);
if (track) {
track->TypeSwitch([&](const WaveTrack* waveTrack) {
auto channels = TrackList::Channels(waveTrack);
int trkchans = channels.size(); // channels->size();
double trkrate = waveTrack->GetRate();
// int64_t len = (int64_t)(maxdur * trkrate + 0.5);
// allow mono expanded to N trkchans, otherwise require a match
if (nchans > 1 && trkchans != nchans) {
snd = cvstring("Error: channel count mismatch.");
} else if (nchans == 0) {
snd = cvstring(
"Error: sound expression did not return a sound.");
} else if (nchans == -1) {
snd = cvstring(
"Error: sound expression returned array with one sound.");
} else if (trkrate != srate) {
snd = cvstring("Error: sample rate mismatch.");
} else {
WaveTrackWriter waveTrackWriter(channels, snd, trkrate,
trkchans, nchans, start, maxdur);
if (waveTrackWriter.error) {
snd = cvstring(waveTrackWriter.error);
} else {
snd = s_true;
}
}
});
}
exit:
xlpop();
return snd;
}
LVAL getLabels(LVAL nameOrNumber, double start, double dur)
{
LVAL labelList, elem;
xlsave1(labelList);
xlsave1(elem); // each sublist ((T0 T1 title))
LVAL last = NULL; // last element in labelList
const Track* track = getTrack(nameOrNumber);
if (track) {
track->TypeSwitch([&](const LabelTrack* labelTrack) {
// int n = labelTrack->GetNumLabels();
for (const auto& label : labelTrack->GetLabels()) {
if (label.getT0() >= start &&
label.getT0() < start + dur) {
elem = consa(cvstring(label.title));
elem = cons(cvflonum(label.getT1()), elem);
elem = cons(cvflonum(label.getT0()), elem);
elem = consa(elem); // ((T0 T1 title))
if (last) {
rplacd(last, elem);
}
else {
labelList = last = elem;
}
}
}
});
}
// if not a LabelTrack, fall through, return NULL
xlpopn(2);
return labelList;
}
// WARNING: FUNCTIONS BELOW NEED IMPLEMENTATIONS AFTER WE GET AUDIO WORKING!
LVAL putLabels(LVAL nameOrNumber, LVAL labels, LVAL merge_flag)
{
return NULL;
}
LVAL getNotes(LVAL nameOrNumber, double start, double dur)
{
return NULL;
}
LVAL putNotes(LVAL nameOrNumber, LVAL notes, LVAL merge_flag)
{
return NULL;
}
LVAL getTimes(LVAL nameOrNumber, double start, double dur)
{
return NULL;
}
LVAL putTimes(LVAL nameOrNumber, LVAL breakpoints)
{
return NULL;
}
// call this when effect has completed
void NyquistAPICleanup()
{
waveTrackReaders.clear();
}

View File

@ -0,0 +1,5 @@
LVAL xlc_aud_do(void);
LVAL xlc_gettext(void);
LVAL xlc_gettextc(void);
LVAL xlc_ngettext(void);
LVAL xlc_ngettextc(void);

View File

@ -4,6 +4,7 @@
#include "stdlib.h"
#endif
#include "../../../lib-src/libnyquist/nyquist/xlisp/xlisp.h"
#include "NyquistAPI.h"
extern LVAL s_true;
#define cvboolean(i) ((i) ? s_true : NIL)
@ -15,7 +16,14 @@ extern LVAL s_true;
extern LVAL RSLT_sym;
#include "nyquistapi.h"
#ifdef __cplusplus
extern "C" {
#endif
#include "nyquistapiintdefs.h"
#ifdef __cplusplus
}
#endif
/* xlc_aud_get_track_info -- interface to C routine getTrackInfo */
/**/
@ -98,10 +106,11 @@ LVAL xlc_aud_get_notes(void)
LVAL arg1 = xlgetarg();
double arg2 = testarg2(xlgaanynum());
double arg3 = testarg2(xlgaanynum());
LVAL arg4 = xlgetarg();
LVAL result;
xllastarg();
result = getNotes(arg1, arg2, arg3);
result = getNotes(arg1, arg2, arg3, arg4);
return (result);
}
@ -113,10 +122,11 @@ LVAL xlc_aud_put_notes(void)
LVAL arg1 = xlgetarg();
LVAL arg2 = xlgetarg();
LVAL arg3 = xlgetarg();
LVAL arg4 = xlgetarg();
LVAL result;
xllastarg();
result = putNotes(arg1, arg2, arg3);
result = putNotes(arg1, arg2, arg3, arg4);
return (result);
}

View File

@ -3,6 +3,7 @@
#include "../AudioIO.h"
#include "../BatchProcessDialog.h"
#include "../NyquistDialog.h"
#include "../Benchmark.h"
#include "../CommonCommandFlags.h"
#include "../Menus.h"
@ -630,6 +631,29 @@ void OnApplyMacroDirectlyByName(const CommandContext& context, const MacroID& Na
}
void OnNyquist(const CommandContext& context)
{
auto& project = context.project;
auto& window = ProjectWindow::Get(project);
NyquistDialog dlg(&window, project);
dlg.ApplyNyquist();
MenuManager::ModifyUndoMenuItems(project);
TranslatableString desc;
EffectManager& em = EffectManager::Get();
auto& undoManager = UndoManager::Get(project);
auto& commandManager = CommandManager::Get(project);
int cur = undoManager.GetCurrentState();
if (undoManager.UndoAvailable()) {
undoManager.GetShortDescription(cur, &desc);
commandManager.Modify(wxT("RepeatLastTool"), XXO("&Repeat Nyquist"));
auto& menuManager = MenuManager::Get(project);
menuManager.mLastTool = wxT("Nyquist");
menuManager.mLastToolRegistration = MenuCreator::repeattypeapplymacro;
}
}
void OnAudacityCommand(const CommandContext & ctx)
{
// using GET in a log message for devs' eyes only
@ -1040,27 +1064,26 @@ BaseItemSharedPtr ToolsMenu()
#endif
Section( "RepeatLast",
// Delayed evaluation:
[](AudacityProject &project)
{
const auto &lastTool = MenuManager::Get(project).mLastTool;
TranslatableString buildMenuLabel;
if (!lastTool.empty())
buildMenuLabel = XO("Repeat %s")
.Format( EffectManager::Get().GetCommandName(lastTool) );
else
buildMenuLabel = XO("Repeat Last Tool");
// Delayed evaluation:
[](AudacityProject &project)
{
const auto &lastTool = MenuManager::Get(project).mLastTool;
TranslatableString buildMenuLabel;
if (!lastTool.empty())
buildMenuLabel = XO("Repeat %s")
.Format( EffectManager::Get().GetCommandName(lastTool) );
else
buildMenuLabel = XO("Repeat Last Tool");
return Command( wxT("RepeatLastTool"), buildMenuLabel,
FN(OnRepeatLastTool),
AudioIONotBusyFlag() |
HasLastToolFlag(),
Options{}.IsGlobal(), findCommandHandler );
}
),
return Command(wxT("RepeatLastTool"), buildMenuLabel,
FN(OnRepeatLastTool),
AudioIONotBusyFlag() | HasLastToolFlag(),
Options{}.IsGlobal(), findCommandHandler);
}
),
Command( wxT("ManageMacros"), XXO("&Macros..."),
FN(OnManageMacros), AudioIONotBusyFlag() ),
Command( wxT("ManageMacros"), XXO("&Macros..."),
FN(OnManageMacros), AudioIONotBusyFlag() ),
Menu( wxT("Macros"), XXO("&Apply Macro"),
// Palette has no access key to ensure first letter navigation of
@ -1075,57 +1098,59 @@ BaseItemSharedPtr ToolsMenu()
[](AudacityProject&)
{ return Items( wxEmptyString, PopulateMacrosMenu( AudioIONotBusyFlag() ) ); }
)
)
),
),
Section( "Other",
Command( wxT("ConfigReset"), XXO("Reset &Configuration"),
FN(OnResetConfig),
AudioIONotBusyFlag() ),
Command( wxT("Nyquist"), XXO("&Nyquist..."),
FN(OnNyquist), AudioIONotBusyFlag() ),
Command( wxT("FancyScreenshot"), XXO("&Screenshot..."),
FN(OnScreenshot), AudioIONotBusyFlag() ),
Section( "Other",
Command( wxT("ConfigReset"), XXO("Reset &Configuration"),
FN(OnResetConfig),
AudioIONotBusyFlag() ),
Command( wxT("FancyScreenshot"), XXO("&Screenshot..."),
FN(OnScreenshot), AudioIONotBusyFlag() ),
// PRL: team consensus for 2.2.0 was, we let end users have this diagnostic,
// as they used to in 1.3.x
//#ifdef IS_ALPHA
// TODO: What should we do here? Make benchmark a plug-in?
// Easy enough to do. We'd call it mod-self-test.
Command( wxT("Benchmark"), XXO("&Run Benchmark..."),
FN(OnBenchmark), AudioIONotBusyFlag() )
// TODO: What should we do here? Make benchmark a plug-in?
// Easy enough to do. We'd call it mod-self-test.
Command( wxT("Benchmark"), XXO("&Run Benchmark..."),
FN(OnBenchmark), AudioIONotBusyFlag() )
//#endif
),
),
Section( "Tools",
// Delayed evaluation:
[](AudacityProject&)
{ return Items( wxEmptyString, PopulateEffectsMenu(
EffectTypeTool,
AudioIONotBusyFlag(),
AudioIONotBusyFlag() )
); }
)
Section( "Tools",
// Delayed evaluation:
[](AudacityProject&)
{ return Items( wxEmptyString, PopulateEffectsMenu(
EffectTypeTool,
AudioIONotBusyFlag(),
AudioIONotBusyFlag() )
); }
)
#ifdef IS_ALPHA
,
Section( "",
Command( wxT("SimulateRecordingErrors"),
XXO("Simulate Recording Errors"),
FN(OnSimulateRecordingErrors),
AudioIONotBusyFlag(),
Options{}.CheckTest(
[](AudacityProject&){
return AudioIO::Get()->mSimulateRecordingErrors; } ) ),
Command( wxT("DetectUpstreamDropouts"),
XXO("Detect Upstream Dropouts"),
FN(OnDetectUpstreamDropouts),
AudioIONotBusyFlag(),
Options{}.CheckTest(
[](AudacityProject&){
return AudioIO::Get()->mDetectUpstreamDropouts; } ) )
)
,
Section( "",
Command( wxT("SimulateRecordingErrors"),
XXO("Simulate Recording Errors"),
FN(OnSimulateRecordingErrors),
AudioIONotBusyFlag(),
Options{}.CheckTest(
[](AudacityProject&){
return AudioIO::Get()->mSimulateRecordingErrors; } ) ),
Command( wxT("DetectUpstreamDropouts"),
XXO("Detect Upstream Dropouts"),
FN(OnDetectUpstreamDropouts),
AudioIONotBusyFlag(),
Options{}.CheckTest(
[](AudacityProject&){
return AudioIO::Get()->mDetectUpstreamDropouts; } ) )
)
#endif
) ) };
) ) ) };
return menu;
}