Skip to content

Commit

Permalink
Fixes and improvements
Browse files Browse the repository at this point in the history
NOTE: re-add MonsterJam controller to avoid duplicate/deprecated settings

* Finalized PLAY button behavior: double-click restarts, click starts/stops, shift-click depends on setting
* Added setting to choose between PLAY button behaviors
* STEP: added setting for alternating note row colors, increased color contrast
* SONG now works as "home" button when not at default clip launcher view
  • Loading branch information
unthingable committed Oct 27, 2022
1 parent 9f8df9f commit 3342856
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 138 deletions.
11 changes: 6 additions & 5 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Chorded buttons are sensitive to order. For example, **SHIFT+CONTROL** is not th
## Global

* **KNOB turn**: Move arranger playhead, jog through the project timeline. Hold **SHIFT** to adjust tempo in finer increments.
* **SONG**: **SuperScene** mode (see below)
* **SONG**: **SuperScene** mode (see below) or "home" (return to default Clip Launcher view)
* **STEP**: Step Sequencer
* **CLEAR**: Use in combination with other buttons to delete a scene (scene buttons), clip (a pad in session mode) or track (group buttons).
* **DUPLICATE**: Combine with a scene pad (duplicate scene) or a track button (duplicate track). To copy clips in session mode keep the Duplicate button pressed; choose the source clip (it must be a clip with content, you can still select a different clip as the source); select the destination clip (this must be an empty clip, which can also be on a different track); release the Duplicate button.
Expand All @@ -66,7 +66,7 @@ Chorded buttons are sensitive to order. For example, **SHIFT+CONTROL** is not th

* **PLAY**: Start/Stop playback
* **REC**: Toggle recording
* **SHIFT+PLAY** (RESTART): Rewind play position (stop if playing)
* **SHIFT+PLAY** Restart or pause/continue (see **Settings**)
* **SHIFT+RECORD**: Toggle launcher overdub
* **SHIFT+PAGE LEFT**: Toggle the metronome
* **SHIFT+PAGE RIGHT**: Toggle transport loop
Expand Down Expand Up @@ -122,7 +122,7 @@ MonsterJam will attempt to retroactively launch the clip "on time" even if you a

* The arrow keys scroll the grid by blocks of 8 tracks/scenes. Keys will light up if there is content to scroll to.
* **SHIFT+...** in regular mode: hold shift and then
* Arrow keys: scroll by 1 track/scene (SHIFT function is flippable in preferences)
* Arrow keys: scroll by 1 track/scene (SHIFT function is flippable in settings)
* **SCENE(1-8)**: directly select from the first 8 pages of scenes
* **TRACK(1-8)**: directly select from the first 8 pages of tracks (if enabled in settings)
* The currently selected scene/track page is **white**, available pages are **yellow**. If the view is currently between pages, two adjacent pages will be orange.
Expand All @@ -138,7 +138,7 @@ MonsterJam will attempt to retroactively launch the clip "on time" even if you a
* **LEVEL**: Toggles between Volume and Panorama editing of 8 tracks.
If Volume is active and playback is started the VU of the tracks is displayed as well.
All strips are lit in their tracks' color.
* Note: maximum slider range can be limited to values other than +6 dB, see **Preferences**.
* Note: maximum slider range can be limited to values other than +6 dB, see **Settings**.
* **AUX**: Edit send levels for a specific send for all tracks. Strips are lit in corresponding Effect track colors.
* **AUX+TRACK(A-H)**: Hold **AUX** down to select from the first 8 Effect tracks. Track buttons are lit in corresponding
Effect track colors, currently selected send is in WHITE.
Expand Down Expand Up @@ -409,7 +409,7 @@ User controls are accessible in two ways:

The 64 controls are grouped in 8 pages. To select a page use the track buttons, currently selected page is brightly lit.

# Preferences
# Settings

## Global

Expand All @@ -422,6 +422,7 @@ will change the colors of the top row of the clip matrix buttons to indicate tha
* _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: Launch tolerance**: 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.
* **Verbose console output**: Enable if you're me or just really curious about internal workings of MonsterJam, otherwise leave it off.

Expand Down
2 changes: 1 addition & 1 deletion 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-b3</version>
<version>8.0-b4</version>

<repositories>
<repository>
Expand Down
3 changes: 3 additions & 0 deletions src/main/scala/com/github/unthingable/JamSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ object JamSettings {
enum DpadScroll:
case `single/page`, `page/single`

enum ShiftPlay:
case Restart, `Pause/Resume`

trait EnumSetting[E] {
def setting: SettableEnumValue
def set(v: E): Unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class MonsterJamExtensionDefinition() extends ControllerExtensionDefinition {

override def getAuthor = "unthingable"

override def getVersion = "8.0-b3"
override def getVersion = "8.0-b4"

override def getId: UUID = MonsterJamExtensionDefinition.DRIVER_ID

Expand Down
15 changes: 10 additions & 5 deletions src/main/scala/com/github/unthingable/MonsterJamExtension.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ 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,
altNoteRow: SettableBooleanValue,
shiftGroup: SettableBooleanValue,
shiftDpad: EnumSetting[JamSettings.DpadScroll],
limitLevel: EnumSetting[JamSettings.LimitLevels],
shiftPlay: EnumSetting[JamSettings.ShiftPlay],
launchTolerance: SettableRangedValue,
debugOutput: SettableBooleanValue,
)
Expand Down Expand Up @@ -79,11 +82,13 @@ class MonsterJamExtension(val definition: MonsterJamExtensionDefinition, val hos
host.getDocumentState,
host.createApplication(),
MonsterPref(
preferences.getBooleanSetting("Show pretty shift commands in matrix", "Options", true),
preferences.getBooleanSetting("SHIFT-TRACK selects track page", "Options", true),
EnumSetting(preferences, "DPAD scroll (regular/SHIFT)", "Options", JamSettings.DpadScroll.`page/single`),
EnumSetting(preferences, "Limit level sliders", "Options", JamSettings.LimitLevels.None),
preferences.getNumberSetting("Launch tolerance", "Launch Q", 0, 1, 0.1, "beats", 0),
preferences.getBooleanSetting("Show pretty shift commands in matrix", "Display", true),
preferences.getBooleanSetting("Alternating note row colors", "Display: 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.getBooleanSetting("Verbose console output", "Debug", false),
),
MonsterDocPrefs(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ object Graph {

case class ModeNode(layer: ModeLayer) {
protected[Graph] var parent: Option[ModeNode] = None
protected[Graph] var subParent: Option[ModeNode] = None
protected[Graph] var subAncestor: Option[ModeNode] = None
protected[Graph] val children: mutable.HashSet[ModeNode] = mutable.HashSet.empty
protected[Graph] val nodeBindings: mutable.Set[Binding[_, _, _]] = mutable.LinkedHashSet.empty
protected[Graph] val nodesToRestore: mutable.HashSet[ModeNode] = mutable.HashSet.empty
Expand Down Expand Up @@ -94,13 +96,15 @@ object Graph {

layerMap.foreach(indexSubs.tupled)

def indexSubs(layer: ModeLayer, node: ModeNode): Unit =
private def indexSubs(layer: ModeLayer, node: ModeNode): Unit =
layer match
case mml: MultiModeLayer =>
mml.subModes.foreach { l =>
Util.println(s"adding submode ${l.id}")
val sub: ModeNode = indexLayer(l)
sub.parent = Some(node)
sub.parent = Some(node)
sub.subParent = Some(node)
sub.subAncestor = node.subAncestor.orElse(Some(node))
indexSubs(l, sub)
}
case _ => ()
Expand Down Expand Up @@ -130,8 +134,8 @@ object Graph {
case None => activateb.foreach(_.bind()) // they're unmanaged for orphan nodes
}

val entryNodes: Seq[ModeNode] = layerMap.values.filter(_.parent.isEmpty).toSeq
val exitNodes: Seq[ModeNode] = layerMap.values.filter(_.children.isEmpty).toSeq
private val entryNodes: Seq[ModeNode] = layerMap.values.filter(_.parent.isEmpty).toSeq
private val exitNodes: Seq[ModeNode] = layerMap.values.filter(_.children.isEmpty).toSeq

ext.host.scheduleTask(
() => {
Expand All @@ -152,6 +156,20 @@ object Graph {
layerMap.getOrElseUpdate(l, ModeNode(l))
}

def isOcculted(l: ModeLayer): Boolean =
layerMap.get(l).exists(_.bumpingMe.nonEmpty)

def reactivate(l: ModeLayer): Unit =
layerMap
.get(l)
.map(node =>
node.bumpingMe.map(n => n.subAncestor.getOrElse(n)).toSet.foreach(deactivate(s"reactivating ${l.id}"))
activate(s"reactivating ${l.id}")(node)
)

def maybeReactivate(layers: ModeLayer*): Unit =
layers.foreach(l => if isOcculted(l) then reactivate(l))

protected def activate(reason: String)(node: ModeNode): Unit = {
Util.println(s"activating node ${node.layer.id}: $reason")

Expand Down Expand Up @@ -198,7 +216,7 @@ object Graph {
)
// .filter(!_.node.contains(node))))
.filter(_.bumped.nonEmpty)

val bumpNodes: Iterable[ModeNode] = bumpBindings
.flatMap(_.bumped.map(_._2))
// FIXME hack: can't bump own submodes
Expand All @@ -220,6 +238,7 @@ object Graph {

node.nodeBindings.foreach(ext.binder.bind(_, node))

node.bumpingMe.clear()
node.layer.onActivate()

Util.println(s"-- activated ${node.layer.id} ---")
Expand Down
87 changes: 64 additions & 23 deletions src/main/scala/com/github/unthingable/jam/Jam.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,38 @@ import com.github.unthingable.{MonsterJamExt, Util}
import com.github.unthingable.JamSettings.ShowHide
import com.github.unthingable.framework.mode.Graph.{Coexist, Exclusive, ModeDGraph}
import com.github.unthingable.framework.mode.{GateMode, ModeButtonLayer, SimpleModeLayer}
import com.github.unthingable.framework.binding.BindingDSL
import com.github.unthingable.framework.binding.{BindingBehavior as BB, BindingDSL, EB}
import com.github.unthingable.jam.surface._
import com.github.unthingable.jam.layer._
import com.bitwig.`extension`.callback.IndexedBooleanValueChangedCallback
import com.github.unthingable.framework.binding.GlobalEvent

class Jam(implicit val ext: MonsterJamExt)
extends BindingDSL, Aux, TransportL, Level, Dpad, TrackL, ClipMatrix, Shift, Control, MacroL, SceneL, StepSequencer {
extends BindingDSL,
Aux,
TransportL,
Level,
Dpad,
TrackL,
ClipMatrix,
Shift,
Control,
MacroL,
SceneL,
StepSequencer {

implicit val j: JamSurface = new JamSurface()

val EIGHT: Vector[Int] = (0 to 7).toVector

object GlobalMode {
// These only set their isOn flags and nothing else
val Clear : ModeButtonLayer = ModeButtonLayer("CLEAR", j.clear, modeBindings = Seq.empty, GateMode.Gate)
val Duplicate: ModeButtonLayer = ModeButtonLayer("DUPLICATE", j.duplicate, modeBindings = Seq.empty, GateMode.Gate)
val Select : ModeButtonLayer = ModeButtonLayer("SELECT", j.select, modeBindings = Seq.empty, GateMode.Gate)
val Clear: ModeButtonLayer =
ModeButtonLayer("CLEAR", j.clear, modeBindings = Seq.empty, GateMode.Gate)
val Duplicate: ModeButtonLayer =
ModeButtonLayer("DUPLICATE", j.duplicate, modeBindings = Seq.empty, GateMode.Gate)
val Select: ModeButtonLayer =
ModeButtonLayer("SELECT", j.select, modeBindings = Seq.empty, GateMode.Gate)
}

lazy val trackBank = ext.trackBank
Expand All @@ -35,9 +49,10 @@ class Jam(implicit val ext: MonsterJamExt)
superBank.scrollPosition().markInterested()

val selectedClipTrack = ext.cursorTrack // FIXME maybe abandon
def selectedObserver(track: Int): IndexedBooleanValueChangedCallback = (idx: Int, selected: Boolean) =>
if (selected)
ext.events.eval("selectObserver")(GlobalEvent.ClipSelected(track, idx))
def selectedObserver(track: Int): IndexedBooleanValueChangedCallback =
(idx: Int, selected: Boolean) =>
if (selected)
ext.events.eval("selectObserver")(GlobalEvent.ClipSelected(track, idx))

(0 until 256).foreach { i =>
val t = superBank.getItemAt(i)
Expand All @@ -46,7 +61,7 @@ class Jam(implicit val ext: MonsterJamExt)

given tracker: TrackTracker = UnsafeTracker(superBank)

lazy val sceneBank : SceneBank = trackBank.sceneBank()
lazy val sceneBank: SceneBank = trackBank.sceneBank()
lazy val masterTrack: MasterTrack = ext.host.createMasterTrack(8)

sceneBank.canScrollForwards.markInterested()
Expand All @@ -56,7 +71,7 @@ class Jam(implicit val ext: MonsterJamExt)
trackBank.cursorIndex().markInterested()

ext.docPrefs.hideDisabled.addValueObserver { v =>
val skip = (v != ShowHide.Show)
val skip = v != ShowHide.Show
trackBank.setSkipDisabledItems(skip)
superBank.setSkipDisabledItems(skip)
}
Expand All @@ -74,21 +89,47 @@ class Jam(implicit val ext: MonsterJamExt)
val bottom = SimpleModeLayer("_|_", modeBindings = Vector.empty)
val unmanaged = SimpleModeLayer("_x_", modeBindings = Vector.empty)

// experimental
lazy val home: SimpleModeLayer = new SimpleModeLayer("home"):
private var noRelease: Boolean = false
override val modeBindings = Vector(
EB(
j.song.st.press,
"song press (restore home)",
() =>
val (noClip :: noScene :: _) = Seq(clipMatrix, sceneCycle).map(graph.isOcculted) : @unchecked
Util.println(s"restore home clip/scene: $noClip $noScene")
if noClip || noScene then
graph.reactivate(sceneCycle)
noRelease = true
else
sceneCycle.press()
noRelease = false
if (noClip)
graph.reactivate(clipMatrix)
,
BB(
managed = false,
// exclusive = false
)
),
)

val graph = new ModeDGraph(
init = Vector(levelCycle, sceneCycle, clipMatrix),
dpad -> top,
play -> top,
position -> Coexist(tempoLayer),
sceneCycle -> top,
bottom -> Coexist(globalQuant, shiftTransport, shiftMatrix, shiftPages),
bottom -> Exclusive(GlobalMode.Clear, GlobalMode.Duplicate, GlobalMode.Select),
trackGroup -> Exclusive(solo, mute),
bottom -> Coexist(clipMatrix, pageMatrix, stepSequencer),
bottom -> stripGroup,
bottom -> Coexist(auxGate, deviceSelector, macroLayer),
trackGroup -> Exclusive(EIGHT.map(trackGate): _*),
init = Vector(levelCycle, sceneCycle, clipMatrix, home),
dpad -> top,
play -> top,
position -> Coexist(tempoLayer),
sceneCycle -> top,
bottom -> Coexist(globalQuant, shiftTransport, shiftMatrix, shiftPages),
bottom -> Exclusive(GlobalMode.Clear, GlobalMode.Duplicate, GlobalMode.Select),
trackGroup -> Exclusive(solo, mute),
bottom -> Coexist(clipMatrix, pageMatrix, stepSequencer),
bottom -> stripGroup,
bottom -> Coexist(auxGate, deviceSelector, macroLayer),
trackGroup -> Exclusive(EIGHT.map(trackGate): _*),
masterButton -> top,
bottom -> Coexist(unmanaged),
bottom -> Coexist(unmanaged),
)

// val newGraph = ModeCommander(
Expand Down
7 changes: 4 additions & 3 deletions src/main/scala/com/github/unthingable/jam/layer/SceneL.scala
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ trait SceneL { this: Jam =>
}

// a hybrid: both a cycle layer for scene buttons and a controller for page matrix
lazy val sceneCycle = new ModeCycleLayer("sceneCycle") {
object sceneCycle extends ModeCycleLayer("sceneCycle") {
override val subModes: Vector[ModeLayer] = Vector(
sceneSub,
superSceneSub
Expand Down Expand Up @@ -240,8 +240,9 @@ trait SceneL { this: Jam =>

override val modeBindings: Seq[Binding[_, _, _]] = Vector(
SupBooleanB(j.song.light.isOn, () => superSceneSub.isOn),
EB(j.song.st.press, "sceneCycle pressed", () => press()),
EB(j.song.st.release, "sceneCycle released", () => release()),
// taken over by experimental home mode
// EB(j.song.st.press, "sceneCycle pressed", () => press()),
EB(j.song.st.release, "sceneCycle released", () => if pressedAt.nonEmpty then release()),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -240,20 +240,20 @@ trait StepSequencer extends BindingDSL { this: Jam =>
)

inline def setStepPage(page: Int) =
scrollXTo(ts.stepPageSize * page)
scrollXTo(ts.stepPageSize * page)

def scrollXTo(offset: Int) =
def scrollXTo(offset: Int) =
setState(ts.copy(stepScrollOffset = offset))
clip.scrollToStep(ts.stepScrollOffset)
fineClip.scrollToStep(ts.stepScrollOffset * fineRes)

def scrollXBy(inc: Int) = scrollXTo(ts.stepScrollOffset + inc)

inline def scrollXBy(dir: UpDown, size: => Int): Unit =
inline def scrollXBy(dir: UpDown, size: => Int): Unit =
scrollXTo(size * (inline dir match
case UpDown.Up => 1
case UpDown.Down => -1
))
))

inline def stepAt(x: Int, y: Int): NoteStep =
clip.getStep(ts.channel, x, y)
Expand Down Expand Up @@ -312,8 +312,9 @@ trait StepSequencer extends BindingDSL { this: Jam =>
(for (col <- EIGHT; row <- EIGHT) yield {
// val (stepNum, x, y) = stepView(row, col)
def xy: (Int, Int) = m2clip(row, col)
def bgColor =
if xy._2 % 2 != 0 then JamColorState(JamColorBase.next(JamColorState.toColorIndex(clipColor)), 0)
def bgColor =
if ext.preferences.altNoteRow.get() && xy._2 % 2 != 0 then
JamColorState(JamColorBase.next(JamColorState.toColorIndex(clipColor), 2), 0)
else JamColorState.empty
// def cachedClip = steps(channel)(xy._1)(xy._2)
Vector(
Expand Down

0 comments on commit 3342856

Please sign in to comment.