Created
October 3, 2023 18:59
-
-
Save arseniiv/3c845e06d375aee05d68beab0c370b87 to your computer and use it in GitHub Desktop.
replacenotes.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ''' | |
| Replaces notes with flats with enharmonic sharps in SFZ and DSPRESET files. | |
| ''' | |
| from pathlib import Path | |
| from typing import Final | |
| import re | |
| DS_EXTS: Final = frozenset(['.dspreset']) | |
| SFZ_EXTS: Final = frozenset(['.sfz']) | |
| _NOTE_PATTERN: Final = r''' | |
| (?P<note> [A-Ga-g] b | [EeBb] \# ) | |
| (?P<oct> -1 | [0-9] )''' | |
| DS_PATTERN: Final = re.compile(fr'''(?x) | |
| (?P<pre> (root | hi | lo) Note = " ) | |
| {_NOTE_PATTERN} | |
| (?P<post> " )''') | |
| SFZ_PATTERN: Final = re.compile(fr'''(?x) | |
| (?P<pre> ((hi | lo |) key | pitch_keycenter) = ) | |
| {_NOTE_PATTERN} | |
| (?P<post> )''') | |
| OCT_SHIFT: Final = {'cb': -1, 'b#': +1} | |
| REPL_UPPER: Final = {'Cb': 'B', 'Db': 'C#', 'Eb': 'D#', 'Fb': 'E', 'Gb': 'F#', | |
| 'Ab': 'G#', 'Bb': 'A#', 'E#': 'F', 'B#': 'C'} | |
| REPL_LOWER: Final = {k.lower(): v.lower() for k, v in REPL_UPPER.items()} | |
| REPL: Final = REPL_UPPER | REPL_LOWER | |
| IDLE_RUN: Final[bool] = False | |
| class NoteReplacer: | |
| def __init__(self) -> None: | |
| self.replaced: dict[str, str] = dict() | |
| def __call__(self, match: re.Match[str]) -> str: | |
| src_note = match['note'] | |
| src_oct = match['oct'] | |
| res = self.replaced.get(src_note + src_oct) | |
| if res is None: | |
| res_note = REPL.get(src_note, src_note) | |
| oct_shift = OCT_SHIFT.get(src_note.lower(), 0) | |
| res = f'{res_note}{int(src_oct) + oct_shift}' | |
| self.replaced[src_note + src_oct] = res | |
| return match['pre'] + res + match['post'] | |
| def main() -> None: | |
| print('HEYA. 🐙 I replace note names like Gb4 with names like F#4.') | |
| print('Type/paste .sfz/.dspreset files here; leave blank to exit:') | |
| while True: | |
| try: | |
| path = input('> ') | |
| except KeyboardInterrupt: | |
| print('\nRuude.') | |
| break | |
| except EOFError: | |
| print() | |
| break | |
| if not path.strip(): | |
| break | |
| path = Path(path) | |
| if not path.is_file(): | |
| print('That’s not a file!') | |
| continue | |
| suffixes = set(map(str.casefold, path.suffixes)) | |
| for exts, pat in ((DS_EXTS, DS_PATTERN), (SFZ_EXTS, SFZ_PATTERN)): | |
| if not exts.isdisjoint(suffixes): | |
| pattern = pat | |
| break | |
| else: | |
| print('This file’s extension is not a known one.') | |
| continue | |
| with open(path, 'r', encoding='utf8') as f: | |
| source = f.read() | |
| note_replacer = NoteReplacer() | |
| new_source = pattern.sub(note_replacer, source) | |
| if new_source != source: | |
| print('Changes are made...') | |
| for from_, to in note_replacer.replaced.items(): | |
| print(f' {from_} -> {to}') | |
| if not IDLE_RUN: | |
| with open(path, 'w', encoding='utf8') as f: | |
| f.write(new_source) | |
| print('OK') | |
| else: | |
| print('The file is left intact!') | |
| print('Later') | |
| if __name__ == '__main__': | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment