Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

transpose

A small, self-contained CLI that transposes musical note names and chords up or down by a number of half steps.

$ ./transpose.py A7 D7/E 'C (add9)' F#m7b5 C7/9/13 2
B7 E7/F# D (add9) G#m7b5 D7/9/13

Usage

transpose.py [-b|--flats] CHORD [CHORD ...] SEMITONES
  • One or more chords (a plain note like C is just a chord with an empty suffix), followed by a single integer half-step count.
  • Output defaults to sharps; pass -b / --flats for flat spellings. The flag is -b because b is the flat glyph itself (Bb, Eb).
  • Quote chords containing spaces or parentheses so the shell keeps them together, e.g. 'C (add9)'.

Examples:

Input Output
A7 1 A#7
-b A7 1 Bb7
D7/E -2 C7/D
C7/9/13 1 C#7/9/13
C7b9 1 C#7b9

Design

Opaque suffix. Only the chord root and a true /bass note are transposed; the entire suffix (quality, extensions, alterations, add9, sus, parens) is preserved verbatim. A / is treated as a bass separator only when the trailing segment is a valid note name (D7/E); otherwise every / is an extension separator (C7/9/13). These conventions match the sibling greene project, though this tool does not import it.

Running

Self-contained uv script (no dependencies to install):

./transpose.py C E G 2                       # run directly via the uv shebang
./test_transpose.py                          # run the tests directly
uv run --with pytest pytest test_transpose.py -q   # ... or via pytest

Installing

It is also a package, so other tools can import it rather than shell out:

uv pip install git+https://github.com/bwagner/transpose.git

That installs the same single transpose.py module plus a transpose console script, so ./transpose.py above keeps working unchanged. Consumers depend on it with transpose @ git+https://github.com/bwagner/transpose.git.

transpose_label_text - whole label texts

transpose_chord expects one chord. A label in a chord track usually holds more: a section marker, a lyric cue, a fingering, a performance note, often alongside the chord. transpose_label_text transposes the chords in such a string and returns everything else byte-identical:

>>> from transpose import transpose_label_text as T
>>> T("Chorus1: Dj7", 2)      # section marker stays, chord moves
'Chorus1: Ej7'
>>> T("A: I let him slip away", 2)   # chord moves, lyric cue stays
'B: I let him slip away'
>>> T("F, Gm, Bb/C", 2)       # every chord moves, not just the first
'G, Am, C/D'
>>> T("Guitar Solo", 2) is None      # nothing here was a chord
True

It returns None when the text held no chord at all, so a caller can report what it left alone rather than silently doing nothing.

Why a strict pattern. parse_chord takes a root plus an opaque suffix, so it reads Guitar Solo as G + uitar Solo and would cheerfully return G#uitar Solo. transpose_label_text therefore requires a token's suffix to be built only from chord vocabulary (QUALITY_WORDS, SUFFIX_NUMBERS, SUFFIX_SYMBOLS), and rejects a digits-only suffix that is not a real extension so B1 and B2 stay section markers. Tokens split on whitespace, commas and colons - never on /, which is meaningful inside a chord (D7/E, 6/9), and never on -, which is a minor marker in some charts (C#-7).

Validated against a real corpus of ~530 distinct chord-track label texts: 365 transposed, 166 passed through, no silent damage; transposing by an octave is a byte-exact no-op.

About

Transpose musical note names and chords by half steps - self-contained uv script CLI

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages