mirror of
https://github.com/cookiengineer/audacity
synced 2025-07-09 17:07:43 +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
|
${TARGET_ROOT}/nyquist/xlisp
|
||||||
$<$<BOOL:${UNIX}>:${TARGET_ROOT}/nyquist/sys/unix>
|
$<$<BOOL:${UNIX}>:${TARGET_ROOT}/nyquist/sys/unix>
|
||||||
$<$<NOT:$<BOOL:${UNIX}>>:${TARGET_ROOT}/nyquist/sys/win/msvc>
|
$<$<NOT:$<BOOL:${UNIX}>>:${TARGET_ROOT}/nyquist/sys/win/msvc>
|
||||||
|
${TARGET_ROOT}/../../src/effects/nyquist
|
||||||
PUBLIC
|
PUBLIC
|
||||||
${TARGET_ROOT}
|
${TARGET_ROOT}
|
||||||
)
|
)
|
||||||
|
@ -5,6 +5,11 @@
|
|||||||
#include "sndfnintdefs.h"
|
#include "sndfnintdefs.h"
|
||||||
#include "seqfnintdefs.h"
|
#include "seqfnintdefs.h"
|
||||||
|
|
||||||
|
/* from Nyquist.cpp */
|
||||||
|
#include "audacityintdefs.h"
|
||||||
|
|
||||||
|
#include "nyquistapiintdefs.h"
|
||||||
|
|
||||||
/* from sndsliders.c */
|
/* from sndsliders.c */
|
||||||
|
|
||||||
LVAL xslider_read(void);
|
LVAL xslider_read(void);
|
||||||
|
@ -7,3 +7,9 @@
|
|||||||
/* extension to xlisp */
|
/* extension to xlisp */
|
||||||
#include "sndfnintptrs.h"
|
#include "sndfnintptrs.h"
|
||||||
#include "seqfnintptrs.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
|
#endif
|
||||||
|
|
||||||
/* the function table */
|
/* the function table */
|
||||||
FUNDEF init_funtab[] = {
|
FUNDEF funtab[] = {
|
||||||
|
|
||||||
/* read macro functions */
|
/* read macro functions */
|
||||||
{ NULL, S, rmhash }, /* 0 */
|
{ 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;
|
FUNDEF *funtab = init_funtab;
|
||||||
static size_t szfuntab = sizeof(init_funtab) / sizeof(*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 */
|
/* To do: deallocate funtab when XLisp runtime shuts down */
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/* xnotimp does not return anything on purpose, so disable
|
/* xnotimp does not return anything on purpose, so disable
|
||||||
* "no return value" warning
|
* "no return value" warning
|
||||||
|
@ -182,7 +182,7 @@ int xlisave(const char *fname)
|
|||||||
/* xlirestore - restore a saved memory image */
|
/* xlirestore - restore a saved memory image */
|
||||||
int xlirestore(const char *fname)
|
int xlirestore(const char *fname)
|
||||||
{
|
{
|
||||||
extern FUNDEF *funtab;
|
extern FUNDEF funtab[];
|
||||||
char fullname[STRMAX+1];
|
char fullname[STRMAX+1];
|
||||||
unsigned char *cp;
|
unsigned char *cp;
|
||||||
int n,i,max,type;
|
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 a_vector,a_closure,a_char,a_ustream,a_extern;
|
||||||
extern LVAL s_gcflag,s_gchook;
|
extern LVAL s_gcflag,s_gchook;
|
||||||
extern LVAL s_search_path;
|
extern LVAL s_search_path;
|
||||||
extern FUNDEF *funtab;
|
extern FUNDEF funtab[];
|
||||||
|
|
||||||
/* forward declarations */
|
/* forward declarations */
|
||||||
FORWARD LOCAL void initwks(void);
|
FORWARD LOCAL void initwks(void);
|
||||||
|
@ -88,7 +88,7 @@ void xladdivar(LVAL cls, const char *var)
|
|||||||
/* xladdmsg - add a message to a class */
|
/* xladdmsg - add a message to a class */
|
||||||
void xladdmsg(LVAL cls, const char *msg, int offset)
|
void xladdmsg(LVAL cls, const char *msg, int offset)
|
||||||
{
|
{
|
||||||
extern FUNDEF *funtab;
|
extern FUNDEF funtab[];
|
||||||
LVAL mptr;
|
LVAL mptr;
|
||||||
|
|
||||||
/* enter the message selector */
|
/* enter the message selector */
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
/* external variables */
|
/* external variables */
|
||||||
extern LVAL s_printcase,k_downcase,k_const,k_nmacro;
|
extern LVAL s_printcase,k_downcase,k_const,k_nmacro;
|
||||||
extern LVAL s_ifmt,s_ffmt;
|
extern LVAL s_ifmt,s_ffmt;
|
||||||
extern FUNDEF *funtab;
|
extern FUNDEF funtab[];
|
||||||
extern char buf[];
|
extern char buf[];
|
||||||
|
|
||||||
LOCAL void putsymbol(LVAL fptr, char *str, int escflag);
|
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)
|
for (p = getstring(str); (ch = *p) != '\0'; ++p)
|
||||||
|
|
||||||
/* check for a control character */
|
/* check for a control character */
|
||||||
if (ch < 040 || ch == '\\' || ch > 0176 /* || ch == '"' */) {
|
if (ch < 040 || ch == '\\' || ch > 0176 || ch == '"') {
|
||||||
xlputc(fptr,'\\');
|
xlputc(fptr,'\\');
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case '\011':
|
case '\011':
|
||||||
|
@ -932,7 +932,7 @@ int xlisnumber(char *str, LVAL *pval)
|
|||||||
/* defmacro - define a read macro */
|
/* defmacro - define a read macro */
|
||||||
void defmacro(int ch, LVAL type, int offset)
|
void defmacro(int ch, LVAL type, int offset)
|
||||||
{
|
{
|
||||||
extern FUNDEF *funtab;
|
extern FUNDEF funtab[];
|
||||||
LVAL subr;
|
LVAL subr;
|
||||||
subr = cvsubr(funtab[offset].fd_subr,funtab[offset].fd_type,offset);
|
subr = cvsubr(funtab[offset].fd_subr,funtab[offset].fd_type,offset);
|
||||||
setelement(getvalue(s_rtable),ch,cons(type,subr));
|
setelement(getvalue(s_rtable),ch,cons(type,subr));
|
||||||
|
@ -538,7 +538,7 @@ void nyx_cleanup()
|
|||||||
free(nyx_audio_name);
|
free(nyx_audio_name);
|
||||||
nyx_audio_name = NULL;
|
nyx_audio_name = NULL;
|
||||||
}
|
}
|
||||||
NyquistAPICleanup();
|
nyquistAPICleanup();
|
||||||
#if defined(NYX_MEMORY_STATS) && NYX_MEMORY_STATS
|
#if defined(NYX_MEMORY_STATS) && NYX_MEMORY_STATS
|
||||||
printf("\nnyx_cleanup\n");
|
printf("\nnyx_cleanup\n");
|
||||||
xmem();
|
xmem();
|
||||||
@ -851,6 +851,11 @@ nyx_rval nyx_compute_type(LVAL expr)
|
|||||||
return ntype;
|
return ntype;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int nyx_load(const char* filename)
|
||||||
|
{
|
||||||
|
return xlload(filename, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
nyx_rval nyx_eval_expression(const char *expr_string)
|
nyx_rval nyx_eval_expression(const char *expr_string)
|
||||||
{
|
{
|
||||||
LVAL expr = NULL;
|
LVAL expr = NULL;
|
||||||
@ -1035,8 +1040,8 @@ finish:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// assumes sound is protected from garbage collection
|
// assumes input_len >= 0
|
||||||
int nyx_pump_audio(LVAL sound, int64_t input_len,
|
int nyx_pump_audio(LVAL sound, double maxdur,
|
||||||
nyx_audio_callback callback, void *userdata)
|
nyx_audio_callback callback, void *userdata)
|
||||||
{
|
{
|
||||||
#define nyx_result do not access this global in nyx_pump_audio
|
#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 result = 0;
|
||||||
int num_channels;
|
int num_channels;
|
||||||
int ch;
|
int ch;
|
||||||
|
int64_t input_len; // in samples
|
||||||
|
|
||||||
// Any variable whose value is set between the _setjmp() and the "finish" label
|
// 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
|
// 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
|
// at this point, input sounds which were referenced by symbol S
|
||||||
// (or nyx_get_audio_name()) could be referenced by sound, but
|
// (or nyx_get_audio_name()) could be referenced by sound, but
|
||||||
// S is now bound to NIL. sound is a protected (garbage
|
// 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
|
// To unify single and multi-channel sounds, we'll create an array
|
||||||
// of one element for single-channel sounds.
|
// 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) {
|
if (num_channels == 1) {
|
||||||
LVAL array = newvector(1);
|
LVAL array = newvector(1);
|
||||||
setelement(array, 0, sound);
|
setelement(array, 0, sound);
|
||||||
sound = array;
|
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++) {
|
for (ch = 0; ch < num_channels; ch++) {
|
||||||
if (ch > 0) { // no need to copy first channel
|
if (ch > 0) { // no need to copy first channel
|
||||||
setelement(sound, ch,
|
setelement(sound, ch,
|
||||||
@ -1140,7 +1140,7 @@ int nyx_pump_audio(LVAL sound, int64_t input_len,
|
|||||||
bool terminated = true;
|
bool terminated = true;
|
||||||
// how many samples to compute before calling callback:
|
// how many samples to compute before calling callback:
|
||||||
int64_t togo = max_sample_block_len;
|
int64_t togo = max_sample_block_len;
|
||||||
if (input_len > 0 && total + togo > input_len) {
|
if (total + togo > input_len) {
|
||||||
togo = input_len - total;
|
togo = input_len - total;
|
||||||
}
|
}
|
||||||
for (ch = 0; ch < num_channels; ch++) {
|
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
|
sound = NULL; // unreference sound array so GC can free it
|
||||||
|
xlpop(); // unprotect sound
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
|
|
||||||
@ -1330,6 +1331,11 @@ void nyx_continue()
|
|||||||
xlcontinue();
|
xlcontinue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void nyx_xlerror(const char *emsg)
|
||||||
|
{
|
||||||
|
xlerror(emsg, s_unbound);
|
||||||
|
}
|
||||||
|
|
||||||
int ostgetc()
|
int ostgetc()
|
||||||
{
|
{
|
||||||
if (nyx_expr_pos < nyx_expr_len) {
|
if (nyx_expr_pos < nyx_expr_len) {
|
||||||
|
@ -34,7 +34,7 @@ extern "C"
|
|||||||
// and all the XLISP declarations, whereas NyquistAPICleanup() is only
|
// and all the XLISP declarations, whereas NyquistAPICleanup() is only
|
||||||
// needed by Nyquist.cpp for Audacity effects as are other functions
|
// needed by Nyquist.cpp for Audacity effects as are other functions
|
||||||
// here in nyx.h.
|
// here in nyx.h.
|
||||||
void NyquistAPICleanup();
|
void nyquistAPICleanup(void);
|
||||||
void nyx_set_xlisp_path(const char *path);
|
void nyx_set_xlisp_path(const char *path);
|
||||||
|
|
||||||
/* should return return 0 for success, -1 for error */
|
/* should return return 0 for success, -1 for error */
|
||||||
@ -60,6 +60,7 @@ extern "C"
|
|||||||
void nyx_stop();
|
void nyx_stop();
|
||||||
void nyx_break();
|
void nyx_break();
|
||||||
void nyx_continue();
|
void nyx_continue();
|
||||||
|
void nyx_xlerror(const char* emsg);
|
||||||
|
|
||||||
void nyx_set_audio_params(double rate, int64_t len);
|
void nyx_set_audio_params(double rate, int64_t len);
|
||||||
|
|
||||||
@ -68,9 +69,15 @@ extern "C"
|
|||||||
int num_channels,
|
int num_channels,
|
||||||
int64_t len, double rate);
|
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();
|
char *nyx_get_audio_name();
|
||||||
void nyx_set_audio_name(const char *name);
|
void nyx_set_audio_name(const char *name);
|
||||||
|
|
||||||
|
int nyx_load(const char* filename);
|
||||||
nyx_rval nyx_eval_expression(const char *expr);
|
nyx_rval nyx_eval_expression(const char *expr);
|
||||||
|
|
||||||
/** @brief Get the number of channels in the Nyquist audio object
|
/** @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_num_channels(LVAL snd);
|
||||||
int nyx_get_audio(nyx_audio_callback callback,
|
int nyx_get_audio(nyx_audio_callback callback,
|
||||||
void* userdata);
|
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);
|
nyx_audio_callback callback, void* userdata);
|
||||||
|
|
||||||
int nyx_get_int();
|
int nyx_get_int();
|
||||||
|
@ -186,6 +186,10 @@ list( APPEND SOURCES
|
|||||||
NoteTrack.cpp
|
NoteTrack.cpp
|
||||||
NoteTrack.h
|
NoteTrack.h
|
||||||
NumberScale.h
|
NumberScale.h
|
||||||
|
NyquistDialog.cpp
|
||||||
|
NyquistDialog.h
|
||||||
|
NyquistAPI.cpp
|
||||||
|
NyquistAPI.h
|
||||||
PitchName.cpp
|
PitchName.cpp
|
||||||
PitchName.h
|
PitchName.h
|
||||||
PlatformCompatibility.cpp
|
PlatformCompatibility.cpp
|
||||||
@ -569,8 +573,6 @@ list( APPEND SOURCES
|
|||||||
effects/nyquist/LoadNyquist.h
|
effects/nyquist/LoadNyquist.h
|
||||||
effects/nyquist/Nyquist.cpp
|
effects/nyquist/Nyquist.cpp
|
||||||
effects/nyquist/Nyquist.h
|
effects/nyquist/Nyquist.h
|
||||||
effects/nyquist/NyquistAPI.cpp
|
|
||||||
effects/nyquist/NyquistAPI.h
|
|
||||||
effects/nyquist/nyquistapiint.cpp
|
effects/nyquist/nyquistapiint.cpp
|
||||||
effects/nyquist/nyquistapiintdefs.h
|
effects/nyquist/nyquistapiintdefs.h
|
||||||
effects/nyquist/nyquistapiintptrs.h
|
effects/nyquist/nyquistapiintptrs.h
|
||||||
|
@ -37,6 +37,7 @@ public:
|
|||||||
/// 'source' variable, the destination sample in the 'dest' variable,
|
/// 'source' variable, the destination sample in the 'dest' variable,
|
||||||
/// and hints to the formats of the samples. Even if the sample formats
|
/// and hints to the formats of the samples. Even if the sample formats
|
||||||
/// are the same, samples are clipped, if necessary.
|
/// are the same, samples are clipped, if necessary.
|
||||||
|
/// (see definition for parameter descriptions)
|
||||||
void Apply(DitherType ditherType,
|
void Apply(DitherType ditherType,
|
||||||
constSamplePtr source, sampleFormat sourceFormat,
|
constSamplePtr source, sampleFormat sourceFormat,
|
||||||
samplePtr dest, sampleFormat destFormat,
|
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
|
// (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
|
// 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.
|
// E.g. if there are two stereo tracks, they are tracks 0 and 1.
|
||||||
@ -60,6 +61,7 @@
|
|||||||
// id is name or number
|
// id is name or number
|
||||||
// start is the project time for the start of a region (beats or seconds)
|
// start is the project time for the start of a region (beats or seconds)
|
||||||
// dur is the duration for 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
|
// returns a list of events with time >= start and time < start + dur
|
||||||
// each event in the returned list has one of two formats:
|
// each event in the returned list has one of two formats:
|
||||||
// (NOTE key time channel pitch loudness duration
|
// (NOTE key time channel pitch loudness duration
|
||||||
@ -72,20 +74,26 @@
|
|||||||
// duration is event duration in beats or seconds (FLONUM)
|
// duration is event duration in beats or seconds (FLONUM)
|
||||||
// each parameter (optional) is a keyword followed by a value.
|
// each parameter (optional) is a keyword followed by a value.
|
||||||
// The keyword prepends a colon (":") to the Allegro attribute
|
// 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].
|
// 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)
|
// (UPDATE key time channel parameter)
|
||||||
// key is an event identifier, normally MIDI key number (FIXNUM)
|
// key is an event identifier, normally MIDI key number (FIXNUM)
|
||||||
// time is the start time (beats or seconds) (FLONUM)
|
// time is the start time (beats or seconds) (FLONUM)
|
||||||
// channel is the (usually MIDI) channel number (FIXNUM)
|
// channel is the (usually MIDI) channel number (FIXNUM)
|
||||||
// parameter is an attribute keyword followed by value as in NOTE.
|
// 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
|
// id is name or number
|
||||||
// notes is an event list in the format returned by AUD-GET-NOTES
|
// 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.
|
// if merge is NULL, replace all events in the track. Otherwise, merge.
|
||||||
//
|
//
|
||||||
// (AUD-GET-TIMES id start dur)
|
// (AUD-GET-TIMES id start dur)
|
||||||
// id is name or number
|
// id is name or number
|
||||||
// start is project time for start of region (FLONUM)
|
// start is project time for start of region (FLONUM)
|
||||||
@ -103,7 +111,7 @@
|
|||||||
#ifndef __AUDACITY_NYQUISTAPI__
|
#ifndef __AUDACITY_NYQUISTAPI__
|
||||||
#define __AUDACITY_NYQUISTAPI__
|
#define __AUDACITY_NYQUISTAPI__
|
||||||
|
|
||||||
void NyquistAPICleanup();
|
void setNyquistProject(const AudacityProject* p);
|
||||||
|
|
||||||
LVAL getTrackInfo(LVAL nameOrNumber);
|
LVAL getTrackInfo(LVAL nameOrNumber);
|
||||||
/* LISP: (AUD-GET-TRACK-INFO ANY) */
|
/* 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);
|
LVAL putLabels(LVAL nameOrNumber, LVAL labels, LVAL merge_flag);
|
||||||
/* LISP: (AUD-PUT-LABELS ANY ANY ANY) */
|
/* LISP: (AUD-PUT-LABELS ANY ANY ANY) */
|
||||||
|
|
||||||
LVAL getNotes(LVAL nameOrNumber, double start, double dur);
|
LVAL getNotes(LVAL nameOrNumber, double start, double dur, LVAL inbeats);
|
||||||
/* LISP: (AUD-GET-NOTES ANY ANYNUM ANYNUM) */
|
/* LISP: (AUD-GET-NOTES ANY ANYNUM ANYNUM ANY) */
|
||||||
|
|
||||||
LVAL putNotes(LVAL nameOrNumber, LVAL notes, LVAL merge_flag);
|
LVAL putNotes(LVAL nameOrNumber, LVAL notes, LVAL inbeats, LVAL merge_flag);
|
||||||
/* LISP: (AUD-PUT-NOTES ANY ANY ANY) */
|
/* LISP: (AUD-PUT-NOTES ANY ANY ANY ANY) */
|
||||||
|
|
||||||
LVAL getTimes(LVAL nameOrNumber, double start, double dur);
|
LVAL getTimes(LVAL nameOrNumber, double start, double dur);
|
||||||
/* LISP: (AUD-GET-TIMES ANY ANYNUM ANYNUM) */
|
/* 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,
|
void CopySamples(constSamplePtr src, sampleFormat srcFormat,
|
||||||
samplePtr dst, sampleFormat dstFormat,
|
samplePtr dst, sampleFormat dstFormat,
|
||||||
unsigned int len,
|
unsigned int len,
|
||||||
|
@ -26,6 +26,7 @@ effects from this one class.
|
|||||||
|
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
long mmmm = std::numeric_limits<long>::max();
|
||||||
#include "../../Audacity.h" // for USE_* macros
|
#include "../../Audacity.h" // for USE_* macros
|
||||||
#include "../../../lib-src/libnyquist/nyquist/xlisp/xlisp.h"
|
#include "../../../lib-src/libnyquist/nyquist/xlisp/xlisp.h"
|
||||||
#include "Nyquist.h"
|
#include "Nyquist.h"
|
||||||
@ -82,6 +83,7 @@ effects from this one class.
|
|||||||
#include "../../widgets/ProgressDialog.h"
|
#include "../../widgets/ProgressDialog.h"
|
||||||
|
|
||||||
#include "../../widgets/FileDialog/FileDialog.h"
|
#include "../../widgets/FileDialog/FileDialog.h"
|
||||||
|
#include "NyquistAPI.h"
|
||||||
|
|
||||||
// DEBUG TODO: REMOVE THIS
|
// DEBUG TODO: REMOVE THIS
|
||||||
#ifndef nyx_returns_start_and_end_time
|
#ifndef nyx_returns_start_and_end_time
|
||||||
@ -94,6 +96,16 @@ effects from this one class.
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
#include "audacityintdefs.h"
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// DEBUG TODO: REMOVE THIS
|
// DEBUG TODO: REMOVE THIS
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@ -117,7 +129,7 @@ void dprintf(const wchar_t *format, ...)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const AudacityProject* theNyquistProject;
|
|
||||||
int NyquistEffect::mReentryCount = 0;
|
int NyquistEffect::mReentryCount = 0;
|
||||||
|
|
||||||
enum
|
enum
|
||||||
@ -775,7 +787,7 @@ bool NyquistEffect::Process()
|
|||||||
(int) AllProjects{}.size());
|
(int) AllProjects{}.size());
|
||||||
mProps += wxString::Format(wxT("(putprop '*PROJECT* \"%s\" 'NAME)\n"), EscapeString(project->GetProjectName()));
|
mProps += wxString::Format(wxT("(putprop '*PROJECT* \"%s\" 'NAME)\n"), EscapeString(project->GetProjectName()));
|
||||||
|
|
||||||
theNyquistProject = project;
|
setNyquistProject(project);
|
||||||
|
|
||||||
int numTracks = 0;
|
int numTracks = 0;
|
||||||
int numWave = 0;
|
int numWave = 0;
|
||||||
@ -3377,7 +3389,7 @@ void NyquistOutputDialog::OnOk(wxCommandEvent & /* event */)
|
|||||||
// Registration of extra functions in XLisp.
|
// Registration of extra functions in XLisp.
|
||||||
#include "../../../lib-src/libnyquist/nyquist/xlisp/xlisp.h"
|
#include "../../../lib-src/libnyquist/nyquist/xlisp/xlisp.h"
|
||||||
|
|
||||||
static LVAL gettext()
|
LVAL xlc_gettext()
|
||||||
{
|
{
|
||||||
auto string = UTF8CTOWX(getstring(xlgastring()));
|
auto string = UTF8CTOWX(getstring(xlgastring()));
|
||||||
#if !HAS_I18N_CONTEXTS
|
#if !HAS_I18N_CONTEXTS
|
||||||
@ -3389,7 +3401,7 @@ static LVAL gettext()
|
|||||||
return cvstring(GetCustomTranslation(string).mb_str(wxConvUTF8));
|
return cvstring(GetCustomTranslation(string).mb_str(wxConvUTF8));
|
||||||
}
|
}
|
||||||
|
|
||||||
static LVAL gettextc()
|
LVAL xlc_gettextc()
|
||||||
{
|
{
|
||||||
#if HAS_I18N_CONTEXTS
|
#if HAS_I18N_CONTEXTS
|
||||||
auto string = UTF8CTOWX(getstring(xlgastring()));
|
auto string = UTF8CTOWX(getstring(xlgastring()));
|
||||||
@ -3402,7 +3414,7 @@ static LVAL gettextc()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static LVAL ngettext()
|
LVAL xlc_ngettext()
|
||||||
{
|
{
|
||||||
auto string1 = UTF8CTOWX(getstring(xlgastring()));
|
auto string1 = UTF8CTOWX(getstring(xlgastring()));
|
||||||
auto string2 = UTF8CTOWX(getstring(xlgastring()));
|
auto string2 = UTF8CTOWX(getstring(xlgastring()));
|
||||||
@ -3417,7 +3429,7 @@ static LVAL ngettext()
|
|||||||
wxGetTranslation(string1, string2, number).mb_str(wxConvUTF8));
|
wxGetTranslation(string1, string2, number).mb_str(wxConvUTF8));
|
||||||
}
|
}
|
||||||
|
|
||||||
static LVAL ngettextc()
|
LVAL xlc_ngettextc()
|
||||||
{
|
{
|
||||||
#if HAS_I18N_CONTEXTS
|
#if HAS_I18N_CONTEXTS
|
||||||
auto string1 = UTF8CTOWX(getstring(xlgastring()));
|
auto string1 = UTF8CTOWX(getstring(xlgastring()));
|
||||||
@ -3484,10 +3496,10 @@ LVAL xlc_aud_do(void)
|
|||||||
return (dst);
|
return (dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "nyquistapiintdefs.h"
|
|
||||||
|
|
||||||
static void RegisterFunctions()
|
static void RegisterFunctions()
|
||||||
{
|
{
|
||||||
|
#ifdef RUNTIME_DEFINE_PRIMITIVES
|
||||||
// Add functions to XLisp. Do this only once,
|
// Add functions to XLisp. Do this only once,
|
||||||
// before the first call to nyx_init.
|
// before the first call to nyx_init.
|
||||||
static bool firstTime = true;
|
static bool firstTime = true;
|
||||||
@ -3506,4 +3518,5 @@ static void RegisterFunctions()
|
|||||||
|
|
||||||
xlbindfunctions( functions, WXSIZEOF( functions ) );
|
xlbindfunctions( functions, WXSIZEOF( functions ) );
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -62,9 +62,6 @@ public:
|
|||||||
int ticks;
|
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
|
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"
|
#include "stdlib.h"
|
||||||
#endif
|
#endif
|
||||||
#include "../../../lib-src/libnyquist/nyquist/xlisp/xlisp.h"
|
#include "../../../lib-src/libnyquist/nyquist/xlisp/xlisp.h"
|
||||||
|
#include "NyquistAPI.h"
|
||||||
|
|
||||||
extern LVAL s_true;
|
extern LVAL s_true;
|
||||||
#define cvboolean(i) ((i) ? s_true : NIL)
|
#define cvboolean(i) ((i) ? s_true : NIL)
|
||||||
@ -15,7 +16,14 @@ extern LVAL s_true;
|
|||||||
|
|
||||||
extern LVAL RSLT_sym;
|
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 */
|
/* xlc_aud_get_track_info -- interface to C routine getTrackInfo */
|
||||||
/**/
|
/**/
|
||||||
@ -98,10 +106,11 @@ LVAL xlc_aud_get_notes(void)
|
|||||||
LVAL arg1 = xlgetarg();
|
LVAL arg1 = xlgetarg();
|
||||||
double arg2 = testarg2(xlgaanynum());
|
double arg2 = testarg2(xlgaanynum());
|
||||||
double arg3 = testarg2(xlgaanynum());
|
double arg3 = testarg2(xlgaanynum());
|
||||||
|
LVAL arg4 = xlgetarg();
|
||||||
LVAL result;
|
LVAL result;
|
||||||
|
|
||||||
xllastarg();
|
xllastarg();
|
||||||
result = getNotes(arg1, arg2, arg3);
|
result = getNotes(arg1, arg2, arg3, arg4);
|
||||||
return (result);
|
return (result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,10 +122,11 @@ LVAL xlc_aud_put_notes(void)
|
|||||||
LVAL arg1 = xlgetarg();
|
LVAL arg1 = xlgetarg();
|
||||||
LVAL arg2 = xlgetarg();
|
LVAL arg2 = xlgetarg();
|
||||||
LVAL arg3 = xlgetarg();
|
LVAL arg3 = xlgetarg();
|
||||||
|
LVAL arg4 = xlgetarg();
|
||||||
LVAL result;
|
LVAL result;
|
||||||
|
|
||||||
xllastarg();
|
xllastarg();
|
||||||
result = putNotes(arg1, arg2, arg3);
|
result = putNotes(arg1, arg2, arg3, arg4);
|
||||||
return (result);
|
return (result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "../AudioIO.h"
|
#include "../AudioIO.h"
|
||||||
#include "../BatchProcessDialog.h"
|
#include "../BatchProcessDialog.h"
|
||||||
|
#include "../NyquistDialog.h"
|
||||||
#include "../Benchmark.h"
|
#include "../Benchmark.h"
|
||||||
#include "../CommonCommandFlags.h"
|
#include "../CommonCommandFlags.h"
|
||||||
#include "../Menus.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)
|
void OnAudacityCommand(const CommandContext & ctx)
|
||||||
{
|
{
|
||||||
// using GET in a log message for devs' eyes only
|
// using GET in a log message for devs' eyes only
|
||||||
@ -1040,27 +1064,26 @@ BaseItemSharedPtr ToolsMenu()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
Section( "RepeatLast",
|
Section( "RepeatLast",
|
||||||
// Delayed evaluation:
|
// Delayed evaluation:
|
||||||
[](AudacityProject &project)
|
[](AudacityProject &project)
|
||||||
{
|
{
|
||||||
const auto &lastTool = MenuManager::Get(project).mLastTool;
|
const auto &lastTool = MenuManager::Get(project).mLastTool;
|
||||||
TranslatableString buildMenuLabel;
|
TranslatableString buildMenuLabel;
|
||||||
if (!lastTool.empty())
|
if (!lastTool.empty())
|
||||||
buildMenuLabel = XO("Repeat %s")
|
buildMenuLabel = XO("Repeat %s")
|
||||||
.Format( EffectManager::Get().GetCommandName(lastTool) );
|
.Format( EffectManager::Get().GetCommandName(lastTool) );
|
||||||
else
|
else
|
||||||
buildMenuLabel = XO("Repeat Last Tool");
|
buildMenuLabel = XO("Repeat Last Tool");
|
||||||
|
|
||||||
return Command( wxT("RepeatLastTool"), buildMenuLabel,
|
return Command(wxT("RepeatLastTool"), buildMenuLabel,
|
||||||
FN(OnRepeatLastTool),
|
FN(OnRepeatLastTool),
|
||||||
AudioIONotBusyFlag() |
|
AudioIONotBusyFlag() | HasLastToolFlag(),
|
||||||
HasLastToolFlag(),
|
Options{}.IsGlobal(), findCommandHandler);
|
||||||
Options{}.IsGlobal(), findCommandHandler );
|
}
|
||||||
}
|
),
|
||||||
),
|
|
||||||
|
|
||||||
Command( wxT("ManageMacros"), XXO("&Macros..."),
|
Command( wxT("ManageMacros"), XXO("&Macros..."),
|
||||||
FN(OnManageMacros), AudioIONotBusyFlag() ),
|
FN(OnManageMacros), AudioIONotBusyFlag() ),
|
||||||
|
|
||||||
Menu( wxT("Macros"), XXO("&Apply Macro"),
|
Menu( wxT("Macros"), XXO("&Apply Macro"),
|
||||||
// Palette has no access key to ensure first letter navigation of
|
// Palette has no access key to ensure first letter navigation of
|
||||||
@ -1075,57 +1098,59 @@ BaseItemSharedPtr ToolsMenu()
|
|||||||
[](AudacityProject&)
|
[](AudacityProject&)
|
||||||
{ return Items( wxEmptyString, PopulateMacrosMenu( AudioIONotBusyFlag() ) ); }
|
{ return Items( wxEmptyString, PopulateMacrosMenu( AudioIONotBusyFlag() ) ); }
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
),
|
|
||||||
|
|
||||||
Section( "Other",
|
Command( wxT("Nyquist"), XXO("&Nyquist..."),
|
||||||
Command( wxT("ConfigReset"), XXO("Reset &Configuration"),
|
FN(OnNyquist), AudioIONotBusyFlag() ),
|
||||||
FN(OnResetConfig),
|
|
||||||
AudioIONotBusyFlag() ),
|
|
||||||
|
|
||||||
Command( wxT("FancyScreenshot"), XXO("&Screenshot..."),
|
Section( "Other",
|
||||||
FN(OnScreenshot), AudioIONotBusyFlag() ),
|
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,
|
// PRL: team consensus for 2.2.0 was, we let end users have this diagnostic,
|
||||||
// as they used to in 1.3.x
|
// as they used to in 1.3.x
|
||||||
//#ifdef IS_ALPHA
|
//#ifdef IS_ALPHA
|
||||||
// TODO: What should we do here? Make benchmark a plug-in?
|
// TODO: What should we do here? Make benchmark a plug-in?
|
||||||
// Easy enough to do. We'd call it mod-self-test.
|
// Easy enough to do. We'd call it mod-self-test.
|
||||||
Command( wxT("Benchmark"), XXO("&Run Benchmark..."),
|
Command( wxT("Benchmark"), XXO("&Run Benchmark..."),
|
||||||
FN(OnBenchmark), AudioIONotBusyFlag() )
|
FN(OnBenchmark), AudioIONotBusyFlag() )
|
||||||
//#endif
|
//#endif
|
||||||
),
|
),
|
||||||
|
|
||||||
Section( "Tools",
|
Section( "Tools",
|
||||||
// Delayed evaluation:
|
// Delayed evaluation:
|
||||||
[](AudacityProject&)
|
[](AudacityProject&)
|
||||||
{ return Items( wxEmptyString, PopulateEffectsMenu(
|
{ return Items( wxEmptyString, PopulateEffectsMenu(
|
||||||
EffectTypeTool,
|
EffectTypeTool,
|
||||||
AudioIONotBusyFlag(),
|
AudioIONotBusyFlag(),
|
||||||
AudioIONotBusyFlag() )
|
AudioIONotBusyFlag() )
|
||||||
); }
|
); }
|
||||||
)
|
)
|
||||||
|
|
||||||
#ifdef IS_ALPHA
|
#ifdef IS_ALPHA
|
||||||
,
|
,
|
||||||
Section( "",
|
Section( "",
|
||||||
Command( wxT("SimulateRecordingErrors"),
|
Command( wxT("SimulateRecordingErrors"),
|
||||||
XXO("Simulate Recording Errors"),
|
XXO("Simulate Recording Errors"),
|
||||||
FN(OnSimulateRecordingErrors),
|
FN(OnSimulateRecordingErrors),
|
||||||
AudioIONotBusyFlag(),
|
AudioIONotBusyFlag(),
|
||||||
Options{}.CheckTest(
|
Options{}.CheckTest(
|
||||||
[](AudacityProject&){
|
[](AudacityProject&){
|
||||||
return AudioIO::Get()->mSimulateRecordingErrors; } ) ),
|
return AudioIO::Get()->mSimulateRecordingErrors; } ) ),
|
||||||
Command( wxT("DetectUpstreamDropouts"),
|
Command( wxT("DetectUpstreamDropouts"),
|
||||||
XXO("Detect Upstream Dropouts"),
|
XXO("Detect Upstream Dropouts"),
|
||||||
FN(OnDetectUpstreamDropouts),
|
FN(OnDetectUpstreamDropouts),
|
||||||
AudioIONotBusyFlag(),
|
AudioIONotBusyFlag(),
|
||||||
Options{}.CheckTest(
|
Options{}.CheckTest(
|
||||||
[](AudacityProject&){
|
[](AudacityProject&){
|
||||||
return AudioIO::Get()->mDetectUpstreamDropouts; } ) )
|
return AudioIO::Get()->mDetectUpstreamDropouts; } ) )
|
||||||
)
|
)
|
||||||
#endif
|
#endif
|
||||||
) ) };
|
) ) ) };
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user