Skip to main content

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 @ABL directives (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, or json
  • --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 path
  • list_tunings() - Get list of all available tuning names
  • parse_ascl_file(filepath) - Parse an .ascl file
  • parse_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 Hz
  • tuning.get_midi_to_freq_dict() -> dict[int, float] - Get full MIDI-to-freq mapping (0-127)

Properties:

  • tuning.description: str - Tuning description from .ascl file
  • tuning.notes_per_octave: int - Number of notes per pseudo-octave
  • tuning.pitches_cents: list[float] - List of pitch classes in cents from 1/1
  • tuning.octave_cents: float - Size of pseudo-octave in cents (default: 1200.0)
  • tuning.reference_frequency: float - Reference frequency in Hz
  • tuning.reference_octave: int - Reference octave number
  • tuning.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:

  1. Description - Single line of text
  2. Notes per octave - Integer count
  3. Pitch definitions - One per line, as cents (decimal) or ratios (e.g., 5/4)
  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 classes
  • NOTE_RANGE_BY_FREQUENCY - Playable range by frequency
  • NOTE_RANGE_BY_INDEX - Playable range by index
  • SOURCE - Documentation of origin
  • LINK - 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 = 19 or 31)
  • Some MIDI notes may be skipped or interpolated depending on usage

Note Range Restrictions:

  • If note_range is 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 algorithm
  • MidiMapper.map_midi_to_cents() - MIDI to cents conversion
  • cents_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

asclpy-0.1.0.tar.gz (74.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

asclpy-0.1.0-py3-none-any.whl (152.8 kB view details)

Uploaded Python 3

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

Hashes for asclpy-0.1.0.tar.gz
Algorithm Hash digest
SHA256 12ee3496f3bc0824c10771b7295f86893f46dbd5eb189861c412e27550c660b3
MD5 e2af2e2318531668cdc9defd2b54a7d3
BLAKE2b-256 96bd190dbfe0eae7f94e59067c0914a52c6a85e561d5fc63e7fc1908297a7b22

See more details on using hashes here.

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

Hashes for asclpy-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cd085b11e6efa1e8573e6533cce8c8d9c6847d69401031b99292a3f85e6890c2
MD5 c553586d5a31d5612fe6b00b180ab629
BLAKE2b-256 17cb10a5f13e1ca60f1c3461b1bb416d17aa65e77150cd1d9a5bda73f7a24d36

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page