Sprites.Media.Sequencer

The sequencer editor: its document helpers, its tune and cell logic, and the Editor that lays out the four stacked areas. The tune area of block slots and its controls (Sprites.Media.Sequencer.Tune) sit at the top, the block-definition grid of beats by channels (Sprites.Media.Sequencer.Grid) fills the height, and the piano keyboard (Sprites.Media.Sequencer.Keyboard) is fixed at the foot, with the always-open Info and Note windows over it. The channels and their capabilities come from a platform's Sprites.Platform.<name>.Sequencer descriptor.

Sprites.Media.Sequencer

Sprites.Media.Sequencer.Editor Void

Sprites.Media.Sequencer.Editor(data, thumb, info, model)

The sequencer editor. Fills the editor stage with four stacked areas: the tune strip of block slots, the block controls under it, the block-definition grid that takes the rest of the height, and the piano keyboard fixed at the foot. The always-open Info and Note windows float over it. The host page renders the standard editor header and the Name: row above the stage, exactly as the other editors do, so this builds only the stage. One cell is selected at a time, and the keyboard and Note window edit it, creating the slot's block on the first edit. The channel layout, note range and effect superset all come from model.

InputTypeDescription
dataSprites.Reactive.ValueThe tune, a two way value of { format, platform, tempo, blocks, tune }. See the sequencer storage format.
thumbSprites.Reactive.ValueA value the editor writes a small preview into for the library listing.
infoSprites.Reactive.ValueThe item's metadata.
modelObjectThe platform's Sequencer descriptor: channelTypes, channels, shared and effects.

Returns nothing.

const open = Sprites.Reactive.Value(false);
Sprites.Ui.Button.SetValue(open, true, 'Open the editor');
Sprites.Reactive.If(open, () => {
  Sprites.Ui.Dom.Tag('div', () => {
    Sprites.Ui.Dom.Style('height', '320px');
    const fresh = Sprites.Reactive.read(Sprites.Media.Sequencer.NewDocument(Sprites.Platform.C64.Sequencer));
    const data = Sprites.Reactive.Value(fresh);
    const thumb = Sprites.Reactive.Value(undefined);
    const info = Sprites.Reactive.Value(undefined);
    Sprites.Media.Sequencer.Editor(data, thumb, info, Sprites.Platform.C64.Sequencer);
  });
});
The whole editor from one call, built on a button press so it does not mount on page load.

Sprites.Media.Sequencer.NewDocument Object

Sprites.Media.Sequencer.NewDocument(model)

A new tune for a platform model, as plain data for the store: one empty block sized from the default tempo, one slot pointing at it, and a trailing empty slot. A document factory: it composes the block and the platform name as reactive values, snapshots them once with Sprites.Reactive.read, and assembles the plain record, the same shape the sprite editor's NewDocument follows.

InputTypeDescription
modelObjectThe platform's Sequencer descriptor, which fixes the channel count.

Returns a { format, platform, tempo, blocks, tune } data object.

const data = Sprites.Media.Sequencer.NewDocument(Sprites.Platform.C64.Sequencer);
const tune = Sprites.Object.Field(data, 'tune');
const count = Sprites.Array.Length(tune);
Sprites.Ui.Dom.Text('channels = ', count);
A new C64 tune: one block, one used slot and one empty slot.

Sprites.Media.Sequencer.BlockSize Number

Sprites.Media.Sequencer.BlockSize(tempo)

The number of beats in a block, the tempo's beatsPerBar times its barsPerBlock. Every block shares this size.

InputTypeDescription
tempoObject or Sprites.Reactive.ValueA { bpm, beatsPerBar, barsPerBlock } tempo.

Returns the block size in beats.

const size = Sprites.Media.Sequencer.BlockSize({ bpm: 125, beatsPerBar: 4, barsPerBlock: 4 });
Sprites.Ui.Dom.Text('block = ', size, ' beats');
Four beats a bar over four bars gives a sixteen beat block.

Sprites.Media.Sequencer.EmptyBlock Object

Sprites.Media.Sequencer.EmptyBlock(id, beatsPerBar, barsPerBlock, channels)

A fresh block value: an id and a Sprites.Grid.Three cell volume of channels columns by beatsPerBar rows by barsPerBlock bars, every cell undefined. A composite over Sprites.Grid.Three.Blank and Sprites.Reactive.ReadOnly, so it is a live value the document factory snapshots and a slot edit appends.

InputTypeDescription
idNumberThe block's stable id.
beatsPerBarNumberThe beats in a bar (the volume's height).
barsPerBlockNumberThe bars in a block (the volume's depth).
channelsNumberThe channel count (the volume's width).

Returns a { id, grid } block object, where grid is a { w, h, d, cells } volume.

const block = Sprites.Media.Sequencer.EmptyBlock(1, 4, 4, 3);
const grid = Sprites.Object.Field(block, 'grid');
const beats = Sprites.Grid.Three.Height(grid);
Sprites.Ui.Dom.Text('beats = ', beats);
An empty four by four beat block over three voices.

Sprites.Media.Sequencer.CurrentBlock Object

Sprites.Media.Sequencer.CurrentBlock(data, slot)

The block a tune slot points at, or undefined when the slot is empty. A composite over Sprites.Array.At, which tolerates a missing index, so it never throws for an empty slot.

InputTypeDescription
dataSprites.Reactive.ValueThe tune data.
slotNumber or Sprites.Reactive.ValueThe tune slot.

Returns the slot's block, or undefined.

const document = Sprites.Media.Sequencer.NewDocument(Sprites.Platform.C64.Sequencer);
const data = Sprites.Reactive.Value(document);
const block = Sprites.Media.Sequencer.CurrentBlock(data, 0);
const id = Sprites.Object.Field(block, 'id');
Sprites.Ui.Dom.Text('block id = ', id);
The selected slot's block, following the selection.

Sprites.Media.Sequencer.SlotFilled Boolean

Sprites.Media.Sequencer.SlotFilled(data, slot)

Whether a tune slot holds a block, that is its block index is not undefined. The grid reads this to decide whether to show the real block or an empty, fillable stand in.

InputTypeDescription
dataSprites.Reactive.ValueThe tune data.
slotNumber or Sprites.Reactive.ValueThe tune slot.

Returns a boolean value, true when the slot holds a block.

const document = Sprites.Media.Sequencer.NewDocument(Sprites.Platform.C64.Sequencer);
const data = Sprites.Reactive.Value(document);
const slot0Filled = Sprites.Media.Sequencer.SlotFilled(data, 0);
const slot1Filled = Sprites.Media.Sequencer.SlotFilled(data, 1);
Sprites.Ui.Dom.Text('slot 0 filled = ', slot0Filled, ', slot 1 filled = ', slot1Filled);
True while the selected slot holds a block.

Sprites.Media.Sequencer.CellValue Object

Sprites.Media.Sequencer.CellValue(data, slot, channel, beatInBar, bar, model)

The value of one cell as a read only reactive value, safe when the slot is empty or the position is blank: the stored cell if present, else the empty cell ({ effects: [] }). The position is a channel, a beat within its bar, and a bar, the three axes of the block volume. A composite over CurrentGrid, Sprites.Grid.Three.At and Sprites.Logic.Select. This is the read half that CellLens wraps.

InputTypeDescription
dataSprites.Reactive.ValueThe tune data.
slotNumber or Sprites.Reactive.ValueThe tune slot.
channelNumber or Sprites.Reactive.ValueThe channel (the volume's x).
beatInBarNumber or Sprites.Reactive.ValueThe beat within its bar (the volume's y).
barNumber or Sprites.Reactive.ValueThe bar (the volume's z).
modelObjectThe platform descriptor, used to size the phantom volume of an empty slot.

Returns the cell, or the empty cell.

const document = Sprites.Media.Sequencer.NewDocument(Sprites.Platform.C64.Sequencer);
const data = Sprites.Reactive.Value(document);
const cell = Sprites.Media.Sequencer.CellValue(data, 0, 0, 0, 0, Sprites.Platform.C64.Sequencer);
const effects = Sprites.Object.Field(cell, 'effects');
const effectCount = Sprites.Array.Length(effects);
Sprites.Ui.Dom.Text('blank cell effects = ', effectCount);
The selected cell, or an empty cell for a blank position.

Sprites.Media.Sequencer.CellLens Sprites.Reactive.Value

Sprites.Media.Sequencer.CellLens(data, slot, channel, beatInBar, bar, model)

A two way value onto one cell of the block: reading gives the cell, or the empty cell when the slot is empty or the position is blank, and setting writes it back into the slot's volume, creating the block if the slot was empty. A cell left with no note and no effects is stored as undefined, so clearing a note empties the cell. Every unrolled grid cell is one of these, and the editor derives the selected one for the keyboard and note window, so a write from any of them updates them all. Because a block can appear in many slots, editing a cell changes every slot that uses that block.

InputTypeDescription
dataSprites.Reactive.ValueThe tune data.
slotNumber or Sprites.Reactive.ValueThe tune slot.
channelNumber or Sprites.Reactive.ValueThe channel (the volume's x).
beatInBarNumber or Sprites.Reactive.ValueThe beat within its bar (the volume's y).
barNumber or Sprites.Reactive.ValueThe bar (the volume's z).
modelObjectThe platform descriptor, used to size a block created on the first edit of an empty slot.

Returns a Sprites.Reactive.Value lens onto the cell.

const document = Sprites.Media.Sequencer.NewDocument(Sprites.Platform.C64.Sequencer);
const data = Sprites.Reactive.Value(document);
const cell = Sprites.Media.Sequencer.CellLens(data, 0, 0, 0, 0, Sprites.Platform.C64.Sequencer);
const note = Sprites.Object.Field(cell, 'optionalNote');
Sprites.Ui.Input.Number(note);
const label = Sprites.Media.Sequencer.NoteText(note);
Sprites.Ui.Dom.Text(' label = ', label);
A lens onto voice 1, beat 1: editing the note writes it back into the block.

Sprites.Media.Sequencer.SetSelectedCell Object

Sprites.Media.Sequencer.SetSelectedCell(data, slot, channel, beatInBar, bar, cell, model)

New tune data with one cell set, creating the slot's block if the slot is empty. When the slot is empty a fresh block sized from the tempo is appended and the slot is pointed at it; when it is filled the existing block's volume is set with Sprites.Grid.Three.Set. The position is a channel, a beat within its bar, and a bar. A composite, so it composes inside a lens and the whole change commits at once. This is the write half that CellLens wraps.

InputTypeDescription
dataSprites.Reactive.ValueThe tune data.
slot, channel, beatInBar, barNumber or Sprites.Reactive.ValueThe position to set: the slot, and the channel, beat within its bar, and bar of the volume.
cellObject or undefinedThe new cell, { optionalNote, effects }, or undefined to clear the position.
modelObjectThe platform descriptor, used to size a block created for an empty slot.

Returns new tune data with the cell set.

const document = Sprites.Media.Sequencer.NewDocument(Sprites.Platform.C64.Sequencer);
const data = Sprites.Reactive.Value(document);
const setNote = Sprites.Reactive.Bindingundefined, () => {
  const cell = { optionalNote: 60, effects: [] };
  const next = Sprites.Media.Sequencer.SetSelectedCell(data, 1, 0, 0, 0, cell, Sprites.Platform.C64.Sequencer);
  Sprites.Reactive.Set(data, next);
});
Sprites.Ui.Button.Act(setNote, 'Set note in empty slot 1');
const cell = Sprites.Media.Sequencer.CellValue(data, 1, 0, 0, 0, Sprites.Platform.C64.Sequencer);
const note = Sprites.Object.Field(cell, 'optionalNote');
Sprites.Ui.Dom.Text(' note = ', note);
Setting a note in an empty slot creates its block.

Sprites.Media.Sequencer.NoteRange Object

Sprites.Media.Sequencer.NoteRange(model)

The { low, high } semitone range a platform plays, read straight from the descriptor's declared range. A composite: a field of the model (Sprites.Object.Field). The descriptor declares the range, so the editor never derives it.

InputTypeDescription
modelObjectThe platform Sequencer descriptor.

Returns a { low, high } semitone range.

const range = Sprites.Media.Sequencer.NoteRange(Sprites.Platform.C64.Sequencer);
const low = Sprites.Object.Field(range, 'low');
const high = Sprites.Object.Field(range, 'high');
Sprites.Ui.Dom.Text('low = ', low, ', high = ', high);
The C64's playable semitone range, from its pitch capability.

Sprites.Media.Sequencer.Summary String

Sprites.Media.Sequencer.Summary(data)

A short listing summary of a tune: the platform, the block and slot counts, and the tempo.

InputTypeDescription
dataSprites.Reactive.ValueThe tune data.

Returns the summary string.

const document = Sprites.Media.Sequencer.NewDocument(Sprites.Platform.C64.Sequencer);
const data = Sprites.Reactive.Value(document);
const summary = Sprites.Media.Sequencer.Summary(data);
Sprites.Ui.Dom.Text(summary);
A one line summary for a listing row.

Sprites.Media.Sequencer.ChannelCount Sprites.Reactive.Value

Sprites.Media.Sequencer.ChannelCount(model)

The number of channels a model has. A composite over the descriptor's channel list, so the editor sizes every block volume to the platform's own voice count.

InputTypeDescription
modelObjectThe platform's Sequencer descriptor, which lists the channels.

Returns the channel count as a number value.

const count = Sprites.Media.Sequencer.ChannelCount(Sprites.Platform.C64.Sequencer);
Sprites.Ui.Dom.Text('channels = ', count);
The C64 declares three voices.

Sprites.Media.Sequencer.CurrentGrid Sprites.Reactive.Value

Sprites.Media.Sequencer.CurrentGrid(data, slot, model)

The cell volume of the selected slot's block, or the phantom volume when the slot is empty. The block is routed through a Sprites.Logic.Select to a defined stand in built with Sprites.Reactive.ReadOnly before the grid field is read, so no read ever sees an undefined source. A composite.

InputTypeDescription
dataSprites.Reactive.ValueThe tune data.
slotNumber or Sprites.Reactive.ValueThe tune slot.
modelObjectThe platform descriptor, used to size the phantom volume of an empty slot.

Returns the block's { w, h, d, cells } volume, or the phantom volume.

const document = Sprites.Media.Sequencer.NewDocument(Sprites.Platform.C64.Sequencer);
const data = Sprites.Reactive.Value(document);
const grid = Sprites.Media.Sequencer.CurrentGrid(data, 0, Sprites.Platform.C64.Sequencer);
const voices = Sprites.Grid.Three.Width(grid);
const beats = Sprites.Grid.Three.Height(grid);
Sprites.Ui.Dom.Text('voices = ', voices, ', beats = ', beats);
The first slot's volume: three voices by four beats.

Sprites.Media.Sequencer.DefaultBarsPerBlock Number

One of the defaults a new tune opens on: the bars in a block. A namespace constant, a plain literal the document factory sizes the first block from.

Value 4.

Sprites.Ui.Dom.Text('bars per block = ', Sprites.Media.Sequencer.DefaultBarsPerBlock);
A new tune opens on four bars a block.

Sprites.Media.Sequencer.DefaultBeatsPerBar Number

One of the defaults a new tune opens on: the beats in a bar. A namespace constant, a plain literal the document factory sizes the first block from.

Value 4.

Sprites.Ui.Dom.Text('beats per bar = ', Sprites.Media.Sequencer.DefaultBeatsPerBar);
A new tune opens on four beats a bar.

Sprites.Media.Sequencer.DefaultBpm Number

One of the defaults a new tune opens on: the beats per minute. A namespace constant, a plain literal the document factory seeds the tempo with.

Value 125.

Sprites.Ui.Dom.Text('default bpm = ', Sprites.Media.Sequencer.DefaultBpm);
A new tune opens at 125 bpm.

Sprites.Media.Sequencer.EmptyCell Object

The empty cell, no note and no effects, the value a blank grid position reads. A namespace constant, a plain literal, never mutated, since edits build new cells. Its optionalNote is absent, so it reads as undefined.

Value { effects: [] }.

const effects = Sprites.Object.Field(Sprites.Media.Sequencer.EmptyCell, 'effects');
const effectCount = Sprites.Array.Length(effects);
Sprites.Ui.Dom.Text('effects = ', effectCount);
A blank position reads a cell with no effects.

Sprites.Media.Sequencer.NoteNames Array

The note names, C to B, that the label composite indexes by a semitone's pitch class. A plain constant list of the twelve names in an octave.

Value ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'].

const names = Sprites.Media.Sequencer.NoteNamesValue;
const count = Sprites.Array.Length(names);
const first = Sprites.Array.At(names, 0);
Sprites.Ui.Dom.Text('names = ', count, ', first = ', first);
Twelve note names, C first, read through the reactive wrapper.

Sprites.Media.Sequencer.NoteNamesValue Sprites.Reactive.Value

The note names wrapped as a reactive value, so the label composite can read them with Sprites.Array.At. It holds the same twelve names as NoteNames.

Value a reactive value of Sprites.Media.Sequencer.NoteNames.

const letter = Sprites.Array.At(Sprites.Media.Sequencer.NoteNamesValue, 9);
Sprites.Ui.Dom.Text('pitch class 9 = ', letter);
Pitch class nine reads as A.

Sprites.Media.Sequencer.NoteText Sprites.Reactive.Value

Sprites.Media.Sequencer.NoteText(note)

The label of a reactive semitone, such as 60 to 'C4'. A composite over Sprites.Maths, Sprites.Array and Sprites.Text, so a live note gives a live label for the cell face, the keyboard and the dropdown.

InputTypeDescription
noteNumber or Sprites.Reactive.ValueThe semitone to label.

Returns the note label as a string value.

const note = Sprites.Reactive.Value(60);
const label = Sprites.Media.Sequencer.NoteText(note);
Sprites.Ui.Input.Number(note);
Sprites.Ui.Dom.Text(' label = ', label);
Semitone 60 labels as C4, and the label follows the number.

Sprites.Media.Sequencer.PhantomGrid Sprites.Reactive.Value

Sprites.Media.Sequencer.PhantomGrid(data, model)

A blank cell volume sized to the current tempo, for an empty slot, so the grid still shows a full, fillable table instead of a blank. A composite over the tempo fields and Sprites.Grid.Three.Blank, so it follows a tempo change. The first edit through SetSelectedCell replaces it with a real block.

InputTypeDescription
dataSprites.Reactive.ValueThe tune data, read for its tempo.
modelObjectThe platform descriptor, read for its channel count.

Returns a blank { w, h, d, cells } volume sized to the tempo.

const document = Sprites.Media.Sequencer.NewDocument(Sprites.Platform.C64.Sequencer);
const data = Sprites.Reactive.Value(document);
const phantom = Sprites.Media.Sequencer.PhantomGrid(data, Sprites.Platform.C64.Sequencer);
const bars = Sprites.Grid.Three.Depth(phantom);
Sprites.Ui.Dom.Text('bars = ', bars);
A blank stand in volume, four bars deep, for an empty slot.

Sprites.Media.Sequencer.ReshapeTempo Object

Sprites.Media.Sequencer.ReshapeTempo(data, field, value, model)

New tune data with a tempo field set and every block's volume resized to match, so changing the beats per bar resizes the beat dimension of every block and changing the bars per block resizes the bar dimension, each landing in its own axis. A composite over Sprites.Object, Sprites.Array.Map and Sprites.Grid.Three.Resize. The field is 'beatsPerBar' or 'barsPerBlock'.

InputTypeDescription
dataSprites.Reactive.ValueThe tune data.
fieldStringThe tempo field to set, 'beatsPerBar' or 'barsPerBlock'.
valueNumber or Sprites.Reactive.ValueThe new field value, the new dimension size.
modelObjectThe platform descriptor, read for its channel count.

Returns new tune data with the tempo set and every block resized.

const document = Sprites.Media.Sequencer.NewDocument(Sprites.Platform.C64.Sequencer);
const data = Sprites.Reactive.Value(document);
const widen = Sprites.Reactive.Bindingundefined, () => {
  const next = Sprites.Media.Sequencer.ReshapeTempo(data, 'beatsPerBar', 8, Sprites.Platform.C64.Sequencer);
  Sprites.Reactive.Set(data, next);
});
Sprites.Ui.Button.Act(widen, '8 beats a bar');
const grid = Sprites.Media.Sequencer.CurrentGrid(data, 0, Sprites.Platform.C64.Sequencer);
const beats = Sprites.Grid.Three.Height(grid);
Sprites.Ui.Dom.Text(' beats = ', beats);
Setting the beats per bar resizes the beat axis of every block.