theHarmonicAlgorithm-3.0.0: Real-time harmonic progression generation for TidalCycles live performance
Safe HaskellSafe-Inferred
LanguageHaskell2010

Harmonic.Rules.Constraints.Filter

Description

This module implements the parsing and filtering system from the legacy Overtone.hs, providing compatibility with the original command line app and TidalCycles integration.

Academic Lineage

The Harmonic Algorithm (South, 2016), Section One: the three tuning systems EAeGB (Electric Contrabass Cittern), EAeGC (with B/C re-tuner), and EADG (standard bass). The overtone series mapping [0, 7, 4, 10, 2] (root, P5, M3, m7, M2) is the equal-temperament approximation of the first five unique partials.

Filter Notation

Overtones/Pitch Set Filter

Limits harmonic choices to pitches within a specified set.

  • Fundamental pitches (derives overtones): "E A D G" (bass tuning)
  • Individual pitches with prime: "E'" "A'" A
  • Combined: "G E' A' A#'" (G overtones + E, A, A# pitches)
  • Wildcard: "*" (all pitches)

Key Filter

Removes pitches not in the specified key. Note names yield a single pitch class; numbered key sigs yield a major scale.

  • Note name: C = only pitch C, Bb = only pitch Bb (PC 10)
  • Numbered key sig: "0#" = C major, "1#" = G major, "2b" = Bb major, "4b"
  • Wildcard: "*" (no key filtering)

Root Notes Filter

Limits bass notes to specified pitch classes or key. Uses the same unified rules as the key filter.

  • Note name: E F Bb → individual pitches [4, 6, 10]
  • Numbered key sig: "1b" = F major roots, "2#" = D major roots
  • Wildcard: "*" (all roots)

Pitch Removal with - Operator

All parsing functions support pitch removal using the - prefix:

  • "C E -E'" → (C overtones ∪ E overtones) \ {E pitch}
  • "1b 2# -G" → (F major ∪ D major) \ {G}
  • "* -C' -F#'" → All pitches except C and F#

Context-Specific Behavior

  • Overtones: note name generates overtone series; prime notation gives single pitch
  • Key: note name = single pitch; numbered sig = major key scale
  • Roots: same as Key (unified rules)

Order of Operations

  1. Union all positive tokens
  2. Union all negative tokens
  3. Subtract: positive \ negative

Edge Cases

  • "*" → all pitches (wildcard shortcut)
  • "* -C'" → all except C
  • "-*" → empty set (no includes)
  • "-C -D" → empty set (no includes)
  • "C -C" → empty set (self-cancellation)
Synopsis

Parsing Functions (Text versions)

parseOvertones :: Text -> [PitchClass] Source #

Parse overtones with 3 overtones (default: root, P5, M3)

parseKey :: Text -> [PitchClass] Source #

Parse key (overtone count not used for keys)

parseFunds :: Text -> [PitchClass] Source #

Parse fundamentals with 3 overtones (default)

parseTuning :: Text -> [PitchClass] Source #

Parse tuning with 3 overtones (default: root, P5, M3 — the distinct pitch classes of the playable tapped-harmonic domain)

resolveRoots :: Text -> Text -> Text -> [PitchClass] Source #

Resolve roots with special options "key" and "tones".

This function allows root selection to be derived from other filter values: * "key": Use the same pitch classes as the key filter produces * "tones": Use the key-filtered overtones (effective upper structure) * Other values: Parsed via parseFunds as normal

Arguments: * overtoneFilter: The overtones/tuning filter string * keyFilter: The key filter string * rootsFilter: The roots filter string (may be "key" or "tones")

Examples: resolveRoots "E A D G" "#" "key" -> G major scale degrees [0,2,4,6,7,9,11] resolveRoots "E A D G" "#" "tones" -> key-filtered overtones from E A D G resolveRoots "E A D G" "#" "C G" -> [0, 7]

Parsing Functions (String versions for Tidal)

overtones :: String -> [PitchClass] Source #

Parse overtones from a String (Tidal-friendly) Example: overtones "E A D G" -> bass tuning overtones

key :: String -> [PitchClass] Source #

Parse key from a String (Tidal-friendly) Example: key "#" -> G major, key "bb" -> Bb major

funds :: String -> [PitchClass] Source #

Parse fundamentals from a String (Tidal-friendly) Example: funds "E F# G" -> [4, 6, 7]

tuning :: String -> [PitchClass] Source #

Parse tuning from a String (Tidal-friendly)

wildcard :: String -> Bool Source #

Check if a string is a wildcard (Tidal-friendly)

High-level API

filterPitchSet :: Text -> [PitchClass] -> [PitchClass] Source #

Filter a pitch class set by overtones/tuning filter

filterByKey :: Text -> [PitchClass] -> [PitchClass] Source #

Filter a pitch class set by key

filterRoots :: Text -> [PitchClass] -> [PitchClass] Source #

Filter root candidates by roots filter

Filtering Predicates

isWildcard :: Text -> Bool Source #

Check if a filter string is a wildcard (matches everything)

matchesPitchSet :: Text -> [PitchClass] -> Bool Source #

Check if a pitch class set matches the overtones filter

matchesKey :: Text -> [PitchClass] -> Bool Source #

Check if a pitch class set matches the key filter

matchesRoots :: Text -> PitchClass -> Bool Source #

Check if a root pitch class matches the roots filter

Bass Direction

data BassDirection Source #

Concrete direction action resolved for a single generation step. When active, the bass/root at the next step is forced to the Nth note above (Rise) or below (Fall) in the allowed set, with mod-12 wrapping. Step size 1 = closest note, 2 = skip one, etc.

Constructors

Rise !Int 
Fall !Int 

data BassDirectionSpec Source #

Parsed specification for a rise/fall direction token. Resolved per step at generation time to a concrete BassDirection (or Nothing when the optional ? flag causes the direction to be skipped for that step).

Constructors

BassDirectionSpec 

Fields

data BDKind Source #

Whether a parsed direction rises or falls.

Constructors

RiseK 
FallK 

Instances

Instances details
Show BDKind Source # 
Instance details

Defined in Harmonic.Rules.Constraints.Filter

Eq BDKind Source # 
Instance details

Defined in Harmonic.Rules.Constraints.Filter

Methods

(==) :: BDKind -> BDKind -> Bool #

(/=) :: BDKind -> BDKind -> Bool #

data BDSelector Source #

How to pick a step size from bdsChoices at each generation step.

Constructors

BDFixed

single value (bare rise, rise2, or rise<n>)

BDRotate

cycle choices by step index (space-delimited <…>)

BDRandomPick

uniform random per step (comma-delimited <…>)

Instances

Instances details
Show BDSelector Source # 
Instance details

Defined in Harmonic.Rules.Constraints.Filter

Eq BDSelector Source # 
Instance details

Defined in Harmonic.Rules.Constraints.Filter

parseBassDirectionSpec :: Text -> Maybe BassDirectionSpec Source #

Extract a bass-direction spec from a roots filter string. Returns the first token that parses as a direction; Nothing otherwise.

Examples: parseBassDirectionSpec "* fall" -- Just (fixed Fall 1) parseBassDirectionSpec "* rise1" -- Just (fixed Rise 1) -- alias for "rise" parseBassDirectionSpec "* rise3" -- Just (fixed Rise 3) parseBassDirectionSpec "* fall2?" -- Just (fixed Fall 2, optional) parseBassDirectionSpec "* fall<3 2 1>" -- Just (rotate Fall [3,2,1]) parseBassDirectionSpec "* rise<1,2>" -- Just (random Rise [1,2]) parseBassDirectionSpec "* rise<1 2,3>?" -- Just (random Rise [1,2,3], optional) parseBassDirectionSpec "C E G" -- Nothing

stripDirectionToken :: Text -> Text Source #

Strip the direction token from a roots filter string, leaving only the pitch set specification for normal parsing.

Examples: stripDirectionToken "* fall" == "*" stripDirectionToken "0# rise1" == "0#" stripDirectionToken "0# rise?" == "0#" stripDirectionToken "C E G fall<2,3>" == "C E G" stripDirectionToken "C E G" == "C E G"

closestAbove :: Int -> IntSet -> Int Source #

Find the closest pitch class above. Equivalent to nthAbove 1.

closestBelow :: Int -> IntSet -> Int Source #

Find the closest pitch class below. Equivalent to nthBelow 1.

nthAbove :: Int -> Int -> IntSet -> Int Source #

Find the Nth pitch class ABOVE the current one in the allowed set, sorted by ascending circular distance. Step 1 = closest, 2 = skip one, etc. When N exceeds the set size, wraps around using modular indexing. If the set has only the current note, returns it (pedal).

Examples (with C major = {0,2,4,5,7,9,11}): nthAbove 1 7 cMaj == 9 (G → A, closest) nthAbove 3 0 cMaj == 5 (C → F, 3rd above) nthAbove 1 11 cMaj == 0 (B → C, wraps mod-12) nthAbove 1 0 {0} == 0 (pedal)

nthBelow :: Int -> Int -> IntSet -> Int Source #

Find the Nth pitch class BELOW the current one in the allowed set, sorted by ascending circular distance downward. Step 1 = closest, etc. When N exceeds the set size, wraps around using modular indexing. If the set has only the current note, returns it (pedal).

Examples (with C major = {0,2,4,5,7,9,11}): nthBelow 1 7 cMaj == 5 (G → F, closest) nthBelow 3 7 cMaj == 2 (G → D, 3rd below) nthBelow 1 0 cMaj == 11 (C → B, wraps mod-12) nthBelow 1 5 {5} == 5 (pedal)

Internal (for testing)

parseOvertones' :: Int -> Text -> [PitchClass] Source #

Parse an overtones filter with full notation support. This is the most general parser, combining tuning notation with individual pitches. Supports removal with - prefix: "C E -E'" removes E pitch from (C ∪ E) overtones

parseKey' :: Int -> Text -> [PitchClass] Source #

Parse a key filter string.

Formats: * Note name: C, Bb, F → single pitch class * Numbered key sig: "0#" = C major, "1#" = G major, "2b" = Bb major * Wildcard: "*" * Removal: "1b -G" (F major minus G), "* -C'" (all minus C)

parseFunds' :: Int -> Text -> [PitchClass] Source #

Parse fundamentals (root notes) filter.

Formats: * Note names: "E F# G" → individual pitches [4,6,7] * Numbered key sig: "1b" = F major roots, "2#" = D major roots * Wildcard: "*" * Removal: "C G -G" (C and G minus G), "* -E -A" (all except E,A)

parseTuning' :: Int -> Text -> [PitchClass] Source #

Parse a tuning string into pitch class set (overtones of fundamentals).

Examples: * "E A D G" → overtones of E, A, D, G (bass tuning) * C → overtones of C * "*" → all pitch classes

partitionTokens :: Text -> ([Text], [Text]) Source #

Partition input tokens into positive and negative (those prefixed with -) Returns (positive tokens, negative tokens with - prefix stripped)

keyToPitchClasses :: Text -> [PitchClass] Source #

Convert a key specification to pitch classes

noteNameToPitchClass :: Text -> Maybe PitchClass Source #

Map note names to pitch classes Handles: c, c#, db, d, d#, eb, e, f, f#, gb, g, g#, ab, a, a#, bb, b Also: b# = c, cb = b, etc.

Overtone Annotation Support

parseTuningNamed :: Text -> [(String, Int)] Source #

Parse a tuning string preserving string names for overtone annotation. Case is preserved for string identification (uppercase = lower octave, lowercase = higher octave per thesis convention).

Examples: "E A D G"[(E,4), (A,9), (D,2), (G,7)] "E A e G B"[(E,4), (A,9), ("e",4), (G,7), (B,11)] "*"[] (wildcard has no named strings)