# Clone and install in dev mode
git clone <repo-url> && cd fl-studio
pip install -e ".[dev]"
# Run tests
pytest tests/ -m "not slow"
# Run linter
ruff check .
# Run type checker
mypy midi_tools audio_tools mixing workflow release --ignore-missing-importsThe project follows a clear convention for error signaling:
Raise exceptions for programmer errors (bad arguments):
# Wrong type, out-of-range, impossible values
if sr <= 0:
raise ValueError(f"Sample rate must be positive, got {sr}")Return result objects for runtime failures (bad files, external issues):
# File not found, corrupt data, external tool missing
result = ProcessingResult(success=False, error="File not found")Specifically:
ValueErrorfor invalid arguments (bad pitch, negative sample rate, out-of-range Q)IOErrorfor file I/O failures (corrupt audio, unreadable MIDI)FileNotFoundErrorfor missing files- Result dataclasses (
ProcessingResult,SliceResult, etc.) for operations that can partially succeed
- Analysis functions return dataclass objects (
BPMResult,KeyResult,SpectrumData, etc.) - Audio processing functions return
(audio_array, sample_rate)tuples - MIDI manipulation functions return
mido.MidiFileobjects - File operations return result dataclasses with success/error fields
- Comparison/info functions return
dict[str, object]
- All public functions must have full type annotations
- Use
dict[str, object]instead of baredictfor return types - Use
tuple[float, float, float, float, float]instead of baretuple - Use
Union[str, Path]for file path parameters - Use
Optional[X]for nullable parameters
Use the centralized validators from midi_tools/_validation.py:
from midi_tools._validation import validate_pitch, validate_velocity, validate_channel
validate_pitch(note) # Raises ValueError if not 0-127
validate_velocity(vel) # Raises ValueError if not 0-127
validate_channel(ch) # Raises ValueError if not 0-15Use the shared utilities from audio_tools/_dsp_utils.py:
from audio_tools._dsp_utils import resample, make_window, midi_to_freq, freq_to_midi
audio = resample(audio, src_sr=44100, target_sr=48000)
window = make_window(2048, "hanning")
freq = midi_to_freq(69) # 440.0 Hz- Unit tests go in
tests/test_<module>.py - Hardened tests (parametrized, negative, edge cases) go in
tests/test_hardened.py - Integration tests go in
tests/test_real_world.py - Shared fixtures are in
tests/conftest.py
- New features must include tests
- Bug fixes must include a regression test
- Use
@pytest.mark.parametrizewhen testing multiple inputs - Include negative tests (invalid inputs that should raise)
- Use fixtures from
conftest.pyinstead of creating ad-hoc test data - Mark slow tests with
@pytest.mark.slow
- Create a feature branch from
main - Make your changes with tests
- Run
ruff check .andpytest tests/ -m "not slow" - Submit a PR with a clear description of changes
- Ensure CI passes before requesting review