From 1bda1f666adad61694688b67335fb5d64a56f0c2 Mon Sep 17 00:00:00 2001 From: Wilson Neto Date: Wed, 26 Nov 2025 23:11:25 -0300 Subject: [PATCH] perf(services): optimize theoryService with O(1) lookups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace O(n) array.indexOf() operations with O(1) Map lookups: - CHROMATIC_NOTES.indexOf() → CHROMATIC_NOTES_MAP.get() - MAJOR_KEYS.indexOf() → MAJOR_KEYS_MAP.get() - MINOR_KEYS.indexOf() → MINOR_KEYS_MAP.get() - ROMAN_NUMERALS.indexOf() → ROMAN_NUMERALS_MAP.get() - Constant arrays for scale semitones to avoid creating new arrays --- services/theoryService.ts | 51 ++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/services/theoryService.ts b/services/theoryService.ts index 61b1e44..125640c 100644 --- a/services/theoryService.ts +++ b/services/theoryService.ts @@ -3,9 +3,27 @@ import { type Chord, LevelType, type Progression } from '../types'; // All 12 chromatic notes for transposition const CHROMATIC_NOTES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] as const; + +// O(1) lookup maps for chromatic notes +const CHROMATIC_NOTES_MAP = new Map(); +CHROMATIC_NOTES.forEach((note, index) => { + CHROMATIC_NOTES_MAP.set(note, index); +}); + const MAJOR_KEYS = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] as const; const MINOR_KEYS = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'] as const; +// O(1) lookup maps for major and minor keys +const MAJOR_KEYS_MAP = new Map(); +MAJOR_KEYS.forEach((key, index) => { + MAJOR_KEYS_MAP.set(key, index); +}); + +const MINOR_KEYS_MAP = new Map(); +MINOR_KEYS.forEach((key, index) => { + MINOR_KEYS_MAP.set(key, index); +}); + type Note = (typeof CHROMATIC_NOTES)[number]; type Key = (typeof MAJOR_KEYS)[number] | (typeof MINOR_KEYS)[number]; @@ -65,12 +83,26 @@ const ROMAN_TO_CHORD_TYPE: Record = { 'IV/V': 'sus4', // Special case for slash chord }; +// O(1) lookup for Roman numerals (defined once) +const ROMAN_NUMERALS = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'] as const; +const ROMAN_NUMERALS_MAP = new Map(); +ROMAN_NUMERALS.forEach((numeral, index) => { + ROMAN_NUMERALS_MAP.set(numeral, index); +}); + +// Scale degree semitones for major and minor keys +const MAJOR_SCALE_SEMITONES = [0, 2, 4, 5, 7, 9, 11] as const; +const MINOR_SCALE_SEMITONES = [0, 2, 3, 5, 7, 8, 10] as const; + +// Scale degree semitones for Roman numerals (for extended chords) +const ROMAN_SEMITONES = [0, 2, 4, 5, 7, 9, 11] as const; + // Transpose a note by semitones const transposeNote = (note: string, semitones: number): string => { const noteName = note.slice(0, -1); // Remove octave const octaveStr = note.slice(-1); const octave = parseInt(octaveStr, 10); - const noteIndex = CHROMATIC_NOTES.indexOf(noteName as Note); + const noteIndex = CHROMATIC_NOTES_MAP.get(noteName as Note) ?? -1; if (noteIndex === -1 || Number.isNaN(octave)) return note; @@ -95,8 +127,8 @@ const generateChordNotes = ( // Generate chord map for a specific key const generateKeyMap = (key: Key, isMinor: boolean = false): Record => { const keyIndex = isMinor - ? MINOR_KEYS.indexOf(key as (typeof MINOR_KEYS)[number]) - : MAJOR_KEYS.indexOf(key as (typeof MAJOR_KEYS)[number]); + ? (MINOR_KEYS_MAP.get(key as (typeof MINOR_KEYS)[number]) ?? -1) + : (MAJOR_KEYS_MAP.get(key as (typeof MAJOR_KEYS)[number]) ?? -1); if (keyIndex === -1) return {}; @@ -112,8 +144,8 @@ const generateKeyMap = (key: Key, isMinor: boolean = false): Record { const chordType = ROMAN_TO_CHORD_TYPE[chordSymbol]; if (chordType) { @@ -151,12 +184,12 @@ const generateKeyMap = (key: Key, isMinor: boolean = false): Record