Parser for .ascl (Ableton Scala) tuning files with bundled tuning library
Project description
asclpy
A Python library and command-line tool for parsing .ascl (Ableton Scala) tuning files and computing MIDI-to-frequency mappings for alternative tuning systems.
Features
- Parse .ascl files - Ableton's extension of the Scala SCL format
- Multiple pitch formats - Support for both cents and ratio-based pitch definitions
- Ableton extensions - Handle all
@ABLdirectives (reference pitch, note names, ranges) - Pre-loaded library - 100+ tuning systems from Ableton Live included
- MIDI mapping - Generate MIDI note (0-127) to frequency (Hz) mappings for any tuning
- Simple access - Load tunings by snake_case names
- Command-line interface - List tunings and generate frequency tables from the terminal
- Multiple output formats - Export tables as formatted text, CSV, or JSON
Installation
pip install -e .
Command-Line Interface
The CLI provides quick access to the tuning library without writing code.
List All Tunings
asclpy list
Displays all available tunings with:
- Description and notes per octave
- Reference pitch (octave, index, frequency)
- Valid MIDI note range
- Validation warnings (if any)
Generate Frequency Table
# Default table format
asclpy table 12_tet_edo
# Specify MIDI range
asclpy table 19_edo --min-midi 60 --max-midi 72
# Export as CSV
asclpy table just_c --format csv > frequencies.csv
# Export as JSON
asclpy table bohlen_pierce --format json > frequencies.json
Options:
--format- Output format:table(default),csv, orjson--min-midi- Minimum MIDI note (default: 0)--max-midi- Maximum MIDI note (default: 127)
The table format includes color-coded frequencies:
- 🔵 Blue: < 100 Hz
- 🟢 Green: 100-1000 Hz
- 🟡 Yellow: 1000-5000 Hz
- 🔴 Red: > 5000 Hz
Version
asclpy --version
Python API Quick Start
import asclpy
# Load a pre-bundled tuning by name
tuning = asclpy.load_tuning("12_tet_edo")
# Get frequency for MIDI note 69 (A4)
freq = tuning.midi_to_freq(69)
print(f"A4 = {freq} Hz") # 440.0 Hz
# Get all MIDI-to-frequency mappings
midi_to_freq = tuning.get_midi_to_freq_dict()
print(f"C4 (MIDI 60) = {midi_to_freq[60]} Hz")
# List all available tunings
print(asclpy.list_tunings())
Examples
Using Different Tuning Systems
import asclpy
# 12-tone equal temperament (standard)
tet = asclpy.load_tuning("12_tet_edo")
print(tet.midi_to_freq(60)) # C4 in 12-TET
# Historical temperament
helmholtz = asclpy.load_tuning("helmholtz_temperament")
print(helmholtz.midi_to_freq(60)) # C4 in Helmholtz temperament
# Just intonation
just = asclpy.load_tuning("harmonic_series_16_32_fundamental_f")
print(just.midi_to_freq(60)) # C4 in harmonic series tuning
# Microtonal systems
edo19 = asclpy.load_tuning("19_edo")
print(f"{edo19.notes_per_octave} notes per octave")
# Non-octave scales (Bohlen-Pierce)
bp = asclpy.load_tuning("bohlen_pierce")
print(f"Pseudo-octave: {bp.octave_cents} cents")
Loading Custom Tuning Files
import asclpy
# Parse a custom .ascl file
tuning = asclpy.load_tuning("/path/to/custom.ascl")
# Or parse from a string
data = asclpy.parse_ascl_string("""
! My custom tuning
12
!
100.0
200.0
300.0
400.0
500.0
600.0
700.0
800.0
900.0
1000.0
1100.0
2/1
""")
tuning = asclpy.Tuning(data)
Exploring Tuning Properties
import asclpy
tuning = asclpy.load_tuning("meantone_one_quarter_quintal_comma")
# Basic properties
print(f"Description: {tuning.description}")
print(f"Notes per octave: {tuning.notes_per_octave}")
print(f"Octave size: {tuning.octave_cents} cents")
# Reference pitch
print(f"Reference: octave {tuning.reference_octave}, "
f"index {tuning.reference_index}, "
f"{tuning.reference_frequency} Hz")
# All pitch classes in cents
print(f"Pitch classes: {tuning.pitches_cents}")
Bundled Tunings
The library includes 100+ tuning systems organized into categories:
- EDO (Equal Divisions of Octave): 12-TET, 19-EDO, 31-EDO, 53-EDO, etc.
- European Historical: Meantone, Pythagorean, Well temperaments (Werckmeister, Kirnberger, etc.)
- Just Intonation: Harmonic series, rational tunings
- Arabic Maqam: Rast, Bayati, Hijaz, Saba, etc.
- Persian Radif: Traditional Persian tunings
- Turkish Makam: Turkish classical music tunings
- Sundanese Gamelan: Indonesian gamelan tunings
- Experimental: Bohlen-Pierce, Wendy Carlos scales
View all available tunings:
import asclpy
tunings = asclpy.list_tunings()
print(f"Total tunings: {len(tunings)}")
print("\n".join(tunings[:10])) # Show first 10
API Reference
Main Functions
load_tuning(name_or_path)- Load a tuning by name or file pathlist_tunings()- Get list of all available tuning namesparse_ascl_file(filepath)- Parse an .ascl fileparse_ascl_string(content)- Parse .ascl content from a string
Tuning Class
Methods:
tuning.midi_to_freq(midi_num: int) -> float- Convert MIDI note to frequency in Hztuning.get_midi_to_freq_dict() -> dict[int, float]- Get full MIDI-to-freq mapping (0-127)
Properties:
tuning.description: str- Tuning description from .ascl filetuning.notes_per_octave: int- Number of notes per pseudo-octavetuning.pitches_cents: list[float]- List of pitch classes in cents from 1/1tuning.octave_cents: float- Size of pseudo-octave in cents (default: 1200.0)tuning.reference_frequency: float- Reference frequency in Hztuning.reference_octave: int- Reference octave numbertuning.reference_index: int- Reference pitch class index (0-based)tuning.note_range: tuple[int, int] | None- Valid MIDI range or None
Constants
TUNINGS: dict[str, Tuning]- Dictionary of all pre-loaded tunings
ASCL Format
ASCL (Ableton Scala) is a backwards-compatible extension of the SCL format used by Scala software. Files contain:
- Description - Single line of text
- Notes per octave - Integer count
- Pitch definitions - One per line, as cents (decimal) or ratios (e.g., 5/4)
- Ableton extensions - In comment lines starting with
! @ABL
Supported @ABL directives:
REFERENCE_PITCH- Set reference tuning (octave, index, frequency)NOTE_NAMES- Default names for pitch classesNOTE_RANGE_BY_FREQUENCY- Playable range by frequencyNOTE_RANGE_BY_INDEX- Playable range by indexSOURCE- Documentation of originLINK- URL for more information
See the ASCL Specification for details.
Algorithm: MIDI-to-Frequency Mapping
The core algorithm maps MIDI note numbers (0-127) to frequencies (Hz) using a tuning system's pitch classes and reference pitch.
Overview
%%{init: { 'theme':'dark', 'flowchart': {'useMaxWidth':false} } }%%
flowchart TD
A[Parse ASCL File] --> B[Extract Pitch Classes]
B --> C[Extract Reference Pitch]
A --> D[Extract Note Range]
C --> E[Calculate Reference MIDI Note]
E --> F{For Each MIDI Note 0-127}
F --> G[Calculate Octave Distance]
G --> H[Calculate Pitch Class Index]
H --> I[Get Pitch Class Cents]
I --> J[Calculate Total Cents from Reference]
J --> K[Apply Frequency Formula]
K --> L[freq = ref_freq × 2^cents/1200]
D --> M{Within Valid Range?}
M -->|Yes| L
M -->|No| N[Skip or Return NaN]
L --> O[MIDI-to-Frequency Table]
Step-by-Step Process
1. Parse ASCL File
Extract three critical components:
Pitch Classes - Intervals from 1/1 (unison) in cents:
# Example: 12-TET
pitches_cents = [0.0, 100.0, 200.0, ..., 1100.0]
octave_cents = 1200.0 # Last interval (2/1)
Reference Pitch - Anchors the tuning to a specific frequency:
# Example: A4 = 440 Hz
reference_octave = 4 # MIDI octave 4
reference_index = 9 # 10th note (A)
reference_frequency = 440.0
Note Range (optional) - Valid MIDI range:
note_range = (0, 127) # Full MIDI range
2. Calculate Reference MIDI Note
The reference MIDI note is computed from the reference octave and pitch class index. Critical: There are two octave numbering systems in ASCL:
User-Specified Reference Pitch (REFERENCE_PITCH directive):
# Uses Ableton's convention: octave N spans MIDI notes (N+1)×12 to (N+2)×12-1
reference_midi = (reference_octave + 1) * 12 + reference_index
# Example: octave=4, index=9 (A4)
reference_midi = (4 + 1) * 12 + 9 = 69 # MIDI 69 = A4 = 440 Hz ✓
Loader-Supplied Defaults (no REFERENCE_PITCH):
# Uses standard MIDI convention: octave N spans MIDI notes N×12 to (N+1)×12-1
MIDI_OCTAVE_OFFSET = -2
reference_midi = (reference_octave - MIDI_OCTAVE_OFFSET) * 12 + reference_index
# Example: octave=3, index=9 (A in default notation)
reference_midi = (3 - (-2)) * 12 + 9 = 69 # MIDI 69 = A4 = 440 Hz ✓
3. Map Each MIDI Note to Frequency
For each MIDI note number m (0-127):
%%{init: { 'theme':'dark', 'flowchart': {'useMaxWidth':false} } }%%
flowchart LR
A[MIDI Note m] --> B[Calculate MIDI Offset]
B --> C[midi_offset = m - reference_midi]
C --> D[Calculate Octave Distance]
D --> E[octave_dist = midi_offset // notes_per_octave]
C --> F[Calculate Pitch Class]
F --> G[index = midi_offset % notes_per_octave]
E --> H[Calculate Total Cents]
G --> H
H --> I[cents = octave_dist × octave_cents<br/>+ pitches_cents[index]]
I --> J[Apply Frequency Formula]
J --> K[freq = ref_freq × 2^cents/1200]
Mathematical formula:
$$ \text{freq}(m) = f_{\text{ref}} \times 2^{\frac{c(m)}{1200}} $$
Where:
- $m$ = MIDI note number (0-127)
- $f_{\text{ref}}$ = Reference frequency (e.g., 440 Hz for A4)
- $c(m)$ = Total cents offset from reference:
$$ c(m) = \left\lfloor \frac{m - m_{\text{ref}}}{N} \right\rfloor \times C_{\text{oct}} + P\left[(m - m_{\text{ref}}) \bmod N\right] $$
Where:
- $m_{\text{ref}}$ = Reference MIDI note
- $N$ = Notes per octave
- $C_{\text{oct}}$ = Octave size in cents (usually 1200)
- $P[i]$ = Pitch class cents at index $i$
4. Example: Computing MIDI 72 (C5) in 12-TET
Given:
- 12-TET:
pitches_cents = [0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100] - Reference: MIDI 69 (A4) = 440 Hz
notes_per_octave = 12,octave_cents = 1200
Calculation:
# Step 1: MIDI offset
midi_offset = 72 - 69 = 3
# Step 2: Octave distance and pitch class
octave_distance = 3 // 12 = 0 # Same octave
index = 3 % 12 = 3 # 4th pitch class (C)
# Step 3: Total cents from reference
cents_from_ref = 0 × 1200 + pitches_cents[3]
= 0 + 300
= 300 cents
# Step 4: Apply frequency formula
freq = 440.0 × 2^(300/1200)
= 440.0 × 2^0.25
= 440.0 × 1.189207...
= 523.251 Hz # C5 ✓
Edge Cases
Non-Octave Scales (e.g., Bohlen-Pierce with octave_cents = 1901.955):
- The algorithm works identically, using the tuning's pseudo-octave size
- Pitch classes still cycle, but at a different interval ratio
Microtonal Systems (e.g., 19-EDO, 31-EDO):
- More pitch classes per octave (
notes_per_octave = 19or31) - Some MIDI notes may be skipped or interpolated depending on usage
Note Range Restrictions:
- If
note_rangeis specified, MIDI notes outside this range may be filtered - The algorithm still computes frequencies, but they may be marked invalid
Implementation
See midi.py for the complete implementation:
MidiMapper.compute_midi_to_frequency_table()- Main algorithmMidiMapper.map_midi_to_cents()- MIDI to cents conversioncents_to_frequency()- Cents to Hz conversion
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file asclpy-0.1.0.tar.gz.
File metadata
- Download URL: asclpy-0.1.0.tar.gz
- Upload date:
- Size: 74.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
12ee3496f3bc0824c10771b7295f86893f46dbd5eb189861c412e27550c660b3
|
|
| MD5 |
e2af2e2318531668cdc9defd2b54a7d3
|
|
| BLAKE2b-256 |
96bd190dbfe0eae7f94e59067c0914a52c6a85e561d5fc63e7fc1908297a7b22
|
File details
Details for the file asclpy-0.1.0-py3-none-any.whl.
File metadata
- Download URL: asclpy-0.1.0-py3-none-any.whl
- Upload date:
- Size: 152.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd085b11e6efa1e8573e6533cce8c8d9c6847d69401031b99292a3f85e6890c2
|
|
| MD5 |
c553586d5a31d5612fe6b00b180ab629
|
|
| BLAKE2b-256 |
17cb10a5f13e1ca60f1c3461b1bb416d17aa65e77150cd1d9a5bda73f7a24d36
|