User Tools

Site Tools


prime4

I own a working force and a force where the internal soundcard doesn't work anymore. On my “not well working force” i use a presonus USB soundcard. With this external soundcard, i have sound output only by using prime4.

To use prime 4, you have to change your productcode to JC11 when binding. (mount -o bind productcode “/sys/firmware/devicetree/base/inmusic,product-code”)

When running prime you'll see which file you can create for your assignments. Below is an example when i run prime 4 :

air.assignments.deviceadapter: Could not find file: "Akai_Pro_Force_Public_Assignments.qml"
air.assignments.deviceadapter: Could not find file: "Akai_Pro_Force_Public_Device.qml"
air.assignments.deviceadapter: Could not find file: "Akai_Pro_Force_Private_Assignments.qml"
air.assignments.deviceadapter: Could not find file: "Akai_Pro_Force_Private_Device.qml"
air.assignments.deviceadapter: Could not find file: "Akai_Pro_Force_MIDI_Port_Assignments.qml"
air.assignments.deviceadapter: Could not find file: "Akai_Pro_Force_MIDI_Port_Assignments.qml"
air.assignments.deviceadapter: Could not find file: "Akai_Pro_Force_MIDI_Port_Device.qml"
air.assignments.deviceadapter: Could not find file: "Akai_Pro_Force_MIDI_Port_Device.qml"
air.assignments.deviceadapter: Could not find file: "AudioBox_USB_96_MIDI_1_Assignments.qml"
air.assignments.deviceadapter: Could not find file: "AudioBox_USB_96_MIDI_1_Assignments.qml"
air.assignments.deviceadapter: Could not find file: "AudioBox_USB_96_MIDI_1_Device.qml"
air.assignments.deviceadapter: Could not find file: "AudioBox_USB_96_MIDI_1_Device.qml"

So all these qml files can be created.

Then you can use the following qml to use it on your force. (qml syntax has been taken from Prime Firmware)

Akai_Pro_Force_Public_Device Akai_Pro_Force_Public_Assignments

Akai_Pro_Force_Private_Device Akai_Pro_Force_Private_Assignments

I will explain a bit the syntax

_Private_ files are used for inputs / actions buttons .

Akai_Pro_Force_Private_Device.qml : 

import airAssignments 1.0
import InputAssignment 0.1
import OutputAssignment 0.1
import Device 0.1
import QtQuick 2.9

// QT_LOGGING_RULES=air.planck.firmware.upgrade=true LD_LIBRARY_PATH=/usr/qt/lib /usr/Engine/JC11 -d0

Device {
	id: device

	property real gamma: 3.5
	property real padGamma: 3.5

	controls: []
	useGlobalShift: false
	numberOfLayers: 0

	property string deviceInfo: ""
	property string currentMixerFirmwareVersion

	///////////////////////////////////////////////////////////////////////////
	// Setup

	function queryAbsoluteControls() {
		console.log("Query absolute position controls")
		Midi.sendSysEx("F0 00 02 0B 7F 0C 04 00 00 F7")
	}

	property Timer initPhaseEndTimer: Timer {
		interval: 1000
		repeat: false
		onTriggered: {
			console.log("Initialization phase ended")
			device.isInitializing = false
			queryAbsoluteControls()
		}
	}

	property bool isInitializing: false

	Component.onCompleted: {
		currentColors = {}
		currentSimpleColors = {}
		console.log("Sending Initilization Message Akai Pro AMX")
		Midi.sendSysEx("47 7F 2C 60 00 04 04 01 00 00");

		isInitializing = true

		requestPowerOnButtonState()

		console.log("Sending initialization message for PRIME GO ...")
		Midi.sendSysEx("F0 00 02 0B 7F 0C 60 00 04 04 01 01 04 F7")

		initPhaseEndTimer.start()
	}

	Component.onDestruction: {
		Midi.sendNoteOff(0, 118)
	}

	property var currentColors
	property var currentSimpleColors

	// Dec to Hex Conversion
	function d2h(d){
		return (+d).toString(16).toUpperCase()
	}

	function midiColorChannel(c, gamma){
		return d2h(Math.min(127, Math.max(0, Math.floor(Math.pow(c, gamma) * 127))))
	}

	function mapColor(color) {
		return Qt.rgba(color.r, color.g, color.b , color.a)
	}

	function midiColor(color, gamma) {
		var c = mapColor(color)
		return midiColorChannel(c.r, gamma) + " " +  midiColorChannel(c.g, gamma) + " " +  midiColorChannel(c.b, gamma)
	}

	function sendNoteOn(channel, index, value) {
		Midi.sendNoteOn(channel, index, value)
	}

	function sendSimpleColor(channel, index, value) {
		currentSimpleColors[index] = value

		if (value === 0) {
			Midi.sendNoteOff(channel, index)
		}
		else {
			Midi.sendNoteOn(channel, index, value)
		}
	}

	//Color Send Function
	function sendColor(channel, index, color)
	{
		currentColors[index] = color

		var g = device.gamma
		if(index >= 15 && index <= 23) {
			g = device.padGamma
		}

		var sysEx = "F0 00 02 0B 7F 0C 03 00 05 " + d2h(channel) + " " + d2h(index) + " " + midiColor(color, g)+" F7"
		Midi.sendSysEx(sysEx)

	}

	function requestPowerOnButtonState() {
		Midi.sendSysEx("F0 00 02 0B 7f 0C 42 00 00 F7")
	}

	function sysExToIntList(sysExString)
	{
		var valueList = sysExString.split(" ")
		var result = []

		for(var i = 0; i < valueList.length; ++i) {
			result.push(parseInt(valueList[i], 16))
		}

		return result
	}

	function sysEx(sysExString) {
		console.info("Received SysEx:", sysExString)
		var valueList = sysExToIntList(sysExString)
		var result = ""

		// 0xf0 0x00 0x02 0x0b 0x00 0x06 0x42 0x00 0x01 0x01 0xf7
		if(valueList[1] === 0x00 && valueList[2] === 0x02 && valueList[3] === 0x0B && valueList[4] === 0x00 && valueList[6] === 0x42)
		{
			if(valueList[9] === 0x0) {
				console.log("No special power on request")
			}
			else if(valueList[9] === 0x1) {
				console.log("Request test-mode entry")
				quitToTestApp()
			}
		}
		else if(valueList[1] === 0x7E && valueList[2] === 0x00 && valueList[3] === 0x06 && valueList[4] === 0x02)
		{
			var i
			for(i = 0; i < 4; ++i) {
				result += valueList[i + 11]
				if(i === 1) {
					result += "."
				}
			}
			deviceInfo = result

			var mixerVersion = ""
			if(valueList.length === 43) {
				//mixerVersion = "00." + valueList[40].toString(16)
				for(i = 0; i < 4; ++i) {
					mixerVersion += valueList[i + 37]
					if(i === 1) {
						mixerVersion += "."
					}
				}
			}

			currentMixerFirmwareVersion = mixerVersion

			console.log("Mixer version:", mixerVersion, "(", valueList, ")")

			var currentSerialNumber = Planck.readFromFile("/sys/firmware/devicetree/base/serial-number")
			var serial = ""

			for(i = 20; i < 35; ++i) {
				if(valueList[i] === 0) {
					break
				}
				serial += String.fromCharCode(valueList[i])
			}

			if(serial !== currentSerialNumber && serial.length > 0) {
				Planck.setDeviceSerialNumber(serial)
			}
		}
	}
}
Akai_Pro_Force_Private_Assignments.qml : 

import airAssignments 1.0
import ControlSurfaceModules 0.1
import Planck 1.0
import QtQuick 2.12
import InputAssignment 0.1
import OutputAssignment 0.1


MidiAssignment {
	objectName: "PRIME 4 Controller Assignment"
	
	Utility {
		id: util
	}

	GlobalAssignmentConfig {
		id: globalConfig
		midiChannel: 0
	}

	GlobalAction {
		id: globalAction
	}

	Back {
		note: 67
		ledType: LedType.Simple
	}

	Forward {
		//note: 115
		shiftAction: Action.Quantize
		ledType: LedType.Simple
	}

	BrowseEncoder {
		//pushNote: 111
		turnCC: 100
		ledType: LedType.Simple
	}

	View {
		note: 0
		holdAction: Action.ToggleControlCenter
		shiftAction: Action.SwitchMainViewLayout
		ledType: LedType.Simple
	}

	Shift {
		note: 49
		ledType: LedType.Simple
	}

	Media {
		mediaButtonsModel: ListModel {
			ListElement {
				name: 'Eject'
				shiftName: 'Source'
				note: 20
				hasLed: true
			}
		}
	}

	Mixer {
		cueMixCC: 12
		cueGainCC: 13
		crossfaderCC: 32
	}

	MicSettings {}

	Mics {
		mic1Note: 36
		mic2Note: 37
		mic1ShiftAction: Action.ToggleMicTalkover
	}




	Repeater {
		model: ListModel {
			ListElement {
				deckName: 'Left'
				deckMidiChannel: 9
				loadNote: 123
			}
		}

		Item {
			DeckAssignmentConfig {
				id: deckConfig
				name: model.deckName
				midiChannel: model.deckMidiChannel
			}

			DeckAction {
				id: deckAction
			}

			Bank {}

			Load {
				note: model.loadNote
				ledType: LedType.Simple
			}

		PerformanceModes {
			ledType: LedType.RGB
			
			modesModel: ListModel {
				ListElement {
					
					note: 110
					view: "CUES"
				}
				ListElement {
					
					note: 111
					view: "LOOPS"
					altView: "AUTO"
				}
				ListElement {
					
					note: 112
					view: "ROLL"
				}
				ListElement {
					
					note: 113
					view: "SLICER"
					altView: "FIXED"
				}
			}
		}

			ActionPads {
			    
				firstPadNote: 102
				ledType: LedType.RGB
			}

			Sync {
				syncNote: 115
				syncHoldAction: Action.KeySync
			}

			PlayCue {
				cueNote: 37
				cueShiftAction: Action.SetCuePoint
				playNote: 114
			}

			PitchBend {
				minusNote: 29
				plusNote: 30
			}

			JogWheel {
				touchNote: 33
				ccUpper: 0x37
				ccLower: 0x4D
				jogSensitivity: 1638.7 * 10.08
				hasTrackSearch: true
			}

			Vinyl {
				note: 35
				holdAction: Action.GridCueEdit
			}

			AutoLoop {
				pushNote: 116
				turnCC: 160
				loopInactiveShiftTurnAction: Action.BeatJump
				ledType: LedType.Simple
			}

			SpeedSlider {
				ccUpper: 0x1F
				ccLower: 0x4B
				invert: true
			}
		}
		
	}



Repeater {
		model: ListModel {
			ListElement {
				deckName: 'Right'
				deckMidiChannel: 9
				loadNote: 124
			}
		}

		Item {
			DeckAssignmentConfig {
				id: deckConfig
				name: model.deckName
				midiChannel: model.deckMidiChannel
			}

			DeckAction {
				id: deckAction
			}

			Bank {}

			Load {
				note: model.loadNote
				ledType: LedType.Simple
			}

		PerformanceModes {
			ledType: LedType.RGB
			modesModel: ListModel {
				ListElement {
					
					note: 78
					view: "CUES"
				}
				ListElement {
					
					note: 79
					view: "LOOPS"
					altView: "AUTO"
				}
				ListElement {
					
					note: 80
					view: "ROLL"
				}
				ListElement {
					
					note: 81
					view: "SLICER"
					altView: "FIXED"
				}
			}
		}

			ActionPads {
				
				firstPadNote: 70
				ledType: LedType.RGB
				
			}

			Sync {
				syncNote: 83
				syncHoldAction: Action.KeySync
			}

			PlayCue {
				cueNote: 9
				cueShiftAction: Action.SetCuePoint
				playNote: 82
			}

			PitchBend {
				minusNote: 29
				plusNote: 30
			}

			JogWheel {
				touchNote: 33
				ccUpper: 0x37
				ccLower: 0x4D
				jogSensitivity: 1638.7 * 10.08
				hasTrackSearch: true
			}

			Vinyl {
				note: 35
				holdAction: Action.GridCueEdit
			}

			AutoLoop {
				pushNote: 56
				turnCC: 230
				loopInactiveShiftTurnAction: Action.BeatJump
				ledType: LedType.Simple
			}

			SpeedSlider {
				ccUpper: 0x1F
				ccLower: 0x4B
				invert: true
			}
		}
	}






	Repeater {
		model: ListModel {
			ListElement {
				mixerChannelName: '1'
				mixerChannelMidiChannel: 0
			}
		}

		Item {
			objectName: 'Mixer Channel %1'.arg(model.mixerChannelName)

			MixerChannelAssignmentConfig {
				id: mixerChannelConfig
				name: model.mixerChannelName
				midiChannel: model.mixerChannelMidiChannel
			}

			MixerChannelCore {
				pflNote: 13
				trimCC: 3
				trebleCC: 21
				midCC: 6
				bassCC: 200
				faderCC: 14
			}

			SweepFxKnob {
				cc: 24
			}

			SweepFxSelect {
				channelNames: [model.mixerChannelName]
				buttonsModel: ListModel {
					ListElement {
						note: 60
						fxIndex: SweepEffect.DualFilter
						
					}
					ListElement {
						note: 61
						fxIndex: SweepEffect.Wash
					}
				}
			}
		}

	}



	Repeater {
		model: ListModel {

			ListElement {
				mixerChannelName: '2'
				mixerChannelMidiChannel: 0
			}
		}

		Item {
			objectName: 'Mixer Channel %1'.arg(model.mixerChannelName)

			MixerChannelAssignmentConfig {
				id: mixerChannelConfig
				name: model.mixerChannelName
				midiChannel: model.mixerChannelMidiChannel
			}

			MixerChannelCore {
				pflNote: 13
				trimCC: 3
				trebleCC: 4
				midCC: 6
				bassCC: 22
				faderCC: 14
			}

			SweepFxKnob {
				cc: 24
			}

			SweepFxSelect {
				channelNames: [model.mixerChannelName]
				buttonsModel: ListModel {
					ListElement {
						note: 62
						fxIndex: SweepEffect.DualFilter
					}
					ListElement {
						note: 63
						fxIndex: SweepEffect.Wash
					}
				}
			}
		}
	}






     ///////////////////////////////////////////////////////////////////////////
	// Effect Controls
	 Repeater {
		model: ListModel {
			ListElement {
				deckName: "Left"
				deckMidiChannel: 0
			}

			ListElement {
				deckName: "Right"
				deckMidiChannel: 1
			}
		}

		Item {
			id: deckFx
			
			ValueNoteAssignment {
				objectName: "FX%1 Active Button".arg(1)
				note: 93
				channel: deckMidiChannel
				enabled: !device.shift
				output: ActionOutput {
					target: PropertyTarget {
						path: "/Engine/Mixer/Channel%1/DJFx/Active".arg(1)
					}
				}
			}
			
			ValueNoteAssignment {
				objectName: "FX%1 Active Button".arg(2)
				note: 05
				channel: deckMidiChannel
				enabled: !device.shift
				output: ActionOutput {
					target: PropertyTarget {
						path: "/Engine/Mixer/Channel%1/DJFx/Active".arg(2)
					}
				}
			}
			

			
			
				ValueCCAssignment {
				objectName: "FX%1 Selection".arg(1)
				cc: 17
				channel: deckMidiChannel

				enabled: Planck.getProperty("/Engine/Mixer/Channel%1/DJFx/Selecting".arg(1)).translator.state
				output: QtObject {
					readonly property QObjProperty pSelectionIndex: Planck.getProperty("/Engine/Mixer/Channel%1/DJFx/PreselectIndex".arg(1))
					function setValue(channel, value, assignmentEnabled) {
						if(assignmentEnabled) {
							if(value > 0.5) { // turn counter clockwise, value normalized
								pSelectionIndex.translator.unnormalized = pSelectionIndex.translator.unnormalized - 1
							}
							else {
								pSelectionIndex.translator.unnormalized = pSelectionIndex.translator.unnormalized + 1
							}
						}
					}
				}
			}
				
						
			ValueCCAssignment {
				objectName: "FX%1 Selecting".arg(1)
				cc: 17
				channel: deckMidiChannel
				enabled: !Planck.getProperty("/Engine/Mixer/Channel%1/DJFx/Selecting".arg(1)).translator.state
				output: ActionOutput {
					behaviour: force.action
					target: PropertyTarget {
						path: "/Engine/Mixer/Channel%1/DJFx/Selecting".arg(1)
					}
				}
			}
			
			ValueCCAssignment {
				objectName: "FX%1 Selection".arg(2)
				cc: 20
				channel: deckMidiChannel

				enabled: Planck.getProperty("/Engine/Mixer/Channel%1/DJFx/Selecting".arg(2)).translator.state
				output: QtObject {
					readonly property QObjProperty pSelectionIndex: Planck.getProperty("/Engine/Mixer/Channel%1/DJFx/PreselectIndex".arg(2))
					function setValue(channel, value, assignmentEnabled) {
						if(assignmentEnabled) {
							if(value > 0.5) { // turn counter clockwise, value normalized
								pSelectionIndex.translator.unnormalized = pSelectionIndex.translator.unnormalized - 1
							}
							else {
								pSelectionIndex.translator.unnormalized = pSelectionIndex.translator.unnormalized + 1
							}
						}
					}
				}
			}
				
						
			ValueCCAssignment {
				objectName: "FX%1 Selecting".arg(2)
				cc: 20
				channel: deckMidiChannel
				enabled: !Planck.getProperty("/Engine/Mixer/Channel%1/DJFx/Selecting".arg(2)).translator.state
				output: ActionOutput {
					behaviour: force.action
					target: PropertyTarget {
						path: "/Engine/Mixer/Channel%1/DJFx/Selecting".arg(2)
					}
				}
			}
			
			
	


//			ValueCCAssignment {
//				objectName: "FX%1 Rate".arg(1)
//				cc: 100
//				channel: deckMidiChannel
//				enabled: Planck.getProperty("/Engine/Mixer/Channel%1/DJFx/Knob2Target".arg(1)).translator.string === "Rate"
//				output: QtObject {
//					readonly property QObjProperty pTarget: Planck.getProperty("/Engine/Mixer/Channel%1/DJFx/Rate".arg(1))
//					function setValue(channel, value, assignmentEnabled) {
//						if(assignmentEnabled) {
//							var counterClock = value > 0.5
//							var changeIndex = counterClock ? -1 : 1
//							pTarget.translator.index = pTarget.translator.index + changeIndex
//						}
//					}
//				}
//			}

			ValueCCAssignment {
				objectName: "FX%1 Amount".arg(1)
				cc: 18
				channel: deckMidiChannel
				enabled: Planck.getProperty("/Engine/Mixer/Channel%1/DJFx/Knob2Target".arg(1)).translator.string === "Amount"
				output: EndlessKnobOutput {
					smallestIncrement: 0.01
					biggestIncrement: 0.1
					target: PropertyTarget { path: "/Engine/Mixer/Channel%1/DJFx/Amount".arg(1) }
				}
			}	
				
			ValueCCAssignment {
				objectName: "FX%1 Amount".arg(2)
				cc: 21
				channel: deckMidiChannel
				enabled: Planck.getProperty("/Engine/Mixer/Channel%1/DJFx/Knob2Target".arg(2)).translator.string === "Amount"
				output: EndlessKnobOutput {
					smallestIncrement: 0.01
					biggestIncrement: 0.1
					target: PropertyTarget { path: "/Engine/Mixer/Channel%1/DJFx/Amount".arg(2) }
				}
			}		
				
				
			ValueCCAssignment {
				objectName: "FX%1 Mix".arg(1)
				cc: 19
				channel: deckMidiChannel
				output: ValueOutput {
					target: PropertyTarget {
						path: "/Engine/Mixer/Channel%1/DJFx/Mix".arg(1)
					}
				}
			}


			ValueCCAssignment {
				objectName: "FX%1 Mix".arg(2)
				cc: 22
				channel: deckMidiChannel
				output: ValueOutput {
					target: PropertyTarget {
						path: "/Engine/Mixer/Channel%1/DJFx/Mix".arg(2)
					}
				}
			}	
							

		}
	}	










/*
	FxAssignmentConfig {
		id: fxConfig
		midiChannel: 0
		channelNames: ['1', '2']
	}

	DJFxSelect {
		pushNote: 53
		touchNote: 91
		turnCC: 100
	}

	DJFxTime {
		pushNote: 8
		turnCC: 18
	}

	DJFxWetDry {
		cc: 19
	}

	DJFxActivate {
		fxActivateType: FxActivateType.Button
		activateControlsModel: ListModel {
			ListElement {
				midiChannel: 0
				note: 93
				
			}
			ListElement {
				midiChannel: 0
				note: 92
			}
			
			
			
		}
	}


	

	DJFxAssign {
	
		notes: [94, 95]
	}
*/

_Public_ files are used to lit leds / pads.

Akai_Pro_Force_Public_Device.qml : 

import airAssignments 1.0
import InputAssignment 0.1
import OutputAssignment 0.1
import Device 0.1
import QtQuick 2.0
import Planck 1.0

Device {
	id: device

	property real gamma: 3.5
	property real padGamma: 3.5
	
	controls: []
	useGlobalShift: false
	numberOfLayers: 0

	property string deviceInfo: ""

	property QObjProperty pRunningDark: Planck.getProperty("/GUI/Scripted/RunningDark");
	property bool isRunningDark: pRunningDark && pRunningDark.translator ? pRunningDark.translator.state : false
	onIsRunningDarkChanged: {
		if(isRunningDark) {
			Midi.sendNoteOn(0, 117, 0)
		} 
		else {
			for(var channel in currentColors) {
				for(var key in currentColors[channel]) {
					sendColor(channel, key, currentColors[channel][key]);
				}
			}
			for(var channel in currentSimpleColors) {
				for(var simpleKey in currentSimpleColors[channel]) {
					sendSimpleColor(channel, simpleKey, currentSimpleColors[channel][simpleKey]);
				}
			}
		}
	}
	
	property QObjProperty pCalibratePlatter: Planck.getProperty("/GUI/Scripted/CalibratePlatter");
	property bool calibratePlatter: pCalibratePlatter && pCalibratePlatter.translator && pCalibratePlatter.translator.state
	onCalibratePlatterChanged: {
		if(calibratePlatter) {
			console.log("calibratePlatter")
			//Midi.sendSysEx("F0 00 02 0B 7F 08 7D 00 02 10 7F F7")
		}
	}

	///////////////////////////////////////////////////////////////////////////
	// Setup
	
	property Timer initTimer: Timer {
		interval: 1000
		repeat: false
		onTriggered: {
			console.log("Initialization phase ended")
				device.isInitializing = false
		}
	}

	property bool isInitializing: false

	Component.onCompleted: {
		currentColors = {}
		currentSimpleColors = {}
		
		//Midi.sendSysEx("F0 7E 00 06 01 F7")
		
		isInitializing = true
		
		requestPowerOnButtonState();

		console.log("Sending initialization message for PRIME 4 ...")
		//Midi.sendSysEx("F0 00 02 0B 7f 08 60 00 04 04 01 01 03 F7")
		initTimer.start()
	}
	
	Component.onDestruction: {
		if(Planck.quitReason() === QuitReasons.ControllerMode) {
			Midi.sendNoteOn(0, 117, 1) // Dim all LEDs
		}
		else {
			Midi.sendNoteOff(0, 117) // Turn all LEDs off
		}
	}
	
	property var currentColors
	property var currentSimpleColors
	
		function padToTwo(str) {
  return str.padStart(2, '0');
}
	
	function dec2hex(d){
		return padToTwo((+d).toString(16).toUpperCase())
		
		
	}
	

	
	function midiColorChannel(c, gamma){
		return dec2hex(Math.min(127, Math.max(0,Math.floor(Math.pow(c, gamma) * 127))))
	}
	
	function mapColor(color) {
		return Qt.rgba(color.r, color.g, color.b , color.a)
	}
	
	function midiColor(color, gamma) {
		var c = mapColor(color)
		return midiColorChannel(c.r, gamma)+ " " +  midiColorChannel(c.g, gamma) + " " +  midiColorChannel(c.b, gamma)
	}
	
	function sendNoteOn(channel, index, value) {
		Midi.sendNoteOn(channel, index, value);
	}
	
	function sendSimpleColor(channel, index, value) {

		if(!currentSimpleColors[channel]) {
			currentSimpleColors[channel] = {};
		}

		currentSimpleColors[channel][index] = value

		if(isRunningDark)
			return;
		
		if(value === 0) {
			Midi.sendNoteOff(channel, index)
		} else {
			Midi.sendNoteOn(channel, index, value)
		}
	}
	
	//Color Send Function
	function sendColor(channel, index, color)
	{
		if(!currentColors[channel]) {
			currentColors[channel] = {};
		}
		currentColors[channel][index] = color;

		if(isRunningDark) {
			return;
		}
		
		var g = device.gamma
		if(index >= 15 && index <= 23) {
			g = device.padGamma
		}
		
		//var sysEx = "F0 47 7F 40 65 00 04 " + dec2hex(channel) + " " + dec2hex(index) + " " + midiColor(color, g)+" F7"
		var sysEx = "F0 47 7F 40 65 00 04 " + dec2hex(index) + " " + padToTwo(midiColor(color, g)) +" F7"
		Midi.sendSysEx(sysEx)
		console.info("ColorSysex:",sysEx)
		
	}

	property list<Item> oleds
	property bool enablePartialOledUpdates: false

	Repeater {
		model: 8
		Item {
			id: self
			property var buffer

			function numberToHex( num )
			{
				var result = "0x";
				if(num < 0x10)
				{
					result += "0"
				}
				result += num.toString(16);
				return result;
			}

			function buildSysEx(painter, x,y,w,h) {
				var result = "00 02 0B 7F 08 0B"

				var imageData = painter.imageToString(x, y, w, h);
				var dataLen = ((imageData.length+1) / 3) + 5;

				result += " " + numberToHex((dataLen  >> 7) & 0x7F)
				result += " " + numberToHex( dataLen   & 0x7F )
				result += " " + index.toString(16);
				result += " " + numberToHex(y/8) ;
				result += " " + numberToHex((y/8)+(h/8));
				result += " " + numberToHex(x) + " ";
				result += " " + numberToHex(x+w-1);
				result += " " + imageData

				return result;
			}

			function update(painter) {
				if(device.enablePartialOledUpdates) {
					var i = 0
					var bufToSend = []
					for(var y = 0; y < 4; y++) {
						for(var x = 0; x < 16; x++) {
							var result = buildSysEx(painter, x*8, y*8, 8, 8)
							if(buffer[i] !== result) {
								//Midi.sendSysEx(result);
								buffer[i] = result;
								bufToSend.push(result);
							}
							i++
						}
					}

					if(bufToSend.length < 20) {
						for(var i = 0; i < bufToSend.length; i++) {
							
							//Midi.sendSysEx(bufToSend[i])
								//console.info("BuftoSend:",bufToSend[i])
						}
					} else {
						var fullSysEx = buildSysEx(painter, 0, 0, 128, 32)
						//console.info("fullSysEx:",fullSysEx)
						//Midi.sendSysEx(fullSysEx);
					}
				} else {
					var fullSysEx = buildSysEx(painter, 0, 0, 128, 32)
					//console.info("fullSysEx2:",fullSysEx)
					//Midi.sendSysEx(fullSysEx);
					
				}
			}

			Component.onCompleted: {
				var b = [];
				for(var i = 0; i < 4 * 16; i++)
				{
					b.push( "" );
				}

				buffer = b;
				device.oleds.push(self)
			}
		}
	}

	function requestPowerOnButtonState() {
		//Midi.sendSysEx("F0 00 02 0B 7F 08 42 00 00 F7");
	}
	
	function sysExToIntList(sysExString)
	{
		var valueList = sysExString.split(" ");
		var result = [];
		
		for(var i=0;i<valueList.length;i++) {
			result.push(parseInt(valueList[i], 16))
		}
		
		return result;
	}

	function sysEx(sysExString) {
		//console.info("Received SysEx:", sysExString)
		var valueList = sysExToIntList(sysExString);
		var result = "";

		// 0xf0 0x00 0x02 0x0b 0x00 0x06 0x42 0x00 0x01 0x01 0xf7
		if(valueList[1] === 0x00 && valueList[2] === 0x02 && valueList[3] === 0x0B && valueList[6] === 0x42)
		{
			if(valueList[9] === 0x0) {
				console.log("Power on self request: No special power on request")
			}
			else if(valueList[9] === 0x1) {
				console.log("Power on self request: Request test-mode entry")
				quitToTestApp()
			}
		}
		else if(valueList[1] === 0x7E && valueList[2] === 0x00 && valueList[3] === 0x06 && valueList[4] === 0x02)
		{
			for(var i=0;i<4;i++) {
				result += valueList[i + 11];
				if(i == 1)
					result += ".";
			}
			deviceInfo = result;
		}
	}
}

}

</code>

Akai_Pro_Force_Public_Assignments.qml :

import airAssignments 1.0
import InputAssignment 0.1
import OutputAssignment 0.1
import ControlSurfaceModules 0.1
import QtQuick 2.12

MidiAssignment {
	objectName: 'PRIME 4 Controller Assignment'
	id: assignment

	Utility {
		id: util
	}

	GlobalAssignmentConfig {
		id: globalConfig
		midiChannel: 0
	}

	GlobalAction {
		id: globalAction
	}

	Back {
		note: 3
		ledType: LedType.Simple
	}

	Forward {
		note: 4
		shiftAction: Action.Quantize
		ledType: LedType.Simple
	}

	BrowseEncoder {
		pushNote: 6
		turnCC: 5
		ledType: LedType.Simple
	}

	View {
		note: 7
		shiftAction: Action.SwitchMainViewLayout
		holdAction: Action.ToggleControlCenter
		ledType: LedType.Simple
	}

	ZoneOut {
		note: 16
	}

	Media {
		mediaButtonsModel: ListModel {
			ListElement {
				name: 'Eject'
				shiftName: 'Source'
				note: 20
				hasLed: true
			}
		}
		removableDeviceModel: ListModel {
			ListElement {	// SD
				slot: '1'
				note: 52
			}
			ListElement {	// USB 1
				slot: '6'
				note: 53
			}
			ListElement {	// USB 2
				slot: '5'
				note: 54
			}
		}
	}

	Repeater {
		model: ListModel {
			ListElement {
				deckName: 'Left'
				deckMidiChannel: 4
				loadNote: 1
			}
		}

		Item {
			objectName: 'Deck %1'.arg(model.deckName)

			DeckAssignmentConfig {
				id: deckConfig
				name: model.deckName
				midiChannel: model.deckMidiChannel
			}

			DeckAction {
				id: deckAction
			}

			Load {
				note: model.loadNote
				ledType: LedType.RGB
			}

			Censor {
				note: 1
			}

			TrackSkip {
				prevNote: 4
				nextNote: 5
			}

			BeatJump {
				prevNote: 6
				nextNote: 7
			}

			Sync {
				syncNote: 8
				syncHoldAction: Action.InstantDouble
			}

			PlayCue {
				cueNote: 9
				cueShiftAction: Action.SetCuePoint
				playNote: 60
				
			}

			PerformanceModes {
				ledType: LedType.RGB
				modesModel: ListModel {
					ListElement {
						note: 56
						view: 'CUES'
					}
					ListElement {
						note: 57
						view: 'LOOPS'
						altView: 'AUTO'
					}
					ListElement {
						note: 58
						view: 'ROLL'
					}
					ListElement {
						note: 59
						view: 'SLICER'
						altView: 'FIXED'
					}
				}
			}

			ActionPads {
				firstPadNote: 48
				ledType: LedType.RGB
			}

			Parameter {
				leftNote: 23
				rightNote: 24
			}

			BeatGrid {
				leftNote: 25
				rightNote: 26
				editGridNote: 27
			}

			Shift {
				note: 28
				ledType: LedType.Simple
			}

			PitchBend {
				minusNote: 29
				plusNote: 30
			}

			JogWheel {
				touchNote: 33
				ccUpper: 0x37
				ccLower: 0x4D
				jogSensitivity: 1638.7 * 3.6
				ledType: LedType.RGB
			}

			KeyLock {
				note: 34
			}

			Vinyl {
				note: 35
			}

			Slip {
				note: 36
			}

			ManualLoop {
				inNote: 37
				outNote: 38
			}

			AutoLoop {
				pushNote: 39
				turnCC: 32
				ledType: LedType.Simple
			}

			SpeedSlider {
				ccUpper: 0x1F
				ccLower: 0x4B
				downLedNote: 42
				centreLedNote: 43
				upLedNote: 44
			}

			DeckSelect {
				primaryDeckIndex: 1 + model.index
				primaryDeckNote: 28 + model.index
			}
		}
	}





	Repeater {
		model: ListModel {
			ListElement {
				deckName: 'Right'
				deckMidiChannel: 5
				loadNote: 2
			}
		}

		Item {
			
			DeckAssignmentConfig {
				id: deckConfig
				name: model.deckName
				midiChannel: model.deckMidiChannel
			}

			DeckAction {
				id: deckAction
			}

			Load {
				note: model.loadNote
				ledType: LedType.RGB
			}

			Censor {
				note: 1
			}

			TrackSkip {
				prevNote: 4
				nextNote: 5
			}

			BeatJump {
				prevNote: 6
				nextNote: 7
			}

			Sync {
				syncNote: 8
				syncHoldAction: Action.InstantDouble
			}

			PlayCue {
				cueNote: 9
				cueShiftAction: Action.SetCuePoint
				playNote: 28
				
			}

			PerformanceModes {
				ledType: LedType.RGB
				modesModel: ListModel {
					ListElement {
						note: 24
						view: 'CUES'
					}
					ListElement {
						note: 25
						view: 'LOOPS'
						altView: 'AUTO'
					}
					ListElement {
						note: 26
						view: 'ROLL'
					}
					ListElement {
						note: 27
						view: 'SLICER'
						altView: 'FIXED'
					}
				}
			}

			ActionPads {
				firstPadNote: 16
				ledType: LedType.RGB
			}

			Parameter {
				leftNote: 23
				rightNote: 24
			}

			BeatGrid {
				leftNote: 25
				rightNote: 26
				editGridNote: 27
			}

			Shift {
				note: 28
				ledType: LedType.Simple
			}

			PitchBend {
				minusNote: 29
				plusNote: 30
			}

			JogWheel {
				touchNote: 33
				ccUpper: 0x37
				ccLower: 0x4D
				jogSensitivity: 1638.7 * 3.6
				ledType: LedType.RGB
			}

			KeyLock {
				note: 34
			}

			Vinyl {
				note: 35
			}

			Slip {
				note: 36
			}

			ManualLoop {
				inNote: 37
				outNote: 38
			}

			AutoLoop {
				pushNote: 39
				turnCC: 32
				ledType: LedType.Simple
			}

			SpeedSlider {
				ccUpper: 0x1F
				ccLower: 0x4B
				downLedNote: 42
				centreLedNote: 43
				upLedNote: 44
			}

			DeckSelect {
				primaryDeckIndex: 1 + model.index
				primaryDeckNote: 28 + model.index
			}
		}
	}



















	// Numbers of channels arranged from left to right:
	readonly property var mixerChannelOrder: [3,1,2,4]
	Repeater {
		model: mixerChannelOrder

		ValueNoteAssignment {
			objectName: "Channel%1 Line".arg(modelData)
			note: 21 + index
			channel: 15
			output: QtObject {
				readonly property QObjProperty pLine: Planck.getProperty("/Engine/Mixer/Channel%1/Line".arg(modelData))
				function setValue(channel, value, assignmentEnabled) {
					if(assignmentEnabled) {
						pLine.translator.value = value
					}
				}
			}
		}
	}

	Repeater {
		model: 4
		Item {
			ColorOutputAssignment {
				objectName: "Mixer Cue color for deck %1".arg(index + 1)
				idx: 13
				channel: index
				
				readonly property color deckColor: Planck.getProperty("/Client/Preferences/Profile/Application/PlayerColor%1".arg(index + 1)).translator.color
				onColor: deckColor
			}
		}
		Component.onCompleted: {
			device.sendSimpleColor(15, 11, 1) //Split
			device.sendSimpleColor(15, 12, 1) //Dub Echo
			device.sendSimpleColor(15, 13, 1) //Noise
			device.sendSimpleColor(15, 14, 1) //Wash Out
			device.sendSimpleColor(15, 15, 1) //Gate

			device.sendSimpleColor(15, 35, 1) //Mic Talkover
			device.sendSimpleColor(15, 36, 1) //Mic 1 On/Off
			device.sendSimpleColor(15, 37, 1) //Mic 2 On/Off
			device.sendSimpleColor(15, 38, 1) //Mic 1 Echo On/Off
			device.sendSimpleColor(15, 39, 1) //Mic 2 Echo On/Off
		}
	}

	/////// FX

	Repeater {
		model: ListModel {
			ListElement {
				deck: "1"
				controlChannel: 8
				ccIndex: 11
			}
			ListElement {
				deck: "2"
				controlChannel: 9
				ccIndex: 11
			}
			ListElement {
				deck: "3"
				controlChannel: 8
				ccIndex: 13
			}
			ListElement {
				deck: "4"
				controlChannel: 9
				ccIndex: 13
			}
		}
		OutputAssignment {
			properties: { 'currentBpm' : '' }

			readonly property QObjProperty pCurrentBPM: Planck.getProperty("/Engine/Deck%1/CurrentBPM".arg(deck))
			readonly property int currentBpm: pCurrentBPM && pCurrentBPM.translator ? Math.round(pCurrentBPM.translator.unnormalized * 10) : 1200
			readonly property int minMixerTempo: 60 * 10
			readonly property int maxMixerTempo: 300 * 10

			function send() {
				if(currentBpm === 0) {
					return
				}
				var bpm = currentBpm
				Math.log2 = Math.log2 || function(x){return Math.log(x)*Math.LOG2E;};
				if(currentBpm < minMixerTempo) {
					var wrapPower = Math.ceil(Math.log2(minMixerTempo / currentBpm))
					bpm = currentBpm * Math.pow(2, wrapPower)
				}
				else if(currentBpm > maxMixerTempo) {
					var wrapPower = Math.ceil(Math.log2(currentBpm / maxMixerTempo))
					bpm = currentBpm / Math.pow(2, wrapPower)
				}
				Midi.sendControlChange(controlChannel, ccIndex, (bpm - minMixerTempo) & 0x7F)
				Midi.sendControlChange(controlChannel, ccIndex + 1, (bpm - minMixerTempo) >> 7)
			}
		}
	}

	property bool displaysActive : false
	onDisplaysActiveChanged: {
		if(!displaysActive) {
			clearPainter.draw();
		}
	}

	ValueNoteAssignment {
		objectName: "MIDI Note Activity"
		note: -1
		channel: -1
		output: QtObject {
			function setValue(channel, value, assignmentEnabled) {
				activityTimer.restart()
			}
		}
	}
	ValueCCAssignment {
		objectName: "MIDI CC Activity"
		cc: -1
		channel: -1
		output: QtObject {
			function setValue(channel, value, assignmentEnabled) {
				activityTimer.restart()
			}
		}
	}

	Connections {
		target: globalConfig
		onCurrentFocusAreaChanged: {
			activityTimer.restart()
		}
	}

	Timer {
		id: activityTimer
		interval: 10 * 60 * 1000
		running: true
		onRunningChanged: {
			if(running) {
				displaysActive = true
			}
		}

		onTriggered: displaysActive = false
	}

	Painter {
		id: clearPainter
		width: 128
		height: 64
		format: Painter.AK01_DISPLAY

		function draw()
		{
			setCompositionMode(Painter.SourceOver);

			clear("black")
			setColor("white")

			for (var displayIndex = 0; displayIndex < device.oleds.length; displayIndex++) {
				device.oleds[displayIndex].update(clearPainter)
			}
		}

		Component.onDestruction: clearPainter.draw()
	}


	Repeater {
		model: ListModel {
			ListElement {
				sideChannel: 6
				controlChannel: 8
				displayIndex: 3
				slotName: "FXSlot1"
			}
			ListElement {
				sideChannel: 7
				controlChannel: 9
				displayIndex: 7
				slotName: "FXSlot2"
			}
		}

		Item {
			id: beatSlot
			readonly property QObjProperty pEffectType: Planck.getProperty("/Client/Mixer/%1/EffectList".arg(slotName))
			readonly property string effectType: pEffectType && pEffectType.translator ? pEffectType.translator.string : ""

			readonly property bool hasBeatLengths: Planck.getProperty("/Client/Mixer/%1/BeatLength".arg(slotName)).translator.numEntries > 0

			ValueCCAssignment {
				objectName: "%1 Knob FX WET/DRY".arg(slotName)
				cc: 4
				channel: sideChannel
				enabled: true
				output: JogOutput {
					jogAcceleration: 1.0
					jogSensitivity: 0.5
					roundRobin: false

					type: 0
					target: PropertyTarget {
						path: "/Client/Mixer/%1/WetDry".arg(slotName)
					}
				}
			}

			ValueNoteAssignment {
				objectName: "%1 Beat Decrease Button".arg(slotName)
				note: 9
				channel: sideChannel
				enabled: beatSlot.hasBeatLengths
				output: IncDecValueOutput {
					target: PropertyTarget {
						path: "/Client/Mixer/%1/BeatLength".arg(slotName)
					}
					increaseValue: false
					roundRobin: false
				}
			}
			ValueNoteAssignment {
				objectName: "%1 Beat Increase Button".arg(slotName)
				note: 10
				channel: sideChannel
				enabled: beatSlot.hasBeatLengths
				output: IncDecValueOutput {
					target: PropertyTarget {
						path: "/Client/Mixer/%1/BeatLength".arg(slotName)
					}
					increaseValue: true
					roundRobin: false
				}
			}
			ValueNoteAssignment {
				objectName: "%1 Beat Decrease Button LED".arg(slotName)
				note: 9
				channel: sideChannel
				initialValue: 0.0
				enabled: beatSlot.hasBeatLengths
				resendValueOnEnabledChanged: true

				output: QtObject {
					function setValue(channel, value, assignmentEnabled) {
						device.sendSimpleColor(sideChannel, 9, assignmentEnabled ? (value ? 127 : 1) : 0)
					}
				}
				Component.onCompleted: output.setValue(channel, initialValue, enabled)
			}
			ValueNoteAssignment {
				objectName: "%1 Beat Increase Button LED".arg(slotName)
				note: 10
				channel: sideChannel
				initialValue: 0.0
				enabled: beatSlot.hasBeatLengths
				resendValueOnEnabledChanged: true

				output: QtObject {
					function setValue(channel, value, assignmentEnabled) {
						device.sendSimpleColor(sideChannel, 10, assignmentEnabled ? (value ? 127 : 1) : 0)
					}
				}
				Component.onCompleted: output.setValue(channel, initialValue, enabled)
			}
			
			OutputAssignment {
				properties: { 'beatLength' : '/Client/Mixer/%1/BeatLength'.arg(slotName) }
				function send() {
					if(effectType === 'Bit Crush') {
						Midi.sendControlChange(controlChannel, 0x13, propertyData.beatLength.string)
					}
					else {
						Midi.sendControlChange(controlChannel, 9, propertyData.beatLength.index)
					}
				}
			}
			OutputAssignment {
				properties: { 'wetDry' : '/Client/Mixer/%1/WetDry'.arg(slotName) }
				function send() {
					Midi.sendControlChange(controlChannel, 7, propertyData.wetDry.value * 127)
				}
				readonly property bool fxEnable: Planck.getProperty("/Client/Mixer/%1/Enable".arg(slotName)).translator.state
				onFxEnableChanged: {
					if (fxEnable) {
						send()
					}
				}
			}

			Item {
				Connections {
					target: assignment
					onDisplaysActiveChanged: {
						if(assignment.displaysActive) {
							beatDrawTimer.start()
						}
					}
				}

				Timer {
					id: beatDrawTimer
					interval: 50
					onTriggered: beatDisplayPainter.draw()
				}

				Painter {
					id: beatDisplayPainter
					width: 128
					height: 64
					font.family: "NimbusSanD"
					font.weight: Font.Bold
					font.pixelSize: 14
					font.capitalization: Font.AllUppercase
					format: Painter.AK01_DISPLAY

					property QObjProperty pBeatLength: Planck.getProperty("/Client/Mixer/%1/BeatLength".arg(slotName))
					property string beatLength: pBeatLength && pBeatLength.translator ? pBeatLength.translator.string : ""
					property int beatLengthIndex: pBeatLength && pBeatLength.translator ? pBeatLength.translator.index : 0

					property QObjProperty pValue: Planck.getProperty("/Client/Mixer/%1/WetDry".arg(slotName))
					property real value: pValue && pValue.translator ? pValue.translator.value : 0.25
					property int iValue: Math.round(value * 124)

					property string effectType: beatSlot.effectType

					function draw()
					{
						setCompositionMode(Painter.SourceOver);

						clear("black")
						setColor("white")

						if(effectType === 'Bit Crush') {
							drawTextInRect(0,18, 128, 16, Painter.AlignCenter, "Bits: " + beatLength)
						}
						else if(beatSlot.hasBeatLengths) {
							drawTextInRect(0,18, 128, 16, Painter.AlignCenter, beatLength + (beatLengthIndex > 6 ? " Beats" : " Beat"))
						}

						drawRect(0, 0, 127, 13)
						fillRect(2, 2, value * 124, 10, "white")

						if(value !== 0.5) {
							if(value > 0.5)
								setColor("black")
							drawLine(62, 1, 62, 12)
						}

						device.oleds[displayIndex].update(beatDisplayPainter)
					}

					onEffectTypeChanged: beatDrawTimer.start()
					onBeatLengthChanged: beatDrawTimer.start()
					onIValueChanged: beatDrawTimer.start()

				}
			}
		}
	}

	Repeater {
		model: ListModel {
			ListElement {
				sideChannel: 6
				controlChannel: 8
				displayIndex: 0
				slotName: "FXSlot1"
			}
			ListElement {
				sideChannel: 7
				controlChannel: 9
				displayIndex: 4
				slotName: "FXSlot2"
			}
		}

		Item {
			id: fxBank
			ValueCCAssignment {
				objectName: "%1 Knob FX SELECT".arg(slotName)
				cc: 1
				channel: sideChannel
				enabled: true
				output: JogOutput {
					jogAcceleration: 1.0
					jogSensitivity: 1.0

					type: 0
					target: PropertyTarget {
						path: "/Client/Mixer/%1/EffectJogWrapper".arg(slotName)
					}
				}
			}

			ValueCCAssignment {
				objectName: "%1 Knob FX PARAMETER".arg(slotName)
				cc: 2
				channel: sideChannel
				enabled: true
				output: JogOutput {
					jogAcceleration: 1.0
					jogSensitivity: 0.5
					roundRobin: false

					type: 0
					target: PropertyTarget {
						path: "/Client/Mixer/%1/ParamValueJogWrapper".arg(slotName)
					}
				}
			}
			ValueCCAssignment {
				objectName: "%1 Knob FX FREQUENCY".arg(slotName)
				cc: 3
				channel: sideChannel
				enabled: true
				output: JogOutput {
					jogAcceleration: 1.0
					jogSensitivity: 0.25
					roundRobin: false

					type: 0
					target: PropertyTarget {
						path: "/Client/Mixer/%1/FrequencyJogWrapper".arg(slotName)
					}
				}
			}

			ValueNoteAssignment {
				note: 6
				channel: sideChannel
				enabled: device.shift
				output: IncDecValueOutput {
					target: PropertyTarget {
						path: "/Client/Mixer/%1/EffectList".arg(slotName)
					}
					changeAmount: 1
					increaseValue: true
					roundRobin: true
				}
			}
			ValueNoteAssignment {
				objectName: '%1 Button ON'.arg(slotName)
				note: 6
				channel: sideChannel
				output: QtObject {
					function setValue(channel, value, assignmentEnabled) {
						if(value<0.5) {
							return
						}
						var pEnable = Planck.getProperty("/Client/Mixer/%1/Enable".arg(slotName))
						if(!device.shift) {
							pEnable.translator.state = !pEnable.translator.state
						} else if(pEnable.translator.state) {
							pEnable.translator.state = false
						}
					}
				}
			}
			ValueNoteAssignment {
				objectName: '%1 Button PARAM'.arg(slotName)
				note: 7
				channel: sideChannel
				enabled: true
				output: QtObject {
					function setValue(channel, value, assignmentEnabled) {
						if(value<0.5) {
							return
						}
						var pTwoStateParam = Planck.getProperty("/Client/Mixer/%1/TwoStateParam".arg(slotName))
						pTwoStateParam.translator.state = !pTwoStateParam.translator.state
					}
				}
			}
			ValueNoteAssignment {
				objectName: '%1 Button RESET'.arg(slotName)
				note: 8
				channel: sideChannel
				enabled: true
				output: QtObject {
					function setValue(channel, value, assignmentEnabled) {
						Midi.sendNoteOn(sideChannel, 8, value<0.5 ? 1 : 127)
						if(value<0.5) {
							return
						}
						Planck.getProperty("/Client/Mixer/%1/FrequencyJogWrapper".arg(slotName)).translator.value = 0.5

						var pTwoStateParam = Planck.getProperty("/Client/Mixer/%1/Reset".arg(slotName))
						pTwoStateParam.translator.state = true
					}
				}
			}

			OutputAssignment {
				properties: { 'type' : '/Client/Mixer/%1/EffectList'.arg(slotName) }
				function send() {
					Midi.sendControlChange(controlChannel, 1, propertyData.type.unnormalized)
					if(propertyData.type.string === 'Bit Crush') {
						Midi.sendControlChange(controlChannel, 0x13, Planck.getProperty("/Client/Mixer/%1/BeatLength".arg(slotName)).translator.string)
					}
					else {
						Midi.sendControlChange(controlChannel, 9, Planck.getProperty("/Client/Mixer/%1/BeatLength".arg(slotName)).translator.index)
					}
				}
			}
			OutputAssignment {
				properties: { 'fxValue' : "/Client/Mixer/%1/ParamValue".arg(slotName) }
				function send() {
					Midi.sendControlChange(controlChannel, 5, propertyData.fxValue.value * 127)
				}
			}
			OutputAssignment {
				properties: { 'fxValue' : "/Client/Mixer/%1/Frequency".arg(slotName) }
				function send() {
					Midi.sendControlChange(controlChannel, 6, propertyData.fxValue.value * 127)
				}
			}
			OutputAssignment {
				properties: { 'fxEnable' : "/Client/Mixer/%1/Enable".arg(slotName) }
				function send() {
					if(propertyData.fxEnable.state) {
						Midi.sendNoteOn(sideChannel, 6, 127) //color
						Midi.sendNoteOn(controlChannel, 1, 127) //inform dsp
					} else {
						Midi.sendNoteOn(sideChannel, 6, 1) //color
						Midi.sendNoteOn(controlChannel, 1, 0) //inform dsp
						Midi.sendNoteOff(controlChannel, 1) //inform dsp

						var pTwoStateParam = Planck.getProperty("/Client/Mixer/%1/TwoStateParam".arg(slotName))
						if (pTwoStateParam.translator.state === true) {
							pTwoStateParam.translator.state = false
						}
					}
				}
			}
			OutputAssignment {

				properties: {
					'fxTwoState' : "/Client/Mixer/%1/TwoStateParam".arg(slotName),
					'fxTwoStateParamName' : "/Client/Mixer/%1/TwoStateParamName".arg(slotName)
				}

				property var flashStates: ({})

				Timer {
					id: freezeTimer
					repeat: true
					interval: 500
					running: false
					property bool isOn: false

					onTriggered: {
						isOn = !isOn
						for(var x = 0; x < Object.keys(parent.flashStates).length; ++x) {
							var channelNumber = parent.flashStates[Object.keys(parent.flashStates)[x]]
							if(channelNumber !== 0){
								if(isOn) {
									Midi.sendNoteOn(channelNumber, 7, 127)
								} else {
									Midi.sendNoteOff(channelNumber, 7)
								}
							}
						}
					}
				}

				function stopTimer() {
					delete flashStates[slotName]
					if(Object.keys(flashStates).length === 0)
						freezeTimer.stop()
				}

				function send() {
					const dspNote = 2
					const colourNote = 7

					const full = 127
					const dim = 1

					if(propertyData.fxTwoStateParamName.string === "") {
						// No param
						Midi.sendNoteOff(sideChannel, colourNote)
						Midi.sendNoteOff(controlChannel, dspNote)
						stopTimer()
					}
					else if(propertyData.fxTwoState.state){
						// Param present and on
						var effectEnabled = Planck.getProperty("/Client/Mixer/%1/Enable".arg(slotName)).translator.state;
						var effectName =  Planck.getProperty("/Client/Mixer/%1/EffectList".arg(slotName)).translator.string
						if(effectEnabled) {
							Midi.sendNoteOn(sideChannel, colourNote, full)
							Midi.sendNoteOn(controlChannel, dspNote, full)
							if(effectName === "Reverb" || effectName === "Echo" || effectName === "Hall Echo") {
								freezeTimer.start()
								flashStates[slotName] = sideChannel
							}
						}
						else {
							propertyData.fxTwoState.state = false
							if(effectName === "Reverb" || effectName === "Echo" || effectName === "Hall Echo") {
								stopTimer()
							}
						}
					}
					else {
						// Param present and off
						Midi.sendNoteOn(sideChannel, colourNote, dim)
						Midi.sendNoteOff(controlChannel, dspNote)
						stopTimer()
					}
				}
			}
			OutputAssignment {
				properties: { 'fxTwoState' : "/Client/Mixer/%1/Reset".arg(slotName) }
				function send() {
					if(propertyData.fxTwoState.state) {
						Midi.sendNoteOn(sideChannel, 8, 127) //color
					} else {
						Midi.sendNoteOn(sideChannel, 8, 1) //color
					}
				}
			}

			Item {

				Timer {
					id: fxSelectDrawTimer
					repeat: false
					interval: 50
					running: false
					onTriggered: fxSelectDisplayPainter.draw()
				}

				Painter {
					id: fxSelectDisplayPainter
					width: 128
					height: 64
					font.family: "NimbusSanD"
					font.pixelSize: 20
					font.capitalization: Font.AllUppercase
					format: Painter.AK01_DISPLAY

					property QObjProperty pType: Planck.getProperty("/Client/Mixer/%1/EffectList".arg(slotName))
					property string type: pType && pType.translator ? pType.translator.string : ""

					function draw()
					{
						setCompositionMode(Painter.SourceOver);
						clear("black");
						setColor("white");

						drawTextInRect(0,0, 128, 32, Painter.AlignCenter, type);
						device.oleds[displayIndex].update(fxSelectDisplayPainter)
					}

					onTypeChanged: fxSelectDrawTimer.start()
				}
			}
			Item {
				Timer {
					id: drawTimer
					repeat: false
					interval: 50
					running: false
					onTriggered: fxDisplayPainter.draw()
				}

				Painter {
					id: fxDisplayPainter
					width: 128
					height: 64
					font.family: "NimbusSanD"
					font.weight: Font.Bold
					font.pixelSize: 14
					font.capitalization: Font.AllUppercase
					format: Painter.AK01_DISPLAY

					property QObjProperty pType: Planck.getProperty("/Client/Mixer/%1/EffectList".arg(slotName))
					property string type: pType && pType.translator ? pType.translator.string : ""

					property QObjProperty pParamName: Planck.getProperty("/Client/Mixer/%1/ParamName".arg(slotName))
					property string paramName: pParamName && pParamName.translator ? pParamName.translator.string : ""

					property QObjProperty pValue: Planck.getProperty("/Client/Mixer/%1/ParamValue".arg(slotName))
					property real value: pValue && pValue.translator ? pValue.translator.value : 0.25
					property int iValue: Math.round(value * 124)
					property string lastPattern: ""

					readonly property var patterns: [
						"xxx xxx xxx xxx ",
						"x x x x x x x x ",
						"x  xx  xx  xx  x",
						"xxxxx x x xxx x ",
						"xxx xx xxxx x x ",
						"xx xx xxxx xx xx",
						"x x xx xx xxx x ",
						"x xxx  xxx xx x ",
						"x xxxx xxx xx x ",
						"xxxxxxxxxxxxxxxx",
						"xxxxxxxxxxxxxxxx",
						"xxxxxxxxxxxxxxxx",
						"xxxxxxxxxxxxxxxx",
						"xxxxxxxxxxxxxxxx",
						"xxxxxxxxxxxxxxxx",
						"xxxxxxxxxxxxxxxx",
						"xxxxxxxxxxxxxxxx",
					]

					function draw()
					{
						setCompositionMode(Painter.SourceOver);

						clear("black");
						setColor("white");

						if(paramName !== 'Pattern') {
							lastPattern = ""
						}
						if(paramName === 'Pattern') {
							drawTextInRect(0,0, 128, 16, Painter.AlignCenter, paramName);

							var patternNumber = (value * 127) >> 3
							var ptn = patterns[patternNumber]
							if(lastPattern === ptn) {
								return
							}
							lastPattern = ptn

							var x = 3
							for (var i = 0; i < ptn.length; i++) {
								if (ptn[i] == 'x') {
									fillRect(x, 20, 6, 10, "white")
								}
								x = x + 7
								if ((i + 1) % 4 === 0) {
									x = x + 3
								}
							}
						}
						else if(paramName !== '') {
							drawTextInRect(0,0, 128, 16, Painter.AlignCenter, paramName);
							if(type === 'Ping Pong') {
								var newValue = (2 * value) - 1
								drawRect(0, 18, 127, 13)
								fillRect(2, 20, 124, 10, "white")
								fillRect(64, 20, -newValue * 62, 10, "black")
								fillRect(64, 20, newValue * 62, 10, "black")
								fillRect(64, 20, 1, 10, "black")
							}
							else {
								drawRect(0, 18, 127, 13)
								fillRect(2, 20, value * 124, 10, "white")
							}
						}

						device.oleds[displayIndex+1].update(fxDisplayPainter)
					}

					onParamNameChanged: drawTimer.start()
					onIValueChanged: drawTimer.start()
				}
			}
			Item {
				Timer {
					id: fqDrawTimer
					repeat: false
					interval: 50
					running: false
					onTriggered: fqDisplayPainter.draw()
				}

				Timer {
					id: waitTimer
					repeat: false
					interval: 2000
					running: false
					onTriggered: fqDisplayPainter.drawFrequencyTitle()
				}

				Painter {
					id: fqDisplayPainter
					width: 128
					height: 64
					font.family: "NimbusSanD"
					font.weight: Font.Bold
					font.pixelSize: 14
					font.capitalization: Font.MixedCase
					format: Painter.AK01_DISPLAY

					property QObjProperty pValue: Planck.getProperty("/Client/Mixer/%1/Frequency".arg(slotName))
					property real value: pValue && pValue.translator ? pValue.translator.value : 0.25
					property int iValue: Math.round(value * 124)
					property bool drawTitle: false

					property var freqencyArray: [
						60,72,79,86,94,103,113,
						124,136,149,163,178,195,214,234,
						256,281,307,336,368,403,442,484,
						554,607,664,727,796,872,955,1046,
						1145,1254,1373,1503,1646,1802,1973,2161,
						2366,2591,2837,3106,3401,3724,4078,4465,
						4889,5354,5862,6419,7028,7696,8427,9227,
						10103,11063,12114,13880,15198,16641,18222]

					function drawFrequencyTitle()
					{
						drawTitle = true
						draw()
					}

					function draw()
					{
						setCompositionMode(Painter.SourceOver);

						clear("black");
						setColor("white");

						waitTimer.restart()

						if(iValue === 62){
							drawTextInRect(0,0, 128, 16, Painter.AlignCenter, "ALL BANDS")
							fillRect(2, 20, 124, 10, "white")
						}
						else if(value < 0.5) {
							var msg = freqencyArray[iValue] + "Hz"
							if(freqencyArray[iValue] >= 1000) {
								msg = (freqencyArray[iValue]/1000).toFixed(1) + "kHz"
							}
							drawTextInRect(0,0, 128, 16, Painter.AlignCenter, "< " + msg)
							fillRect(2, 20, value * 2 * 124, 10, "white")
						} else {
							var val = iValue-63
							var msg = freqencyArray[val] + "Hz"
							if(freqencyArray[val] >= 1000) {
								msg = (freqencyArray[val]/1000).toFixed(1) + "kHz"
							}
							drawTextInRect(0,0, 128, 16, Painter.AlignCenter, "> " + msg)
							fillRect(2 + (value - 0.5) * 2 * 125, 20, 124, 10, "white")
						}
						drawRect(0, 18, 127, 13)

						if(drawTitle === true){
							setCompositionMode(Painter.SourceOver);
							fillRect(0,0, 128, 16, "black")
							drawTextInRect(0,0, 128, 16, Painter.AlignCenter, "FREQUENCY")
							drawTitle = false
							waitTimer.stop()
						}

						device.oleds[displayIndex+2].update(fqDisplayPainter)
					}

					onIValueChanged: fqDrawTimer.start()
				}
			}

			Connections {
				target: assignment
				onDisplaysActiveChanged: {
					if(assignment.displaysActive) {
						fxSelectDrawTimer.start()
						drawTimer.start()
						fqDrawTimer.start()
					}
				}
			}
		}
	}

	readonly property var activeDecks: Planck.getProperty("/GUI/Decks/ActiveDecks").translator.entries
	property var deckThru: [0, 0, 0, 0]
	property bool xFaderStartLeft: false
	property bool xFaderStartRight: false
	
	ValueNoteAssignment {
		note: 25
		channel: 15
		output: QtObject {
			function setValue(channel, value, assignmentEnabled) {
				xFaderStartLeft = value !== 0
			}
		}
	}
	ValueNoteAssignment {
		note: 27
		channel: 15
		output: QtObject {
			function setValue(channel, value, assignmentEnabled) {
				xFaderStartRight = value !== 0
			}
		}
	}

	property var crossFaderTable: [
		0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00005623,0.00010000,0.00017783,0.00031623,0.00056234,0.00074989,0.00100000,0.00115478,0.00133352,0.00153993,0.00177828,
		0.00205353,0.00237137,0.00273842,0.00316228,0.00354813,0.00398107,0.00446684,0.00501187,0.00546387,0.00595662,0.00649382,0.00707946,0.00771792,0.00841395,0.00917276,0.01000000,
		0.01090184,0.01188502,0.01295687,0.01412538,0.01539927,0.01678804,0.01830206,0.01995262,0.02175204,0.02371374,0.02585235,0.02818383,0.03072557,0.03349654,0.03651741,0.03981072,
		0.04340103,0.04731513,0.05158222,0.05623413,0.05956621,0.06309573,0.06683439,0.07079458,0.07391796,0.07717915,0.08058422,0.08413951,0.08785167,0.09172759,0.09577452,0.10000000,
		0.10441190,0.10901845,0.11382823,0.11885022,0.12409378,0.12956867,0.13528511,0.14125375,0.14748573,0.15399265,0.16078665,0.16788040,0.17528712,0.18302061,0.19109530,0.19952623,
		0.20832913,0.21752040,0.22711719,0.23713737,0.24759963,0.25852348,0.26992928,0.28183829,0.29006812,0.29853826,0.30725574,0.31622777,0.32546178,0.33496544,0.34474661,0.35481339,
		0.35995648,0.36517413,0.37046740,0.37583740,0.38128525,0.38681205,0.39241898,0.39810717,0.40387782,0.40973211,0.41567126,0.42169650,0.42780908,0.43401026,0.44030133,0.44668359,
		0.44990933,0.45315836,0.45643086,0.45972699,0.46304692,0.46639083,0.46975888,0.47315126,0.47656813,0.48000968,0.48347609,0.48696753,0.49048418,0.49402622,0.49759385,0.50118723,
		0.50626161,0.51138737,0.51359998,0.51582217,0.51954719,0.52329911,0.52707813,0.53088444,0.53471824,0.53857972,0.54246909,0.54638655,0.55033230,0.55430654,0.55830948,0.56234133,
		0.56640229,0.57049258,0.57461241,0.57876199,0.58294153,0.58715126,0.59139139,0.59566214,0.59996373,0.60429639,0.60866033,0.61305579,0.61748299,0.62194216,0.62643354,0.63095734,
		0.63551382,0.64010320,0.64472573,0.64938163,0.65219130,0.65501312,0.66164495,0.66834392,0.67511071,0.68194602,0.68489658,0.68785991,0.69282731,0.69783058,0.70286999,0.70794578,
		0.71511354,0.72235386,0.72547926,0.72861817,0.73387991,0.73917965,0.74451765,0.74989421,0.75530959,0.76076408,0.76625796,0.77179152,0.77736503,0.78297879,0.78863310,0.79432823,
		0.79719121,0.80006450,0.80294815,0.80584219,0.80874666,0.81166160,0.81458705,0.81752304,0.82046961,0.82342680,0.82639466,0.82937320,0.83236249,0.83536255,0.83837342,0.84139514,
		0.84442776,0.84747130,0.85052582,0.85359134,0.85666791,0.85975557,0.86285436,0.86596432,0.86908549,0.87221791,0.87536162,0.87851666,0.88168307,0.88486089,0.88805017,0.89125094,
		0.89446325,0.89768713,0.90092264,0.90416981,0.90742868,0.91069929,0.91398170,0.91727594,0.92058204,0.92390007,0.92723005,0.93057204,0.93392607,0.93729219,0.94067045,0.94406088,
		0.94746353,0.95087844,0.95430566,0.95774524,0.96119721,0.96466162,0.96813852,0.97162795,0.97512996,0.97864459,0.98217189,0.98571190,0.98926467,0.99283025,0.99640868,1.00000000
	];

	function calculateExternalMixerVolume(deckIndex)
	{
		var crossFader = Planck.getProperty("/Engine/Mixer/CrossfaderPosition").translator.value;
		var deckVolume = Planck.getProperty("/Engine/Deck%1/DeckFaderVolume".arg(deckIndex)).translator.value;
		var externalVolume

		if(deckThru[deckIndex - 1] === 1) {
			externalVolume = deckVolume
		} else {
			var deckIdx = parseInt(deckIndex, 10)
			if(deckIdx === 1 || deckIdx === 3) {
				if(deckThru[deckIdx - 1] === 2) {
					crossFader = 1 - crossFader
				}
			} else if(deckIdx === 2 || deckIdx === 4) {
				if(deckThru[deckIdx - 1] === 0) {
					crossFader = 1 - crossFader
				}
			}
			var faderIndex = crossFader * 511;

			if (deckIdx === 1 || deckIdx === 3)
			{
				faderIndex = 511 - faderIndex;
			}
			if (faderIndex > 255)
			{
				faderIndex = 255;
			}

			var crossFaderLow = crossFaderTable[Math.floor(faderIndex)]
			var crossFaderHigh = crossFaderTable[Math.ceil(faderIndex)]

			var interpolatedTableValue = crossFaderLow + ((crossFaderHigh - crossFaderLow) * (crossFader - Math.floor(crossFader)));
			externalVolume = deckVolume * interpolatedTableValue;
		}

		Planck.getProperty("/Engine/Deck%1/ExternalMixerVolume".arg(deckIndex)).translator.setValue(externalVolume);

	}

	ValueCCAssignment {
		id: xfaderStartStop
		objectName: "Cross Fader Start/Stop"
		cc: 14
		channel: 15
		enabled: xFaderStartLeft || xFaderStartRight

		property QObjProperty pCrossFaderPosition: Planck.getProperty("/Engine/Mixer/CrossfaderPosition")

		output: QtObject {
			function setValue(channel, value, assignmentEnabled) {
				xfaderStartStop.pCrossFaderPosition.translator.value = value
				if(assignmentEnabled) {
					for (var i = 0; i < deckThru.length; i++) {
						calculateExternalMixerVolume(i+1);
						var deckIndex = "%1".arg(i+1)
						var deckIsActive = activeDecks.indexOf(deckIndex) !== -1
						if(xFaderStartLeft && deckIsActive && deckThru[i] === 0) {
							if (value === 1)
								Planck.getProperty("/Engine/Deck%1/RemoteStop".arg(deckIndex)).translator.state = true
							else
								Planck.getProperty("/Engine/Deck%1/RemoteStart".arg(deckIndex)).translator.state = true
						}
						if (xFaderStartRight && deckIsActive && deckThru[i] === 2) {
							if (value === 0)
								Planck.getProperty("/Engine/Deck%1/RemoteStop".arg(deckIndex)).translator.state = true
							else
								Planck.getProperty("/Engine/Deck%1/RemoteStart".arg(deckIndex)).translator.state = true
						}
					}
				} else {
					for (var index = 1;  index < deckThru.length + 1; index++) {
						calculateExternalMixerVolume(index);
					}
				}
			}
		}
	}


	Repeater {
		model: parseInt(Planck.getProperty("/Configuration/NumberOfDecks").translator.string)
		Item {
			property string deckIndex: "%1".arg(index+1)

			ValueCCAssignment {
				objectName: "Channel Fader %1".arg(deckIndex)
				cc: 14
				channel: index
				output: QtObject {
					function setValue(channel, value, assignmentEnabled) {
						Planck.getProperty("/Engine/Deck%1/DeckFaderVolume".arg(deckIndex)).translator.setValue(value);
						calculateExternalMixerVolume(deckIndex);
					}
				}
			}

			ValueNoteAssignment {
				objectName: "Thru %1".arg(deckIndex)
				note: 15
				channel: index
				normalizeValue: false
				output: QtObject {
					function setValue(channel, value, assignmentEnabled) {
						deckThru[channel] = value
						calculateExternalMixerVolume(deckIndex)
					}
				}
			}
		}
	}
}

And what it gives : I'm in slicer mode, on the 2 decks.

dnttalo.cluster029.hosting.ovh.net_pictures_prime4slicer.jpg

prime4.txt · Last modified: 2023/09/11 23:01 by ounsatn

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki