termtab-0.1.0.0: TUI guitar tablature and score editor
Safe HaskellNone
LanguageGHC2021

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

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).