mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-14 00:20:51 +00:00
14361d9313
- Did anybody actually use this? Use WildMidi instead if you want something that sounds more like Timidity++ without actually being Timidity++, since not even the old Timidity manages that.
687 lines
16 KiB
C++
687 lines
16 KiB
C++
/*
|
|
TiMidity -- Experimental MIDI to WAVE converter
|
|
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#ifndef TIMIDITY_H
|
|
#define TIMIDITY_H
|
|
|
|
#include "doomtype.h"
|
|
#include "pathexpander.h"
|
|
|
|
class FileReader;
|
|
|
|
namespace Timidity
|
|
{
|
|
|
|
/*
|
|
config.h
|
|
*/
|
|
|
|
/* Acoustic Grand Piano seems to be the usual default instrument. */
|
|
#define DEFAULT_PROGRAM 0
|
|
|
|
/* 9 here is MIDI channel 10, which is the standard percussion channel.
|
|
Some files (notably C:\WINDOWS\CANYON.MID) think that 16 is one too.
|
|
On the other hand, some files know that 16 is not a drum channel and
|
|
try to play music on it. This is now a runtime option, so this isn't
|
|
a critical choice anymore. */
|
|
#define DEFAULT_DRUMCHANNELS (1<<9)
|
|
/*#define DEFAULT_DRUMCHANNELS ((1<<9) | (1<<15))*/
|
|
|
|
#define MAXCHAN 16
|
|
#define MAXNOTE 128
|
|
|
|
/* 1000 here will give a control ratio of 22:1 with 22 kHz output.
|
|
Higher CONTROLS_PER_SECOND values allow more accurate rendering
|
|
of envelopes and tremolo. The cost is CPU time. */
|
|
#define CONTROLS_PER_SECOND 1000
|
|
|
|
/* A scalar applied to the final mix to try and approximate the
|
|
volume level of FMOD's built-in MIDI player. */
|
|
#define FINAL_MIX_SCALE 0.5
|
|
|
|
/* How many bits to use for the fractional part of sample positions.
|
|
This affects tonal accuracy. The entire position counter must fit
|
|
in 32 bits, so with FRACTION_BITS equal to 12, the maximum size of
|
|
a sample is 1048576 samples (2 megabytes in memory). The GUS gets
|
|
by with just 9 bits and a little help from its friends...
|
|
"The GUS does not SUCK!!!" -- a happy user :) */
|
|
#define FRACTION_BITS 12
|
|
|
|
/* For some reason the sample volume is always set to maximum in all
|
|
patch files. Define this for a crude adjustment that may help
|
|
equalize instrument volumes. */
|
|
//#define ADJUST_SAMPLE_VOLUMES
|
|
|
|
/* The number of samples to use for ramping out a dying note. Affects
|
|
click removal. */
|
|
#define MAX_DIE_TIME 20
|
|
|
|
/**************************************************************************/
|
|
/* Anything below this shouldn't need to be changed unless you're porting
|
|
to a new machine with other than 32-bit, big-endian words. */
|
|
/**************************************************************************/
|
|
|
|
/* change FRACTION_BITS above, not these */
|
|
#define INTEGER_BITS (32 - FRACTION_BITS)
|
|
#define INTEGER_MASK (0xFFFFFFFF << FRACTION_BITS)
|
|
#define FRACTION_MASK (~ INTEGER_MASK)
|
|
#define MAX_SAMPLE_SIZE (1 << INTEGER_BITS)
|
|
|
|
/* This is enforced by some computations that must fit in an int */
|
|
#define MAX_CONTROL_RATIO 255
|
|
|
|
#define MAX_AMPLIFICATION 800
|
|
|
|
/* The TiMiditiy configuration file */
|
|
#define CONFIG_FILE "timidity.cfg"
|
|
|
|
typedef float sample_t;
|
|
typedef float final_volume_t;
|
|
#define FINAL_VOLUME(v) (v)
|
|
|
|
#define FSCALE(a,b) ((a) * (float)(1<<(b)))
|
|
#define FSCALENEG(a,b) ((a) * (1.0L / (float)(1<<(b))))
|
|
|
|
/* Vibrato and tremolo Choices of the Day */
|
|
#define SWEEP_TUNING 38
|
|
#define VIBRATO_AMPLITUDE_TUNING 1.0
|
|
#define VIBRATO_RATE_TUNING 38
|
|
#define TREMOLO_AMPLITUDE_TUNING 1.0
|
|
#define TREMOLO_RATE_TUNING 38
|
|
|
|
#define SWEEP_SHIFT 16
|
|
#define RATE_SHIFT 5
|
|
|
|
#define VIBRATO_SAMPLE_INCREMENTS 32
|
|
|
|
#ifndef PI
|
|
#define PI 3.14159265358979323846
|
|
#endif
|
|
|
|
#if defined(__GNUC__) && !defined(__clang__) && (defined(__i386__) || defined(__x86_64__))
|
|
// [RH] MinGW's pow() function is terribly slow compared to VC8's
|
|
// (I suppose because it's using an old version from MSVCRT.DLL).
|
|
// On an Opteron running x86-64 Linux, this also ended up being about
|
|
// 100 cycles faster than libm's pow(), which is why I'm using this
|
|
// for GCC in general and not just for MinGW.
|
|
// [CE] Clang doesn't yet support some inline ASM operations so I disabled it for that instance
|
|
|
|
extern __inline__ double pow_x87_inline(double x,double y)
|
|
{
|
|
double result;
|
|
|
|
if (y == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
if (x == 0)
|
|
{
|
|
if (y > 0)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
union { double fp; long long ip; } infinity;
|
|
infinity.ip = 0x7FF0000000000000ll;
|
|
return infinity.fp;
|
|
}
|
|
}
|
|
__asm__ (
|
|
"fyl2x\n\t"
|
|
"fld %%st(0)\n\t"
|
|
"frndint\n\t"
|
|
"fxch\n\t"
|
|
"fsub %%st(1),%%st(0)\n\t"
|
|
"f2xm1\n\t"
|
|
"fld1\n\t"
|
|
"faddp\n\t"
|
|
"fxch\n\t"
|
|
"fld1\n\t"
|
|
"fscale\n\t"
|
|
"fstp %%st(1)\n\t"
|
|
"fmulp\n\t"
|
|
: "=t" (result)
|
|
: "0" (x), "u" (y)
|
|
: "st(1)", "st(7)" );
|
|
return result;
|
|
}
|
|
#define pow pow_x87_inline
|
|
#endif
|
|
|
|
/*
|
|
common.h
|
|
*/
|
|
|
|
extern void *safe_malloc(size_t count);
|
|
|
|
/*
|
|
controls.h
|
|
*/
|
|
|
|
enum
|
|
{
|
|
CMSG_INFO,
|
|
CMSG_WARNING,
|
|
CMSG_ERROR
|
|
};
|
|
|
|
enum
|
|
{
|
|
VERB_NORMAL,
|
|
VERB_VERBOSE,
|
|
VERB_NOISY,
|
|
VERB_DEBUG
|
|
};
|
|
|
|
void cmsg(int type, int verbosity_level, const char *fmt, ...);
|
|
|
|
|
|
/*
|
|
instrum.h
|
|
*/
|
|
|
|
enum
|
|
{
|
|
PATCH_16 = (1<<0),
|
|
PATCH_UNSIGNED = (1<<1),
|
|
PATCH_LOOPEN = (1<<2),
|
|
PATCH_BIDIR = (1<<3),
|
|
PATCH_BACKWARD = (1<<4),
|
|
PATCH_SUSTAIN = (1<<5),
|
|
PATCH_NO_SRELEASE = (1<<6),
|
|
PATCH_FAST_REL = (1<<7),
|
|
};
|
|
|
|
struct Sample
|
|
{
|
|
SDWORD
|
|
loop_start, loop_end, data_length,
|
|
sample_rate;
|
|
float
|
|
low_freq, high_freq, root_freq;
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
BYTE rate[6], offset[6];
|
|
} gf1;
|
|
struct
|
|
{
|
|
short delay_vol;
|
|
short attack_vol;
|
|
short hold_vol;
|
|
short decay_vol;
|
|
short sustain_vol;
|
|
short release_vol;
|
|
} sf2;
|
|
} envelope;
|
|
sample_t *data;
|
|
SDWORD
|
|
tremolo_sweep_increment, tremolo_phase_increment,
|
|
vibrato_sweep_increment, vibrato_control_ratio;
|
|
BYTE
|
|
tremolo_depth, vibrato_depth,
|
|
low_vel, high_vel,
|
|
type;
|
|
WORD
|
|
modes;
|
|
SWORD
|
|
panning;
|
|
WORD
|
|
scale_factor, key_group;
|
|
SWORD
|
|
scale_note;
|
|
bool
|
|
self_nonexclusive;
|
|
float
|
|
left_offset, right_offset;
|
|
|
|
// SF2 stuff
|
|
SWORD tune;
|
|
SBYTE velocity;
|
|
|
|
float initial_attenuation;
|
|
};
|
|
|
|
void convert_sample_data(Sample *sample, const void *data);
|
|
void free_instruments();
|
|
|
|
/* Magic file words */
|
|
|
|
#define ID_RIFF MAKE_ID('R','I','F','F')
|
|
#define ID_LIST MAKE_ID('L','I','S','T')
|
|
#define ID_INFO MAKE_ID('I','N','F','O')
|
|
#define ID_sfbk MAKE_ID('s','f','b','k')
|
|
#define ID_sdta MAKE_ID('s','d','t','a')
|
|
#define ID_pdta MAKE_ID('p','d','t','a')
|
|
#define ID_ifil MAKE_ID('i','f','i','l')
|
|
#define ID_iver MAKE_ID('i','v','e','r')
|
|
#define ID_irom MAKE_ID('i','r','o','m')
|
|
#define ID_smpl MAKE_ID('s','m','p','l')
|
|
#define ID_sm24 MAKE_ID('s','m','2','4')
|
|
#define ID_phdr MAKE_ID('p','h','d','r')
|
|
#define ID_pbag MAKE_ID('p','b','a','g')
|
|
#define ID_pmod MAKE_ID('p','m','o','d')
|
|
#define ID_pgen MAKE_ID('p','g','e','n')
|
|
#define ID_inst MAKE_ID('i','n','s','t')
|
|
#define ID_ibag MAKE_ID('i','b','a','g')
|
|
#define ID_imod MAKE_ID('i','m','o','d')
|
|
#define ID_igen MAKE_ID('i','g','e','n')
|
|
#define ID_shdr MAKE_ID('s','h','d','r')
|
|
|
|
/* Instrument definitions */
|
|
|
|
enum
|
|
{
|
|
INST_GUS,
|
|
INST_DLS,
|
|
INST_SF2
|
|
};
|
|
|
|
struct Instrument
|
|
{
|
|
Instrument();
|
|
~Instrument();
|
|
|
|
int samples;
|
|
Sample *sample;
|
|
};
|
|
|
|
struct ToneBankElement
|
|
{
|
|
ToneBankElement() :
|
|
note(0), pan(0), strip_loop(0), strip_envelope(0), strip_tail(0)
|
|
{}
|
|
|
|
FString name;
|
|
int note, pan, fontbank, fontpreset, fontnote;
|
|
SBYTE strip_loop, strip_envelope, strip_tail;
|
|
};
|
|
|
|
/* A hack to delay instrument loading until after reading the entire MIDI file. */
|
|
#define MAGIC_LOAD_INSTRUMENT ((Instrument *)(-1))
|
|
|
|
enum
|
|
{
|
|
MAXPROG = 128,
|
|
MAXBANK = 128
|
|
};
|
|
|
|
struct ToneBank
|
|
{
|
|
ToneBank();
|
|
~ToneBank();
|
|
|
|
ToneBankElement *tone;
|
|
Instrument *instrument[MAXPROG];
|
|
};
|
|
|
|
|
|
#define SPECIAL_PROGRAM -1
|
|
|
|
/*
|
|
instrum_font.cpp
|
|
*/
|
|
|
|
class FontFile
|
|
{
|
|
public:
|
|
FontFile(FString filename);
|
|
virtual ~FontFile();
|
|
|
|
FString Filename;
|
|
FontFile *Next;
|
|
|
|
virtual Instrument *LoadInstrument(struct Renderer *song, int drum, int bank, int program) = 0;
|
|
virtual Instrument *LoadInstrumentOrder(struct Renderer *song, int order, int drum, int bank, int program) = 0;
|
|
virtual void SetOrder(int order, int drum, int bank, int program) = 0;
|
|
virtual void SetAllOrders(int order) = 0;
|
|
};
|
|
|
|
void font_freeall();
|
|
FontFile *font_find(const char *filename);
|
|
void font_add(const char *filename, int load_order);
|
|
void font_remove(const char *filename);
|
|
void font_order(int order, int bank, int preset, int keynote);
|
|
Instrument *load_instrument_font(struct Renderer *song, const char *font, int drum, int bank, int instrument);
|
|
Instrument *load_instrument_font_order(struct Renderer *song, int order, int drum, int bank, int instrument);
|
|
|
|
FontFile *ReadDLS(const char *filename, FileReader *f);
|
|
|
|
/*
|
|
mix.h
|
|
*/
|
|
|
|
extern void mix_voice(struct Renderer *song, float *buf, struct Voice *v, int c);
|
|
extern int recompute_envelope(struct Voice *v);
|
|
extern void apply_envelope_to_amp(struct Voice *v);
|
|
|
|
/*
|
|
playmidi.h
|
|
*/
|
|
|
|
/* Midi events */
|
|
enum
|
|
{
|
|
ME_NOTEOFF = 0x80,
|
|
ME_NOTEON = 0x90,
|
|
ME_KEYPRESSURE = 0xA0,
|
|
ME_CONTROLCHANGE = 0xB0,
|
|
ME_PROGRAM = 0xC0,
|
|
ME_CHANNELPRESSURE = 0xD0,
|
|
ME_PITCHWHEEL = 0xE0
|
|
};
|
|
|
|
/* Controllers */
|
|
enum
|
|
{
|
|
CTRL_BANK_SELECT = 0,
|
|
CTRL_DATA_ENTRY = 6,
|
|
CTRL_VOLUME = 7,
|
|
CTRL_PAN = 10,
|
|
CTRL_EXPRESSION = 11,
|
|
CTRL_SUSTAIN = 64,
|
|
CTRL_HARMONICCONTENT = 71,
|
|
CTRL_RELEASETIME = 72,
|
|
CTRL_ATTACKTIME = 73,
|
|
CTRL_BRIGHTNESS = 74,
|
|
CTRL_REVERBERATION = 91,
|
|
CTRL_CHORUSDEPTH = 93,
|
|
CTRL_NRPN_LSB = 98,
|
|
CTRL_NRPN_MSB = 99,
|
|
CTRL_RPN_LSB = 100,
|
|
CTRL_RPN_MSB = 101,
|
|
CTRL_ALL_SOUNDS_OFF = 120,
|
|
CTRL_RESET_CONTROLLERS = 121,
|
|
CTRL_ALL_NOTES_OFF = 123
|
|
};
|
|
|
|
/* RPNs */
|
|
enum
|
|
{
|
|
RPN_PITCH_SENS = 0x0000,
|
|
RPN_FINE_TUNING = 0x0001,
|
|
RPN_COARSE_TUNING = 0x0002,
|
|
RPN_RESET = 0x3fff
|
|
};
|
|
|
|
struct Channel
|
|
{
|
|
int
|
|
bank, program, sustain, pitchbend,
|
|
mono, /* one note only on this channel */
|
|
pitchsens;
|
|
BYTE
|
|
volume, expression;
|
|
SBYTE
|
|
panning;
|
|
WORD
|
|
rpn, nrpn;
|
|
bool
|
|
nrpn_mode;
|
|
float
|
|
pitchfactor; /* precomputed pitch bend factor to save some fdiv's */
|
|
};
|
|
|
|
/* Causes the instrument's default panning to be used. */
|
|
#define NO_PANNING -1
|
|
|
|
struct MinEnvelope
|
|
{
|
|
int stage;
|
|
BYTE bUpdating;
|
|
};
|
|
|
|
struct GF1Envelope : public MinEnvelope
|
|
{
|
|
int volume, target, increment;
|
|
int rate[6], offset[6];
|
|
|
|
void Init(struct Renderer *song, Voice *v);
|
|
bool Update(struct Voice *v);
|
|
bool Recompute(struct Voice *v);
|
|
void ApplyToAmp(struct Voice *v);
|
|
void Release(struct Voice *v);
|
|
};
|
|
|
|
struct SF2Envelope : public MinEnvelope
|
|
{
|
|
float volume;
|
|
float DelayTime; // timecents
|
|
float AttackTime; // timecents
|
|
float HoldTime; // timecents
|
|
float DecayTime; // timecents
|
|
float SustainLevel; // -0.1%
|
|
float ReleaseTime; // timecents
|
|
float SampleRate;
|
|
int HoldStart;
|
|
float RateMul;
|
|
float RateMul_cB;
|
|
|
|
void Init(struct Renderer *song, Voice *v);
|
|
bool Update(struct Voice *v);
|
|
void ApplyToAmp(struct Voice *v);
|
|
void Release(struct Voice *v);
|
|
};
|
|
|
|
struct Envelope
|
|
{
|
|
union
|
|
{
|
|
MinEnvelope env;
|
|
GF1Envelope gf1;
|
|
SF2Envelope sf2;
|
|
};
|
|
|
|
BYTE Type;
|
|
|
|
void Init(struct Renderer *song, struct Voice *v);
|
|
bool Update(struct Voice *v)
|
|
{
|
|
if (Type == INST_GUS)
|
|
return gf1.Update(v);
|
|
return sf2.Update(v);
|
|
}
|
|
void ApplyToAmp(struct Voice *v)
|
|
{
|
|
if (Type == INST_GUS)
|
|
return gf1.ApplyToAmp(v);
|
|
return sf2.ApplyToAmp(v);
|
|
}
|
|
void Release(struct Voice *v)
|
|
{
|
|
if (Type == INST_GUS)
|
|
return gf1.Release(v);
|
|
return sf2.Release(v);
|
|
}
|
|
};
|
|
|
|
struct Voice
|
|
{
|
|
BYTE
|
|
status, channel, note, velocity;
|
|
Sample *sample;
|
|
float
|
|
orig_frequency, frequency;
|
|
int
|
|
sample_offset, sample_increment,
|
|
tremolo_sweep, tremolo_sweep_position,
|
|
tremolo_phase, tremolo_phase_increment,
|
|
vibrato_sweep, vibrato_sweep_position;
|
|
|
|
Envelope eg1, eg2;
|
|
|
|
final_volume_t left_mix, right_mix;
|
|
|
|
float
|
|
attenuation, left_offset, right_offset;
|
|
float
|
|
tremolo_volume;
|
|
int
|
|
vibrato_sample_increment[VIBRATO_SAMPLE_INCREMENTS];
|
|
int
|
|
vibrato_phase, vibrato_control_ratio, vibrato_control_counter,
|
|
control_counter;
|
|
|
|
int
|
|
sample_count;
|
|
};
|
|
|
|
/* Voice status options: */
|
|
enum
|
|
{
|
|
VOICE_RUNNING = (1<<0),
|
|
VOICE_SUSTAINING = (1<<1),
|
|
VOICE_RELEASING = (1<<2),
|
|
VOICE_STOPPING = (1<<3),
|
|
|
|
VOICE_LPE = (1<<4),
|
|
NOTE_SUSTAIN = (1<<5),
|
|
};
|
|
|
|
/* Envelope stages: */
|
|
enum
|
|
{
|
|
GF1_ATTACK,
|
|
GF1_HOLD,
|
|
GF1_DECAY,
|
|
GF1_RELEASE,
|
|
GF1_RELEASEB,
|
|
GF1_RELEASEC
|
|
};
|
|
|
|
enum
|
|
{
|
|
SF2_DELAY,
|
|
SF2_ATTACK,
|
|
SF2_HOLD,
|
|
SF2_DECAY,
|
|
SF2_SUSTAIN,
|
|
SF2_RELEASE,
|
|
SF2_FINISHED
|
|
};
|
|
|
|
#define ISDRUMCHANNEL(c) ((drumchannels & (1<<(c))))
|
|
|
|
/*
|
|
resample.h
|
|
*/
|
|
|
|
extern sample_t *resample_voice(struct Renderer *song, Voice *v, int *countptr);
|
|
extern void pre_resample(struct Renderer *song, Sample *sp);
|
|
|
|
/*
|
|
tables.h
|
|
*/
|
|
|
|
const double log_of_2 = 0.69314718055994529;
|
|
|
|
#define sine(x) (sin((2*PI/1024.0) * (x)))
|
|
|
|
#define note_to_freq(x) (float(8175.7989473096690661233836992789 * pow(2.0, (x) / 12.0)))
|
|
#define freq_to_note(x) (log((x) / 8175.7989473096690661233836992789) * (12.0 / log_of_2))
|
|
|
|
#define calc_gf1_amp(x) (pow(2.0,((x)*16.0 - 16.0))) // Actual GUS equation
|
|
#define cb_to_amp(x) (pow(10.0, (x) * (1 / -200.0))) // centibels to amp
|
|
|
|
/*
|
|
timidity.h
|
|
*/
|
|
struct DLS_Data;
|
|
int LoadConfig(const char *filename);
|
|
int LoadDMXGUS();
|
|
extern int LoadConfig();
|
|
extern void FreeAll();
|
|
extern PathExpander pathExpander;
|
|
|
|
extern ToneBank *tonebank[MAXBANK];
|
|
extern ToneBank *drumset[MAXBANK];
|
|
|
|
struct Renderer
|
|
{
|
|
float rate;
|
|
DLS_Data *patches;
|
|
Instrument *default_instrument;
|
|
int default_program;
|
|
int resample_buffer_size;
|
|
sample_t *resample_buffer;
|
|
Channel channel[16];
|
|
Voice *voice;
|
|
int control_ratio, amp_with_poly;
|
|
int drumchannels;
|
|
int adjust_panning_immediately;
|
|
int voices;
|
|
int lost_notes, cut_notes;
|
|
|
|
Renderer(float sample_rate);
|
|
~Renderer();
|
|
|
|
void HandleEvent(int status, int parm1, int parm2);
|
|
void HandleLongMessage(const BYTE *data, int len);
|
|
void HandleController(int chan, int ctrl, int val);
|
|
void ComputeOutput(float *buffer, int num_samples);
|
|
void MarkInstrument(int bank, int percussion, int instr);
|
|
void Reset();
|
|
|
|
int load_missing_instruments();
|
|
int set_default_instrument(const char *name);
|
|
int convert_tremolo_sweep(BYTE sweep);
|
|
int convert_vibrato_sweep(BYTE sweep, int vib_control_ratio);
|
|
int convert_tremolo_rate(BYTE rate);
|
|
int convert_vibrato_rate(BYTE rate);
|
|
|
|
void recompute_freq(int voice);
|
|
void recompute_amp(Voice *v);
|
|
void recompute_pan(Channel *chan);
|
|
|
|
void kill_key_group(int voice);
|
|
float calculate_scaled_frequency(Sample *sample, int note);
|
|
void start_note(int chan, int note, int vel);
|
|
bool start_region(int chan, int note, int vel, Sample *sp, float freq);
|
|
|
|
void note_on(int chan, int note, int vel);
|
|
void note_off(int chan, int note, int vel);
|
|
void all_notes_off(int chan);
|
|
void all_sounds_off(int chan);
|
|
void adjust_pressure(int chan, int note, int amount);
|
|
void adjust_panning(int chan);
|
|
void drop_sustain(int chan);
|
|
void adjust_pitchbend(int chan);
|
|
void adjust_volume(int chan);
|
|
|
|
void reset_voices();
|
|
void reset_controllers(int chan);
|
|
void reset_midi();
|
|
|
|
int allocate_voice();
|
|
|
|
void kill_note(int voice);
|
|
void finish_note(int voice);
|
|
|
|
void DataEntryCoarseRPN(int chan, int rpn, int val);
|
|
void DataEntryFineRPN(int chan, int rpn, int val);
|
|
void DataEntryCoarseNRPN(int chan, int nrpn, int val);
|
|
void DataEntryFineNRPN(int chan, int nrpn, int val);
|
|
|
|
static void compute_pan(double panning, int type, float &left_offset, float &right_offset);
|
|
};
|
|
|
|
}
|
|
#endif
|