Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MU3] Fix #127341: Implements portamento for fluid #5853

Merged
merged 1 commit into from
Oct 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -460,6 +460,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 @@ -759,6 +761,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