Metadata-Version: 2.4
Name: flexinfer-chiptune
Version: 0.5.2
Summary: Retro game music generation library with semantic API for 8-bit/16-bit style MIDI and audio composition
Author-email: Cody Blevins <cody@flexinfer.ai>
License: MIT
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Artistic Software
Classifier: Topic :: Multimedia :: Sound/Audio :: MIDI
Requires-Python: >=3.11
Requires-Dist: midiutil>=1.2.1
Requires-Dist: music21>=9.0.0
Requires-Dist: numpy>=1.20.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: scipy>=1.10.0
Requires-Dist: soundfile>=0.12.0
Provides-Extra: audio
Requires-Dist: sounddevice>=0.4.6; extra == 'audio'
Provides-Extra: dev
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Requires-Dist: sounddevice>=0.4.6; extra == 'dev'
Description-Content-Type: text/markdown

![Banner](assets/banner.png)
# flexinfer-chiptune

A Python library for generating 8-bit/16-bit style music and sound effects with a semantic API that agents can use to programmatically compose music. Supports both MIDI export and direct audio synthesis with authentic retro chip waveforms.

## Features

- **Music Theory Foundation**: Scales, chords, arpeggios with emotion-to-music mapping
- **Game Theme Templates**: Pre-built patterns for battle, boss, victory, overworld, etc.
- **Multiple Chip Systems**: NES 2A03, Game Boy DMG, C64 SID, and Sega Genesis FM
- **Audio Effects**: Vibrato, tremolo, echo, chorus, tape delay, and effect chains
- **Procedural Generation**: Markov-based melodies, auto bass lines, countermelodies
- **Adaptive Intensity**: Dynamic music that responds to game state (0.0-1.0)
- **Preset Library**: One-liner genre presets, effect chains, and quick composers
- **CLI Tool**: Generate music from the command line
- **Agent-Friendly API**: Natural language context to music generation
- **Dual Export**: MIDI files or direct WAV synthesis

## Installation

```bash
pip install flexinfer-chiptune
```

Or with uv:

```bash
uv add flexinfer-chiptune
```

For audio playback support:

```bash
pip install flexinfer-chiptune[audio]
```

## Quick Start

### Command Line Interface

```bash
# Generate a battle theme
chiptune generate --preset battle --output battle.wav

# Generate with custom settings
chiptune generate --preset overworld --root G --bpm 130 --bars 16 --effects space

# Generate a sound effect
chiptune sfx coin --output coin.mid

# List all available presets
chiptune list all
```

### Using Genre Presets

```python
from chiptune.presets import GenrePresets, QuickCompose

# One-liner battle track with drums
composer = QuickCompose.battle_track(length_bars=16)
track = composer.build()

# Pre-configured genre composers
battle = GenrePresets.battle_theme()       # A minor, 165 BPM
overworld = GenrePresets.overworld()       # C major, 120 BPM
dungeon = GenrePresets.dungeon()           # E dorian, 85 BPM
title = GenrePresets.title_screen()        # F major, 130 BPM
chase = GenrePresets.chase_scene()         # E phrygian, 185 BPM
```

### Using the Composer API

```python
from chiptune import ChiptuneComposer, AudioExporter
from chiptune.presets import EffectPresets

# Create a heroic battle theme
composer = ChiptuneComposer.create(bpm=150, root="A", mode="minor")
composer.set_progression(["i", "VII", "VI", "V"])
composer.add_melody(length_bars=8, contour="ascending")
composer.add_countermelody(motion="contrary")
composer.add_bass(style="octave")
composer.add_drums(pattern="driving")

# Build and export with effects
track = composer.build()
effects = EffectPresets.retro_gaming()

exporter = AudioExporter()
exporter.export_wav(track, "battle_theme.wav", effects=effects)
```

### Audio Effects

```python
from chiptune.output.audio.effects import (
    Tremolo, Vibrato, Echo, TapeDelay, Chorus, EffectChain
)

# Individual effects
tremolo = Tremolo(rate=5.0, depth=0.3)
echo = Echo(delay_time=0.25, feedback=0.5)
chorus = Chorus.lush()

# Chain effects together
chain = EffectChain(effects=[
    Tremolo.subtle(),
    Chorus.classic(),
    Echo.medium(),
])

# Or use the | operator
chain = Tremolo() | Chorus.subtle() | Echo.short()

# Apply to audio
output = chain.process(samples, sample_rate=44100)
```

### Effect Presets

```python
from chiptune.presets import EffectPresets

# Pre-built effect chains
clean = EffectPresets.clean()           # No effects
retro = EffectPresets.retro_gaming()    # Subtle chorus + echo
space = EffectPresets.space_adventure() # Lush chorus + space delay
lofi = EffectPresets.lo_fi_nostalgia()  # Tape delay + tremolo
arcade = EffectPresets.arcade_cabinet() # Chorus + slapback
dreamy = EffectPresets.dreamy()         # Heavy modulation effects
dungeon = EffectPresets.dungeon_reverb() # Cavernous echo
boss = EffectPresets.boss_battle()       # Punchy minimal effects
```

### Procedural Melody Generation

```python
from chiptune.generation import (
    MarkovMelodyGenerator, MelodyStyle,
    BassGenerator, CountermelodyGenerator,
    IntensityProfile, IntensityController
)

# Generate melody from Markov chain
gen = MarkovMelodyGenerator(order=2)
gen.train_from_midi_pitches([60, 62, 64, 65, 67, 65, 64, 62, 60])
notes = gen.generate(16, start_pitch=60)

# Use style presets
heroic = MelodyStyle.heroic()
mysterious = MelodyStyle.mysterious()
chiptune = MelodyStyle.chiptune()

# Auto-generate bass lines
bass_gen = BassGenerator.jazz()
bass_notes = bass_gen.generate(chords, beats_per_chord=4)

# Generate countermelody
counter_gen = CountermelodyGenerator.contrary()
counter = counter_gen.generate(melody, scale=scale)
```

### Adaptive Intensity

```python
from chiptune.generation import IntensityProfile, IntensityController

# Intensity profiles for different game states
calm = IntensityProfile.calm()           # Low, steady
building = IntensityProfile.building()    # Gradually increasing
battle = IntensityProfile.battle()        # High, volatile
boss = IntensityProfile.boss_fight()      # Maximum intensity

# Controller maps intensity to musical parameters
controller = IntensityController.action()
tempo = controller.get_tempo(progress=0.5)      # 80-160 BPM
velocity = controller.get_velocity(progress=0.5) # 60-120
layers = controller.get_active_layers(0.5, total_layers=4)
```

### Chip Systems

```python
from chiptune.chip.systems import NES2A03, GameBoyChip, SIDChip, GenesisChip

# NES 2A03 (2 pulse + triangle + noise)
nes = NES2A03.create()
nes_fami = NES2A03.famitracker_style()

# Game Boy DMG
gameboy = GameBoyChip.create()

# Commodore 64 SID
sid = SIDChip.create()

# Sega Genesis YM2612 (FM synthesis)
genesis = GenesisChip.create()
```

### Sound Effects

```python
from chiptune import SFXGenerator

sfx = SFXGenerator()

# Generate common game sounds (returns MIDI bytes)
coin = sfx.coin_collect()
jump = sfx.jump()
explosion = sfx.explosion()
powerup = sfx.powerup()
laser = sfx.laser()
teleport = sfx.teleport()
door_open = sfx.door_open()
menu_select = sfx.menu_select()

# Save to file
sfx.save(coin, "coin.mid")
```

### Jingles

```python
from chiptune import Jingle

# Quick musical phrases
victory = Jingle.victory_fanfare(root="C", tempo=140)
item = Jingle.item_get(root="G")
level_up = Jingle.level_up()
game_over = Jingle.game_over()
```

### Agent API

```python
from chiptune import MusicAgent

agent = MusicAgent()

# Generate music from natural language context
music = agent.compose_for_context(
    context="player enters the boss arena",
    duration_seconds=30.0,
    intensity=0.9,
)

# Generate a sound effect
sfx = agent.generate_sfx("coin_collect")
```

## CLI Reference

```
chiptune generate [options]
  --preset, -p    Genre preset: battle, overworld, dungeon, victory,
                  title, shop, sad, chase (default: overworld)
  --output, -o    Output file path (default: output.wav)
  --bars, -b      Length in bars (default: 8)
  --root, -r      Root note (C, D, E, F, G, A, B with optional #/b)
  --bpm           Tempo in BPM (overrides preset default)
  --effects, -e   Effect preset: clean, retro, space, lofi, arcade,
                  dreamy, dungeon, boss (default: retro)
  --format, -f    Output format: wav, midi (default: wav)
  --no-drums      Exclude drums
  --no-bass       Exclude bass
  --no-counter    Exclude countermelody
  --seed, -s      Random seed for reproducibility

chiptune sfx <type> [options]
  type            Effect type: jump, land, coin, powerup, damage,
                  explosion, laser, shield, teleport, menu_select,
                  menu_confirm, menu_back, success, error,
                  door_open, door_close, heartbeat, countdown
  --output, -o    Output file path (default: sfx.mid)

chiptune list [category]
  category        What to list: presets, effects, sfx, all (default: all)
```

## Semantic Mappings

### Emotions to Musical Elements

| Emotion | Scale/Mode | Tempo | Character |
|---------|------------|-------|-----------|
| Heroic | Major/Lydian | Fast | Driving, 4ths/5ths |
| Mysterious | Dorian | Medium | Syncopated |
| Danger | Phrygian/Locrian | Fast | Urgent, chromatic |
| Peaceful | Pentatonic | Slow | Flowing, 3rds/6ths |
| Sad | Minor/Aeolian | Slow | Simple, minor 3rds |

### Game Contexts to Themes

| Context | Characteristics |
|---------|-----------------|
| Battle | Fast, minor, syncopated, driving drums |
| Boss | Epic, chromatic, heavy bass, intense |
| Victory | Major fanfare, I-IV-V-I, triumphant |
| Overworld | Adventurous, major, walking bass |
| Dungeon | Modal, sparse, atmospheric |
| Shop | Relaxed, major, simple rhythm |
| Chase | Very fast, phrygian, urgent |

## Development

```bash
# Clone the repository
git clone https://gitlab.flexinfer.ai/libs/py-chiptune.git
cd py-chiptune

# Install dependencies
uv sync

# Run tests
uv run pytest

# Type checking
uv run mypy src/chiptune

# Linting
uv run ruff check src/chiptune
```

## License

MIT License
