| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Termtab.UI.Editing
Description
All operations that mutate the Song inside an AppState: entering
fret numbers, deleting notes, changing durations, subdividing/combining
beats, and adding/deleting measures — plus the undo/redo stack every
one of them goes through via withUndo.
Every editing function here takes and returns a whole AppState (not
just a Song) because several of them also need to read or update
cursor position, selection, or the in-progress fret-digit buffer
alongside the document edit itself. Pure cursor movement that doesn't
touch the document lives in Termtab.UI.Types instead; which keys call
which of these functions is decided in Termtab.UI.Keybindings.
Synopsis
- enterFretDigit :: Int -> AppState -> AppState
- deleteNoteAtCursor :: AppState -> AppState
- insertRest :: AppState -> AppState
- cycleDuration :: AppState -> AppState
- subdivideBeat :: Int -> AppState -> AppState
- combineBeats :: AppState -> AppState
- addMeasure :: AppState -> AppState
- deleteMeasure :: AppState -> AppState
- withUndo :: (Song -> Song) -> AppState -> AppState
- undo :: AppState -> AppState
- redo :: AppState -> AppState
- clearFretEntry :: AppState -> AppState
- fretToPitch :: Instrument -> StringIndex -> FretNumber -> Pitch
- modifyTrack :: TrackIndex -> (Track -> Track) -> Song -> Song
- modifyBeats :: MeasureIndex -> ([Beat] -> [Beat]) -> Track -> Track
Documentation
enterFretDigit :: Int -> AppState -> AppState Source #
Handle one typed fret digit at the cursor position. Consecutive digits
accumulate into a multi-digit fret number via asFretEntry
(combined = prev * 10 + digit), reset back to just the new digit if
the combined value would exceed 24 (an invalid fret number). Writes a
Note with a fixed Velocity of 100 and records the edit on the undo
stack.
deleteNoteAtCursor :: AppState -> AppState Source #
Remove whatever note is on the cursor's string at the cursor's beat.
insertRest :: AppState -> AppState Source #
Turn the beat at the cursor into a rest, clearing any notes it had.
cycleDuration :: AppState -> AppState Source #
Advance the duration of the beat at the cursor to the next value in
nextDuration's cycle.
subdivideBeat :: Int -> AppState -> AppState Source #
Split the beat at the cursor into n equal beats (each getting the
subdivided Duration from subdivideDuration, and all n copies of the
original beat's content — notes are duplicated across the new beats, not
distributed). A no-op for n < 2. If the cursor sits past the current
beat list, the list is padded first (via ensureLength) so there's a
beat there to subdivide.
combineBeats :: AppState -> AppState Source #
Merge the beats between asSelectionStart and the cursor (inclusive,
order-independent) into a single beat, on the condition that the
selection doesn't cross a measure boundary. The merged beat's notes are
the concatenation of all selected beats' notes (so overlapping strings
across beats are not deduplicated — a chord built from previously
separate beats keeps every note) and its duration is the running sum via
addDurations. Clears the selection and returns to NormalMode on
success; sets a status message and makes no edit if there's no active
selection or it spans measures.
addMeasure :: AppState -> AppState Source #
Insert a new measure immediately after the cursor's current measure,
inheriting its time/key signature (but not its tempo change), and
populate it with default rest beats (via defaultMeasureBeats) in every
track. All measures after the insertion point are re-indexed, and every
track's trackBeats keys past that point are shifted up by one measure
so existing content stays attached to the same musical measure it was
in before the insertion.
deleteMeasure :: AppState -> AppState Source #
Delete the cursor's current measure, refusing (with a status message)
if it's the song's only measure. Every track's beats for that measure
are dropped and later measures' trackBeats keys shift down by one to
close the gap, mirroring addMeasure's shift in the opposite direction.
The cursor is clamped to the new last measure if it was on (or past) the
one just removed.
withUndo :: (Song -> Song) -> AppState -> AppState Source #
Apply an edit f to the current song, pushing the pre-edit song onto
asUndoStack (capped at 100 entries) and clearing asRedoStack — as
with any standard undo model, making a new edit discards the redo
history from before it. Every editing operation in this module goes
through this rather than setting asSong directly, so undo/redo stay
consistent.
undo :: AppState -> AppState Source #
Pop the most recent song off asUndoStack and make it current,
pushing the song being replaced onto asRedoStack. Sets asMessage to
report whether there was anything to undo.
redo :: AppState -> AppState Source #
The inverse of undo: pop asRedoStack and make it current, pushing
the replaced song back onto asUndoStack.
clearFretEntry :: AppState -> AppState Source #
Reset the multi-digit fret accumulator (asFretEntry), e.g. when the
cursor moves away from the beat being entered.
fretToPitch :: Instrument -> StringIndex -> FretNumber -> Pitch Source #
Compute the sounding Pitch for a fret on a string of an Instrument:
open-string pitch (from the tuning, if the string index is in range)
plus the fret number. A Standard instrument (or an out-of-range
string) has no tuning to add, so the fret number is used directly as the
pitch — a fallback rather than a meaningful pitch, since fret entry
shouldn't normally happen on a non-fretted track.
modifyTrack :: TrackIndex -> (Track -> Track) -> Song -> Song Source #
Apply f to the track at tIdx within a Song. The standard
entry point every editing operation below starts from — they compose
this with modifyBeats and/or modifyBeatAt to reach the specific
beat/note being edited.
modifyBeats :: MeasureIndex -> ([Beat] -> [Beat]) -> Track -> Track Source #
Apply f to the beat list for measure mi of a track, treating a
missing entry as an empty list (so editing a measure a track has never
had beats in creates the entry rather than being a no-op).