Generator API Reference

Complete reference for the BeatBlocks Generator API

Overview

The BeatBlocks Generator API provides tools for procedurally generating music compositions from templates. It handles layer selection, arrangement creation, and ensures musical coherence based on configurable rules.

Basic Usage

// Generate a composition from a template
const beatBlock = new BeatBlock(audioContext);

// Define input data with template
const inputData = {
  details: {
    title: "Generated Composition",
    author: "BeatBlocks Creator",
    bpm: 120
  },
  layers: [
    // Layer definitions
  ],
  generationConfig: {
    seed: 12345,
    groups: ["main", "alt"],
    mutexes: ["drums", "bass"]
  },
  template: [
    {
      length: 8,
      layerCount: 3,
      inclusions: ["drums"],
      exclusions: []
    },
    {
      length: 4,
      layerCount: 5,
      inclusions: ["drums", "bass"],
      exclusions: ["fx"]
    }
  ]
};

// Generate the arrangement
const arrangedSong = await beatBlock.generate(inputData);

// Initialize with the generated arrangement
await beatBlock.initialize(arrangedSong);

Generation Method

generate(songData)

Generates a complete arrangement from a template-based song definition.

Parameters

  • songData - A BeatBlockInputData object containing template and layer definitions

Returns

Promise<BeatBlockInputData> - The complete song data with generated arrangement

Example

const generatedSong = await beatBlock.generate({
  details: {
    title: "My Generated Song",
    author: "BeatBlocks Creator",
    bpm: 90
  },
  generationConfig: {
    seed: 42,
    groups: ["aa", "bb", "cc"],
    mutexes: ["drums", "bass", "vox"]
  },
  layers: [
    {
      id: "kick-1",
      loopLength: 2,
      path: "/content/kick-1.opus",
      volume: 1,
      loop: true,
      alignment: "start",
      groups: ["aa"],
      mutex: ["drums"],
      weight: 10
    },
    {
      id: "snare-1",
      loopLength: 2,
      path: "/content/snare-1.opus",
      volume: 0.8,
      loop: true,
      alignment: "start",
      groups: ["bb"],
      mutex: ["drums"],
      weight: 5
    },
    {
      id: "bass-1",
      loopLength: 4,
      path: "/content/bass-1.opus",
      volume: 0.9,
      loop: true,
      alignment: "start",
      groups: ["aa"],
      mutex: ["bass"],
      weight: 8
    }
  ],
  template: [
    {
      length: 4,
      layerCount: 2,
      inclusions: ["drums"],
      exclusions: []
    },
    {
      length: 8,
      layerCount: 4,
      inclusions: ["drums", "bass"],
      exclusions: []
    },
    {
      length: 4,
      layerCount: 1,
      inclusions: [],
      exclusions: ["drums"]
    }
  ]
});

console.log(generatedSong.arrangement);

Random Generation

mulberry32(seed)

Creates a deterministic random number generator based on a seed value.

Parameters

  • seed - A numeric seed value

Returns

Function - A function that returns a random number between 0 and 1

Example

// Create a seeded random number generator
const random = beatBlock.mulberry32(12345);

// Generate random numbers
const randomValue1 = random(); // e.g., 0.23456789
const randomValue2 = random(); // e.g., 0.87654321

// Using the same seed will produce the same sequence
const sameRandom = beatBlock.mulberry32(12345);
const sameValue1 = sameRandom(); // Will be 0.23456789
const sameValue2 = sameRandom(); // Will be 0.87654321

selectWeightedLayer(layers, random)

Selects a layer from an array based on weight values.

Parameters

  • layers - Array of layers with optional weight properties
  • random - Random number generator function

Returns

Layer - The selected layer

Example

// Array of layers with weights
const layers = [
  { id: "layer1", weight: 10 },
  { id: "layer2", weight: 5 },
  { id: "layer3", weight: 1 }
];

// Create random generator
const random = beatBlock.mulberry32(12345);

// Select a layer based on weights
// layer1 has 10/16 chance, layer2 has 5/16 chance, layer3 has 1/16 chance
const selectedLayer = selectWeightedLayer(layers, random);

Generation Algorithm

How Generation Works

The BeatBlocks generation algorithm follows these steps:

  1. Initialize Random Generator: Create a deterministic random number generator using the provided seed.
  2. Process Template: For each section in the template:
    • Determine how many layers to include based on the layerCount property
    • Filter layers based on inclusions and exclusions
    • Ensure mutually exclusive layers don't conflict (using mutex groups)
    • Select layers based on their weights
  3. Create Arrangement: Build the final arrangement with selected layers for each section.
  4. Return Complete Song: Return the original song data with the generated arrangement.

Pseudocode

function generate(songData) {
  // Initialize random generator with seed
  const random = mulberry32(songData.generationConfig.seed);
  
  // Create empty arrangement
  const arrangement = [];
  
  // Process each template section
  for (const templateSection of songData.template) {
    // Create section with specified length
    const section = {
      length: templateSection.length,
      layers: []
    };
    
    // Get available layers based on inclusions/exclusions
    let availableLayers = songData.layers.filter(layer => {
      // Check if layer matches inclusion/exclusion criteria
      const matchesInclusions = templateSection.inclusions.length === 0 || 
        templateSection.inclusions.some(tag => layer.mutex.includes(tag));
      
      const matchesExclusions = !templateSection.exclusions.some(tag => 
        layer.mutex.includes(tag));
      
      return matchesInclusions && matchesExclusions;
    });
    
    // Track selected mutex groups to avoid conflicts
    const selectedMutexes = new Set();
    
    // Select layers up to layerCount
    for (let i = 0; i < templateSection.layerCount; i++) {
      // Filter out layers with mutex conflicts
      const nonConflictingLayers = availableLayers.filter(layer => 
        !layer.mutex.some(mutex => selectedMutexes.has(mutex))
      );
      
      if (nonConflictingLayers.length === 0) break;
      
      // Select layer based on weights
      const selectedLayer = selectWeightedLayer(nonConflictingLayers, random);
      
      // Add layer to section
      section.layers.push(selectedLayer);
      
      // Mark mutex groups as used
      selectedLayer.mutex.forEach(mutex => selectedMutexes.add(mutex));
      
      // Remove selected layer from available layers
      availableLayers = availableLayers.filter(layer => layer.id !== selectedLayer.id);
    }
    
    // Add section to arrangement
    arrangement.push(section);
  }
  
  // Return complete song data with arrangement
  return {
    ...songData,
    arrangement
  };
}

Complete Example

A complete example of using the BeatBlocks Generator API:

// Initialize audio context and BeatBlock
const audioContext = new AudioContext();
const beatBlock = new BeatBlock(audioContext);

// Define input data with template
const inputData = {
  details: {
    title: "Procedural Beat",
    author: "BeatBlocks Creator",
    bpm: 90
  },
  generationConfig: {
    seed: Math.floor(Math.random() * 1000000), // Random seed
    groups: ["main", "alt", "fx"],
    mutexes: ["drums", "bass", "melody", "vox"]
  },
  layers: [
    // Drum layers
    {
      id: "kick-1",
      loopLength: 2,
      path: "/content/kick-1.opus",
      volume: 1,
      loop: true,
      alignment: "start",
      groups: ["main"],
      mutex: ["drums"],
      weight: 10
    },
    {
      id: "kick-2",
      loopLength: 2,
      path: "/content/kick-2.opus",
      volume: 1,
      loop: true,
      alignment: "start",
      groups: ["alt"],
      mutex: ["drums"],
      weight: 5
    },
    
    // Bass layers
    {
      id: "bass-1",
      loopLength: 4,
      path: "/content/bass-1.opus",
      volume: 0.9,
      loop: true,
      alignment: "start",
      groups: ["main"],
      mutex: ["bass"],
      weight: 8
    },
    {
      id: "bass-2",
      loopLength: 4,
      path: "/content/bass-2.opus",
      volume: 0.9,
      loop: true,
      alignment: "start",
      groups: ["alt"],
      mutex: ["bass"],
      weight: 4
    },
    
    // Melody layers
    {
      id: "melody-1",
      loopLength: 8,
      path: "/content/melody-1.opus",
      volume: 0.8,
      loop: true,
      alignment: "start",
      groups: ["main"],
      mutex: ["melody"],
      weight: 6
    },
    
    // FX layers
    {
      id: "fx-1",
      loopLength: 1,
      path: "/content/fx-1.opus",
      volume: 0.7,
      loop: false,
      alignment: "start",
      groups: ["fx"],
      mutex: [],
      weight: 3
    }
  ],
  template: [
    // Intro section
    {
      length: 4,
      layerCount: 1,
      inclusions: ["drums"],
      exclusions: []
    },
    // Build-up section
    {
      length: 4,
      layerCount: 2,
      inclusions: ["drums", "bass"],
      exclusions: []
    },
    // Main section
    {
      length: 8,
      layerCount: 3,
      inclusions: ["drums", "bass"],
      exclusions: []
    },
    // Break section
    {
      length: 4,
      layerCount: 1,
      inclusions: [],
      exclusions: ["drums"]
    },
    // Outro section
    {
      length: 4,
      layerCount: 2,
      inclusions: ["drums"],
      exclusions: []
    }
  ]
};

// Generate and play
async function generateAndPlay() {
  try {
    // Generate arrangement
    const generatedSong = await beatBlock.generate(inputData);
    console.log("Generated arrangement:", generatedSong.arrangement);
    
    // Initialize player with generated song
    await beatBlock.initialize(generatedSong);
    
    // Play the composition
    await beatBlock.play();
    
    // Export the generated song as JSON
    const jsonString = JSON.stringify(generatedSong, null, 2);
    const blob = new Blob([jsonString], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    
    // Create download link
    const a = document.createElement('a');
    a.href = url;
    a.download = 'generated-beatblock.json';
    a.textContent = 'Download Generated BeatBlock';
    document.body.appendChild(a);
    
  } catch (error) {
    console.error("Generation error:", error);
  }
}

// Start generation when page loads
document.addEventListener('DOMContentLoaded', generateAndPlay);