ref: 40d48401892ddcb0a196dbcbea9261b92bfcf3c5
parent: d21c521c2f7fc85b9d490a5852fa05f2994171eb
author: Bernhard Schelling <[email protected]>
date: Sat Feb 17 22:37:24 EST 2018
Added higher level channel based playback API for better MIDI support - Ability to play and stop notes on a channel - Set pitch wheel, volume and panning for the channels (affect currently and future playing notes) Removed global tsf_set_panning, same effect can be achieved with tsf_channel_set_pan
--- a/examples/example3.c
+++ b/examples/example3.c
@@ -10,7 +10,6 @@
static tsf* g_TinySoundFont;
// Holds global MIDI playback state
-static int g_MidiChannelPreset[16]; //active channel presets
static double g_Msec; //current playback time
static tml_message* g_MidiMessage; //next message to be played
@@ -31,21 +30,54 @@
switch (g_MidiMessage->type)
{
case TML_PROGRAM_CHANGE: //channel program (preset) change
- if (g_MidiMessage->channel == 9) //10th MIDI channel uses percussion sound bank (128)
+ if (g_MidiMessage->channel == 9)
{
+ //10th MIDI channel uses percussion sound bank (128)
Preset = tsf_get_presetindex(g_TinySoundFont, 128, g_MidiMessage->program);
if (Preset < 0) Preset = tsf_get_presetindex(g_TinySoundFont, 128, 0);
if (Preset < 0) Preset = tsf_get_presetindex(g_TinySoundFont, 0, g_MidiMessage->program);
}
else Preset = tsf_get_presetindex(g_TinySoundFont, 0, g_MidiMessage->program);
- g_MidiChannelPreset[g_MidiMessage->channel] = (Preset < 0 ? 0 : Preset);
+ tsf_channel_set_preset(g_TinySoundFont, g_MidiMessage->channel, (Preset < 0 ? 0 : Preset));
break;
case TML_NOTE_ON: //play a note
- tsf_note_on(g_TinySoundFont, g_MidiChannelPreset[g_MidiMessage->channel], g_MidiMessage->key, g_MidiMessage->velocity / 127.0f);
+ tsf_channel_note_on(g_TinySoundFont, g_MidiMessage->channel, g_MidiMessage->key, g_MidiMessage->velocity / 127.0f);
break;
case TML_NOTE_OFF: //stop a note
- tsf_note_off(g_TinySoundFont, g_MidiChannelPreset[g_MidiMessage->channel], g_MidiMessage->key);
+ tsf_channel_note_off(g_TinySoundFont, g_MidiMessage->channel, g_MidiMessage->key);
break;
+ case TML_PITCH_BEND: //pitch wheel modification
+ tsf_channel_set_pitchwheel(g_TinySoundFont, g_MidiMessage->channel, g_MidiMessage->pitch_bend);
+ break;
+ case TML_CONTROL_CHANGE: //MIDI controller messages
+ switch (g_MidiMessage->control)
+ {
+ #define FLOAT_APPLY_MSB(val, msb) (((((int)(val*16383.5f)) & 0x7f) | (msb << 7)) / 16383.0f)
+ #define FLOAT_APPLY_LSB(val, lsb) (((((int)(val*16383.5f)) & ~0x7f) | lsb ) / 16383.0f)
+ case TML_VOLUME_MSB: case TML_EXPRESSION_MSB:
+ tsf_channel_set_volume(g_TinySoundFont, g_MidiMessage->channel, FLOAT_APPLY_MSB(tsf_channel_get_volume(g_TinySoundFont, g_MidiMessage->channel), g_MidiMessage->control_value));
+ break;
+ case TML_VOLUME_LSB: case TML_EXPRESSION_LSB:
+ tsf_channel_set_volume(g_TinySoundFont, g_MidiMessage->channel, FLOAT_APPLY_LSB(tsf_channel_get_volume(g_TinySoundFont, g_MidiMessage->channel), g_MidiMessage->control_value));
+ break;
+ case TML_BALANCE_MSB: case TML_PAN_MSB:
+ tsf_channel_set_pan(g_TinySoundFont, g_MidiMessage->channel, FLOAT_APPLY_MSB(tsf_channel_get_pan(g_TinySoundFont, g_MidiMessage->channel), g_MidiMessage->control_value));
+ break;
+ case TML_BALANCE_LSB: case TML_PAN_LSB:
+ tsf_channel_set_pan(g_TinySoundFont, g_MidiMessage->channel, FLOAT_APPLY_LSB(tsf_channel_get_pan(g_TinySoundFont, g_MidiMessage->channel), g_MidiMessage->control_value));
+ break;
+ case TML_ALL_SOUND_OFF:
+ tsf_channel_sounds_off_all(g_TinySoundFont, g_MidiMessage->channel);
+ break;
+ case TML_ALL_CTRL_OFF:
+ tsf_channel_set_volume(g_TinySoundFont, g_MidiMessage->channel, 1.0f);
+ tsf_channel_set_pan(g_TinySoundFont, g_MidiMessage->channel, 0.5f);
+ break;
+ case TML_ALL_NOTES_OFF:
+ tsf_channel_note_off_all(g_TinySoundFont, g_MidiMessage->channel);
+ break;
+ }
+ break;
}
}
@@ -94,6 +126,9 @@
return 1;
}
+ //Initialize preset on special 10th MIDI channel to use percussion sound bank (128) if available
+ tsf_channel_set_bank_preset(g_TinySoundFont, 9, 128, 0);
+
// Set the SoundFont rendering output mode with -18 db volume gain
tsf_set_output(g_TinySoundFont, TSF_STEREO_INTERLEAVED, OutputAudioSpec.freq, -18.0f);
@@ -111,7 +146,7 @@
//Wait until the entire MIDI file has been played back (until the end of the linked message list is reached)
while (g_MidiMessage != NULL) SDL_Delay(100);
- // We could call tsf_close(g_TinySoundFont) and tsf_free(TinyMidiLoader)
+ // We could call tsf_close(g_TinySoundFont) and tml_free(TinyMidiLoader)
// here to free the memory and resources but we just let the OS clean up
// because the process ends here.
return 0;
--- a/tsf.h
+++ b/tsf.h
@@ -19,7 +19,6 @@
- Support for ChorusEffectsSend and ReverbEffectsSend generators
- Better low-pass filter without lowering performance too much
- Support for modulators
- - Channel interface to better support MIDI playback
LICENSE (MIT)
@@ -129,11 +128,6 @@
// global_gain_db: volume gain in decibels (>0 means higher, <0 means lower)
TSFDEF void tsf_set_output(tsf* f, enum TSFOutputMode outputmode, int samplerate, float global_gain_db CPP_DEFAULT0);
-// Adjust global panning values. Mono output will apply the average of both.
-// pan_factor_left: volume gain factor for the left channel
-// pan_factor_right: volume gain factor for the right channel
-TSFDEF void tsf_set_panning(tsf* f, float pan_factor_left, float pan_factor_right);
-
// Start playing a note
// preset_index: preset index >= 0 and < tsf_get_presetcount()
// key: note value between 0 and 127 (60 being middle C)
@@ -140,12 +134,14 @@
// vel: velocity as a float between 0.0 (equal to note off) and 1.0 (full)
// bank: instrument bank number (alternative to preset_index)
// preset_number: preset number (alternative to preset_index)
+// (bank_note_on returns 0 if preset does not exist, otherwise 1)
TSFDEF void tsf_note_on(tsf* f, int preset_index, int key, float vel);
-TSFDEF void tsf_bank_note_on(tsf* f, int bank, int preset_number, int key, float vel);
+TSFDEF int tsf_bank_note_on(tsf* f, int bank, int preset_number, int key, float vel);
// Stop playing a note
+// (bank_note_off returns 0 if preset does not exist, otherwise 1)
TSFDEF void tsf_note_off(tsf* f, int preset_index, int key);
-TSFDEF void tsf_bank_note_off(tsf* f, int bank, int preset_number, int key);
+TSFDEF int tsf_bank_note_off(tsf* f, int bank, int preset_number, int key);
// Stop playing all notes
TSFDEF void tsf_note_off_all(tsf* f);
@@ -159,6 +155,38 @@
TSFDEF void tsf_render_short(tsf* f, short* buffer, int samples, int flag_mixing CPP_DEFAULT0);
TSFDEF void tsf_render_float(tsf* f, float* buffer, int samples, int flag_mixing CPP_DEFAULT0);
+// Higher level channel based functions, set up channel parameters
+// preset_index: preset index >= 0 and < tsf_get_presetcount()
+// bank: instrument bank number (alternative to preset_index)
+// preset_number: preset number (alternative to preset_index)
+// channel: channel number
+// pitch_wheel: pitch wheel position 0 to 16383 (default 8192 unpitched)
+// pan: stereo panning value from 0.0 (left) to 1.0 (right) (default 0.5 center)
+// volume: linear volume scale factor (default 1.0 full)
+// (set_bank_preset returns 0 if preset does not exist, otherwise 1)
+TSFDEF void tsf_channel_set_preset(tsf* f, int channel, int preset_index);
+TSFDEF int tsf_channel_set_bank_preset(tsf* f, int channel, int bank, int preset_number);
+TSFDEF void tsf_channel_set_pitchwheel(tsf* f, int channel, int pitch_wheel);
+TSFDEF void tsf_channel_set_pan(tsf* f, int channel, float pan);
+TSFDEF void tsf_channel_set_volume(tsf* f, int channel, float volume);
+
+// Start or stop playing notes on a channel (needs channel preset to be set)
+// channel: channel number
+// key: note value between 0 and 127 (60 being middle C)
+// vel: velocity as a float between 0.0 (equal to note off) and 1.0 (full)
+TSFDEF void tsf_channel_note_on(tsf* f, int channel, int key, float vel);
+TSFDEF void tsf_channel_note_off(tsf* f, int channel, int key);
+TSFDEF void tsf_channel_note_off_all(tsf* f, int channel); //end with sustain and release
+TSFDEF void tsf_channel_sounds_off_all(tsf* f, int channel); //end immediatly
+
+// Get current values set on the channels
+TSFDEF int tsf_channel_get_preset_index(tsf* f, int channel);
+TSFDEF int tsf_channel_get_preset_bank(tsf* f, int channel);
+TSFDEF int tsf_channel_get_preset_number(tsf* f, int channel);
+TSFDEF int tsf_channel_get_pitchwheel(tsf* f, int channel);
+TSFDEF float tsf_channel_get_pan(tsf* f, int channel);
+TSFDEF float tsf_channel_get_volume(tsf* f, int channel);
+
#ifdef __cplusplus
# undef CPP_DEFAULT0
}
@@ -195,9 +223,10 @@
#if !defined(TSF_POW) || !defined(TSF_POWF) || !defined(TSF_EXPF) || !defined(TSF_LOG) || !defined(TSF_TAN) || !defined(TSF_LOG10) || !defined(TSF_SQRT)
# include <math.h>
-# if !defined(__cplusplus) && !defined(NAN) && !defined(powf) && !defined(expf)
-# define powf (float)pow // deal with old math.h files that
-# define expf (float)exp // come without powf and expf
+# if !defined(__cplusplus) && !defined(NAN) && !defined(powf) && !defined(expf) && !defined(sqrtf)
+# define powf (float)pow // deal with old math.h
+# define expf (float)exp // files that come without
+# define sqrtf (float)sqrt // powf, expf and sqrtf
# endif
# define TSF_POW pow
# define TSF_POWF powf
@@ -205,7 +234,7 @@
# define TSF_LOG log
# define TSF_TAN tan
# define TSF_LOG10 log10
-# define TSF_SQRT sqrt
+# define TSF_SQRTF sqrtf
#endif
#ifndef TSF_NO_STDIO
@@ -237,18 +266,17 @@
struct tsf_preset* presets;
float* fontSamples;
struct tsf_voice* voices;
+ struct tsf_channels* channels;
float* outputSamples;
int presetNum;
- int fontSampleCount;
int voiceNum;
int outputSampleSize;
unsigned int voicePlayIndex;
- float outSampleRate;
enum TSFOutputMode outputmode;
- float globalGainDB, globalPanFactorLeft, globalPanFactorRight;
-
+ float outSampleRate;
+ float globalGainDB;
};
#ifndef TSF_NO_STDIO
@@ -356,21 +384,35 @@
struct tsf_voice
{
- int playingPreset, playingKey, curPitchWheel;
+ int playingPreset, playingKey, playingChannel;
struct tsf_region* region;
double pitchInputTimecents, pitchOutputFactor;
double sourceSamplePosition;
float noteGainDB, panFactorLeft, panFactorRight;
- unsigned int playIndex, sampleEnd, loopStart, loopEnd;
+ unsigned int playIndex, loopStart, loopEnd;
struct tsf_voice_envelope ampenv, modenv;
struct tsf_voice_lowpass lowpass;
struct tsf_voice_lfo modlfo, viblfo;
};
+struct tsf_channel
+{
+ int presetIndex, pitchWheel;
+ float panOffset, gainDB;
+};
+
+struct tsf_channels
+{
+ void (*setupVoice)(tsf* f, struct tsf_voice* voice);
+ struct tsf_channel* channels;
+ int channelNum, activeChannel;
+};
+
static double tsf_timecents2Secsd(double timecents) { return TSF_POW(2.0, timecents / 1200.0); }
static float tsf_timecents2Secsf(float timecents) { return TSF_POWF(2.0f, timecents / 1200.0f); }
static float tsf_cents2Hertz(float cents) { return 8.176f * TSF_POWF(2.0f, cents / 1200.0f); }
static float tsf_decibelsToGain(float db) { return (db > -100.f ? TSF_POWF(10.0f, db * 0.05f) : 0); }
+static float tsf_gainToDecibels(float gain) { return (gain <= .00001f ? -100.f : (float)(20.0 * TSF_LOG10(gain))); }
static TSF_BOOL tsf_riffchunk_read(struct tsf_riffchunk* parent, struct tsf_riffchunk* chunk, struct tsf_stream* stream)
{
@@ -437,7 +479,7 @@
case ModEnvToFilterFc: region->modEnvToFilterFc = amount->shortAmount; break;
case EndAddrsCoarseOffset: region->end += amount->shortAmount * 32768; break;
case ModLfoToVolume: region->modLfoToVolume = amount->shortAmount; break;
- case Pan: region->pan = amount->shortAmount * (2.0f / 10.0f); break;
+ case Pan: region->pan = amount->shortAmount / 1000.0f; break;
case DelayModLFO: region->delayModLFO = amount->shortAmount; break;
case FreqModLFO: region->freqModLFO = amount->shortAmount; break;
case DelayVibLFO: region->delayVibLFO = amount->shortAmount; break;
@@ -493,7 +535,7 @@
else p->sustain = p->sustain / 10.0f;
}
-static void tsf_load_presets(tsf* res, struct tsf_hydra *hydra)
+static void tsf_load_presets(tsf* res, struct tsf_hydra *hydra, unsigned int fontSampleCount)
{
enum { GenInstrument = 41, GenKeyRange = 43, GenVelRange = 44, GenSampleID = 53 };
// Read each preset.
@@ -630,8 +672,8 @@
zoneRegion.delayVibLFO = (zoneRegion.delayVibLFO < -11950.0f ? 0.0f : tsf_timecents2Secsf(zoneRegion.delayVibLFO));
// Pin values to their ranges.
- if (zoneRegion.pan < -100.0f) zoneRegion.pan = -100.0f;
- else if (zoneRegion.pan > 100.0f) zoneRegion.pan = 100.0f;
+ if (zoneRegion.pan < -0.5f) zoneRegion.pan = -0.5f;
+ else if (zoneRegion.pan > 0.5f) zoneRegion.pan = 0.5f;
if (zoneRegion.initialFilterQ < 1500 || zoneRegion.initialFilterQ > 13500) zoneRegion.initialFilterQ = 0;
pshdr = &hydra->shdrs[pigen->genAmount.wordAmount];
@@ -642,6 +684,9 @@
if (pshdr->endLoop > 0) zoneRegion.loop_end -= 1;
if (zoneRegion.pitch_keycenter == -1) zoneRegion.pitch_keycenter = pshdr->originalPitch;
zoneRegion.tune += pshdr->pitchCorrection;
+ zoneRegion.sample_rate = pshdr->sampleRate;
+ if (zoneRegion.end && zoneRegion.end < fontSampleCount) zoneRegion.end++;
+ else zoneRegion.end = fontSampleCount;
// Pin initialAttenuation to max +6dB.
if (zoneRegion.volume > 6.0f)
@@ -651,7 +696,6 @@
}
preset->regions[region_index] = zoneRegion;
- preset->regions[region_index].sample_rate = pshdr->sampleRate;
region_index++;
hadSampleID = 1;
}
@@ -675,7 +719,7 @@
}
}
-static void tsf_load_samples(float** fontSamples, int* fontSampleCount, struct tsf_riffchunk *chunkSmpl, struct tsf_stream* stream)
+static void tsf_load_samples(float** fontSamples, unsigned int* fontSampleCount, struct tsf_riffchunk *chunkSmpl, struct tsf_stream* stream)
{
// Read sample data into float format buffer.
float* out; unsigned int samplesLeft, samplesToRead, samplesToConvert;
@@ -871,15 +915,12 @@
v->modenv.parameters.release = 0.0f; tsf_voice_envelope_nextsegment(&v->modenv, TSF_SEGMENT_SUSTAIN, outSampleRate);
}
-static void tsf_voice_calcpitchratio(struct tsf_voice* v, float outSampleRate)
+static void tsf_voice_calcpitchratio(struct tsf_voice* v, int pitch_wheel, float outSampleRate)
{
- double note = v->playingKey, adjustedPitch;
- note += v->region->transpose;
- note += v->region->tune / 100.0;
+ double note = v->playingKey + v->region->transpose + v->region->tune / 100.0;
+ double adjustedPitch= v->region->pitch_keycenter + (note - v->region->pitch_keycenter) * (v->region->pitch_keytrack / 100.0);
+ if (pitch_wheel != 8192) adjustedPitch += ((4.0 * pitch_wheel / 16383.0) - 2.0);
- adjustedPitch = v->region->pitch_keycenter + (note - v->region->pitch_keycenter) * (v->region->pitch_keytrack / 100.0);
- if (v->curPitchWheel != 8192) adjustedPitch += ((4.0 * v->curPitchWheel / 16383.0) - 2.0);
-
v->pitchInputTimecents = adjustedPitch * 100.0;
v->pitchOutputFactor = v->region->sample_rate / (tsf_timecents2Secsd(v->region->pitch_keycenter * 100.0) * outSampleRate);
}
@@ -897,7 +938,7 @@
TSF_BOOL updateVibLFO = (v->viblfo.delta && (region->vibLfoToPitch));
TSF_BOOL isLooping = (v->loopStart < v->loopEnd);
unsigned int tmpLoopStart = v->loopStart, tmpLoopEnd = v->loopEnd;
- double tmpSampleEndDbl = (double)v->sampleEnd, tmpLoopEndDbl = (double)tmpLoopEnd + 1.0;
+ double tmpSampleEndDbl = (double)region->end, tmpLoopEndDbl = (double)tmpLoopEnd + 1.0;
double tmpSourceSamplePosition = v->sourceSamplePosition;
struct tsf_voice_lowpass tmpLowpass = v->lowpass;
@@ -952,7 +993,7 @@
switch (f->outputmode)
{
case TSF_STEREO_INTERLEAVED:
- gainLeft = gainMono * f->globalPanFactorLeft * v->panFactorLeft, gainRight = gainMono * f->globalPanFactorRight * v->panFactorRight;
+ gainLeft = gainMono * v->panFactorLeft, gainRight = gainMono * v->panFactorRight;
while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl)
{
unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1);
@@ -973,7 +1014,7 @@
break;
case TSF_STEREO_UNWEAVED:
- gainLeft = gainMono * f->globalPanFactorLeft * v->panFactorLeft, gainRight = gainMono * f->globalPanFactorRight * v->panFactorRight;
+ gainLeft = gainMono * v->panFactorLeft, gainRight = gainMono * v->panFactorRight;
while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl)
{
unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1);
@@ -994,7 +1035,6 @@
break;
case TSF_MONO:
- gainMono *= (f->globalPanFactorLeft + f->globalPanFactorRight) * .5f;
while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl)
{
unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1);
@@ -1032,7 +1072,7 @@
struct tsf_riffchunk chunkList;
struct tsf_hydra hydra;
float* fontSamples = TSF_NULL;
- int fontSampleCount;
+ unsigned int fontSampleCount;
if (!tsf_riffchunk_read(TSF_NULL, &chunkHead, stream) || !TSF_FourCCEquals(chunkHead.id, "sfbk"))
{
@@ -1097,11 +1137,9 @@
res->presetNum = hydra.phdrNum - 1;
res->presets = (struct tsf_preset*)TSF_MALLOC(res->presetNum * sizeof(struct tsf_preset));
res->fontSamples = fontSamples;
- res->fontSampleCount = fontSampleCount;
res->outSampleRate = 44100.0f;
- res->globalPanFactorLeft = res->globalPanFactorRight = 1.0f;
fontSamples = TSF_NULL; //don't free below
- tsf_load_presets(res, &hydra);
+ tsf_load_presets(res, &hydra, fontSampleCount);
}
TSF_FREE(hydra.phdrs); TSF_FREE(hydra.pbags); TSF_FREE(hydra.pmods);
TSF_FREE(hydra.pgens); TSF_FREE(hydra.insts); TSF_FREE(hydra.ibags);
@@ -1150,46 +1188,35 @@
TSFDEF void tsf_set_output(tsf* f, enum TSFOutputMode outputmode, int samplerate, float global_gain_db)
{
- f->outSampleRate = (float)(samplerate >= 1 ? samplerate : 44100.0f);
f->outputmode = outputmode;
+ f->outSampleRate = (float)(samplerate >= 1 ? samplerate : 44100.0f);
f->globalGainDB = global_gain_db;
}
-TSFDEF void tsf_set_panning(tsf* f, float pan_factor_left, float pan_factor_right)
-{
- f->globalPanFactorLeft = pan_factor_left;
- f->globalPanFactorRight = pan_factor_right;
-}
-
TSFDEF void tsf_note_on(tsf* f, int preset_index, int key, float vel)
{
int midiVelocity = (int)(vel * 127), voicePlayIndex;
- TSF_BOOL haveGroupedNotesPlaying = TSF_FALSE;
- struct tsf_voice *v, *vEnd; struct tsf_region *region, *regionEnd;
+ struct tsf_region *region, *regionEnd;
if (preset_index < 0 || preset_index >= f->presetNum) return;
if (vel <= 0.0f) { tsf_note_off(f, preset_index, key); return; }
- // Are any grouped notes playing? (Needed for group stopping) Also stop any voices still playing this note.
- for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++)
- {
- if (v->playingPreset != preset_index) continue;
- if (v->region->group) haveGroupedNotesPlaying = TSF_TRUE;
- }
-
// Play all matching regions.
voicePlayIndex = f->voicePlayIndex++;
for (region = f->presets[preset_index].regions, regionEnd = region + f->presets[preset_index].regionNum; region != regionEnd; region++)
{
- struct tsf_voice* voice = TSF_NULL; double adjustedPan; TSF_BOOL doLoop; float filterQDB;
+ struct tsf_voice *voice, *v, *vEnd; TSF_BOOL doLoop; float filterQDB;
if (key < region->lokey || key > region->hikey || midiVelocity < region->lovel || midiVelocity > region->hivel) continue;
- if (haveGroupedNotesPlaying && region->group)
- for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++)
- if (v->playingPreset == preset_index && v->region->group == region->group)
- tsf_voice_endquick(v, f->outSampleRate);
+ voice = TSF_NULL, v = f->voices, vEnd = v + f->voiceNum;
+ if (region->group)
+ {
+ for (; v != vEnd; v++)
+ if (v->playingPreset == preset_index && v->region->group == region->group) tsf_voice_endquick(v, f->outSampleRate);
+ else if (v->playingPreset == -1 && !voice) voice = v;
+ }
+ else for (; v != vEnd; v++) if (v->playingPreset == -1) { voice = v; break; }
- for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++) if (v->playingPreset == -1) { voice = v; break; }
if (!voice)
{
f->voiceNum += 4;
@@ -1202,24 +1229,22 @@
voice->playingPreset = preset_index;
voice->playingKey = key;
voice->playIndex = voicePlayIndex;
+ voice->noteGainDB = f->globalGainDB + region->volume - tsf_gainToDecibels(1.0f / vel);
- // Pitch.
- voice->curPitchWheel = 8192;
- tsf_voice_calcpitchratio(voice, f->outSampleRate);
+ if (f->channels)
+ {
+ f->channels->setupVoice(f, voice);
+ }
+ else
+ {
+ tsf_voice_calcpitchratio(voice, 8192, f->outSampleRate);
+ // The SFZ spec is silent about the pan curve, but a 3dB pan law seems common. This sqrt() curve matches what Dimension LE does; Alchemy Free seems closer to sin(adjustedPan * pi/2).
+ voice->panFactorLeft = TSF_SQRTF(0.5f - region->pan);
+ voice->panFactorRight = TSF_SQRTF(0.5f + region->pan);
+ }
- // Gain.
- voice->noteGainDB = f->globalGainDB + region->volume;
- // Thanks to <http:://www.drealm.info/sfz/plj-sfz.xhtml> for explaining the velocity curve in a way that I could understand, although they mean "log10" when they say "log".
- voice->noteGainDB += (float)(-20.0 * TSF_LOG10(1.0 / vel));
- // The SFZ spec is silent about the pan curve, but a 3dB pan law seems common. This sqrt() curve matches what Dimension LE does; Alchemy Free seems closer to sin(adjustedPan * pi/2).
- adjustedPan = (region->pan + 100.0) / 200.0;
- voice->panFactorLeft = (float)TSF_SQRT(1.0 - adjustedPan);
- voice->panFactorRight = (float)TSF_SQRT(adjustedPan);
-
// Offset/end.
voice->sourceSamplePosition = region->offset;
- voice->sampleEnd = f->fontSampleCount;
- if (region->end > 0 && region->end < voice->sampleEnd) voice->sampleEnd = region->end + 1;
// Loop.
doLoop = (region->loop_mode != TSF_LOOPMODE_NONE && region->loop_start < region->loop_end);
@@ -1243,9 +1268,12 @@
}
}
-TSFDEF void tsf_bank_note_on(tsf* f, int bank, int preset_number, int key, float vel)
+TSFDEF int tsf_bank_note_on(tsf* f, int bank, int preset_number, int key, float vel)
{
- tsf_note_on(f, tsf_get_presetindex(f, bank, preset_number), key, vel);
+ int preset_index = tsf_get_presetindex(f, bank, preset_number);
+ if (preset_index == -1) return 0;
+ tsf_note_on(f, preset_index, key, vel);
+ return 1;
}
TSFDEF void tsf_note_off(tsf* f, int preset_index, int key)
@@ -1268,9 +1296,12 @@
}
}
-TSFDEF void tsf_bank_note_off(tsf* f, int bank, int preset_number, int key)
+TSFDEF int tsf_bank_note_off(tsf* f, int bank, int preset_number, int key)
{
- tsf_note_off(f, tsf_get_presetindex(f, bank, preset_number), key);
+ int preset_index = tsf_get_presetindex(f, bank, preset_number);
+ if (preset_index == -1) return 0;
+ tsf_note_off(f, preset_index, key);
+ return 1;
}
TSFDEF void tsf_note_off_all(tsf* f)
@@ -1317,6 +1348,164 @@
for (; v != vEnd; v++)
if (v->playingPreset != -1)
tsf_voice_render(f, v, buffer, samples);
+}
+
+static void tsf_channel_setup_voice(tsf* f, struct tsf_voice* v)
+{
+ float newpan = v->region->pan + f->channels->channels[f->channels->activeChannel].panOffset;
+ v->playingChannel = f->channels->activeChannel;
+ v->noteGainDB += f->channels->channels[f->channels->activeChannel].gainDB;
+ tsf_voice_calcpitchratio(v, f->channels->channels[f->channels->activeChannel].pitchWheel, f->outSampleRate);
+ if (newpan <= -0.5f) { v->panFactorLeft = 1.0f; v->panFactorRight = 0.0f; }
+ else if (newpan >= 0.5f) { v->panFactorLeft = 0.0f; v->panFactorRight = 1.0f; }
+ else { v->panFactorLeft = TSF_SQRTF(0.5f - newpan); v->panFactorRight = TSF_SQRTF(0.5f + newpan); }
+}
+
+static void tsf_channel_init(tsf* f, int channel)
+{
+ int i;
+ if (f->channels && channel < f->channels->channelNum) return;
+ if (!f->channels)
+ {
+ f->channels = (struct tsf_channels*)TSF_MALLOC(sizeof(struct tsf_channels));
+ f->channels->setupVoice = &tsf_channel_setup_voice;
+ f->channels->channels = NULL;
+ f->channels->channelNum = 0;
+ f->channels->activeChannel = 0;
+ }
+ i = f->channels->channelNum;
+ f->channels->channelNum = channel + 1;
+ f->channels->channels = (struct tsf_channel*)TSF_REALLOC(f->channels->channels, f->channels->channelNum * sizeof(struct tsf_channel));
+ for (; i <= channel; i++)
+ {
+ f->channels->channels[i].presetIndex = 0;
+ f->channels->channels[i].pitchWheel = 8192;
+ f->channels->channels[i].panOffset = 0.0f;
+ f->channels->channels[i].gainDB = 0.0f;
+ }
+}
+
+TSFDEF void tsf_channel_set_preset(tsf* f, int channel, int preset_index)
+{
+ tsf_channel_init(f, channel);
+ f->channels->channels[channel].presetIndex = preset_index;
+}
+
+TSFDEF int tsf_channel_set_bank_preset(tsf* f, int channel, int bank, int preset_number)
+{
+ int preset_index = tsf_get_presetindex(f, bank, preset_number);
+ if (preset_index == -1) return 0;
+ tsf_channel_init(f, channel);
+ f->channels->channels[channel].presetIndex = preset_index;
+ return 1;
+}
+
+TSFDEF void tsf_channel_set_pitchwheel(tsf* f, int channel, int pitch_wheel)
+{
+ struct tsf_voice *v, *vEnd;
+ for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++)
+ if (v->playingChannel == channel && v->playingPreset != -1)
+ tsf_voice_calcpitchratio(v, pitch_wheel, f->outSampleRate);
+ tsf_channel_init(f, channel);
+ f->channels->channels[channel].pitchWheel = pitch_wheel;
+}
+
+TSFDEF void tsf_channel_set_pan(tsf* f, int channel, float pan)
+{
+ struct tsf_voice *v, *vEnd;
+ for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++)
+ if (v->playingChannel == channel && v->playingPreset != -1)
+ {
+ float newpan = v->region->pan + pan - 0.5f;
+ if (newpan <= -0.5f) { v->panFactorLeft = 1.0f; v->panFactorRight = 0.0f; }
+ else if (newpan >= 0.5f) { v->panFactorLeft = 0.0f; v->panFactorRight = 1.0f; }
+ else { v->panFactorLeft = TSF_SQRTF(0.5f - newpan); v->panFactorRight = TSF_SQRTF(0.5f + newpan); }
+ }
+ tsf_channel_init(f, channel);
+ f->channels->channels[channel].panOffset = pan - 0.5f;
+}
+
+TSFDEF void tsf_channel_set_volume(tsf* f, int channel, float volume)
+{
+ struct tsf_voice *v, *vEnd; float gainDBChange, gainDB = tsf_gainToDecibels(volume);
+ tsf_channel_init(f, channel);
+ for (gainDBChange = gainDB - f->channels->channels[channel].gainDB, v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++)
+ if (v->playingChannel == channel && v->playingPreset != -1)
+ v->noteGainDB += gainDBChange;
+ f->channels->channels[channel].gainDB = gainDB;
+}
+
+TSFDEF void tsf_channel_note_on(tsf* f, int channel, int key, float vel)
+{
+ if (!f->channels) return;
+ f->channels->activeChannel = channel;
+ tsf_note_on(f, f->channels->channels[channel].presetIndex, key, vel);
+}
+
+TSFDEF void tsf_channel_note_off(tsf* f, int channel, int key)
+{
+ struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum, *vMatchFirst = TSF_NULL, *vMatchLast;
+ for (; v != vEnd; v++)
+ {
+ //Find the first and last entry in the voices list with matching channel, key and look up the smallest play index
+ if (v->playingPreset == -1 || v->playingChannel != channel || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE) continue;
+ else if (!vMatchFirst || v->playIndex < vMatchFirst->playIndex) vMatchFirst = vMatchLast = v;
+ else if (v->playIndex == vMatchFirst->playIndex) vMatchLast = v;
+ }
+ if (!vMatchFirst) return;
+ for (v = vMatchFirst; v <= vMatchLast; v++)
+ {
+ //Stop all voices with matching channel, key and the smallest play index which was enumerated above
+ if (v != vMatchFirst && v != vMatchLast &&
+ (v->playIndex != vMatchFirst->playIndex || v->playingPreset == -1 || v->playingChannel != channel || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE)) continue;
+ tsf_voice_end(v, f->outSampleRate);
+ }
+}
+
+TSFDEF void tsf_channel_note_off_all(tsf* f, int channel)
+{
+ struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum;
+ for (; v != vEnd; v++)
+ if (v->playingPreset != -1 && v->playingChannel == channel && v->ampenv.segment < TSF_SEGMENT_RELEASE)
+ tsf_voice_end(v, f->outSampleRate);
+}
+
+TSFDEF void tsf_channel_sounds_off_all(tsf* f, int channel)
+{
+ struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum;
+ for (; v != vEnd; v++)
+ if (v->playingPreset != -1 && v->playingChannel == channel && (v->ampenv.segment < TSF_SEGMENT_RELEASE || v->ampenv.parameters.release))
+ tsf_voice_endquick(v, f->outSampleRate);
+}
+
+TSFDEF int tsf_channel_get_preset_index(tsf* f, int channel)
+{
+ return (f->channels ? f->channels->channels[channel].presetIndex : 0);
+}
+
+TSFDEF int tsf_channel_get_preset_bank(tsf* f, int channel)
+{
+ return (f->channels ? f->presets[f->channels->channels[channel].presetIndex].bank : 0);
+}
+
+TSFDEF int tsf_channel_get_preset_number(tsf* f, int channel)
+{
+ return (f->channels ? f->presets[f->channels->channels[channel].presetIndex].preset : 0);
+}
+
+TSFDEF int tsf_channel_get_pitchwheel(tsf* f, int channel)
+{
+ return (f->channels ? f->channels->channels[channel].pitchWheel : 8192);
+}
+
+TSFDEF float tsf_channel_get_pan(tsf* f, int channel)
+{
+ return (f->channels ? f->channels->channels[channel].panOffset - 0.5f : 0.5f);
+}
+
+TSFDEF float tsf_channel_get_volume(tsf* f, int channel)
+{
+ return (f->channels ? tsf_decibelsToGain(f->channels->channels[channel].gainDB) : 1.0f);
}
#ifdef __cplusplus