mgnr

generative music library for js

Playground

import * as mgnr from '@mgnr/tone'
import * as Tone from 'tone'
import { beat } from './patterns'
import { setupChannels } from './mix'

/**
 * demo song for mgnr playground.
 *
 * by clicking the button in the sandbox browser on the right, it starts playing audio.
 *
 * modify the code and explore random sequences it generates :-)
 * (click on the sandbox browser's reload button whenever you want to apply new code)
 */
export const main = () => {
  // mgnr provides a mixer and channels like one in DAW
  const channels = setupChannels()

  // @mgnr/tone depends on Tone.js Transport.
  Tone.Transport.bpm.value = 132

  // base config for the scales from which generators should pick random notes
  const scaleSource = mgnr.createScaleSource({
    key: mgnr.pickRandomPitchName(),
    pref: 'omit25',
    range: { min: 50, max: 80 },
  })
  Tone.Transport.scheduleRepeat(() => {
    scaleSource.modulateAll({ key: mgnr.pickRandomPitchName() }, 4)
  }, '8m')

  prepareDrums(channels.drumCh)
  preparePad(channels.padCh, scaleSource)
  prepareBass(channels.bassCh, scaleSource)
}

const preparePad = (channel: mgnr.InstChannel, scaleSource: mgnr.ScaleSource) => {
  const scale = scaleSource.createScale({ range: { min: 64, max: 102 } })

  // SequenceGenerator generates random sequences based on these configurations
  const generator = mgnr.SequenceGenerator.create({
    scale: scale,
    sequence: {
      length: 12,
      density: 0.8, // tries to fill 80% of 12 positions
      division: 4, // the unit for the notes and their positions (4=quarter notes)
      polyphony: 'mono',
      fillStrategy: 'fill',
    },
    note: {
      duration: { // the range in which note duration can be
        min: 2,
        max: 4,
      },
      harmonizer: { // random notes come with these harmonizing notes
        degree: ['2', '5'],
      },
    },
  })

  // You can supply another generator as a second sequence layer, which make more complex poly rhythm
  const generator2 = mgnr.SequenceGenerator.create({
    scale: scale,
    sequence: {
      length: 6,
      density: 0.4,
      division: 16,
      polyphony: 'mono',
      fillStrategy: 'fill',
    },
    note: {
      duration: {
        min: 1,
        max: 4,
      },
    },
  })

  // generate the initial random sequences
  generator.constructNotes()
  generator2.constructNotes()

  // assign the generator to the channel (i.e. synth pad)
  const outlet = mgnr.createOutlet(channel.inst)
  outlet
    .assignGenerator(generator)
    .loopSequence(4)
    .onElapsed((generator) => {
      generator.mutate({ rate: 0.5, strategy: 'inPlace' }) // changes the note's pitch in place
    })
    .onEnded((generator) => {
      generator.mutate({ rate: 0.5, strategy: 'randomize' }) // changes the note's position and pitch
    })
  outlet
    .assignGenerator(generator2)
    .loopSequence(4)
    .onElapsed((generator) => {
      generator.mutate({ rate: 0.5, strategy: 'inPlace' })
    })
    .onEnded((generator) => {
      generator.mutate({ rate: 0.5, strategy: 'randomize' })
    })
}

const prepareBass = (channel: mgnr.InstChannel, scaleSource: mgnr.ScaleSource) => {
  const scale = scaleSource.createScale({ pref: 'major', range: { min: 24, max: 52 } })

  const generator1 = mgnr.SequenceGenerator.create({
    scale: scale,
    sequence: {
      length: 4,
      division: 1,
      density: 1,
    },
    note: {
      duration: 1,
    },
  })
  const generator2 = mgnr.SequenceGenerator.create({
    scale: scale,
    sequence: {
      length: 24,
      division: 16,
      density: 0.2,
    },
    note: {
      duration: {
        min: 1,
        max: 2,
      },
    },
  })
  generator1.constructNotes()
  generator2.constructNotes()

  const outlet = mgnr.createOutlet(channel.inst, Tone.Transport.toSeconds('16n'))
  outlet
    .assignGenerator(generator1)
    .loopSequence(2)
    .onEnded((g) => g.resetNotes())
  outlet
    .assignGenerator(generator2)
    .loopSequence(1)
    .onEnded((g) => g.resetNotes())
}

const prepareDrums = (channel: mgnr.InstChannel) => {
  const scale = mgnr.createScale([30, 42, 90])
  const generator = mgnr.SequenceGenerator.create({
    scale: scale,
    note: {
      duration: 1,
    },
    sequence: {
      length: 16,
      division: 16,
      density: 0.5,
      polyphony: 'mono',
    },
  })
  const generator2 = mgnr.SequenceGenerator.create({
    scale: scale,
    sequence: {
      length: 12,
      division: 16,
      density: 0.25,
      polyphony: 'mono',
    },
    note: {
      duration: 1,
    },
  })
  generator.constructNotes(beat)
  generator2.constructNotes()

  const outlet = mgnr.createOutlet(channel.inst, Tone.Transport.toSeconds('16n'))
  outlet
    .assignGenerator(generator)
    .loopSequence(4)
    .onElapsed((g, loopNth) => {
      if (loopNth % 2 === 1) {
        g.mutate({ strategy: 'inPlace', rate: 0.1 })
      }
      if (Math.random() > 0.95) {
        g.updateConfig({
          sequence: {
            density: g.sequence.density + (Math.random() - 0.5) * 0.1,
          },
        })
      }
    })
    .onEnded((g) => {
      if (Math.random() > 0.5) {
        g.resetNotes(beat)
      } else {
        g.eraseSequenceNotes()
      }
    })
  outlet
    .assignGenerator(generator2)
    .loopSequence(4)
    .onElapsed((g) => {
      g.mutate({ rate: 0.1, strategy: 'inPlace' })
    })
    .onEnded((g) => {
      g.updateConfig({
        sequence: {
          density: Math.max(0.0, Math.min(0.5, g.sequence.density + (Math.random() - 0.5))),
          length: g.sequence.length + Math.floor(Math.random() * 2),
        },
      })
    })
}

Installation

npm i @mgnr/tone

Documentation

coming soon

Development

made by Katsumi "Khei" Yoshida
source code: https://github.com/kheiyoshida/mgnr