Skip to content

hacktunes/hacktunes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

116 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hacktun.es

Hacktunes is a monthly collaborative music programming project. Each month, we pick a song to cover using the Web Audio API. Anyone can contribute tracks (written in JavaScript/ES6) which generate a part of the song in real-time. We mash all of these tracks together into a combined performance, creating a Hacktune!

This month, we're covering Still Alive by Jonathan Coulton.


How it works

To make songs, we need two things: instruments (:guitar::saxophone::microphone:), and a sequence of notes to play (:musical_score:). Hacktunes "tracks" are instruments using the Web Audio API, triggered by MIDI events. We provide a basic MIDI file as a starting point, but can add your own to make new melodies and rhythms. You can implement synthesizers, drum machines, sample audio clips, etc -- the sky's the limit!

The hacktunes codebase doubles as a development environment. We use Hot Module Replacement to update tracks immediately: you can make changes to the code during playback and hear them live! ⚡⚡⚡

Getting started

  1. Clone the repository.
  2. npm install
  3. npm start

The best way to jump in is to modify an existing track. Hear something you like? Copy it and start playing around. This month's tracks can be found in /songs/still-alive.

Submitting a track to "Still Alive" (December 2015)

Everyone is welcome to contribute!

  1. Build your own track in a subdirectory of /songs/still-alive
  2. Send us a pull request (squash your commits, please!)

When we merge your commit, your track will automatically go live on hacktun.es.


API

🚧 Hacktunes is in early development. Need something that isn't listed here? Please file an issue for discussion.

⚡ Prefer to read code? Here's an annotated example.

Your track is a module consisting of two functions: load, and create.

load({ loadAudio, loadMIDI })

Declare resources your track needs to fetch here. Use the require function to refer to file paths relative to the current module.

Return an object mapping resource names to resources (see below).

loadMIDI(url)

Fetch a MIDI file at the specified URL.

loadAudio(url)

Fetch and decode an audio file at the specified URL.

Example:

export function load({ loadAudio, loadMIDI }) {
  return {
    midi: loadMIDI(require('../still-alive.mid')),
    sample: loadMIDI(require('./meow.wav')),
  }
}

create({ transport, res, ctx, out })

Instantiate an instrument and trigger it by playing MIDI files. This will be called before the song begins, as well as any time your track hot reloads.

transport.playMIDI(midiResource, eventHandler)

Queue a MIDI file to be played. Pass it a MIDI resource from res. While playing, your eventHandler will be called with MIDI events right before they happen. Use this callback to queue Web Audio operations at the precise time specified by each MIDI event.

MIDI events

Your eventHandler(ev) callback will be called with MIDI events of the following structure:

{
  "time": 2545.03,                           // Precise AudioContext event time (seconds)
  "channel": 0,                              // MIDI channel number
  "note": 66,                                // MIDI note number
  "frequency": 369.99,                       // Note frequency (Hz)
  "type": 8,                                 // MIDI event type constant
  "subtype": 8,                              // MIDI event subtype constant
  "param1": 66,                              // MIDI event param value
  "param2": 0,                               // MIDI event param value
  "midi": {
    "data": [0, 255, 1, 0, 0, 128, 66, 0 ],  // Raw MIDI protocol bytes, suitable for Web MIDI API
    "time": 2545670.91,                      // Web MIDI time (ms relative to navigation start)
  },
}

Event type constants can be imported from the midievents module:

import {
  EVENT_MIDI_NOTE_ON,
  EVENT_MIDI_NOTE_OFF,
} from 'midievents'

res

An object containing fetched resources specified by your load function.

ctx

The AudioContext for the playing song.

out

The destination AudioNode to output audio to. Connect your Web Audio API nodes here.

Example

import { EVENT_MIDI_NOTE_ON } from 'midievents'

export function create({ transport, res, ctx, out }) {
  transport.playMIDI(res.midi, ev => {
    if (ev.subtype === EVENT_MIDI_NOTE_ON) {
      const osc = ctx.createOscillator()
      osc.type = 'sine'
      osc.frequency.value = ev.frequency
      noteGain.gain.setValueAtTime(0, ev.time)
      noteGain.gain.linearRampToValueAtTime(1, ev.time + 10)
      noteGain.gain.linearRampToValueAtTime(0, ev.time + 100)
      osc.start(ev.time)
      osc.stop(ev.time + 100)
      osc.connect(out)
    }
  })
}

About

A monthly music programming collaborative art project.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors