Arturia Keylab : display on LCD screen

I tried to copy your script in the gig script editor but I got an error. What did I wrong?
On the Global Rackspace I created a midi out block named (handle) Keylab88MidiOut.

I found the fault. I have to copy it in the GlobalRackspace Script instead of the Gig Script. Sorry for that…

3 Likes

You tried to use the script as a “GIG script”, which is not the same as the script of the Global Rackspace! (That’s what the error message is saying too).
Change over to the Global Rackspace, then use the “normal” Script editor for rackspace scripts… this should work then.

I found a description for the Arturia Keylab where you can use SysEx messages to control the LEDs via script.

Hope this is usefull for all.

With the exception of MMC messages, all Keylab specific sysex messages (out/to and in/from Keylab) follow the form:

F0 00 20 6B 7F 42 PAYLOAD F7

Where the PAYLOAD is one of the following:
Request a Value: 01 00 PARM CTRLID
Response from Keylab 02 00 PARM CTRLID VALUE
Set a Value: 02 00 PARM CTRLID VALUE
Set LED on/off: 02 00 10 00 00 CTRLID ONOFF
Set Display Line 1: 04 00 60 01 LINE1 00 7F
Set Display Line 2: 04 00 60 02 LINE2 00 7F
Set Display Line 1&2: 04 00 60 01 LINE1 00 02 LINE2 00 7F
Load Keyboard Preset: 05 PRESET
Save Keyboard Preset: 06 PRESET

Where:
LINE1 & LINE2 16 character ASCII string.
ONOFF on=7F, off=00.
PARM See Parameters section below.
CTRLID See Controls section below.
VALUE The 7-bit value either being set or reported by Keylab

Parameters
The PARM values are different for global settings vs control settings.
Generally, a control is configured by sequentially sending 6 command values (numbers 1 through 6).
For faders and encoders, parameters 0x40 and 0x41 can optionally be sent to.

For controls, the following trush table describes the meaning of each command:
PARM: 1 2 3 4 5 6 40 41 Faders Encoders Buttons Pads Transports
Off 00 - - - - - Yes Yes Yes Yes Yes
CC Fader 01 CH CC MIN MAX 0 0 1 Yes
CC Encoder 01 CH CC MIN MAX 0 1 5 Yes
CC Relative 01 CH CC 0 7F 1 1 5 Yes
CC Duration 05 CH CC CC CC2 0 0 Yes Yes
CC Toggle 08 CH CC OFF ON 0 Yes Yes Yes
CC Gate 08 CH CC OFF ON 1 Yes Yes Yes
Note Toggle 09 CH NOTE 0 VEL 0 Yes Yes Yes
Note Gate 09 CH NOTE 0 VEL 1 Yes Yes Yes
Keyboard Preset 0B 0 0-9 0 0 0 Yes Yes Yes
MMC 07 0 MMC 0 0 0 Yes Yes
NRPN 04 CH RPN MIN MAX 0 Yes Yes Yes
RPN 04 CH NRPN MIN MAX 1 Yes Yes Yes
Program Change 0B CH PROG LSB MSB 1 Yes Yes
Where:
MIN, MAX Midi cc values from 0 - 0x7F to be sent by the control.
ON, OFF Midi cc values to be sent by buttons for on/off or down/up actions
CC Midi CC number for continuous controller messages or brief-press messages.
CC2 Midi CC number for long-press (duration) messages.
CH Midi Channel: 0-0x0F=1-16, 0x41 = Part1, 0x40 = Part2, 0x7E = All, 0x7F = Panel.
MMC The MMC identifier to be sent in a MMC message when a button is pressed. Sysex received on click is F0 7F MMC 06 F7.

Control identifiers:
The following values represent the CTRLID used in the aforementioned messages:
Volume 0x30
Param 0x31
Value 0x33
Param Button 0x32
Value Button 0x34
Sound 0x1E
Multi 0x1F
Bank1 0x1D
Bank2 0x1C
Play 0x58
Stop 0x59
Record 0x5A
Rewind 0x5B
Forward 0x5C
Loop 0x5D
Switch 1-10 0x12 - 0x1B
Encoder 1-4 Bank 1 0x01 - 0x04
Encoder 6-9 Bank 1 0x05 - 0x08
Encoder 5 Bank 1 0x09
Encoder 10 Bank 1 0x0A // can be 0x6E on some firmware. Seriously Arturia?
Encoder 1-4 Bank 2 0x21 - 0x24
Encoder 6-9 Bank 2 0x25 - 0x28
Encoder 5 Bank 2 0x29
Encoder 10 Bank 2 0x2A
Faders 1-4 Bank 1 0x0B - 0x0E
Faders 5-9 Bank 1 0x4B - 0x4F
Faders 1-4 Bank 2 0x2B - 0x2E
Faders 5-9 Bank 2 0x6B-0x6F
Pad 01-16 0x70 - 0x7F
Mod Wheel 0x40

Global Configuration
Configuration of global values is similar to controller configuration, except that PARM is always 0x40, and the CTRLID stipulates which global value is being set or requested.
Global Parameter: CTRLID Values
Relative Knob Mode 0x02 0x01 = Absolute, 0x7F = Relative
Drawbar Mode 0x01 0x01 = Normal, 0x7F = Drawbar
Part 1 Midi Chanel 0x06 0x00-0x0F = Midi Channel 1-16
Part 2 Midi Chanel 0x05 0x00-0x0F = Midi Channel 1-16
Keyboard Split Mode 0x07D 0x01 = Off, 0x7F = Split Mode On
Keyboard Split Point 0x0D number - Midi note value. 0x3C=Middle C, 0=C-2
After Touch Chanel 0x0B 0x00-0x0F = Midi Channel 1-16, 0x7E=All, 0x40=Part1, 0x41=Part2, 0x7F=Panel }
Part 1 Transpose Octave 0x03 number: 0=No Transpose, 1/2=Octave+1/2, 0x41/42=Octave-1/2
Part 2 Transpose Octave 0x10 number: 0=No Transpose, 1/2=Octave+1/2, 0x41/42=Octave-1/2
Part 1 Transpose Chromatic 0x06 number
Part 2 Transpose Chromatic 0x11 number
Octave Buttons Assign 0x12 string, { 0=Part1, 1=Part2, 2=Both" })

Notes:
Keylab handles signed values as 6-bit signed integers, so that bit 6 (0x40) is the sign bit.
This means that positive numbers are in the range 0x01 to 0x3F, and negative numbers span 0x41 to 0x7F…
Actual Number: 3 2 1 0 -1 -2 -3
Keylab 6-bit value: 3 2 1 0 0x41 0x42 0x43

2 Likes

I tried to change the Pad LED when a Rackspace is aktive but I can’t get it work. May anybody help me?

This ist the part of the Global Rackspace Script:


// ******************* Callbacks *******************

// Called when you switch to another variation
On Variation (oldVariation : Integer, newVariation : Integer)

var
NewSong : String
SongIndex : String
SongPartIndex : Int
LED : SysexMessage

If InSetlistMode() Then
    NewSong = GetCurrentSongName()
Else
    NewSong = "Rackspaces"
End

KeylabLCD("S:" + NewSong, "P:" + GetCurrentSongPartName())

SongIndex = "69" + IntToHexString(GetCurrentSongIndex())
SongPartIndex = 11 + GetCurrentSongPart()

LED = "# F0 00 20 6B 7F 42 02 00 10" + SongIndex + " 7f F7"

SendSysexExternal(KeyLab88MidiOut, LED)


End

Thanks

Does it compile? I see you have a function ‘KeylabLCD’ being called, but I don’t see that function declaration in your script. What is that meant to do?

This is the whole Global Rackspace Script

var
SysexMng : SysexMessage
KeyLab88MidiOut : MidiOutBlock
ProtokolMidiOut : MidiOutBlock
MidiIn : MidiInBlock
KEYLAB_LCD_PRE : String
KEYLAB_LCD_SEP : String
KEYLAB_LCD_END : String
ASCII_STRING : String
ASCII_HEX_STRING : String array

Initialization
ASCII_STRING = " ! #%$&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[Y]^_`abcdefghijklmnopqrstuvwxyz{|}><";
ASCII_HEX_STRING = ["20","21","22","23","24","25","26","27","28","29","2A","2B","2C","2D","2E","2F","30","31","32","33","34","35","36","37","38","39","3A","3B","3C","3D","3E","3F","40","41","42","43","44","45","46","47","48","49","4A","4B","4C","4D","4E","4F","50","51","52","53","54","55","56","57","58","59","5A","5B","5C","5D","5E","5F","60","61","62","63","64","65","66","67","68","69","6A","6B","6C","6D","6E","6F","70","71","72","73","74","75","76","77","78","79","7A","7B","7C","7D","7E","7F"];
KEYLAB_LCD_PRE = "# F0 00 20 6B 7F 42 04 00 60 01 "
KEYLAB_LCD_SEP = " 00 02 "
KEYLAB_LCD_END = " 00 F7"
End

// ******************** Utils ********************

Function Trim(s : String) returns string
/* Trim the string if its length is greater than 16 characters */

var temp : String  // Avoid modifying argument in-place

If Length(s) > 16 Then
    temp = CopySubstring(s, 0, 16)
Else
    temp = s
End
result = temp
End

Function StringHexString(str : String) Returns String
/* Hack provided by David-san */

var i : Integer
result = ""

For i=0; i<Length(str) ; i=i+1 Do
    result = result + ASCII_HEX_STRING[IndexOfSubstring(ASCII_STRING, CopySubstring(str,i,1), True)]
End
End

// ***************** Keylab display *****************

Function KeylabLCD(line1 : String, line2 : String)
/* Display each line on the LCD screen of the Keylab */

var msg : SysexMessage

msg = KEYLAB_LCD_PRE + StringHexString(Trim(line1)) + KEYLAB_LCD_SEP + StringToHexString(Trim(line2)) + KEYLAB_LCD_END
// next three lines were relevant to GPScript V3 but are now depreciated!
//SM_CreateSysexFromString(SysexMng, msg)
//SM_SendMidiOut(SysexMng, Keylab88MidiOut)
//SM_SendMidiOut(SysexMng, Keylab61MidiOut)
SendSysexExternal(KeyLab88MidiOut, msg)

End

// ******************* Callbacks *******************

// Called when you switch to another variation
On Variation (oldVariation : Integer, newVariation : Integer)

var
NewSong : String
SongIndex : Int
SongIndexHex : String
SongPartIndex : Int
SongPartIndexHex : String
LED : SysexMessage

If InSetlistMode() Then
    NewSong = GetCurrentSongName()
Else
    NewSong = "Rackspaces"
End

KeylabLCD("S:" + NewSong, "P:" + GetCurrentSongPartName())

SongPartIndex = GetCurrentSongPart()

SongPartIndexHex = IntToHexString(SongPartIndex)


LED = "# F0 00 20 6B 7F 42 02 00 10 " + SongPartIndexHex + " 7f F7"


SendSysexExternal(KeyLab88MidiOut, LED)
SendSysexExternal(ProtokolMidiOut, LED)

End

//Called when you switch to another Rackspace
On Rackspace (oldRackspace : Integer, newRackspace : Integer)

var
NewSong : String
SongIndex : Int
SongIndexHex : String
SongPartIndex : Int
SongPartIndexHex : String
LED : SysexMessage

If InSetlistMode() Then
    NewSong = GetCurrentSongName()
Else
    NewSong = "Rackspaces"
End

//KeylabLCD("S:" + NewSong, "P:" + GetCurrentSongPartName())

SongIndex = GetCurrentSongIndex()

SongIndexHex = IntToHexString(112+SongIndex)

LED = "# F0 00 20 6B 7F 42 02 00 10 " + SongIndexHex + " 7f F7"


SendSysexExternal(KeyLab88MidiOut, LED)
SendSysexExternal(ProtokolMidiOut, LED)

End

@tobifis so this is Keylab 1st Generation documentation, but not MkII?

Could you also provide information about MkII? The text above is from Arturia Forum, right?

Yes it is for KeyLab88 MKI. I have the information from Arturia Forum. I think you should give it a try.

So, I have been curious: I just build a small gig and sent the MIDI outs to a MIDI monitor by IAC

I can see a few things in the MIDI monitor SysEx traces

  • If you change song parts in setlistmode the calculated CTLDID number starts from 0x00 to …, as there’s no offset in the On Variation callback, so there’s no corresponding PAD LED selected
  • in your On Rackspace Callback you calculate an offset of 112 = 0x70 = PAD 1 - seems OK
  • there’s no LED off so far…

As I have no MkI and there’s not confirmed documentation about the same assignment of CTLIDs for the MkII I did no further tests with real hardware

Tipps:

  • use the Script Log
    • add some Print() statements to your scripts
  • add an additional MIDI Output and redirect SysEX MIDI to a Monitor

HTH BBB

hi just stumbled over this ,old, topic.
did you manage to get it to work? (keylab49 mkII).
thanks

1 Like

Hi Megapeng

unfortunaty I did‘t try it until now. It is still a topic to do for me.

I got it working. But I want to send 2 Button via sysex message and the keylab just take the first. Is there a chance to send a sysex by changing songparts and another sysex to an other LED when changing Songs?

I’ve been finding it frustrating that whenever a controller on the Keylab mkii is adjusted, the details of this over-write the Song and Rackspace displayed on the LCD screen - I have made a further adjustment to the Global Rackspace Script and added a callback which resends the sysex to update the LCD screen every 3.5 seconds. Its a bit clunky but I can’t think of a better way to do it. The On TimePassing callback will be repeatedly called, where-as I was hoping to be able to use the On GeneratorEndCycle callback, so it is only called once at the end of the 3.5 second ramp, but it seems like the Global Rackspace Script can only use a restricted subset of the callbacks which are available elsewhere.

Any bright ideas for a more elegant solution? Ideally I wanted some method which only starts the ramp after any controller adjustments have been detected, and which is only called when the one shot ramp has ended? Anyway, here’s the updated Global Rackspace Script

var
SysexMng : SysexMessage
Keylab88MidiOut : MidiOutBlock // Replace ‘KeylabMidiOut’ with your own handle
Keylab61MidiOut : MidiOutBlock // Replace ‘KeylabMidiOut’ with your own handle
MidiIn : MidiInBlock
KEYLAB_LCD_PRE : String
KEYLAB_LCD_SEP : String
KEYLAB_LCD_END : String
RMP : Ramp
RSindex : integer
ThisRackspace : String
var NewSong : String

Initialization
KEYLAB_LCD_PRE = “# F0 00 20 6B 7F 42 04 00 60 01 "
KEYLAB_LCD_SEP = " 00 02 "
KEYLAB_LCD_END = " 00 F7”
SetGeneratorOneShot(RMP, true) // Generator will only run once when triggered
RMP.SetGeneratorLength(5000); // Five seconds
End

// ******************** Utils ********************

Function Trim(s : String) returns string
/* Trim the string to 16 characters */

result = CopySubstring(s, 0, 16)
End

// ***************** Keylab display *****************

Function KeylabLCD(line1 : String, line2 : String)
/* Display each line on the LCD screen of the Keylab */

var msg : SysexMessage
msg = KEYLAB_LCD_PRE + StringToHexString(Trim(line1)) + KEYLAB_LCD_SEP + StringToHexString(Trim(line2)) + KEYLAB_LCD_END//StringToHexString is now a built in function in v4
SendSysexExternal(Keylab88MidiOut, msg)
SendSysexExternal(Keylab61MidiOut, msg)

End

// ******************* Callbacks *******************

// Called when you switch to another rackspace
On Rackspace(oldRackspaceIndex : integer, newRackspaceIndex : integer)

If InSetlistMode() Then
    NewSong = GetCurrentSongName()
Else
    NewSong = "N/A"

End

RMP.EnableGenerator(false)
RMP.EnableGenerator(True)
SetTimersRunning(true)
KeylabLCD("S:" + NewSong, "R:" + GetRackspaceNameAtIndex(newRackspaceIndex))
ThisRackspace = GetRackspaceNameAtIndex(newRackspaceIndex)
Print(NewSong)
Print(GetRackspaceNameAtIndex(newRackspaceIndex))

End

On TimePassing(timex : integer, amplitudeY : double) from RMP// repeatedly send NewSong name every 3.5 seconds to over-write LCD display after CC change on keyboard.

if timex > 3500 then
    
    RMP.EnableGenerator(false)//reset ramp - might be a better way to do this
    RMP.EnableGenerator(true)//reset ramp - might be a better way to do this

    If InSetlistMode() Then
        NewSong = GetCurrentSongName()
    Else
        NewSong = "N/A"
    

    
    End
    

KeylabLCD("S:" + NewSong, "R:" + ThisRackspace)
//Print(NewSong)    just to test internally
//Print(ThisRackspace)    just to test internally

End

End

1 Like

Just correcting myself here - I just figured out that On GeneratorEndCycle works after GP4.5 (I was using 4.0something) Much better solution. I’ll repost when I work out how to post it with the correct indents!

If you use it a s a timer, make sure it goes over the value where you want to stop it…

var
    SysexMng         : SysexMessage
    Keylab88MidiOut  : MidiOutBlock  // Replace 'KeylabMidiOut' with your own handle 
    Keylab61MidiOut  : MidiOutBlock  // Replace 'KeylabMidiOut' with your own handle
    MidiIn : MidiInBlock
    KEYLAB_LCD_PRE   : String
    KEYLAB_LCD_SEP   : String
    KEYLAB_LCD_END   : String
    RMP : Ramp
    RSindex : integer
    ThisRackspace : String
    var NewSong : String
 
Initialization
    KEYLAB_LCD_PRE  = "# F0 00 20 6B 7F 42 04 00 60 01 "
    KEYLAB_LCD_SEP  = " 00 02 "
    KEYLAB_LCD_END  = " 00 F7"
    SetGeneratorOneShot(RMP, true) // Generator will only run once when triggered         
    RMP.SetGeneratorLength(3500); // 3.5 seconds
End

// ******************** Utils ********************

Function Trim(s : String) returns string
/* Trim the string to 16 characters */

   result = CopySubstring(s, 0, 16)
End

// ***************** Keylab display ***************** 

Function KeylabLCD(line1 : String, line2 : String)
    /* Display each line on the LCD screen of the Keylab */
    
    var msg : SysexMessage
    msg = KEYLAB_LCD_PRE + StringToHexString(Trim(line1)) + KEYLAB_LCD_SEP + StringToHexString(Trim(line2)) + KEYLAB_LCD_END//StringToHexString is now a built in function in v4
    SendSysexExternal(Keylab88MidiOut, msg)
    SendSysexExternal(Keylab61MidiOut, msg)
    
End

// ******************* Callbacks *******************

// Called when you switch to another rackspace
On Rackspace(oldRackspaceIndex : integer, newRackspaceIndex : integer)

    If InSetlistMode() Then
        NewSong = GetCurrentSongName()
    Else
        NewSong = "N/A"
    
    End
    
    RMP.EnableGenerator(false)// these two lines to restart the ramp
    RMP.EnableGenerator(True)// these two lines to restart the ramp
    SetTimersRunning(true)
    KeylabLCD("S:" + NewSong, "R:" + GetRackspaceNameAtIndex(newRackspaceIndex))
    ThisRackspace = GetRackspaceNameAtIndex(newRackspaceIndex)
    //Print(NewSong)//test
    //Print(GetRackspaceNameAtIndex(newRackspaceIndex))//test

End

//On TimePassing(timex : integer, amplitudeY : double) from RMP// repeatedly send NewSong name every 3.5 seconds to over-write LCD display after CC change on keyboard.
On GeneratorEndCycle(timex : integer) from RMP// repeatedly send NewSong name every 3.5 seconds to over-write LCD display after CC change on keyboard.
    //if timex > 3500 then
    RMP.EnableGenerator(false)//reset ramp - might be a better way to do this
    RMP.EnableGenerator(true)//restart ramp - might be a better way to do this!
    If InSetlistMode() Then
        NewSong = GetCurrentSongName()
    Else
        NewSong = "N/A"
    End
        
    KeylabLCD("S:" + NewSong, "R:" + ThisRackspace)
    //Print(NewSong)//test
    //Print(ThisRackspace)//test
  
End
1 Like

I think I would remove this from the On GeneratorEndCycle callback… :wink:

Ah yes of course, thanks!

I made an updated version that uses some of the new built-in functions - makes the script much shorter.

This is a really useful script since I don’t like to look at my computer when I’m performing. Combining this cool feature and using BandHelper PC’s to do Rackspace changes (and then seeing it on the LCD of my Keylab) makes it even easier to “Own The Stage”

I use Rackspaces rather than Songs - but it’s easy enough to change - here’s my updated script:

//Global Rackspace must have a MIDI OUT Plugin with the handle KeyLab88MIDIOut
var KeyLab88MIDIOut : MidiOutBlock


Function KeylabLCD(line1 : String, line2 : String)
    //Assemble a string in the format required by Arturia
    var 
        KEYLAB_LCD_PRE : String = "F0 00 20 6B 7F 42 04 00 60 01 "
        KEYLAB_LCD_SEP : String = "00 02 "
        KEYLAB_LCD_END : String ="00 F7"
        msg : SysexMessage = 
            KEYLAB_LCD_PRE 
            + StringToHexString(CopySubstring(line1,0,16)) 
            + KEYLAB_LCD_SEP 
            + StringToHexString(CopySubstring(line2,0,16)) 
            + KEYLAB_LCD_END
    
    SendSysexExternal(KeyLab88MIDIOut, msg)
End

//Callbacks
On Variation (old : Integer, new : Integer)
    var VariationName : String = GetVariationName(GetCurrentVariation())
    
    If GetVariationCount() ==1 Then
        VariationName=""
    End
    
    KeylabLCD(GetRackspaceNameAtIndex(GetCurrentRackspaceIndex()) ,VariationName)
End
4 Likes