Skip to content

Commit

Permalink
Step sequencer fixes and improvements
Browse files Browse the repository at this point in the history
* Added small delay between step press and note param activation (sliders) for less flashy sequence editing. Delay is equal to when the step press becomes long press.
* Added a setting to keep notes within their original steps when nudging
* In "Quick Clip Selector" (hold STEP), pressing an empty slot will create a new clip and select it
* When transport is playing, newly created clips will play too
* Added "note interlace" setting
* Changed alternating note colors to 8 rainbow colors, for better visual separation
* Refactored controller settings: re-add MonsterJam in your Bitwig
  • Loading branch information
unthingable committed May 24, 2023
1 parent 81f4488 commit 32e6c18
Show file tree
Hide file tree
Showing 30 changed files with 487 additions and 405 deletions.
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "3.6.1"
version = "3.7.3"
runner.dialect = scala3

style = defaultWithAlign
Expand Down
84 changes: 67 additions & 17 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ The button grid is bottom-focused. Note mode will push the grid up. When using n

### Quick clip selector

Hold **STEP** to see the clip matrix. Currently selected clip will be bright WHITE. Press any other clip to focus the step sequencer on it.
Hold **STEP** to see the clip matrix. Currently selected clip will be bright WHITE. Press any other clip to focus the step sequencer on it. If a slot is empty, new clip will be created. If transport is playing, newly created clips will play automatically.

### Scene buttons

Expand Down Expand Up @@ -246,6 +246,35 @@ The step sequencer matrix is a windowed view of a clip, showing up to 64 steps a
* 4: 8 notes per 8 rows (1 row per note)
* **KNOB**: change step size

## Note interlace

With nontrivial grid sizes (i.e. not 8x8 or 1x64) you can choose between different wrapping modes and pick what works for you. The toggle is in controller settings.

For example, if Grid is 4x16 (showing 4 notes, 16 steps each), the different modes might look as follows, with `-` being the currently playing step:

Interlaced:
```
11-11111
11111111
22-22222
22222222
33-33333
33333333
44-44444
44444444
```
Non-interlaced (whole pattern wrap):
```
11-11111
22-22222
33-33333
44-44444
11111111
22222222
33333333
44444444
```

## Note/Velocity submode

* **NOTES**: activate note/velocity selectors
Expand Down Expand Up @@ -511,22 +540,31 @@ The 64 controls are grouped in 8 pages. To select a page use the track buttons,

## Global

* **Show pretty shift commands in matrix**: when enabled, holding **SHIFT**
will change the colors of the top row of the clip matrix buttons to indicate that they are special.
* **Step sequencer: Alternating row colors**: in step sequencer, each note row gets a different color
* **Behavior: Step sequencer pattern follow**: active step sequencer pattern page will automatically follow playing position
* **Behavior: Step sequencer note preview**: pressing steps in sequencer will play corresponding notes
* **SHIFT-TRACK selects track page**: **SHIFT** turns track group row into page selectors, see **Page navigation**
* **DPAD scroll (regular/SHIFT)**: Flip **DPAD** function with and without **SHIFT**: arrows scroll by page/SHIFT-arrow by single row, or vice versa.
* **Limit level sliders**: slider range when controlling track levels
* _None_: sliders behave as shown in the app (i.e., +6 dB is maximum)
* _0 dB_: slider maximum is 0 dB for all tracks
* _-10 dB_: slider maximum is -10 dB for all tracks
* _Smart_: maximums are 0 dB for group tracks and -10 dB for regular tracks
* **SHIFT+PLAY**: Toggle between restart and pause/continue
* **Launch Q forgiveness**: how late you can be for retroactive launch Q to work. 0.0 turns it off, 0.5-0.8 is probably a good range.
* **Launch Q lookahead**: compensation for event processing delay. If you attempt to launch on-beat but still miss the window, increase this value.
* **Verbose console output**: Enable if you're me or just really curious about internal workings of MonsterJam, otherwise leave it off.
* **Display**
* **Show pretty shift commands in matrix**: when enabled, holding **SHIFT**
will change the colors of the top row of the clip matrix buttons to indicate that they are special (like in DbM)
* **Step Sequencer**
* **Note interlace**: control how long note rows are wrapped, depending on Grid settings
* On: rows grouped by note (each note row wrapped individually)
* Off: single row per note (the whole pattern is wrapped)
* **Alternating row colors**: in step sequencer, each note row gets a different color
* **Step sequencer pattern follow**: active step sequencer pattern page will automatically follow playing position
* **Step sequencer note preview**: pressing steps in sequencer will play corresponding notes
* **Keep note end**: when nudging steps right, automatically reduce note duration keep the note from extending into next empty step
* **Behavior**
* **SHIFT-TRACK selects track page**: **SHIFT** turns track group row into page selectors, see **Page navigation**
* **DPAD scroll (regular/SHIFT)**: Flip **DPAD** function with and without **SHIFT**: arrows scroll by page/SHIFT-arrow by single row, or vice versa.
* **Limit level sliders**: slider range when controlling track levels
* _None_: sliders behave as shown in the app (i.e., +6 dB is maximum)
* _0 dB_: slider maximum is 0 dB for all tracks
* _-10 dB_: slider maximum is -10 dB for all tracks
* _Smart_: maximums are 0 dB for group tracks and -10 dB for regular tracks
* **SHIFT+PLAY**: Toggle between restart and pause/continue
* **Launcher**
* **Launch Q forgiveness**: how late you can be for retroactive launch Q to work. 0.0 turns it off, 0.5-0.8 is probably a good range.
* **Launch Q lookahead**: compensation for event processing delay. If you attempt to launch on-beat but still miss the window, increase this value.
* **Debug**
* **Verbose console output**: Enable if you're me or just really curious about internal workings of MonsterJam, otherwise leave it off.

After changing preferences it may be necessary to reinitialize the extension (turn it off an on again in Controllers settings, or select a different project).

Expand All @@ -538,6 +576,18 @@ After changing preferences it may be necessary to reinitialize the extension (tu

# Changelog

## 8.0b17

Step sequencer fixes and improvements

* Added small delay between step press and note param activation (sliders) for less flashy sequence editing. Delay is equal to when the step press becomes long press.
* Added a setting to keep notes within their original steps when nudging
* In "Quick Clip Selector" (hold STEP), pressing an empty slot will create a new clip and select it
* When transport is playing, newly created clips will play too
* Added "note interlace" setting
* Changed alternating note colors to 8 rainbow colors, for better visual separation
* Refactored controller settings: re-add MonsterJam in your Bitwig

## 8.0b16

Step sequencer fixes and improvements
Expand Down
23 changes: 19 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<artifactId>monsterjam</artifactId>
<packaging>jar</packaging>
<name>MonsterJam</name>
<version>8.0-b16</version>
<version>8.0-b17</version>

<repositories>
<repository>
Expand All @@ -32,14 +32,28 @@
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala3-library_3</artifactId>
<version>3.2.1</version>
<version>3.3.0-RC5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.scala-lang.modules/scala-xml -->
<dependency>
<groupId>org.scala-lang.modules</groupId>
<artifactId>scala-xml_3</artifactId>
<version>2.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.specs2/specs2-core -->
<dependency>
<groupId>org.specs2</groupId>
<artifactId>specs2-core_3</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.specs2/specs2-junit -->
<dependency>
<groupId>org.specs2</groupId>
<artifactId>specs2-junit_3</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<profiles>
Expand Down Expand Up @@ -85,17 +99,18 @@
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>4.8.0</version>
<version>4.8.1</version>
<configuration>
<args>
<arg>-language:strictEquality</arg>
<arg>-Wunused:imports</arg>
</args>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<!-- <goal>testCompile</goal>-->
<goal>testCompile</goal>
</goals>
</execution>
</executions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class MonsterJamExtensionDefinition() extends ControllerExtensionDefinition:

override def getAuthor = "unthingable"

override def getVersion = "8.0-b16"
override def getVersion = "8.0-b17"

override def getId: UUID = MonsterJamExtensionDefinition.DRIVER_ID

Expand Down
19 changes: 11 additions & 8 deletions src/main/scala/com/github/unthingable/MonsterJamExtension.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ package com.github.unthingable
import com.bitwig.extension.controller.ControllerExtension
import com.bitwig.extension.controller.api.*
import com.github.unthingable.JamSettings.EnumSetting
import com.github.unthingable.JamSettings
import com.github.unthingable.framework.EventBus
import com.github.unthingable.framework.binding.Binder
import com.github.unthingable.framework.binding.Event
import com.github.unthingable.jam.Jam
import com.github.unthingable.framework.binding.{Binder, Event}
import com.github.unthingable.jam.surface.XmlMap
import com.github.unthingable.jam.surface.XmlMap.loadMap
import java.util.EnumSet

case class MonsterPref(
shiftRow: SettableBooleanValue,
stepNoteInterlace: SettableBooleanValue,
altNoteRow: SettableBooleanValue,
stepFollow: SettableBooleanValue,
stepNotePreview: SettableBooleanValue,
stepKeepEnd: SettableBooleanValue,
shiftGroup: SettableBooleanValue,
shiftDpad: EnumSetting[JamSettings.DpadScroll],
limitLevel: EnumSetting[JamSettings.LimitLevels],
Expand Down Expand Up @@ -98,15 +99,17 @@ class MonsterJamExtension(val definition: MonsterJamExtensionDefinition, val hos
host.createApplication(),
MonsterPref(
preferences.getBooleanSetting("Show pretty shift commands in matrix", "Display", true),
preferences.getBooleanSetting("Alternating note row colors", "Display: step sequencer", true),
preferences.getBooleanSetting("Step sequencer pattern follow", "Behavior", false),
preferences.getBooleanSetting("Step sequencer note preview", "Behavior", false),
preferences.getBooleanSetting("Note interlace", "Step Sequencer", true),
preferences.getBooleanSetting("Alternating note row colors", "Step Sequencer", true),
preferences.getBooleanSetting("Step sequencer pattern follow", "Step Sequencer", false),
preferences.getBooleanSetting("Step sequencer note preview", "Step Sequencer", false),
preferences.getBooleanSetting("Keep note end", "Step Sequencer", true),
preferences.getBooleanSetting("SHIFT-TRACK selects track page", "Behavior", true),
EnumSetting(preferences, "DPAD scroll (regular/SHIFT)", "Behavior", JamSettings.DpadScroll.`page/single`),
EnumSetting(preferences, "Limit level sliders", "Behavior", JamSettings.LimitLevels.None),
EnumSetting(preferences, "SHIFT+PLAY", "Behavior", JamSettings.ShiftPlay.`Pause/Resume`),
preferences.getNumberSetting("Launch Q forgiveness", "Behaviour", 0, 1, 0.1, "beats", 0.5),
preferences.getNumberSetting("Launch Q lookahead", "Behaviour", 0, .5, 0.02, "beats", 0.1),
preferences.getNumberSetting("Launch Q forgiveness", "Launcher", 0, 1, 0.1, "beats", 0.5),
preferences.getNumberSetting("Launch Q lookahead", "Launcher", 0, .5, 0.02, "beats", 0.1),
preferences.getBooleanSetting("Verbose console output", "Debug", false),
),
MonsterDocPrefs(
Expand Down
46 changes: 28 additions & 18 deletions src/main/scala/com/github/unthingable/Util.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,15 @@ package com.github.unthingable

import com.bitwig.extension.api.Color
import com.bitwig.extension.controller.api.Bank
import com.bitwig.extension.controller.api.CursorRemoteControlsPage
import com.bitwig.extension.controller.api.ObjectProxy
import com.bitwig.extension.controller.api.Preferences
import com.bitwig.extension.controller.api.SettableBooleanValue
import com.bitwig.extension.controller.api.SettableEnumValue
import com.bitwig.extension.controller.api.Settings
import com.github.unthingable.jam.surface.JamColor.JamColorBase.CYAN
import com.github.unthingable.jam.surface.JamColor.JamColorBase.FUCHSIA
import com.github.unthingable.jam.surface.JamColor.JamColorBase.GREEN
import com.github.unthingable.jam.surface.JamColor.JamColorBase.LIME
import com.github.unthingable.jam.surface.JamColor.JamColorBase.MAGENTA
import com.github.unthingable.jam.surface.JamColor.JamColorBase.ORANGE
import com.github.unthingable.jam.surface.JamColor.JamColorBase.RED
import com.github.unthingable.jam.surface.JamColor.JamColorBase.YELLOW
import com.github.unthingable.jam.surface.JamColor.JamColorBase.*

import java.awt.event.ActionEvent
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets
import java.time.Duration
import java.time.Instant
import javax.swing.Timer
Expand All @@ -33,7 +20,29 @@ import scala.collection.mutable
import scala.deriving.Mirror
import scala.util.Try

transparent trait Util:
trait Math:
import Ordering.Implicits.*
import Integral.Implicits.*

/** Find next and previous multiple of step */
trait Steppable[A]:
def next(n: A, step: A): A
def prev(n: A, step: A): A

given s_int[A](using int: Integral[A]): Steppable[A] with
inline def next(n: A, step: A) = if n % step > int.fromInt(0) then step * ((n / step) + int.fromInt(1)) else n
inline def prev(n: A, step: A) = if n % step > int.fromInt(0) then step * (n / step) else n

given s_frac: Steppable[Double] with
inline def next(n: Double, step: Double) = (n / step).ceil * step
inline def prev(n: Double, step: Double) = (n / step).floor * step

extension [A](n: A)(using s: Steppable[A])
inline def next(step: A): A = s.next(n, step)
inline def prev(step: A): A = s.prev(n, step)
end Math

transparent trait Util extends Math:
implicit class SeqOps[A, S[B] <: Iterable[B]](seq: S[A]):
def forindex(f: (A, Int) => Unit): S[A] =
seq.zipWithIndex.foreach(f.tupled)
Expand All @@ -45,7 +54,7 @@ transparent trait Util:
case class Timed[A](value: A, instant: Instant)

extension [A <: ObjectProxy](bank: Bank[A])
def view: IndexedSeqView[A] = (0 until bank.itemCount().get()).view.map(bank.getItemAt)
def itemView: IndexedSeqView[A] = (0 until bank.itemCount().get()).view.map(bank.getItemAt)

def fullView: IndexedSeqView[A] = (0 until bank.getCapacityOfBank()).view.map(bank.getItemAt)

Expand All @@ -56,6 +65,7 @@ object Util extends Util:

type SelfEqual[A] = CanEqual[A, A]

/** Useful generic stuff like tracing and casting */
extension [A](obj: A)
transparent inline def trace(): A =
Util.println(obj.toString)
Expand Down Expand Up @@ -95,7 +105,7 @@ object Util extends Util:
val arr = ByteBuffer.allocate(4).putFloat(v.toFloat).array()
Util.println(arr.toSeq.map(_ & 0xff).map(s => f"$s%02x").mkString(" "))
}
val rainbow = Vector(RED, ORANGE, YELLOW, GREEN, LIME, CYAN, MAGENTA, FUCHSIA)
val rainbow8 = Vector(RED, LIGHT_ORANGE, YELLOW, LIME, MINT, BLUE, PLUM, PURPLE)
val rainbow16 = (0 until 16).map(i => (i + 1) * 4).toVector

def serialize[A](o: A): String =
Expand Down Expand Up @@ -126,7 +136,7 @@ object Util extends Util:
given CanEqual[B, B] = CanEqual.derived
f(a) == f(b)

def schedule(f: => Unit, delay: Int)(using ext: MonsterJamExt): Unit =
def delay(delay: Int, f: => Unit)(using ext: MonsterJamExt): Unit =
ext.host.scheduleTask(() => f, delay)

inline def popup(s: String)(using ext: MonsterJamExt): Unit =
Expand Down
1 change: 0 additions & 1 deletion src/main/scala/com/github/unthingable/action/package.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.github.unthingable

import com.bitwig.extension.controller.api.ControllerHost
import com.bitwig.extension.controller.api.TrackBank

package object action:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.github.unthingable.jam.surface.WithSource

import scala.annotation.targetName
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.reflect.ClassTag

class EventBus[BaseE]:
Expand Down Expand Up @@ -86,7 +85,6 @@ class EventBus[BaseE]:
.asInstanceOf[mutable.HashSet[Reactor[E]]]

private def receiversFor[E <: BaseE](e: E): Iterable[Reactor[E]] =
(valueSubs.get(e).toSeq.flatten
++ classSubs.get(e.getClass()).toSeq.flatten)
(valueSubs.get(e).toSeq.flatten ++ classSubs.get(e.getClass()).toSeq.flatten)
.asInstanceOf[Iterable[Reactor[E]]]
end EventBus
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,17 @@ import com.github.unthingable.MonsterJamExt
import com.github.unthingable.Util
import com.github.unthingable.framework.HasId
import com.github.unthingable.framework.binding.HB.HBS
import com.github.unthingable.framework.mode.Graph
import com.github.unthingable.jam.surface.HasButtonState
import com.github.unthingable.jam.surface.HasHwButton
import com.github.unthingable.jam.surface.HasOnOffLight
import com.github.unthingable.jam.surface.HasRgbLight
import com.github.unthingable.jam.surface.JamColorState
import com.github.unthingable.jam.surface.JamOnOffButton
import com.github.unthingable.jam.surface.JamRgbButton
import com.github.unthingable.jam.surface.WithSource

import java.time.Instant
import java.util.function.BooleanSupplier
import java.util.function.Supplier
import scala.collection.mutable

import reflect.Selectable.reflectiveSelectable

trait Clearable:
// stop the binding from doing its thing
def clear(): Unit
Expand Down Expand Up @@ -99,11 +93,12 @@ class HB[S](
bindings.addAll(
Vector(
bindingSource.addBinding(target)
) ++ (if behavior.tracked then
operatedActions
.find(bindingSource.canBindTo)
.map(bindingSource.addBinding)
else Seq.empty)
) ++
(if behavior.tracked then
operatedActions
.find(bindingSource.canBindTo)
.map(bindingSource.addBinding)
else Seq.empty)
)
isActive = true

Expand Down
Loading

0 comments on commit 32e6c18

Please sign in to comment.