README.md

# RealBook

> Jazz Standards Data Provider for Elixir

RealBook is an Elixir library that provides access to 1,193 jazz standards with chord progressions from the Real Book. It's designed to be a simple, read-only data provider for other music projects in the Elixir ecosystem.

## Features

- **1,193 Jazz Standards** - Comprehensive collection of classic jazz songs
- **Structured Chord Progressions** - Song sections, measures, and chord changes
- **Harmony Integration** - Full integration with the [Harmony](https://hex.pm/packages/harmony) library for music theory
- **Fast In-Memory Access** - GenServer loads all songs at startup for instant retrieval
- **Search & Filter** - Find songs by author, genre, year, or title
- **Clean API** - Simple, functional interface designed for composition

## Installation

Add `real_book` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:real_book, path: "../real_book_ex"},
    {:harmony, "~> 0.1.2"}
  ]
end
```

Or if using from a local path:

```elixir
def deps do
  [
    {:real_book, path: "../real_book_ex"}
  ]
end
```

Note: Harmony is automatically included as a dependency.

## Quick Start

```elixir
# Get a specific song
song = RealBook.get("Autumn Leaves")
# => %RealBook.Song{
#      title: "Autumn Leaves",
#      author: "Kosma, Joseph",
#      year: "1947",
#      genre: "jazz",
#      form: "A,A,B,A",
#      sections: %{...}
#    }

# Get all songs
all_songs = RealBook.all()
# => [%Song{}, %Song{}, ...] (1,193 songs)

# Get song count
RealBook.count()
# => 1193

# Search by author
coltrane_songs = RealBook.search(author: "Coltrane")
# => [%Song{title: "Giant Steps", ...}, %Song{title: "Impressions", ...}, ...]

# Get random song for practice
practice_song = RealBook.random()
```

## API Reference

### Core Functions

#### `RealBook.get(title)`

Get a song by its exact title. Returns an empty `%RealBook.Song{}` if not found.

```elixir
RealBook.get("So What")
# => %Song{title: "So What", author: "Davis, Miles", ...}
```

#### `RealBook.all()`

Returns a list of all 1,193 songs.

```elixir
all_songs = RealBook.all()
length(all_songs)
# => 1193
```

#### `RealBook.count()`

Get the total number of songs in the database.

```elixir
RealBook.count()
# => 1193
```

### Search & Discovery

#### `RealBook.search(criteria)`

Search for songs matching specific criteria. Supports multiple filters:

- `:author` - Substring match (case-insensitive)
- `:genre` - Exact match
- `:year` - Exact match
- `:title` - Substring match or regex

```elixir
# Find all Coltrane compositions
RealBook.search(author: "Coltrane")

# Find songs from 1959
RealBook.search(year: "1959")

# Combine multiple criteria
RealBook.search(author: "Davis", genre: "jazz")

# Use regex for title search
RealBook.search(title: ~r/Blue/)
```

#### `RealBook.random(opts \\ [])`

Get a random song, optionally matching criteria.

```elixir
# Random song from entire collection
RealBook.random()

# Random bebop song
RealBook.random(genre: "bebop")

# Get 5 random Miles Davis songs
RealBook.random(author: "Davis", count: 5)
```

#### `RealBook.authors()`

List all unique composers/authors in alphabetical order.

```elixir
RealBook.authors()
# => ["Blake, Ran", "Blakey, Art", "Bloom, Rube", ...]
```

#### `RealBook.genres()`

List all unique genres in the database.

```elixir
RealBook.genres()
# => ["jazz", "bebop", "swing", ...]
```

### Chord Progressions

#### `RealBook.progression(title)`

Get a simplified chord progression structure for a song.

```elixir
RealBook.progression("Giant Steps")
# => [
#   %{
#     section: "A",
#     key: "B",
#     time: [4, 4],
#     measures: [
#       ["Bmaj7", "D7"],
#       ["Gmaj7", "Bb7"],
#       ...
#     ]
#   },
#   ...
# ]
```

#### `RealBook.chords(title)`

Get a flat list of all unique chords used in a song.

```elixir
RealBook.chords("All The Things You Are")
# => ["Fmaj7", "Bbm7", "Eb7", "Abmaj7", "Dbmaj7", "G7", ...]
```

## Data Structures

### Song

```elixir
%RealBook.Song{
  title: String.t(),        # "Autumn Leaves"
  author: String.t(),       # "Kosma, Joseph"
  year: String.t(),         # "1947"
  genre: String.t(),        # "jazz"
  form: String.t(),         # "A,A,B,A"
  sections: %{              # Map of section name to Section struct
    "A" => %RealBook.Section{...},
    "B" => %RealBook.Section{...}
  }
}
```

### Section

```elixir
%RealBook.Section{
  name: String.t(),              # "A", "B", "Bridge", etc.
  key: String.t(),               # "C", "F#", etc.
  time: [integer()],             # [4, 4] for 4/4 time
  measures: [RealBook.Measure.t()]
}
```

### Measure

```elixir
%RealBook.Measure{
  chords: [RealBook.ChordBeat.t()]
}
```

### ChordBeat

```elixir
%RealBook.ChordBeat{
  beats: float(),            # Duration in beats (2.0, 4.0, etc.)
  chord: Harmony.Chord.t()   # Full Harmony chord struct
}
```

## Use Cases & Examples

### Practice & Education

```elixir
# Random song generator for practice sessions
defmodule PracticeSession do
  def daily_song do
    song = RealBook.random()

    """
    Today's practice: #{song.title} by #{song.author} (#{song.year})
    Form: #{song.form}
    Tempo suggestion: #{suggest_tempo(song.genre)}
    """
  end

  # Practice specific skills
  def practice_ii_v_i do
    RealBook.all()
    |> Enum.filter(fn song ->
      chords = RealBook.chords(song.title)
      has_ii_v_i_pattern?(chords)
    end)
    |> Enum.random()
  end
end
```

### Music Analysis & Research

```elixir
# Analyze chord complexity across different eras
defmodule JazzAnalysis do
  def complexity_by_decade do
    RealBook.all()
    |> Enum.group_by(&decade_from_year/1)
    |> Enum.map(fn {decade, songs} ->
      avg_chords = songs
        |> Enum.map(&(length(RealBook.chords(&1.title))))
        |> Enum.sum()
        |> div(length(songs))

      {decade, avg_chords}
    end)
  end

  # Find songs with specific harmonic features
  def find_modal_jazz do
    RealBook.search(genre: "jazz")
    |> Enum.filter(fn song ->
      progression = RealBook.progression(song.title)
      is_modal?(progression)
    end)
  end
end
```

### Interactive Applications

```elixir
# Web API for jazz standards
defmodule JazzAPI do
  # Phoenix controller example
  def show(conn, %{"title" => title}) do
    song = RealBook.get(title)

    json(conn, %{
      title: song.title,
      author: song.author,
      year: song.year,
      chords: RealBook.chords(song.title),
      form: song.form
    })
  end

  # Random song endpoint
  def random(conn, params) do
    filters = build_filters(params) # author, genre, year
    song = RealBook.random(filters)

    json(conn, serialize(song))
  end
end

# Chat bot integration
defmodule MusicBot do
  def handle_message("!standards " <> query) do
    songs = RealBook.search(author: query)

    songs
    |> Enum.take(5)
    |> Enum.map(&"#{&1.title} (#{&1.year})")
    |> Enum.join("\n")
  end
end
```

### Transposition & Arrangement

```elixir
# Transpose songs for different instruments
defmodule Transposer do
  def for_instrument(song_title, instrument) do
    transposition = case instrument do
      :alto_sax -> "6M"  # Eb instrument
      :tenor_sax -> "9M" # Bb instrument
      :trumpet -> "2M"   # Bb instrument
      _ -> "1P"          # Concert pitch
    end

    RealBook.chords(song_title)
    |> Enum.map(&Harmony.Chord.transpose(&1, transposition))
  end

  # Find songs in a vocalist's range
  def songs_in_key(preferred_key) do
    RealBook.all()
    |> Enum.filter(fn song ->
      sections = Map.values(song.sections)
      Enum.any?(sections, &(&1.key == preferred_key))
    end)
  end
end
```

### Learning Tools & Flashcards

```elixir
# Chord progression trainer
defmodule ChordTrainer do
  def quiz_mode do
    song = RealBook.random()
    progression = RealBook.progression(song.title)

    first_section = List.first(progression)

    %{
      question: "What comes after these chords in #{song.title}?",
      given: Enum.take(first_section.measures, 4),
      answer: Enum.drop(first_section.measures, 4) |> Enum.take(4)
    }
  end

  # Ear training: guess the standard
  def name_that_tune do
    song = RealBook.random()
    first_four = song
      |> RealBook.progression()
      |> List.first()
      |> Map.get(:measures)
      |> Enum.take(4)

    %{chords: first_four, answer: song.title}
  end
end
```

### Repertoire Building

```elixir
# Setlist generator for gigs
defmodule SetlistBuilder do
  def create_setlist(duration_minutes, tempo \\ :medium) do
    songs_per_set = div(duration_minutes, 5) # ~5 min per song

    RealBook.random(count: songs_per_set)
    |> balance_tempos()
    |> balance_keys()
    |> order_by_energy()
  end

  # Find contrafacts (songs sharing the same changes)
  def find_contrafacts(song_title) do
    target_progression = RealBook.progression(song_title)

    RealBook.all()
    |> Enum.filter(fn song ->
      similar_progression?(
        RealBook.progression(song.title),
        target_progression
      )
    end)
  end
end
```

## Data Source

The `.jazz` files in the `data/` directory use a Humdrum-inspired format with metadata and chord changes:

```
!!!OTL: Autumn Leaves
!!!COM: Kosma, Joseph
!!!ODT: 1947
**jazz
*>[A,A,B,A]
*>A
*M4/4
*C:
2C:min7
2F7
=
...
```

## Roadmap

### Current (v0.1.0)
- ✅ 1,193 jazz standards with chord progressions
- ✅ Search and filter API
- ✅ Harmony integration
- ✅ GenServer for fast in-memory access

### Future
- 📋 Melody data from PDF Real Books (using OCR/OMR)
- 📋 MusicXML import/export
- 📋 MIDI generation from chord progressions
- 📋 Scale recommendations for improvisation
- 📋 Voice leading analysis

## Architecture

RealBook uses a simple GenServer architecture:

1. **Application Start** - Loads all 1,193 `.jazz` files from `data/`
2. **Parse** - Converts text format to structured Elixir data
3. **Index** - Stores songs in memory by title for O(1) lookup
4. **Serve** - Provides fast, read-only access via public API

All data is loaded at compile/start time, making runtime access extremely fast.

## Development

```bash
# Get dependencies
mix deps.get

# Run tests
mix test

# Run performance benchmarks
mix bench

# Generate documentation
mix docs

# Interactive session
iex -S mix
```

## Testing

```elixir
# Run all tests
mix test

# Run specific test file
mix test test/real_book/songs_test.exs
```

## License

MIT

## Credits

- Jazz standard data sourced from the iRb Corpus 1.0
- Built with [Harmony](https://hex.pm/packages/harmony) for music theory
- Part of the ~/dev/music Elixir music ecosystem