Skip to content

Commit

Permalink
fix #127341: implements portamento for fluid
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelFroelich authored and vpereverzev committed Oct 23, 2020
1 parent 78f1218 commit cf03d9a
Show file tree
Hide file tree
Showing 13 changed files with 226 additions and 17 deletions.
15 changes: 12 additions & 3 deletions audio/exports/exportmidi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -336,18 +336,27 @@ bool ExportMidi::write(QIODevice* device, bool midiExpandRepeats, bool exportRPN
// ignore noteoff but restrike noteon
continue;

if (!exportRPNs && event.type() == ME_CONTROLLER && event.portamento())
// ignore portamento control events if exportRPN isn't switched on
continue;

char eventPort = cs->masterScore()->midiPort(event.channel());
char eventChannel = cs->masterScore()->midiChannel(event.channel());
if (port != eventPort || channel != eventChannel)
continue;

if (event.type() == ME_NOTEON) {
track.insert(pauseMap.addPauseTicks(i->first), MidiEvent(ME_NOTEON, channel,
event.pitch(), event.velo()));
// use the note values instead of the event values if portamento is suppressed
if (!exportRPNs && event.portamento())
track.insert(pauseMap.addPauseTicks(i->first), MidiEvent(ME_NOTEON, channel,
event.note()->pitch(), event.velo()));
else
track.insert(pauseMap.addPauseTicks(i->first), MidiEvent(ME_NOTEON, channel,
event.pitch(), event.velo()));
}
else if (event.type() == ME_CONTROLLER) {
track.insert(pauseMap.addPauseTicks(i->first), MidiEvent(ME_CONTROLLER, channel,
event.controller(), event.value()));
event.controller(), event.value()));
}
else if(event.type() == ME_PITCHBEND) {
track.insert(pauseMap.addPauseTicks(i->first), MidiEvent(ME_PITCHBEND, channel,
Expand Down
15 changes: 14 additions & 1 deletion audio/midi/event.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ class Score;

enum class BeatType : char;

// 4 is the default for the majority of synthesisers, aka VSTis
const int PITCH_BEND_SENSITIVITY = 4;

const int MIDI_ON_SIGNAL = 127;

//---------------------------------------------------------
// Event types
//---------------------------------------------------------
Expand Down Expand Up @@ -103,10 +108,11 @@ enum {
CTRL_MODULATION = 0x01,
CTRL_BREATH = 0x02,
CTRL_FOOT = 0x04,
CTRL_PORTAMENTO_TIME = 0x05,
CTRL_PORTAMENTO_TIME_MSB= 0x05,
CTRL_VOLUME = 0x07,
CTRL_PANPOT = 0x0a,
CTRL_EXPRESSION = 0x0b,
CTRL_PORTAMENTO_TIME_LSB= 0x25,
CTRL_SUSTAIN = 0x40,
CTRL_PORTAMENTO = 0x41,
CTRL_SOSTENUTO = 0x42,
Expand Down Expand Up @@ -242,6 +248,7 @@ class NPlayEvent : public PlayEvent {
const Harmony* _harmony{nullptr};
int _origin = -1;
int _discard = 0;
bool _portamento = false;

public:
NPlayEvent() : PlayEvent() {}
Expand All @@ -260,6 +267,12 @@ class NPlayEvent : public PlayEvent {
void setDiscard(int d) { _discard = d; }
int discard() const { return _discard; }
bool isMuted() const;
void setPortamento(bool p) { _portamento = p; }
bool portamento() const {
return (_portamento == true ||
(this->type() == ME_CONTROLLER &&
(this->controller() == CTRL_PORTAMENTO || this->controller() == CTRL_PORTAMENTO_CONTROL ||
this->controller() == CTRL_PORTAMENTO_TIME_MSB || this->controller() == CTRL_PORTAMENTO_TIME_LSB))); }
};

//---------------------------------------------------------
Expand Down
6 changes: 6 additions & 0 deletions audio/midi/fluid/chan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ int Channel::getCC(int num)
return ((num >= 0) && (num < 128))? cc[num] : 0;
}

int Channel::getFromKeyPortamento() {
if (synth)
return synth->getFromKeyPortamento();
return -1;
}

//---------------------------------------------------------
// pitchBend
//---------------------------------------------------------
Expand Down
39 changes: 39 additions & 0 deletions audio/midi/fluid/fluid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ namespace FluidS {

bool Fluid::initialized = false;

/* better than a macro to determine inappropriate values for notes*/
bool validNote(const int input) {
return (input < 255 && input > -1);
}

/* default modulators
* SF2.01 page 52 ff:
*
Expand Down Expand Up @@ -98,6 +103,9 @@ void Fluid::init(float sampleRate)
_tuning[i] = i * 100.0;
_masterTuning = 440.0;

fromkey_portamento = Channel::INVALID_NOTE;
lastNote = Channel::INVALID_NOTE;

for (int i = 0; i < 512; i++)
freeVoices.append(new Voice(this));
}
Expand Down Expand Up @@ -317,6 +325,37 @@ void Fluid::pitch_wheel_sens(int chan, int val)
channel[chan]->pitchWheelSens(val);
}

/*
* setFromKeyPortamento
* requires an input for a default value, usually the TPC
*/
void Fluid::setFromKeyPortamento(int chan, int defaultValue) {
int ptc = get_cc(chan, PORTAMENTO_CTRL);
if (validNote(ptc)) {
resetPortamento(chan);
fromkey_portamento = ptc;
/*
// Assumedly this fixed some sort of bug in FluidSynth2
if (!validNote(defaultValue))
defaultValue = ptc;*/
}
else {

/* determines and returns fromkey portamento */
fromkey_portamento = Channel::INVALID_NOTE;

if (portamentoTime(chan)) {
/* Portamento when Portamento pedal is On */
/* 'fromkey portamento'is determined from the portamento mode
and the most recent note played (prev_note)*/
if (validNote(defaultValue))
fromkey_portamento = ptc;
else
fromkey_portamento = lastNote;
}
}
}

/*
* fluid_synth_get_preset
*/
Expand Down
13 changes: 13 additions & 0 deletions audio/midi/fluid/fluid.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ class Channel {
Preset* _preset;

public:
enum { INVALID_NOTE = -1};

int channum;
short channel_pressure;
short pitch_bend;
Expand Down Expand Up @@ -287,6 +289,7 @@ class Channel {
int channelPressure() const { return channel_pressure; }
void setKeyPressure(int key, int val);
int keyPressure(int key) const { return key_pressure[key]; }
int getFromKeyPortamento();
};

// subsystems:
Expand Down Expand Up @@ -321,6 +324,9 @@ class Fluid : public Synthesizer {
//the variable is used to stop loading samples from the sf files
bool _globalTerminate = false;

int fromkey_portamento = Channel::INVALID_NOTE;
int lastNote = Channel::INVALID_NOTE;

protected:
int _state; // the synthesizer state

Expand Down Expand Up @@ -372,6 +378,10 @@ class Fluid : public Synthesizer {
void update_presets();

int get_cc(int chan, int num) const { return channel[chan]->cc[num]; }
void resetPortamento(int chan) const { channel[chan]->cc[PORTAMENTO_CTRL] = Channel::INVALID_NOTE; }
int portamentoTime(int chan) const {
return get_cc(chan, PORTAMENTO_TIME_MSB) * 128 + get_cc(chan, PORTAMENTO_TIME_LSB);
}

void system_reset();
void program_change(int chan, int prognum);
Expand Down Expand Up @@ -423,6 +433,9 @@ class Fluid : public Synthesizer {

bool globalTerminate() { return _globalTerminate; }
void setGlobalTerminate(bool terminate = true) { _globalTerminate = terminate; }
int getFromKeyPortamento() { return fromkey_portamento; }
void setFromKeyPortamento(int chan, int defaultValue);
void setLastNote(int last_note) { this->lastNote = last_note; }

friend class Voice;
friend class Preset;
Expand Down
9 changes: 6 additions & 3 deletions audio/midi/fluid/sfont.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ bool Preset::noteon(Fluid* synth, unsigned id, int chan, int key, int vel, doubl
Instrument* inst = preset_zone->get_inst();
Zone* global_inst_zone = inst->get_global_zone();

/* set portamento attributes*/
synth->setFromKeyPortamento(chan, key);

/* run thru all the zones of this instrument */
for(Zone* inst_zone : inst->get_zone()) {
/* make sure this instrument zone has a valid sample */
Expand Down Expand Up @@ -342,15 +345,15 @@ bool Preset::noteon(Fluid* synth, unsigned id, int chan, int key, int vel, doubl
mod = mod_list[i];
if ((mod != 0) && (mod->amount != 0)) { /* disabled modulators can be skipped. */
/* Preset modulators -add- to existing instrument /
* default modulators. SF2.01 page 70 first bullet on
* page */
* default modulators. SF2.01 page 70 first bullet on
* page */
voice->add_mod(mod, FLUID_VOICE_ADD);
}
}

/* add the synthesis process to the synthesis loop. */
synth->start_voice(voice);

synth->setLastNote(key);
/* Store the ID of the first voice that was created by this noteon event.
* Exclusive class may only terminate older voices.
* That avoids killing voices, which have just been created.
Expand Down
72 changes: 65 additions & 7 deletions audio/midi/fluid/voice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ namespace FluidS {
/* min vol envelope release (to stop clicks) in SoundFont timecents */
#define FLUID_MIN_VOLENVRELEASE -7200.0f /* ~16ms */

/* buffer size*/
#define FILTER_TRANSITION_SAMPLES 64

//---------------------------------------------------------
// triangle - calc value of triangle function for lfos
Expand All @@ -77,6 +79,14 @@ int samplesToNextTurningPoint(int dur, int pos) {
return ((dur/2)-(pos%(dur/2))) % (dur/2);
}

//---------------------------------------------------------
// channelPortamentoTime
// Grabs the Portamento time CC from the midi instructions
//---------------------------------------------------------

float samplesToNextTurningPoint(Channel* c) {
return ((c)->cc[PORTAMENTO_TIME_MSB] * 128 + (c)->cc[PORTAMENTO_TIME_LSB]);
}

//---------------------------------------------------------
// Voice
Expand Down Expand Up @@ -176,6 +186,10 @@ void Voice::init(Sample* _sample, Channel* _channel, int _key, int _vel,
hist1 = 0;
hist2 = 0;

/* portamento */
pitchinc = 0;
pitchoffset = 0;

/* Set all the generators to their default value, according to SF
* 2.01 section 8.1.3 (page 48). The value of NRPN messages are
* copied from the channel to the voice's generators. The sound font
Expand Down Expand Up @@ -498,19 +512,32 @@ bool Voice::generateDataForDSPChain(unsigned framesBufCount)
}

fluid_check_fpe ("voice_write amplitude calculation");

/* Calculate the number of samples, that the DSP loop advances
* through the original waveform with each step in the output
* buffer. It is the ratio between the frequencies of original
* waveform and output waveform.*/

{
float cent = pitch + modlfo_val * modlfo_to_pitch
float cent = pitch + pitchoffset + modlfo_val * modlfo_to_pitch
+ viblfo_val * viblfo_to_pitch
+ modenv_val * modenv_to_pitch;
phase_incr = _fluid->ct2hz_real(cent) / root_pitch_hz;
}


if (pitchoffset < 0.0f) {
float diff = pitchinc * framesBufCount;
pitchoffset -= diff;
if ( pitchoffset > 0.0f)
pitchoffset = 0.0f;
}
else if (pitchoffset > 0.0f) {
float diff = pitchinc * framesBufCount;
pitchoffset -= diff;
if (pitchoffset < 0.0f)
pitchoffset = 0.0f;
}

/* if phase_incr is not advancing, set it to the minimum fraction value (prevent stuckage) */
if (phase_incr == 0)
phase_incr = 1;
Expand Down Expand Up @@ -599,9 +626,6 @@ bool Voice::generateDataForDSPChain(unsigned framesBufCount)
* the filter is still too 'grainy', then increase this number
* at will.
*/

#define FILTER_TRANSITION_SAMPLES 64 // (FLUID_BUFSIZE)

a1_incr = (a1_temp - a1) / FILTER_TRANSITION_SAMPLES;
a2_incr = (a2_temp - a2) / FILTER_TRANSITION_SAMPLES;
b02_incr = (b02_temp - b02) / FILTER_TRANSITION_SAMPLES;
Expand Down Expand Up @@ -812,6 +836,15 @@ void Voice::voice_start()
for (int i = 0; list_of_generators_to_initialize[i] != -1; i++)
update_param(list_of_generators_to_initialize[i]);

/* fromkey note comes from "GetFromKeyPortamentoLegato()" detector.
When fromkey is set to ValidNote , portamento is started */
/* Return fromkey portamento */
int fromkey = channel->getFromKeyPortamento();
if (fromkey != Channel::INVALID_NOTE) {
/* Send portamento parameters to the voice dsp */
updatePortamento(fromkey, key);
}

/* Make an estimate on how loud this voice can get at any time (attenuation). */
min_attenuation_cB = get_lower_boundary_for_attenuation();

Expand Down Expand Up @@ -1933,5 +1966,30 @@ void Voice::effects(int startBufIdx, int count, float* out, float* reverb, float
}
}
}
}

/** legato update functions --------------------------------------------------*/
/* Updates voice portamento parameters
*
* @fromkey the beginning pitch of portamento.
* @tokey the ending pitch of portamento.
*
* The function calculates pitch offset and increment, then these parameters
* are send to the dsp.
*/
void Voice::updatePortamento(int fromkey, int tokey) {
/* calculates pitch offset */
float PitchBeg = gen[GEN_SCALETUNE].val
* (fromkey - root_pitch / 100.0f) + root_pitch;
float PitchEnd = gen[GEN_SCALETUNE].val
* (tokey - root_pitch / 100.0f) + root_pitch;
float pitchoffset = PitchBeg - PitchEnd;
setPortamento(samplesToNextTurningPoint(channel), pitchoffset);
}

void Voice::setPortamento(unsigned int countinc, float pitchoffset) {
if (countinc) {
this->pitchoffset += pitchoffset;
this->pitchinc = pitchoffset/(_fluid->sampleRate()*(0.001f*countinc));//countinc;
}
}
}
8 changes: 8 additions & 0 deletions audio/midi/fluid/voice.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ class Voice
int loopstart;
int loopend;

/* Stuff needed for portamento calculations */
float pitchoffset; /* the portamento range in midicents */
float pitchinc; /* the portamento increment in midicents */

/* vol env */
fluid_env_data_t volenv_data[FLUID_VOICE_ENVLAST];
unsigned int volenv_count;
Expand Down Expand Up @@ -268,6 +272,10 @@ class Voice
int dsp_float_interpolate_linear(unsigned);
int dsp_float_interpolate_4th_order(unsigned);
int dsp_float_interpolate_7th_order(unsigned);

void updatePortamento(int fromKey, int toKey);
float fluid_voice_calculate_pitch(int key);
void setPortamento(unsigned int countinc, float pitchoffset);
};
}

Expand Down
4 changes: 4 additions & 0 deletions libmscore/property.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,8 @@ QVariant propertyFromString(Pid id, QString value)
return QVariant(int(GlissandoStyle::BLACK_KEYS));
else if ( value == "diatonic")
return QVariant(int(GlissandoStyle::DIATONIC));
else if ( value == "portamento")
return QVariant(int(GlissandoStyle::PORTAMENTO));
else // e.g., normally "Chromatic"
return QVariant(int(GlissandoStyle::CHROMATIC));
}
Expand Down Expand Up @@ -758,6 +760,8 @@ QString propertyToString(Pid id, QVariant value, bool mscx)
return "whitekeys";
case GlissandoStyle::DIATONIC:
return "diatonic";
case GlissandoStyle::PORTAMENTO:
return "portamento";
case GlissandoStyle::CHROMATIC:
return "Chromatic";
}
Expand Down
Loading

0 comments on commit cf03d9a

Please sign in to comment.