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:
var
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))
Else
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))
End
End
initialization
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
myRamp.SetGeneratorLength(ramp_time);
// 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
end
// 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
end
On WidgetValueChanged (mute_state : double) from mute_button
If mute_state >0.9 Then
SetWidgetLabel (mute_button, "PRESS TO UNMUTE")
EnableGenerator(myRamp, True)
SetTimersRunning(true)
volUp=false
Else
SetWidgetLabel (mute_button, "PRESS TO MUTE")
EnableGenerator(myRamp, True)
SetTimersRunning(true)
volUp=true
End
end
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”…
Just check “fuzzy”
Just check if the value is grater than for example 0.99
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.
Here is an example:
var
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
Initialization
env.SetADSRAttackLevel(1.0) // Half way
env.SetADSRAttackTime(1000) // 500 milliseconds
env.SetADSRReleaseTime(2000)
//SetGeneratorCoarseness(env, 100 )
env.EnableGenerator(true)
SetTimersRunning(true)
//StopTimers();
OpenLogWindow()
ClearLogWindow()
Print("Initialization")
End
on Activate
Print("Activate")
SetTimersRunning(true)
End
On Deactivate
SetTimersRunning(false)
Print("Dectivate")
End
var
SL88 : MidiInBlock // This is my keyboard
On NoteOnEvent(m : NoteMessage) from SL88
var
n : integer
n = m.GetNoteNumber()
//Print(n)
// 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
end
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
end
var
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)
End
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…