Send time-based midi message

I want to press a button on a keyboard controller and have Gig Performer intercept the midi cc and then send a time-based midi command to another midi enabled device (e.g. x32 mixer).
English version of command would be, for example:
On channel 1, CC 54, Send data 0 and increase from 0 to 127 in 70 milliseconds.
PURPOSE: I would want to ultimately want to use a momentary button to have effect of unmute when pressed, and mute when released. But instead of sound of “instant unmute/mute” it has softer quick fade up from zero, and softer quick fade back down to zero. This should give more musical sound when manipulating an “effect send fader” to capture single words to which to apply long tempo-based delay.

Have you looked at something like the Melda MCCGenerator VST? It’s part of Melda’s free bundle.

I haven’t used it myself, but I think it’s built for that kind of thing.

It is looking like Bome Midi Translator is only tool I know can do what I want. So far, I did not find any indication that Melda MCC Generator can do time-based midi “scripts”.

You will be able to use GPScript to do this. Take a look at this thread:

That script was written for me to use. I’m sure it works ok, but I didnt have much joy with it. In the end, I bought the Melda Rhythmizer vst which works very well. Mind you, I send the midi commands from an iPad over a wireless network which doesn’t involve any widgets etc.

I set up a rackspace with a script for the desired functionality because i liked the idea of smoothly mute/unmute a channel with one press on a button.
I Tried the rackspace and the script on my Behringer XR18 and it worked quite well - however there is a behaviour that i don’t understand and that i could’nt get to work really properly.
The ramp function i am using in the script to get a value change from 0 to 1 over a given time base, almost never reaches it’s extreme-values (means, it mostly stops some numbers before 0 or 127, so it’ll never completely mute or crank up the fader).
Am i missing something?

Autofade.rackspace (8.2 KB)

Here’s the script:

myRamp  : Ramp // A generator that moves smoothly from 0.0 to 1.0 over some specified time
mute_button,  MASTER : widget
extMidi_out     : MidiOutBlock
vol_value : double
ramp_time,CC_nr, send_chan   : integer
ramp_running, volUp : boolean

//user defined function to set and send values (widget related lines are optional)
function SetValues (volumeUp : boolean, valY : double)
    if volumeUp==true then
        SetWidgetLabel(MASTER, ParamToMidi(valY))
        SetWidgetValue(MASTER, valY)
        //next line sends out the CC-message
        SendNowExternal(extMidi_out, MakeControlChangeMessageEx(CC_nr, ParamToMidi(valY), send_chan))
        SetWidgetLabel(MASTER, ParamToMidi(1.0-valY))
        SetWidgetValue(MASTER, 1.0-valY)
        //next line sends out the CC-message
        SendNowExternal(extMidi_out, MakeControlChangeMessageEx(CC_nr, ParamToMidi(1.0-valY), send_chan))
    CC_nr = 16 //CC-Number to send
    send_chan =1 //MIDI channel to send to
    ramp_time = 500 // 500ms Ramp duration
    SetGeneratorOneShot(myRamp, true) // Generator will only run once when triggered

   // Also, check the SetGeneratorCoarseness function
   // which can be used to control how often we get called back
   // Choose values wisely! You're trading accuracy against CPU cycles


// This gets called by the ramp generator as time passes
on TimePassing(timeX : integer, amplitudeY : double) from myRamp
    SetValues(volUp,amplitudeY) //calls the user defined function from above

On WidgetValueChanged (mute_state : double) from mute_button
    If mute_state >0.9 Then
        SetWidgetLabel (mute_button, "PRESS TO UNMUTE")
        EnableGenerator(myRamp, True)
        SetWidgetLabel (mute_button, "PRESS TO MUTE")
        EnableGenerator(myRamp, True)

You have to very careful with this stuff — time is not smooth — the timer gets a call every so often to increment the ramp. So for example (this is just an analogy), suppose you wanted to go from 0 to 127 in steps of 10

We would trigger at 0, 10, 20, 30, … 110, 120

But the next value at which we would trigger is 130 and that’s greater than our desired final value (127) so we won’t trigger any more.

You might want to look at the function SetGeneratorCoarseness to see if it can help.

I expected this ramp-generator to produce a value starting at 0 and rising to 1 within a defined period of time, and this is what i (almost) got from it. How really “smooth” this transition will be in the end is secondary.
The values from 0-127 are the result of a ParamToMidi conversion of the 0-1 values which come from the amplitudeY parameter.
So i thought a ramp (when started) would always start at a value of 0 and end up at a value of 1.0, but it doesn’t seem to do so, and this is what i don’t understand. Why does it start and end at random values around 0 and 1 but only seldom reaches the ends of the range? What is the benefit of such a behaviour?
I also tried diffrent values for SetGeneratorCoarseness (100, 10, 1) but it didn’t really help.
Is there a way to check when the ramp has reached its “real” end, or has stopped running, so i could then manually set the 1.0 value?
Maybe a callback for this could be useful? Something like “On RampEndReached”… :wink:

Just check “fuzzy”

Just check if the value is grater than for example 0.99

1 Like

There were deviations of 20% and more at the limits!
So i had to check if the value is between 0.2 and 0.8 and even with this wide tolerance, i experienced cases where even this was not enough. You might agree that this is absolutely unacceptable…

Yes 20% is too much.

Just an idea, did you try to use an ADSR?

That’s indeed something i could try… but i first have to check how to use that. :grimacing:

Here is an example:

   env : ADSR;    // An LFO function generator - yeah just a single period :-)
   Detune : Widget // A widget with the script name Detune which I have associated
                   // with the Detune parameter in FM8


   env.SetADSRAttackLevel(1.0)  // Half way
   env.SetADSRAttackTime(1000)   // 500 milliseconds
   //SetGeneratorCoarseness(env, 100 )

on Activate

On Deactivate

   SL88 : MidiInBlock   // This is my keyboard

On NoteOnEvent(m : NoteMessage) from SL88
   n : integer

   n = m.GetNoteNumber()

   // Set the value of the Detune widget by scaling the keyboard range into a desired range
   Detune.SetWidgetValue(Scale(n, 0, 127, 0.3, 0.7))

   SL88.SendNow(m)  // If we don't include this, the note won't get sent

   env.StartAttackPhase()  // Trigger the ADSR

On NoteOffEvent(m : NoteMessage) from SL88
var amp : double
   amp = GetGeneraterAmplitude(env)
   env.SetADSRSustainLevel( amp)
   env.StartReleasePhase()  // Switch to the release phase of the ADSR
   SL88.SendNow(m)  // If you don't include this you will get stuck notes

   EnvFollow : Widget  // We'll control this widget with the ADSR
   FM8 : PluginBlock

// Called by ADSR as time passes. Y is the current value of the ADSR
On TimePassing(millisecondsPassed : int, y : double) from env
   EnvFollow.SetWidgetValue( Scale( y, 0.0, 1.0, 0.3, 0.8) )
   //SetParameter(FM8, 34, y)


When i initialize the ADSR:
InitADSR ( f , attackTime , attackLevel , decayTime , sustainLevel , releaseTime )
Can i only use the attackTime (i.e. 500) and the attackLevel (1.0) and set the rest to 0 ?
Do i need to start the release time manually or is it triggered automatically?
I am not at my home-PC at the moment, so i can’t try it…