Case Study VI: Bird Im-Migration#

The bird_im_migration package adapts a spectral-composition workflow into the larger compositions-abjad repository. It begins from a field recording of birds and a SPEAR partial-tracking analysis of that recording. The compositional goal is to reduce bird-like spectral behavior into performable notation while preserving a visible link to the source analysis.

Unlike the more abstract or algorithmic case studies in this repository, this package starts with a fixed audio analysis file and then applies musical reduction on top of it. That makes it a useful counterexample in the book because the score does not begin from a pitch set or a generative procedure. It begins from tracked partials in a real sound recording.

Download#

Listen#

q16 = 16th notes#

q32 - 32nd notes#

Score Preview#

q16 - 16th notes#

First page preview of Bird Im-Migration q16

q32 - 32nd notes#

First page preview of Bird Im-Migration q32

Source Materials#

The package keeps its source materials inside the package data directory.

  • src/bird_im_migration/data/DL_parkbirds.wav

  • src/bird_im_migration/data/DL_parkbirds_partials.txt

The WAV file is the original recording. The partials text file is the SPEAR analysis export that drives the score generation.

How We Analyze the Spectrum#

The analysis code focuses on a bird-like high-frequency band around 4.5-6.0 kHz. That band is not meant to be a universal description of birdsong. It is a practical filter for isolating the recurring chirp-like material in this particular recording.

The package keeps two layers of interpretation. It can infer approximate candidate regions from the partials automatically. It also preserves a curated set of bird regions that serve as the score timeline.

The curated regions for this recording are:

  • Early birds: 0.6-1.9 s

  • Middle birds: 2.8-5.6 s

  • Strong middle/late birds: 7.3-9.1 s

  • Late birds: 10.8-13.9 s

How We Generate the Music#

The package parses the SPEAR text file, filters for bird-like partials, quantizes onset material within each curated region, reduces each rhythmic bin to one or two salient pitches, and groups repeated bins into notated events. Because the underlying sound is spectrally complex, the resulting notation sometimes uses small pitch clusters rather than a single line.

The package currently supports at least two useful quantization settings.

  • q16 for a 16th-note reduction (more suitable for humans)

  • q32 for a 32nd-note reduction (more suitable for computers/synthesizers)

Those quantization choices appear directly in the generated output stems so that both versions can coexist in the build directory:

Implementation#

The analysis pipeline lives in bird_im_migration.analysis. The Abjad score builder lives in bird_im_migration.score. The command-line entry point lives in bird_im_migration.cli.

The analysis module defines the spectral parsing and reduction logic:

Quantizing bird-like partials into notated pitch bins.#
def quantize_region_pitches(
    partials: list[Partial],
    region: Region,
    *,
    bins_per_measure: int,
    min_frequency: float = 4500.0,
    max_frequency: float = 6000.0,
    min_amplitude: float = 0.02,
    max_notes_per_bin: int = 2,
) -> list[tuple[str, ...]]:
    buckets: list[dict[str, float]] = [dict() for _ in range(bins_per_measure)]
    span = region.end - region.start
    if span <= 0:
        return [tuple() for _ in range(bins_per_measure)]
    for partial in partials:
        if not (region.start <= partial.start <= region.end):
            continue
        if not (min_frequency <= partial.peak_frequency <= max_frequency):
            continue
        if partial.peak_amplitude < min_amplitude:
            continue
        position = (partial.start - region.start) / span
        bucket_index = min(bins_per_measure - 1, int(position * bins_per_measure))
        midi_number = round(69 + 12 * math.log2(partial.peak_frequency / 440.0))
        note_name = midi_to_note_name(midi_number)
        current = buckets[bucket_index].get(note_name, 0.0)
        if partial.peak_amplitude > current:
            buckets[bucket_index][note_name] = partial.peak_amplitude
    result: list[tuple[str, ...]] = []
    for bucket in buckets:
        top = sorted(bucket.items(), key=lambda item: item[1], reverse=True)[:max_notes_per_bin]
        result.append(tuple(note for note, _ in top))
    return result

The score module turns those reduced events into an Abjad score with region labels and score metadata:

Building the Bird Im-Migration LilyPond file from the partials analysis.#
def build_lilypond_file(
    *,
    partials_path: str | Path = DEFAULT_PARTIALS_PATH,
    quantization: int = 32,
    tempo_bpm: int = 90,
    midi_instrument: str = "acoustic grand",
):
    partials = parse_spear_partials(partials_path)
    score = build_score(
        partials,
        quantization=quantization,
        tempo_bpm=tempo_bpm,
        midi_instrument=midi_instrument,
    )

    header_block = abjad.Block(name="header")
    header_block.items.append(rf'title = "{TITLE}"')
    header_block.items.append(rf'subtitle = "{SUBTITLE}"')
    header_block.items.append(rf'composer = "{COMPOSER}"')
    header_block.items.append(r"tagline = ##f")

    layout_block = abjad.Block(name="layout")
    layout_block.items.append(
        r"""
\context {
  \Staff
  ottavation = "8va"
}
""".strip()
    )
    midi_block = abjad.Block(name="midi")

    score_block = abjad.Block(name="score")
    score_block.items.append(score)
    score_block.items.append(layout_block)
    score_block.items.append(midi_block)
    return abjad.LilyPondFile(items=[header_block, score_block])

Build and Use#

The repository build script now generates both quantized Bird Im-Migration variants alongside the other case studies. The package can also be invoked directly:

python -m bird_im_migration -o build --quantization 16 --pdf --midi
python -m bird_im_migration -o build --quantization 32 --pdf --midi

This case study shows that the Abjad approach can also absorb analysis-driven material rather than only symbolic or algorithmically invented content. It expands the technical report from score construction alone into the broader territory of spectral reduction and transcription as compositional method.