Easiest way to set up chord progression using a foot pedal and GPScript?

Hi all,

Jordan Rudess of Dream Theater does this cool thing where he’s programmed a chord progression pad using a foot pedal to step through them, letting him play his solos on top. This is probably a very common technique and one I’m tempted to go for using GigPerformer.

As I’ve mentioned in previous threads, I’m no coder/scripter. I can pull of very basic stuff, but here I’m not completely sure where to begin.

I have a MidiInBlock, “K49”. I want to use the sustain pedal as a trigger for a sustained note. The MIDI Monitor says “CC 64 Hold Pedal (on/off): 127 Channel 1” when I press the pedal. I figure I can use this in an On MidiEvent script. BUT, I quickly get stuck when checking to see if the script received an on or off signal:

m is a string, right? So why does it say mismatched? Am I using the if/else statement wrong?
I had only one = in that script. Using == gives this error:

As you can see, I’m getting really confused trying to do these very simple steps and I haven’t even begun to figure out the tricky part of triggering a sequence of preprogrammed chords.

My plan is to use RipChord to map a all notes C1-C2 to different chords, then have this script just step through the full octave, triggering 12 chords. This way I can reuse the script and use RipChord to program what chords I want to be played.

Here’s an early attempt at this. This isn’t working, it’s just to show the simple idea:

var
  K49 : MidiInBlock
  i : integer

Initialization //or triggered by a widget
  i = 0
End

On MidiEvent(m : MidiMessage) from K49
  if m = "CC 64 Hold Pedal (on/off): 127 Channel 1" then
    SendNow(K49, Transpose(C1, i)) //C1 is the first note to be sent through RipChord
    i += 1
  else
  end
End

Any input on this would be highly appreciated! As soon as I get a basic script up and running, I’ll be able to expand on it to create more advanced functionality. But TBH I don’t find the script documentation very helpful. Like what is wrong in the screenshots I provided? How do I figure this stuff out on my own?

try this:

var
  K49 : MidiInBlock
  i : integer

Initialization //or triggered by a widget
  i = 0
End

On ControlChangeEvent(m : ControlChangeMessage) matching 64 from K49
  if GetCCValue(m) == 127 then
    i = i+1
  end  
End

On NoteEvent(m : NoteMessage) from K49
 SendNow(K49, Transpose(m, i))
end
1 Like

The Midi Message is AFAIK not a simple string. It contains various information, the note value, the velocity, the note off, aftertouch… (and probably many more).
If you “catch” a whole midi message, you’d have to separate the particular part-message you are actually after. There are diffrent functions for this.
But you can simplify your task by using a “constrained callback” for a special Control Change Event.
Your sustain pedal sends out the control change message (aka “CC”) number 64 - that’s what the midi monitor shows you. Each midi controller has its own (channel) number - Sustain is CC#64.
So if you press your sustain pedal, a value of 127 (=max=ON) will be sent on CC#64, if you relase it, a value of 0 (=min=OFF) will be sent.
You’d just have to intercept all changes that come in from CC#64 and checke for the value that is sent.
That’s what PianoPaul showed in his code snippet: Contrained Callback for CC change!
See also the manual for this:
https://gigperformer.com/docs/GPScript36/content/reference/list-of-callbacks.html#constrained-midi-events

2 Likes

Hey, that’s great!

How should I go about declaring a note (C1) instead of the On NoteEvent?

image
So it starts at CC#36, if I read this correctly.

So I want the script to react to CC#64 and send this sustained note plus transposition.

Thanks, schamass. That makes sense. With paul’s snippet I now have a way to check for the correct trigger.

try this - did not test it :wink:

var
  K49 : MidiInBlock
  i : integer
  NM : NoteMessage

Initialization //or triggered by a widget
  i = 0
  NM = MakeNoteMessage(C1, 127)
End

On ControlChangeEvent(m : ControlChangeMessage) matching 64 from K49
  if GetCCValue(m) == 127 then
    i = i+1
    SendNow(K49, Transpose(NM, i))
  end  
End
1 Like

So it starts at CC#36, if I read this correctly.

No, CC# are not notes!
What you see here is the note value which means “the note #36” is equal to what we call C1 (note #37 would be C#1, #38 = D1, and so on…). A MIDI-Value always goes from 0 to 127.

1 Like

This works. However, the last note is still sustained when triggering the next one.

Oh right, #36 is C1, CC#36 is just a control change. I’m not very good with MIDI, so it’s easy to slip up.

OK, let me think :wink:

1 Like

Again try this:

var
  K49 : MidiInBlock
  i : integer
  NM : NoteMessage
  PN : Integer

Initialization //or triggered by a widget
  i = 0
  PN = 0
  NM = MakeNoteMessage(C1, 127)
End

On ControlChangeEvent(m : ControlChangeMessage) matching 64 from K49
  if PN > 0 then
    SendNow(K49, ReinterpretAsNoteOffMessage(Transpose(NM,PN)))
  end
  
  if GetCCValue(m) == 127 then
    i = i+1
    SendNow(K49, Transpose(NM, i))
    PN = i
  end  
End

On NoteEvent(m : NoteMessage) from K49
 SendNow(K49, Transpose(m, i))
end
1 Like

Great stuff! But now I need to keep CC#64 pressed to get a sustained note. How would I make the note sustained until the next time CC#64 is pressed?

var
  K49 : MidiInBlock
  i : integer
  NM : NoteMessage
  PN : Integer

Initialization //or triggered by a widget
  i = 0
  PN = 0
  NM = MakeNoteMessage(C1, 127)
End

On ControlChangeEvent(m : ControlChangeMessage) matching 64 from K49
  if PN > 0 and GetCCValue(m) == 127 then
    SendNow(K49, ReinterpretAsNoteOffMessage(Transpose(NM,PN)))
  end
  
  if GetCCValue(m) == 127 then
    i = i+1
    SendNow(K49, Transpose(NM, i))
    PN = i
  end  
End

On NoteEvent(m : NoteMessage) from K49
 SendNow(K49, Transpose(m, i))
end
1 Like

Thank you! This works great. One last question. I added reset widget button which resets i, PN and NM. I also want it to send an All Off message, since I now can’t stop it from playing the last note.

Either an All Off or just the last pressed note with a value of 0.

Create a Button Widget and give it the OSC/GPScript Name NOTE_OFF

var
  K49 : MidiInBlock
  i : integer
  NM : NoteMessage
  PN : Integer
  NOTE_OFF : Widget

Initialization //or triggered by a widget
  i = 0
  PN = 0
  NM = MakeNoteMessage(C1, 127)
End

On ControlChangeEvent(m : ControlChangeMessage) matching 64 from K49
  if PN > 0 and GetCCValue(m) == 127 then
    SendNow(K49, ReinterpretAsNoteOffMessage(Transpose(NM,PN)))
  end
  
  if GetCCValue(m) == 127 then
    i = i+1
    SendNow(K49, Transpose(NM, i))
    PN = i
  end  
End

On NoteEvent(m : NoteMessage) from K49
 SendNow(K49, Transpose(m, i))
end

// Called when a widget value has changed
On WidgetValueChanged(newValue : double) from NOTE_OFF
 if newValue == 1.0 then
    SendNow(K49, ReinterpretAsNoteOffMessage(Transpose(NM,PN)))
    NOTE_OFF.SetWidgetValue(0.0)
 end   
End
1 Like

Thanks again. You guys are so freaking amazing helping us noobs out in this forum!

I removed

On NoteEvent(m : NoteMessage) from K49
 SendNow(K49, Transpose(m, i))
end

since it made every key transpose, not just the pedal-triggered note.
I also added

i = 0
PN = 0
NM = MakeNoteMessage(C1, 127)    

to the OnWidgetValueChanged-event. But is there a way to just reinitialize, so I don’t need the same script twice?

In another application using a similar script language, I create sub-routines that can be called from within different events, letting me reuse much of my code. Is there an equivalent in GPScript?

Fine, that I forgot…

You can create local functions in a rackspace script and reuse that in different events.

That’s the thing. I’m used to create global functions AND sub-routines, serving different purposes. In this case, I would need a sub that could be called both on initialize and on the reset-widget. Would a function be able to do that?

@dhj, if you’re reading this, that’s a feature request for GP4. :wink:

That should be already there…
Please read the Scripting manual!
See also here:
https://gigperformer.com/docs/GPScript36/content/language-manual/introduction.html#user-functions

If you have read the whole manual and you should have specific questions, we are pleased to help you with those. :slight_smile:
I should say “RTFM” but i won’t. :smiley: :wink: (please don’t feel offended by this, i am just kidding)

Huh? What’s stopping you? For example…

 Function Foo (i : integer)
     Print(i)
 End


Initialization
  Foo(42)
End

 On Activate
    Foo(63)
 End

 On WidgetValueChanged(newValue : Double) from someWidget
     Foo(Round(newValue*100))
 End
1 Like