@@ -8,6 +8,8 @@ import com.bitwig.extension.controller.api.*
88class StepRecorderExtension (definition : ControllerExtensionDefinition , host : ControllerHost ) :
99 ControllerExtension (definition, host) {
1010
11+ private lateinit var fixedVelocityToggle: ISettableBooleanValue
12+ private lateinit var fixedVelocitySetting: SettableRangedValue
1113 private lateinit var documentState: DocumentState
1214 private lateinit var application: Application
1315 private lateinit var transport: Transport
@@ -34,7 +36,7 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
3436
3537 // Chord detection
3638 private val activeNotes = mutableMapOf<Int , Long >()
37- private val pendingNotes = mutableSetOf< Int >()
39+ private val pendingNotes = mutableListOf< Pair < Int , Int > >()
3840 private var firstNoteTime: Long = 0
3941 private var lastNoteOnTime: Long = 0
4042 private val chordThresholdMs = 100L // Notes within 100ms are considered a chord
@@ -72,6 +74,7 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
7274 setupClearToggle()
7375 setupNoteSelectionObserver()
7476 setClearOldNotesWhenRecording()
77+ setupVelocity()
7578
7679 updateStepLength()
7780 resetCursorClipToPlayStart()
@@ -97,7 +100,7 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
97100 if (velocity > 0 ) {
98101 // Note On
99102 activeNotes[note] = currentTime
100- pendingNotes.add(note)
103+ pendingNotes.add(note to velocity )
101104
102105 // Track first note time only when starting a new chord
103106 if (pendingNotes.size == 1 ) {
@@ -133,6 +136,18 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
133136 }
134137 }
135138
139+ private fun setupVelocity () {
140+ fixedVelocityToggle = documentState.getEnumBasedBooleanSetting(" Fixed velocity" , " Step Recorder" , false )
141+ MidiLearnBinding (host, " Fixed Velocity Binding" ) {
142+ fixedVelocityToggle.set(! (fixedVelocityToggle.get()))
143+ }
144+ fixedVelocitySetting =
145+ documentState.getNumberSetting(" Note Velocity" , " Step Recorder" , 0.0 , 127.0 , 1.0 , " units (0-127)" , 127.0 )
146+ MidiLearnBinding (host, " Fixed Note Velocity Binding" ) {
147+ fixedVelocitySetting.set(it.toDouble())
148+ }
149+ }
150+
136151 private fun updateStepLength () {
137152 stepper.updateNoteLengthInIntegerRepresentation(stepLengthValueSetting.get(), tripletSetting.get())
138153 }
@@ -149,8 +164,6 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
149164 MidiLearnBinding (
150165 host,
151166 " Toggle triplet" ,
152- " MIDI Learn" ,
153- " Not Mapped" ,
154167 {
155168 val current = tripletSetting.get()
156169 val newValue = if (current == = " Regular" ) " Triplet" else " Regular"
@@ -174,7 +187,7 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
174187 }
175188
176189 cursorForwardAction.addSignalObserver(action)
177- midiLearnBindings.add(MidiLearnBinding (host, " Forward Button" , " MIDI Learn " , " Not Mapped " , action.withArg()))
190+ midiLearnBindings.add(MidiLearnBinding (host, " Forward Button" , action.withArg()))
178191 }
179192
180193 private fun setupBackward () {
@@ -193,7 +206,7 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
193206 }
194207
195208 cursorBackwardAction.addSignalObserver(action)
196- midiLearnBindings.add(MidiLearnBinding (host, " Backward Button" , " MIDI Learn " , " Not Mapped " , action.withArg()))
209+ midiLearnBindings.add(MidiLearnBinding (host, " Backward Button" , action.withArg()))
197210 }
198211
199212 private lateinit var clearToggleSetting: ISettableBooleanValue
@@ -210,8 +223,6 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
210223 MidiLearnBinding (
211224 host,
212225 " Clear Toggle on forward/backward Button" ,
213- " MIDI Learn" ,
214- " Not Mapped" ,
215226 { clearToggleSetting.set(! clearToggleSetting.get()) }
216227 )
217228 )
@@ -229,8 +240,6 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
229240 MidiLearnBinding (
230241 host,
231242 " Clear old notes on note input Toggle" ,
232- " MIDI Learn" ,
233- " Not Mapped" ,
234243 { clearNotesOnInputSetting.set(! clearNotesOnInputSetting.get()) }
235244 )
236245 )
@@ -247,8 +256,6 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
247256 MidiLearnBinding (
248257 host,
249258 " Clear Button" ,
250- " MIDI Learn" ,
251- " Not Mapped" ,
252259 this ::clearNotesAtCurrentStepRange.withArg()
253260 )
254261 )
@@ -271,7 +278,7 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
271278
272279 midiLearnBindings.add(
273280 MidiLearnBinding (
274- host, " Step Length Value Control" , " MIDI Learn " , " Not Mapped " ,
281+ host, " Step Length Value Control" ,
275282 { changeStepLengthValue(it) })
276283 )
277284 }
@@ -306,7 +313,7 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
306313 }
307314 }
308315
309- midiLearnBindings.add(MidiLearnBinding (host, " Enable/Disable" , " MIDI Learn " , " Not Mapped " ) {
316+ midiLearnBindings.add(MidiLearnBinding (host, " Enable/Disable" ) {
310317 val newEnabled = ! enableSetting.get()
311318 enableSetting.set(newEnabled)
312319 val text = " Step recorder is now ${if (newEnabled) " enabled" else " disabled" } "
@@ -353,18 +360,18 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
353360
354361 private fun processPendingNotes () {
355362 if (pendingNotes.isNotEmpty()) {
356- val noteList = pendingNotes.sorted()
363+ val noteList = pendingNotes
357364
358365 if (noteList.size == 1 ) {
359- addNotesToCurrentClip(noteList, 127 )
366+ addNotesToCurrentClip(noteList)
360367 } else {
361368 val noteTimeSpan = lastNoteOnTime - firstNoteTime
362369
363370 if (noteTimeSpan <= chordThresholdMs) {
364- addNotesToCurrentClip(noteList, 127 )
371+ addNotesToCurrentClip(noteList)
365372 } else {
366373 noteList.forEach { note ->
367- addNotesToCurrentClip(listOf (note), 127 )
374+ addNotesToCurrentClip(listOf (note))
368375 }
369376 }
370377 }
@@ -373,7 +380,7 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
373380 }
374381 }
375382
376- private fun addNotesToCurrentClip (notes : List <Int >, velocity : Int ) {
383+ private fun addNotesToCurrentClip (notes : List <Pair < Int , Int >> ) {
377384 if (! clipLauncherCursorClip.exists().get()) {
378385 host.showPopupNotification(" No active clip found. Create or select a clip and open piano roll." )
379386 return
@@ -395,14 +402,15 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
395402 }
396403
397404 // Add all notes at the same position (chord)
398- notes.forEach { note ->
405+ notes.forEach { ( note, velocity) ->
399406 host.println (" setStep y:${note} , x:${stepper.x} " )
400407 // - 0.0001 prevents note lengths too close to the next and clearing deletes both
401408 val nl = stepper.stepLengthInBeats - 0.0001
402- clipLauncherCursorClip.setStep(0 , stepper.x, note, velocity, nl)
409+ val actualVelocity = if (fixedVelocityToggle.get()) fixedVelocitySetting.raw.toInt() else velocity
410+ clipLauncherCursorClip.setStep(0 , stepper.x, note, actualVelocity, nl)
403411 }
404412
405- val noteNames = notes.joinToString(" , " ) { getNoteNameFromMidi(it) }
413+ val noteNames = notes.joinToString(" , " ) { getNoteNameFromMidi(it.first ) }
406414 host.println (" Added $noteNames at step ${stepper.x} " )
407415
408416 updateCursorSelection(stepper.x)
@@ -432,7 +440,7 @@ class StepRecorderExtension(definition: ControllerExtensionDefinition, host: Con
432440
433441 private fun resetCursorClipToPlayStart () {
434442 val playStartValue = clipLauncherCursorClip.playStart.get()
435- val newStep = stepper.resetXFromBeats(playStartValue, clipLauncherCursorClip)
443+ stepper.resetXFromBeats(playStartValue, clipLauncherCursorClip)
436444
437445 host.println (
438446 " Reset step recorder cursor to play start ${
0 commit comments