adamrenklint/dilla

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# Dilla

[![npm](https://img.shields.io/npm/v/dilla.svg?style=flat-square)](https://www.npmjs.com/package/dilla) [![npm](https://img.shields.io/npm/dm/dilla.svg?style=flat-square)](https://www.npmjs.com/package/dilla) [![GitHub stars](https://img.shields.io/github/stars/adamrenklint/dilla.svg?style=flat-square)](https://github.com/adamrenklint/dilla/stargazers) [![GitHub forks](https://img.shields.io/github/forks/adamrenklint/dilla.svg?style=flat-square)](https://github.com/adamrenklint/dilla/network)

[![Travis branch](https://img.shields.io/travis/adamrenklint/dilla.svg?style=flat-square)](https://travis-ci.org/adamrenklint/dilla) [![Code Climate](https://img.shields.io/codeclimate/github/adamrenklint/dilla.svg?style=flat-square)](https://codeclimate.com/github/adamrenklint/dilla) [![Code Climate](https://img.shields.io/codeclimate/coverage/github/adamrenklint/dilla.svg?style=flat-square)](https://codeclimate.com/github/adamrenklint/dilla) [![David dependencies](https://img.shields.io/david/adamrenklint/dilla.svg?style=flat-square)](https://david-dm.org/adamrenklint/dilla) [![David devDependencies](https://img.shields.io/david/dev/adamrenklint/dilla.svg?style=flat-square)](https://david-dm.org/adamrenklint/dilla#info=devDependencies)

> Schedule looped playback of Web Audio notes at 96 ticks per beat

Based on [ditty](https://github.com/mmckegg/ditty) and [bopper](https://github.com/mmckegg/bopper). Named after one of [the greatest](http://en.wikipedia.org/wiki/J_Dilla) to ever touch a drum machine.

## Install

```
$ npm install --save dilla
```

## Usage

```javascript
var Dilla = require('dilla');
var audioContext = new AudioContext();
var dilla = new Dilla(audioContext, options);
```

#### Options and defaults

```json
{
  "tempo": 120,
  "beatsPerBar": 4,
  "loopLength": 2
}
```

Note that ```loopLength``` is measured in bars, i.e. the default loop length above is 8 beats.

### Example

The "hello world" of audio libraries, the simple metronome: check out the [demo](http://adamrenklint.github.io/dilla) or [code](https://github.com/adamrenklint/dilla/blob/master/example.js).

```javascript
var high = {
  'position': '*.1.01',
  'freq': 440,
  'duration': 15
};
var low = { 'freq': 330, 'duration': 15 };

dilla.set('metronome', [
  high,
  ['*.>1.01', low]
]);

var oscillator, gainNode;
dilla.on('step', function (step) {
  if (step.event === 'start') {
    oscillator = step.context.createOscillator();
    gainNode = step.context.createGain();
    oscillator.connect(gainNode);
    gainNode.connect(step.context.destination);
    oscillator.frequency.value = step.args.freq;
    gainNode.gain.setValueAtTime(1, step.time);
    oscillator.start(step.time);
  }
  else if (step.event === 'stop' && oscillator) {
    gainNode.gain.setValueAtTime(1, step.time);
    gainNode.gain.linearRampToValueAtTime(0, step.time + 0.1);
    oscillator.stop(step.time + 0.1);
    oscillator = null;
    gainNode = null;
  }
});

dilla.start();
```

### Tutorials

- [Making a boombap beat with Dilla and the Web Audio API](http://adamrenklint.com/making-boombap-beat-with-dilla/)
- [Using expressions in Dilla](http://adamrenklint.com/using-expressions-in-dilla/)

### API

#### Playback controls

- **dilla.start()** start playback at current position
- **dilla.pause()** stop playback at current position
- **dilla.stop()** stop playback and set position to start of loop

#### Scheduling

- **dilla.set(id, notes)** schedule playback of array of *notes* on channel with *id*, clearing any previously scheduled notes on same channel. A note can be defined as a [note object](#note) (must contain position) or an array with position at index 0 and params in an object at index 1 (see metronome example above)
- **dilla.get(id)** returns an array of notes scheduled on channel with *id*
- **dilla.channels()** returns an array of all channel ids
- **dilla.clear(id)** clear notes for channel
- **dilla.clear()** clear notes for all channels

#### Position and options

- **dilla.position** returns current [position string](#position), ```"BAR.BEAT.TICK"```
- **dilla.setPosition(position)** set position to ```"BAR.BEAT.TICK"```
- **dilla.setTempo(bpm)** set playback tempo, default ```120```
- **dilla.setBeatsPerBar(beats)** change playback time signature, default ```4```
- **dilla.setLoopLength(bars)** change bars per loop, default ```2```

### Objects

#### Position

- A string in the format ```BAR.BEAT.TICK```
- Where each part is a (1-based index) number
- Tick values under 10 are padded with a leading zero
- Can contain [expressions](http://adamrenklint.com/using-expressions-in-dilla/) which are expanded by ```dilla.set()```

#### Note

- An object that must define ```position```
- Can define ```duration``` in ticks (optional) or any other other params, like frequency or playback rate

### Events

#### tick

Fires when the bar, beat or tick value of ```dilla.position()``` is updated.

```javascript
dilla.on('tick', function (tick) {
  console.log(tick.position) // "1.1.01"
});
```

#### step

Fires when a scheduled note should start or stop. For notes with undefined or falsy duration value (i.e. oneshots), no *stop* step event is triggered.

```javascript
dilla.on('step', function (step) {
  console.log(step.event); // "start" or "stop"
  console.log(step.time); // offset in seconds
  console.log(step.args); // note data originally passed to set()
});
```

## Develop

- ```make test```
- ```make coverage```
- ```make publish```

## Changelog

- **0.1.0**
  - Initial release, ported from **bap** project
- **1.0.0**
  - Improved release for metronome example oscillator
  - Warn in console when events provided to ```dilla.set()``` is out of bounds
- **1.0.1**
  - FIXED: ```dilla.getClockPositionFromPosition()``` returns incorrect value for ticks [#4](https://github.com/adamrenklint/dilla/issues/4)
  - FIXED: ```step.position``` is incorrect [#1](https://github.com/adamrenklint/dilla/issues/1)
- **1.0.2**
  - FIXED: ```dilla.getPositionWithOffset()``` returns incorrect position when offset is falsy [#5](https://github.com/adamrenklint/dilla/issues/5)
  - FIXED: ```"stop"``` fires for events with falsy duration (oneshots) [#3](https://github.com/adamrenklint/dilla/issues/3)
- **1.1.0**
  - NEW: Use [expressions](https://www.npmjs.com/package/dilla-expressions) to insert repeating events [#2](https://github.com/adamrenklint/dilla/issues/2)
  - FIXED: "Out of bounds" warning does not say which channel [#6](https://github.com/adamrenklint/dilla/issues/6)
- **1.1.1**
  - FIXED: Ambiguous use of the word "*event*" [#8](https://github.com/adamrenklint/dilla/issues/8)
- **1.2.0**
  - CHANGED: Note passed to ```dilla.set()``` can be an array with position at index 0, or a note object, and will be merged into a note object
  - NEW: Added lots of unit tests
- **1.3.0**
  - NEW: [Modulus operator](https://github.com/adamrenklint/dilla-expressions#modulus) expression
  - NEW: [Add custom matcher](https://github.com/adamrenklint/dilla-expressions#custom-matchers) with ```dilla.expressions.addMatcher```
- **1.3.1**
  - FIXED: Minifying dilla function names breaks everything
- **1.3.2**
  - FIXED: "step" event stops triggering when tab is put to background [#14](https://github.com/adamrenklint/dilla/issues/14)
  - FIXED: Metronome example is leaking, never stops oscillator [#15](https://github.com/adamrenklint/dilla/issues/15)
- **1.3.3**
  - DOCS: Added link to [expressions tutorial](http://adamrenklint.com/using-expressions-in-dilla/)
  - CHANGED: Improved code complexity and test coverage
- **1.3.4**
  - DOCS: Fix typos
- **1.3.5**
  - FIXED: Updated [dilla-expressions](https://github.com/adamrenklint/dilla-expressions) to solve [modulus by 1 issue](https://github.com/adamrenklint/dilla-expressions/commit/889be0251a9837c062abc8452328759627582903)
- **1.4.0**
  - ADDED: Define ```options.expandNote(note)``` to transform note after expression expansion
  - CHANGED: Use local version of Ditty with longer lookahead
  - CHANGED: Use dilla-expressions v1.2, with greater than and less than operators
- **1.5.0**
  - CHANGED: Use dilla-expressions v2.0, with 25-1500 times better performance
  - FIXED: Use prefixed AudioContext in Safari
- **1.6.0**
  - CHANGED: Reduce intensity of keepalive pings to improve CPU performance
  - CHANGED: Memoize expensive methods
- **1.7.0**
  - CHANGED: Use forked version of Bopper, with less object creation
- **1.8.0**
  - CHANGED: Memoize inner part of set method, for better performance and less allocation
  - CHANGED: Use meeemo 1.1.1, which uses Map instead of plain object when possible
  - CHANGED: Refactor ditty to avoid deoptimization of inner loop bodies
- **1.8.1**
  - FIXED: Drops notes when `beatsPerBar` is above 9 [#22](https://github.com/adamrenklint/dilla/issues/22)
- **1.8.2**
  - FIXED: Changing beats per bar leads to confusion in the order of the steps [#23](https://github.com/adamrenklint/dilla/issues/23)
- **1.8.3**
  - FIXED: Correct memoization key for dilla.getClockPositionFromPosition [#23](https://github.com/adamrenklint/dilla/issues/23)
- **1.8.4** (2017-12-14)
  - FIXED: setBeatsPerBar not working [#26](https://github.com/adamrenklint/dilla/issues/26)

## License

MIT © [Adam Renklint](http://adamrenklint.com)