Chain together ML models for music, LangChain-style
Project description
🎵 SongChain 🔗
LangChain made it easy to string together language models and their data processing. SongChain does the same for music models — voice, audio, and MIDI-based generation — so musicians and developers can compose AI music pipelines as easily as language pipelines.
Key differences from language chaining:
- Focus on audio rather than language: musicians work with audio and MIDI clips, and craft songs from variations of them
- Audio and MIDI need their own encodings: clips expose waveforms, spectrograms, piano rolls, note sequences, and EnCodec tokens — the representations music models actually consume
- Chaining is temporal too: outputs are concatenated, layered, and looped in time to craft larger pieces
Installation
Into your own project (not yet on PyPI — install from GitHub or a local clone):
pip install git+https://github.com/asampat3090/songchain.git # from GitHub
pip install /path/to/songchain # from a local clone
# optional model backends (work with either form)
pip install "songchain[generation] @ git+https://github.com/asampat3090/songchain.git" # MusicGen / AudioGen
pip install "/path/to/songchain[transcription]" # Basic Pitch, Pop2Piano
For working on songchain itself:
git clone https://github.com/asampat3090/songchain.git && cd songchain
pip install -e ".[dev]"
Then in your downstream code:
# my_project/make_jingle.py
from songchain import Chain, PromptTemplate, concat
from songchain.models import NoteSequence, Transpose, MIDISynth, Fade
jingle_chain = Chain([
PromptTemplate("{n1}5:0.2 {n2}5:0.2 {n3}5:0.2 {n3}5:0.6"),
NoteSequence(),
Transpose(semitones=-5),
MIDISynth(sample_rate=22050),
Fade(fade_out=0.3),
])
a = jingle_chain.run(n1="C", n2="E", n3="G")
b = jingle_chain.run(n1="D", n2="F", n3="A")
concat([a, b], crossfade=0.05).save("jingle.wav")
Quickstart
Chain a prompt through MIDI generation, transformation, and synthesis to audio — no pretrained models needed:
from songchain import Chain, PromptTemplate
from songchain.models import NoteSequence, Transpose, MIDISynth, Fade
chain = Chain([
PromptTemplate("{root}4 {third}4 {fifth}4 {root}5:1.0"),
NoteSequence(duration=0.25), # text -> MIDI
Transpose(semitones=-12), # MIDI -> MIDI
MIDISynth(sample_rate=44100), # MIDI -> audio
Fade(fade_out=0.5), # audio -> audio
])
clip = chain.run(root="C", third="E", fifth="G")
clip.save("arpeggio.wav")
Generate music with a pretrained model (requires [generation]):
from songchain import Chain, PromptTemplate, concat
from songchain.models import MusicGen
chain = Chain([
PromptTemplate("{genre} beat with {mood} vibes, {bpm} BPM"),
MusicGen(size="small", duration=8.0),
])
verse = chain.run(genre="lofi", mood="chill", bpm=80)
chorus = chain.run(genre="lofi", mood="uplifting", bpm=80)
song = concat([verse, chorus, verse], crossfade=0.5)
song.save("song.wav")
Chains validate modalities up front — a MIDI model feeding an audio effect fails at construction time, before any weights load. Compose chains with |: Chain([...]) | MIDISynth().
Data: AudioClip and MIDIClip
Clips wrap a file (or in-memory data) and expose every ML representation, so models can be swapped without rewriting preprocessing:
from songchain import AudioClip, MIDIClip
audio = AudioClip("song.wav")
audio.waveform # (channels x frames) float32 tensor
audio.spectrogram # STFT power spectrogram
audio.mel_spectrogram # mel-scaled spectrogram
audio.encodec # EnCodec discrete codes (requires [generation])
audio.to_mono().resample(32000).slice(0, 10).save("intro.wav")
midi = MIDIClip("melody.mid")
midi.pianoroll(type="binary") # (128 x steps) — MidiNet, MuseGAN style
midi.pianoroll(type="velocity") # velocity-aware — Music Transformer style
midi.notes(type="index") # monophonic — MelodyRNN, MusicVAE style
midi.notes(type="note") # note names — DeepBach style
midi.transpose(5).time_stretch(0.5).synthesize().save("variation.wav")
Models
All models share one interface — declare input/output modality, implement generate — and register by name (get_model("musicgen", duration=8.0)).
| Model | Chain | Backend | Extra |
|---|---|---|---|
NoteSequence |
text → MIDI | built-in | — |
Transpose, MIDITimeStretch |
MIDI → MIDI | built-in | — |
MIDISynth |
MIDI → audio | built-in | — |
Gain, Reverse, Fade |
audio → audio | built-in | — |
MusicGen |
text → audio | Meta audiocraft | [generation] |
AudioGen |
text → audio | Meta audiocraft | [generation] |
BasicPitch |
audio → MIDI | Spotify basic-pitch | [transcription] |
Pop2Piano |
audio → MIDI | HuggingFace | [transcription] |
Add your own by subclassing a base (e.g. TextToAudioModel) and decorating with @register("name").
Temporal composition
from songchain import concat, overlay, loop, concat_midi
track = concat([intro, verse, chorus], crossfade=0.25) # join in time
mix = overlay([drums, bass, melody]) # layer tracks
beat = loop(bar, times=8) # repeat
medley = concat_midi([melody_a, melody_b]) # join MIDI
Dataset preparation
Split songs into fixed-size chunks with text descriptions — the layout audiocraft-style fine-tuning expects:
from songchain.util import split_audio_and_save
split_audio_and_save(30, "my_songs/", "chill bollywood beats with vocals")
# -> my_songs/output/000_000.wav, 000_000.txt, ...
End-to-end example
examples/make_song.py composes a complete short song — a four-chord progression (C–Am–F–G) with an arpeggiated melody chain overlaid on a bass chain, arranged bar-by-bar, looped, and faded — and writes both the audio and the MIDI:
python examples/make_song.py
# wrote ./song.wav (15.7s) and ./song.mid
This exact workflow is verified by the integration tests in tests/test_integration.py, which run the example and assert the artifacts are real: audible audio at the right duration, and MIDI that reloads with the full arrangement and can itself be fed back into a chain.
Development
pip install -e ".[dev]"
pytest --cov=songchain # run tests with coverage
coverage-badge -o coverage.svg -f # refresh the badge
black songchain tests && flake8 # format + lint
The test suite runs without any heavy model downloads — pretrained backends are exercised through fakes.
Project details
Release history Release notifications | RSS feed
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 songchain-0.2.0.tar.gz.
File metadata
- Download URL: songchain-0.2.0.tar.gz
- Upload date:
- Size: 27.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
677b0574aabf8d8082950c9820d1b5dbf6ac25fe36c02584f39acf6eaa01dacb
|
|
| MD5 |
90ffd032ff085b56f1df861253ccee82
|
|
| BLAKE2b-256 |
10aef84f742de2ff6d65b852d302f07c9fbef90c6911968a966837ff11b17f9c
|
Provenance
The following attestation bundles were made for songchain-0.2.0.tar.gz:
Publisher:
release.yml on asampat3090/songchain
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
songchain-0.2.0.tar.gz -
Subject digest:
677b0574aabf8d8082950c9820d1b5dbf6ac25fe36c02584f39acf6eaa01dacb - Sigstore transparency entry: 1775868064
- Sigstore integration time:
-
Permalink:
asampat3090/songchain@359f416f210291dce2bae1b4f5a9734ac8f427fb -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/asampat3090
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@359f416f210291dce2bae1b4f5a9734ac8f427fb -
Trigger Event:
push
-
Statement type:
File details
Details for the file songchain-0.2.0-py3-none-any.whl.
File metadata
- Download URL: songchain-0.2.0-py3-none-any.whl
- Upload date:
- Size: 21.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
829434370736e3059d6b075c0a92400c1e9ad72527ba87b219e1922b62ad08d6
|
|
| MD5 |
62ee0d78173f52b2c4546c66cebb1e59
|
|
| BLAKE2b-256 |
babd7c4e2f0a187275fa0b483f4274ae37a2f3c12086c2fab30b350eb7bd1afa
|
Provenance
The following attestation bundles were made for songchain-0.2.0-py3-none-any.whl:
Publisher:
release.yml on asampat3090/songchain
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
songchain-0.2.0-py3-none-any.whl -
Subject digest:
829434370736e3059d6b075c0a92400c1e9ad72527ba87b219e1922b62ad08d6 - Sigstore transparency entry: 1775868154
- Sigstore integration time:
-
Permalink:
asampat3090/songchain@359f416f210291dce2bae1b4f5a9734ac8f427fb -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/asampat3090
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@359f416f210291dce2bae1b4f5a9734ac8f427fb -
Trigger Event:
push
-
Statement type: