2017-06-09 06:39:37 +00:00
|
|
|
/* Extended Module Player
|
|
|
|
* Copyright (C) 1996-2016 Claudio Matsuoka and Hipolito Carraro Jr
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
|
|
* to deal in the Software without restriction, including without limitation
|
|
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
* THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
|
|
|
#include "format.h"
|
|
|
|
#include "virtual.h"
|
|
|
|
#include "mixer.h"
|
|
|
|
|
|
|
|
const char *xmp_version = XMP_VERSION;
|
|
|
|
const unsigned int xmp_vercode = XMP_VERCODE;
|
|
|
|
|
|
|
|
xmp_context xmp_create_context()
|
|
|
|
{
|
|
|
|
struct context_data *ctx;
|
|
|
|
|
2017-06-09 06:39:59 +00:00
|
|
|
ctx = (struct context_data *)calloc(1, sizeof(struct context_data));
|
2017-06-09 06:39:37 +00:00
|
|
|
if (ctx == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->state = XMP_STATE_UNLOADED;
|
|
|
|
ctx->m.defpan = 100;
|
|
|
|
ctx->s.numvoc = SMIX_NUMVOC;
|
|
|
|
|
|
|
|
return (xmp_context)ctx;
|
|
|
|
}
|
|
|
|
|
|
|
|
void xmp_free_context(xmp_context opaque)
|
|
|
|
{
|
|
|
|
struct context_data *ctx = (struct context_data *)opaque;
|
|
|
|
|
|
|
|
if (ctx->state > XMP_STATE_UNLOADED)
|
|
|
|
xmp_release_module(opaque);
|
|
|
|
|
|
|
|
free(opaque);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void set_position(struct context_data *ctx, int pos, int dir)
|
|
|
|
{
|
|
|
|
struct player_data *p = &ctx->p;
|
|
|
|
struct module_data *m = &ctx->m;
|
|
|
|
struct xmp_module *mod = &m->mod;
|
|
|
|
struct flow_control *f = &p->flow;
|
|
|
|
int seq;
|
|
|
|
int has_marker;
|
|
|
|
|
|
|
|
/* If dir is 0, we can jump to a different sequence */
|
|
|
|
if (dir == 0) {
|
|
|
|
seq = libxmp_get_sequence(ctx, pos);
|
|
|
|
} else {
|
|
|
|
seq = p->sequence;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (seq == 0xff) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
has_marker = HAS_QUIRK(QUIRK_MARKER);
|
|
|
|
|
|
|
|
if (seq >= 0) {
|
|
|
|
int start = m->seq_data[seq].entry_point;
|
|
|
|
|
|
|
|
p->sequence = seq;
|
|
|
|
|
|
|
|
if (pos >= 0) {
|
|
|
|
int pat;
|
|
|
|
|
|
|
|
while (has_marker && mod->xxo[pos] == 0xfe) {
|
|
|
|
if (dir < 0) {
|
|
|
|
if (pos > start) {
|
|
|
|
pos--;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pat = mod->xxo[pos];
|
|
|
|
|
|
|
|
if (pat < mod->pat) {
|
|
|
|
if (has_marker && pat == 0xff) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pos > p->scan[seq].ord) {
|
|
|
|
f->end_point = 0;
|
|
|
|
} else {
|
|
|
|
f->num_rows = mod->xxp[pat]->rows;
|
|
|
|
f->end_point = p->scan[seq].num;
|
|
|
|
f->jumpline = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pos < mod->len) {
|
|
|
|
if (pos == 0) {
|
|
|
|
p->pos = -1;
|
|
|
|
} else {
|
|
|
|
p->pos = pos;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int xmp_next_position(xmp_context opaque)
|
|
|
|
{
|
|
|
|
struct context_data *ctx = (struct context_data *)opaque;
|
|
|
|
struct player_data *p = &ctx->p;
|
|
|
|
struct module_data *m = &ctx->m;
|
|
|
|
|
|
|
|
if (ctx->state < XMP_STATE_PLAYING)
|
|
|
|
return -XMP_ERROR_STATE;
|
|
|
|
|
|
|
|
if (p->pos < m->mod.len)
|
|
|
|
set_position(ctx, p->pos + 1, 1);
|
|
|
|
|
|
|
|
return p->pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
int xmp_prev_position(xmp_context opaque)
|
|
|
|
{
|
|
|
|
struct context_data *ctx = (struct context_data *)opaque;
|
|
|
|
struct player_data *p = &ctx->p;
|
|
|
|
struct module_data *m = &ctx->m;
|
|
|
|
|
|
|
|
if (ctx->state < XMP_STATE_PLAYING)
|
|
|
|
return -XMP_ERROR_STATE;
|
|
|
|
|
|
|
|
if (p->pos == m->seq_data[p->sequence].entry_point) {
|
|
|
|
set_position(ctx, -1, -1);
|
|
|
|
} else if (p->pos > m->seq_data[p->sequence].entry_point) {
|
|
|
|
set_position(ctx, p->pos - 1, -1);
|
|
|
|
}
|
|
|
|
return p->pos < 0 ? 0 : p->pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
int xmp_set_position(xmp_context opaque, int pos)
|
|
|
|
{
|
|
|
|
struct context_data *ctx = (struct context_data *)opaque;
|
|
|
|
struct player_data *p = &ctx->p;
|
|
|
|
struct module_data *m = &ctx->m;
|
|
|
|
|
|
|
|
if (ctx->state < XMP_STATE_PLAYING)
|
|
|
|
return -XMP_ERROR_STATE;
|
|
|
|
|
|
|
|
if (pos >= m->mod.len)
|
|
|
|
return -XMP_ERROR_INVALID;
|
|
|
|
|
|
|
|
set_position(ctx, pos, 0);
|
|
|
|
|
|
|
|
return p->pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
void xmp_stop_module(xmp_context opaque)
|
|
|
|
{
|
|
|
|
struct context_data *ctx = (struct context_data *)opaque;
|
|
|
|
struct player_data *p = &ctx->p;
|
|
|
|
|
|
|
|
if (ctx->state < XMP_STATE_PLAYING)
|
|
|
|
return;
|
|
|
|
|
|
|
|
p->pos = -2;
|
|
|
|
}
|
|
|
|
|
|
|
|
void xmp_restart_module(xmp_context opaque)
|
|
|
|
{
|
|
|
|
struct context_data *ctx = (struct context_data *)opaque;
|
|
|
|
struct player_data *p = &ctx->p;
|
|
|
|
|
|
|
|
if (ctx->state < XMP_STATE_PLAYING)
|
|
|
|
return;
|
|
|
|
|
|
|
|
p->loop_count = 0;
|
|
|
|
p->pos = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int xmp_seek_time(xmp_context opaque, int time)
|
|
|
|
{
|
|
|
|
struct context_data *ctx = (struct context_data *)opaque;
|
|
|
|
struct player_data *p = &ctx->p;
|
|
|
|
struct module_data *m = &ctx->m;
|
|
|
|
int i, t;
|
|
|
|
|
|
|
|
if (ctx->state < XMP_STATE_PLAYING)
|
|
|
|
return -XMP_ERROR_STATE;
|
|
|
|
|
|
|
|
for (i = m->mod.len - 1; i >= 0; i--) {
|
|
|
|
int pat = m->mod.xxo[i];
|
|
|
|
if (pat >= m->mod.pat) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (libxmp_get_sequence(ctx, i) != p->sequence) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
t = m->xxo_info[i].time;
|
|
|
|
if (time >= t) {
|
|
|
|
set_position(ctx, i, 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i < 0) {
|
|
|
|
xmp_set_position(opaque, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return p->pos < 0 ? 0 : p->pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
int xmp_channel_mute(xmp_context opaque, int chn, int status)
|
|
|
|
{
|
|
|
|
struct context_data *ctx = (struct context_data *)opaque;
|
|
|
|
struct player_data *p = &ctx->p;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (ctx->state < XMP_STATE_PLAYING)
|
|
|
|
return -XMP_ERROR_STATE;
|
|
|
|
|
|
|
|
if (chn < 0 || chn >= XMP_MAX_CHANNELS) {
|
|
|
|
return -XMP_ERROR_INVALID;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = p->channel_mute[chn];
|
|
|
|
|
|
|
|
if (status >= 2) {
|
|
|
|
p->channel_mute[chn] = !p->channel_mute[chn];
|
|
|
|
} else if (status >= 0) {
|
|
|
|
p->channel_mute[chn] = status;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int xmp_channel_vol(xmp_context opaque, int chn, int vol)
|
|
|
|
{
|
|
|
|
struct context_data *ctx = (struct context_data *)opaque;
|
|
|
|
struct player_data *p = &ctx->p;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (ctx->state < XMP_STATE_PLAYING)
|
|
|
|
return -XMP_ERROR_STATE;
|
|
|
|
|
|
|
|
if (chn < 0 || chn >= XMP_MAX_CHANNELS) {
|
|
|
|
return -XMP_ERROR_INVALID;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = p->channel_vol[chn];
|
|
|
|
|
|
|
|
if (vol >= 0 && vol <= 100) {
|
|
|
|
p->channel_vol[chn] = vol;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef USE_VERSIONED_SYMBOLS
|
|
|
|
EXPORT extern int xmp_set_player_v40__(xmp_context, int, int);
|
|
|
|
EXPORT extern int xmp_set_player_v41__(xmp_context, int, int)
|
|
|
|
__attribute__((alias("xmp_set_player_v40__")));
|
|
|
|
EXPORT extern int xmp_set_player_v43__(xmp_context, int, int)
|
|
|
|
__attribute__((alias("xmp_set_player_v40__")));
|
|
|
|
EXPORT extern int xmp_set_player_v44__(xmp_context, int, int)
|
|
|
|
__attribute__((alias("xmp_set_player_v40__")));
|
|
|
|
|
|
|
|
asm(".symver xmp_set_player_v40__, xmp_set_player@XMP_4.0");
|
|
|
|
asm(".symver xmp_set_player_v41__, xmp_set_player@XMP_4.1");
|
|
|
|
asm(".symver xmp_set_player_v43__, xmp_set_player@XMP_4.3");
|
|
|
|
asm(".symver xmp_set_player_v44__, xmp_set_player@@XMP_4.4");
|
|
|
|
|
|
|
|
#define xmp_set_player__ xmp_set_player_v40__
|
|
|
|
#else
|
|
|
|
#define xmp_set_player__ xmp_set_player
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int xmp_set_player__(xmp_context opaque, int parm, int val)
|
|
|
|
{
|
|
|
|
struct context_data *ctx = (struct context_data *)opaque;
|
|
|
|
struct player_data *p = &ctx->p;
|
|
|
|
struct module_data *m = &ctx->m;
|
|
|
|
struct mixer_data *s = &ctx->s;
|
|
|
|
int ret = -XMP_ERROR_INVALID;
|
|
|
|
|
|
|
|
|
|
|
|
if (parm == XMP_PLAYER_SMPCTL || parm == XMP_PLAYER_DEFPAN) {
|
|
|
|
/* these should be set before loading the module */
|
|
|
|
if (ctx->state >= XMP_STATE_LOADED) {
|
|
|
|
return -XMP_ERROR_STATE;
|
|
|
|
}
|
|
|
|
} else if (parm == XMP_PLAYER_VOICES) {
|
|
|
|
/* these should be set before start playing */
|
|
|
|
if (ctx->state >= XMP_STATE_PLAYING) {
|
|
|
|
return -XMP_ERROR_STATE;
|
|
|
|
}
|
|
|
|
} else if (ctx->state < XMP_STATE_PLAYING) {
|
|
|
|
return -XMP_ERROR_STATE;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (parm) {
|
|
|
|
case XMP_PLAYER_AMP:
|
|
|
|
if (val >= 0 && val <= 3) {
|
|
|
|
s->amplify = val;
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_MIX:
|
|
|
|
if (val >= -100 && val <= 100) {
|
|
|
|
s->mix = val;
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_INTERP:
|
|
|
|
if (val >= XMP_INTERP_NEAREST && val <= XMP_INTERP_SPLINE) {
|
|
|
|
s->interp = val;
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_DSP:
|
|
|
|
s->dsp = val;
|
|
|
|
ret = 0;
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_FLAGS: {
|
|
|
|
p->player_flags = val;
|
|
|
|
ret = 0;
|
|
|
|
break; }
|
|
|
|
|
|
|
|
/* 4.1 */
|
|
|
|
case XMP_PLAYER_CFLAGS: {
|
|
|
|
int vblank = p->flags & XMP_FLAGS_VBLANK;
|
|
|
|
p->flags = val;
|
|
|
|
if (vblank != (p->flags & XMP_FLAGS_VBLANK))
|
|
|
|
libxmp_scan_sequences(ctx);
|
|
|
|
ret = 0;
|
|
|
|
break; }
|
|
|
|
case XMP_PLAYER_SMPCTL:
|
|
|
|
m->smpctl = val;
|
|
|
|
ret = 0;
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_VOLUME:
|
|
|
|
if (val >= 0 && val <= 200) {
|
|
|
|
p->master_vol = val;
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_SMIX_VOLUME:
|
|
|
|
if (val >= 0 && val <= 200) {
|
|
|
|
p->smix_vol = val;
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* 4.3 */
|
|
|
|
case XMP_PLAYER_DEFPAN:
|
|
|
|
if (val >= 0 && val <= 100) {
|
|
|
|
m->defpan = val;
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* 4.4 */
|
|
|
|
case XMP_PLAYER_MODE:
|
|
|
|
p->mode = val;
|
|
|
|
libxmp_set_player_mode(ctx);
|
|
|
|
libxmp_scan_sequences(ctx);
|
|
|
|
ret = 0;
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_VOICES:
|
|
|
|
s->numvoc = val;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef USE_VERSIONED_SYMBOLS
|
|
|
|
EXPORT extern int xmp_get_player_v40__(xmp_context, int);
|
|
|
|
EXPORT extern int xmp_get_player_v41__(xmp_context, int)
|
|
|
|
__attribute__((alias("xmp_get_player_v40__")));
|
|
|
|
EXPORT extern int xmp_get_player_v42__(xmp_context, int)
|
|
|
|
__attribute__((alias("xmp_get_player_v40__")));
|
|
|
|
EXPORT extern int xmp_get_player_v43__(xmp_context, int)
|
|
|
|
__attribute__((alias("xmp_get_player_v40__")));
|
|
|
|
EXPORT extern int xmp_get_player_v44__(xmp_context, int)
|
|
|
|
__attribute__((alias("xmp_get_player_v40__")));
|
|
|
|
|
|
|
|
asm(".symver xmp_get_player_v40__, xmp_get_player@XMP_4.0");
|
|
|
|
asm(".symver xmp_get_player_v41__, xmp_get_player@XMP_4.1");
|
|
|
|
asm(".symver xmp_get_player_v42__, xmp_get_player@XMP_4.2");
|
|
|
|
asm(".symver xmp_get_player_v43__, xmp_get_player@XMP_4.3");
|
|
|
|
asm(".symver xmp_get_player_v44__, xmp_get_player@@XMP_4.4");
|
|
|
|
|
|
|
|
#define xmp_get_player__ xmp_get_player_v40__
|
|
|
|
#else
|
|
|
|
#define xmp_get_player__ xmp_get_player
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int xmp_get_player__(xmp_context opaque, int parm)
|
|
|
|
{
|
|
|
|
struct context_data *ctx = (struct context_data *)opaque;
|
|
|
|
struct player_data *p = &ctx->p;
|
|
|
|
struct module_data *m = &ctx->m;
|
|
|
|
struct mixer_data *s = &ctx->s;
|
|
|
|
int ret = -XMP_ERROR_INVALID;
|
|
|
|
|
|
|
|
if (parm == XMP_PLAYER_SMPCTL || parm == XMP_PLAYER_DEFPAN) {
|
|
|
|
// can read these at any time
|
|
|
|
} else if (parm != XMP_PLAYER_STATE && ctx->state < XMP_STATE_PLAYING) {
|
|
|
|
return -XMP_ERROR_STATE;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (parm) {
|
|
|
|
case XMP_PLAYER_AMP:
|
|
|
|
ret = s->amplify;
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_MIX:
|
|
|
|
ret = s->mix;
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_INTERP:
|
|
|
|
ret = s->interp;
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_DSP:
|
|
|
|
ret = s->dsp;
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_FLAGS:
|
|
|
|
ret = p->player_flags;
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* 4.1 */
|
|
|
|
case XMP_PLAYER_CFLAGS:
|
|
|
|
ret = p->flags;
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_SMPCTL:
|
|
|
|
ret = m->smpctl;
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_VOLUME:
|
|
|
|
ret = p->master_vol;
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_SMIX_VOLUME:
|
|
|
|
ret = p->smix_vol;
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* 4.2 */
|
|
|
|
case XMP_PLAYER_STATE:
|
|
|
|
ret = ctx->state;
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* 4.3 */
|
|
|
|
case XMP_PLAYER_DEFPAN:
|
|
|
|
ret = m->defpan;
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* 4.4 */
|
|
|
|
case XMP_PLAYER_MODE:
|
|
|
|
ret = p->mode;
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_MIXER_TYPE:
|
|
|
|
ret = XMP_MIXER_STANDARD;
|
|
|
|
if (p->flags & XMP_FLAGS_A500) {
|
|
|
|
if (IS_AMIGA_MOD()) {
|
|
|
|
#ifdef LIBXMP_PAULA_SIMULATOR
|
|
|
|
if (p->filter) {
|
|
|
|
ret = XMP_MIXER_A500F;
|
|
|
|
} else {
|
|
|
|
ret = XMP_MIXER_A500;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case XMP_PLAYER_VOICES:
|
|
|
|
ret = s->numvoc;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-06-09 06:39:48 +00:00
|
|
|
const char **xmp_get_format_list()
|
2017-06-09 06:39:37 +00:00
|
|
|
{
|
|
|
|
return format_list();
|
|
|
|
}
|
|
|
|
|
|
|
|
void xmp_inject_event(xmp_context opaque, int channel, struct xmp_event *e)
|
|
|
|
{
|
|
|
|
struct context_data *ctx = (struct context_data *)opaque;
|
|
|
|
struct player_data *p = &ctx->p;
|
|
|
|
|
|
|
|
if (ctx->state < XMP_STATE_PLAYING)
|
|
|
|
return;
|
|
|
|
|
|
|
|
memcpy(&p->inject_event[channel], e, sizeof(struct xmp_event));
|
|
|
|
p->inject_event[channel]._flag = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int xmp_set_instrument_path(xmp_context opaque, char *path)
|
|
|
|
{
|
|
|
|
struct context_data *ctx = (struct context_data *)opaque;
|
|
|
|
struct module_data *m = &ctx->m;
|
|
|
|
|
|
|
|
if (m->instrument_path != NULL)
|
|
|
|
free(m->instrument_path);
|
|
|
|
|
|
|
|
m->instrument_path = strdup(path);
|
|
|
|
if (m->instrument_path == NULL) {
|
|
|
|
return -XMP_ERROR_SYSTEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|