bni

Library API

bni can be used as a C library as well as a CLI.

Build with:

make

This produces:

libbni.a
libbni.dylib    # macOS
libbni.so       # Linux

Install with:

make install PREFIX=$HOME/.local

This installs bni, include/bni.h, and the static/shared libraries.

Include bni.h and link with -lbni plus the htslib linker flags.

Example:

cc example.c -I$HOME/.local/include -L$HOME/.local/lib -lbni $(pkg-config --libs htslib)

Build an Index

#include <bni.h>

int rc = bni_build_index("reads.name.bam", NULL, NULL, NULL);

Pass a bni_build_options_t to control threads, overwrite behavior, and header checking:

bni_build_options_t opts = {0};
opts.threads = 4;
opts.force = 1;

bni_build_stats_t stats = {0};
int rc = bni_build_index("reads.name.bam", "reads.name.bam.bni", &opts, &stats);

Search an Index

Load a .bni directly and search the BGZF-block entry table:

bni_index_t *idx = bni_index_open("reads.name.bam.bni");
const bni_entry_t *entry = bni_find_entry(idx, "READ_ID");
if (entry != NULL) {
    const char *first = bni_entry_first_name(idx, entry);
    const char *last  = bni_entry_last_name(idx, entry);
    /* candidate block: first <= possible READ_ID <= last */
    /* entry->beg_voff, entry->end_voff, entry->n_records */
}
bni_index_close(idx);

bni_find_entry() returns the earliest entry whose last_qname >= target. The target may still be absent; the reader must scan forward and stop when QNAME > target. Usually you should use bni_reader_fetch() rather than interpreting entries directly.

Use bni_load_index_file() and bni_index_destroy() if you want caller-owned storage:

bni_index_t idx;
if (bni_load_index_file("reads.name.bam.bni", &idx) == 0) {
    const bni_entry_t *entry = bni_find_entry(&idx, "READ_ID");
    bni_index_destroy(&idx);
}

Loaded indexes keep decoded entries in memory. The string table is backed by mmap() when available, with an in-memory fallback, so callers should always release indexes with bni_index_destroy() or bni_index_close().

Fetch BAM Records

#include <stdio.h>
#include <bni.h>

static int on_record(const bam1_t *record, const sam_hdr_t *header, void *user) {
    (void)header;
    (void)user;
    puts(bam_get_qname(record));
    return 0;
}

int main(void) {
    bni_reader_t *reader = NULL;
    if (bni_reader_open("reads.name.bam", NULL, NULL, &reader) != 0) return 1;

    uint32_t n_records = 0;
    int rc = bni_reader_fetch(reader, "READ_ID", on_record, NULL, &n_records);
    if (rc == 1) {
        /* not found */
    }

    bni_reader_close(reader);
    return rc < 0 ? 1 : 0;
}

bni_reader_fetch() returns:

The callback receives a reused htslib bam1_t; copy it inside the callback if you need to keep it after the callback returns.

Reader Options

bni_reader_options_t opts = {0};
opts.threads = 4;
opts.ignore_metadata = 1;

bni_reader_t *reader = NULL;
int rc = bni_reader_open("reads.name.bam", "custom.bni", &opts, &reader);

By default, bni_reader_open() checks BAM size, mtime, and SAM header hash against the index metadata.