Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 82 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,64 @@

A token-efficient, unambiguous text-based musical chord notation system designed for seamless integration with Large Language Models.

## Overview
## Purpose

**Noten** (also known as LLM-Chart) is a chord progression format that balances three key requirements:
- **Unambiguous**: Clear rhythm alignment eliminates guesswork
- **Token-Efficient**: Concise syntax minimizes LLM context usage
- **Human-Readable**: Intuitive for musicians to read and write
**Noten** (also known as LLM-Chart) solves the problem of ambiguous and token-heavy musical notation in AI applications. Standard formats like MusicXML are too verbose for LLMs, while text formats like ChordPro lack rhythmic precision.

## Quick Start
Noten balances three key requirements:
- **Unambiguous**: Clear rhythm alignment eliminates guesswork.
- **Token-Efficient**: Concise syntax minimizes LLM context usage.
- **Human-Readable**: Intuitive for musicians to read and write.

## Setup

### Installation

The noten package is now properly packaged and can be installed with `uv` (recommended) or `pip`:
The noten package is designed to be lightweight and easy to install.

```bash
# Install with uv (recommended - fast!)
uv pip install -e .
1. **Clone the repository:**
```bash
git clone https://github.com/yourusername/noten.git
cd noten
```

# Or with traditional pip
pip install -e .
```
2. **Install with `uv` (recommended) or `pip`:**
```bash
# Install with uv (faster)
uv pip install -e .

# Or with traditional pip
pip install -e .
```

For detailed installation instructions, see [INSTALLATION.md](INSTALLATION.md).

### Running Tests

To ensure everything is working correctly:

```bash
# Run tests
python test_noten.py
```

### Running Demos

Explore the capabilities of Noten with the included demos:

# Run demos
```bash
# Simulated Reharmonization Demo (No API key needed)
python demo_reharmonization.py
python demo_real_llm.py # Requires API keys

# Real LLM Integration Demo (Requires API keys)
python demo_real_llm.py
```

## Usage

### Basic Example

A typical Noten file looks like this:

```
{title: My Song}
{key: C}
Expand All @@ -48,6 +72,48 @@ python demo_real_llm.py # Requires API keys
|: C . Am . | F . G . :| x2
```

### Python API

You can easily integrate Noten into your Python projects.

#### Parsing a Song

```python
from noten import parse

noten_string = """
{time: 4/4}
| C . . G | Am . F . |
"""

ast = parse(noten_string)
ast_dict = ast.to_dict()
# ast_dict now contains the Abstract Syntax Tree of the song
```

#### Rhythm Analysis

To calculate the exact timing and duration of each chord:

```python
from noten import parse, calculate_durations, print_rhythm_analysis

noten = """
{time: 4/4}
| C . . G | (Am G F) C |
"""

ast = parse(noten)
events = calculate_durations(ast.to_dict())

# Print a formatted table
print_rhythm_analysis(events)

# Access event data programmatically
for event in events:
print(f"Chord: {event['chord']['root']}, Start: {event['start']}, Duration: {event['duration']}")
```

## Format Specification

### Annotations
Expand Down
57 changes: 42 additions & 15 deletions demo_real_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ def create_reharmonization_prompt(noten_input: str, style: str = "jazz") -> str:
"""
Create a prompt for LLM chord reharmonization.

Constructs a detailed prompt explaining the Noten format rules and the
desired reharmonization style.

Args:
noten_input: Original chord progression in noten format
style: Target style (jazz, bossa nova, gospel, etc.)
noten_input: Original chord progression in noten format.
style: Target style (e.g., "jazz", "bossa nova", "gospel"). Defaults to "jazz".

Returns:
Formatted prompt for the LLM
str: A formatted prompt string ready to be sent to the LLM.
"""
prompt = f"""You are an expert music arranger. I'll provide a chord progression in "noten" format, and you should reharmonize it in {style} style.

Expand Down Expand Up @@ -66,12 +69,18 @@ def call_llm(prompt: str, model: str = "claude-3-5-sonnet-20241022") -> str:
"""
Call LLM using litellm.

Sends the prompt to the specified model via the litellm library and returns
the generated content.

Args:
prompt: The prompt to send
model: Model identifier (e.g., "anthropic/claude-3-5-sonnet-20241022")
prompt: The prompt text to send to the LLM.
model: Model identifier (e.g., "anthropic/claude-3-5-sonnet-20241022").

Returns:
LLM response text
str: The response text from the LLM.

Raises:
Exception: If the API call fails.
"""
print(f"\n🤖 Calling LLM: {model}")
print(f" (This may take 10-30 seconds...)")
Expand All @@ -95,11 +104,17 @@ def extract_noten_from_response(response: str) -> str:
"""
Extract noten format from LLM response (in case there's extra text).

Attempts to locate the Noten code block within the LLM's response, handling
markdown code blocks and conversational text.

Args:
response: LLM response text
response: The full text response from the LLM.

Returns:
Extracted noten format string
str: The extracted Noten format string.

Raises:
ValueError: If no valid Noten format can be found in the response.
"""
# Remove markdown code blocks if present
response = response.strip()
Expand Down Expand Up @@ -143,12 +158,18 @@ def analyze_progression(noten_input: str, title: str = "Progression") -> tuple[O
"""
Parse and analyze a noten progression.

Parses the input string, extracts metadata, calculates rhythm, and prints
a summary of the analysis.

Args:
noten_input: Chord progression in noten format
title: Title for display
noten_input: Chord progression in noten format.
title: Title to display above the analysis.

Returns:
Tuple of (ast_dict, events) or (None, None) if parsing fails
tuple: A tuple containing:
- ast_dict (Optional[Dict[str, Any]]): The parsed AST as a dictionary.
- events (Optional[list]): List of calculated rhythm events.
Returns (None, None) if parsing fails.
"""
print(f"\n{'═' * 70}")
print(f"{title}")
Expand Down Expand Up @@ -197,11 +218,11 @@ def analyze_progression(noten_input: str, title: str = "Progression") -> tuple[O

def compare_progressions(original_events, reharmonized_events):
"""
Compare original and reharmonized progressions.
Compare original and reharmonized progressions side-by-side.

Args:
original_events: Rhythm events from original
reharmonized_events: Rhythm events from reharmonization
original_events: List of rhythm events from the original progression.
reharmonized_events: List of rhythm events from the reharmonized progression.
"""
print(f"\n{'═' * 70}")
print("COMPARISON: Original vs Reharmonized")
Expand Down Expand Up @@ -243,7 +264,13 @@ def compare_progressions(original_events, reharmonized_events):

def demo_interactive():
"""
Interactive demo allowing user to choose options.
Run an interactive demo allowing the user to choose models and input options.

This function handles:
1. Checking for available API keys.
2. Prompting the user to select an LLM model.
3. Prompting the user to select an example or provide custom input.
4. Running the reharmonization process and displaying results.
"""
print("""
╔══════════════════════════════════════════════════════════════════╗
Expand Down
22 changes: 18 additions & 4 deletions demo_reharmonization.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ def analyze_progression(noten_input: str, title: str = "Progression"):
Args:
noten_input: Chord progression in noten format
title: Title for display

Returns:
Tuple containing the AST dictionary and the list of rhythm events, or (None, None) on failure.
"""
print(f"\n{'=' * 70}")
print(f"{title}")
Expand Down Expand Up @@ -138,11 +141,11 @@ def analyze_progression(noten_input: str, title: str = "Progression"):

def compare_progressions(original_events, reharmonized_events):
"""
Compare original and reharmonized progressions.
Compare original and reharmonized progressions side-by-side.

Args:
original_events: Rhythm events from original
reharmonized_events: Rhythm events from reharmonization
original_events: List of rhythm events from the original progression.
reharmonized_events: List of rhythm events from the reharmonized progression.
"""
print(f"\n{'=' * 70}")
print("COMPARISON")
Expand Down Expand Up @@ -176,6 +179,13 @@ def compare_progressions(original_events, reharmonized_events):
def demo_reharmonization():
"""
Run the complete reharmonization demo.

This function executes the full pipeline:
1. Parsing an example original progression.
2. Generating a reharmonization prompt.
3. Simulating an LLM response (or calling one if configured).
4. Parsing the reharmonized result.
5. Comparing both versions.
"""
print("""
╔══════════════════════════════════════════════════════════════════╗
Expand Down Expand Up @@ -269,7 +279,11 @@ def demo_reharmonization():


def main():
"""Main entry point for the demo."""
"""
Main entry point for the demo script.

Runs the reharmonization demo and prints instructions for real LLM integration.
"""
demo_reharmonization()

print("\nTo integrate with a real LLM:")
Expand Down
Loading