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:
parent
bbc3ef44ce
commit
596be2b50e
@ -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}
|
||||
)
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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':
|
||||
|
@ -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));
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
926
src/NyquistAPI.cpp
Normal 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, ¶ms->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(¬eList, last, event, SNOTE, STRUE);
|
||||
} else if (event->is_update()) {
|
||||
last = appendUpdate(¬eList, 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();
|
||||
}
|
||||
|
@ -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
389
src/NyquistDialog.cpp
Normal 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
93
src/NyquistDialog.h
Normal 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
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
5
src/effects/nyquist/audacityintdefs.h
Normal file
5
src/effects/nyquist/audacityintdefs.h
Normal 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);
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user