Comparing version
{ | ||
"name": "codebeat", | ||
"version": "1.0.10", | ||
"version": "1.1.0", | ||
"description": "A browser-based audio programming language for music composition and audio synthesis.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -114,1 +114,35 @@ | ||
This ensures that the entire duration of the slide is dictated by the duration of the source note. | ||
## Advanced features | ||
### Effects | ||
Dynamics like gain, reverb, detuning, instrument, and distortion can be easily altered in a composition using the '@' tag. | ||
~~~ | ||
@reverb 2/1.1/0.7, | ||
@gain 75, | ||
@instrument square, | ||
h g3, w a5, | ||
@instrument sine, | ||
h g3, w a4 | ||
~~~ | ||
In the above example, the instrument is set to square and later changed to sine. Reverb and gain are set for the entire piece. | ||
#### Reverb | ||
@reverb accepts 3 optional parameters delimited with '/': channel #, reverb length as a function of the sample rate, and decay rate. | ||
#### Gain | ||
@gain accepts 1 parameter: the amount of gain from 0 (mute) to 100 (maximum volume). | ||
#### Detune | ||
@detune accepts 1 parameter: the value in cents to detune a frequency. | ||
#### Instrument | ||
@instrument accepts 1 parameter: the instrument name. |
const Frequency = require('./frequency'); | ||
const Duration = require('./duration'); | ||
const Note = require('./note'); | ||
const FX = require('./fx'); | ||
@@ -17,3 +18,10 @@ class Codebeat { | ||
this.loop = props.loop || false; | ||
this.context = new (props.context || global.AudioContext || global.webkitAudioContext)(); | ||
this.convolverNode = this.context.createConvolver(); | ||
this.convolverNode.buffer = FX.createReverbBuffer(this, '1/1/20'); | ||
this.gainNode = this.context.createGain(); | ||
this.convolverNode.connect(this.gainNode); | ||
this.gainNode.connect(this.context.destination); | ||
this.parseNotes(); | ||
@@ -59,2 +67,7 @@ this.getBPM(); | ||
this.context = new (nodeContext || global.AudioContext || global.webkitAudioContext)(); | ||
this.convolverNode = this.context.createConvolver(); | ||
this.convolverNode.buffer = FX.createReverbBuffer(this, '1/1/20'); | ||
this.gainNode = this.context.createGain(); | ||
this.convolverNode.connect(this.gainNode); | ||
this.gainNode.connect(this.context.destination); | ||
} | ||
@@ -66,8 +79,20 @@ | ||
parseNotes(notes = this.notes) { | ||
// prepare variable names for notes delimiter=; | ||
notes = Codebeat._expandMotifs(notes); | ||
// prepare note multiples delimiter=* | ||
notes = Codebeat._expandMultiples(notes); | ||
// prepare note slides delimiter=- | ||
notes = Codebeat._expandSlides(notes); | ||
// prepare chords delimiter=+ | ||
notes = Codebeat._expandPoly(notes); | ||
// translate data to Note object delimiter=, | ||
notes = Codebeat._expandNotes(notes); | ||
// update or reset notesParsed | ||
this.notesParsed = notes; | ||
return this.notesParsed | ||
@@ -85,14 +110,31 @@ } | ||
const oscillators = []; | ||
const playNext = () => { | ||
n < this.notesParsed.length - 1 | ||
? this.play(n += 1) | ||
: this.ended(); | ||
} | ||
if (note.fx[0]) { | ||
if (note.fx[0].indexOf('@') === 0) { | ||
FX[note.fx[0].slice(1)](this, note.value); | ||
return playNext(); | ||
} | ||
} | ||
// create and configure oscillators | ||
note.outputFrequency.forEach(f => { | ||
let o = this.context.createOscillator(); | ||
o.connect(this.context.destination); | ||
o.connect(this.convolverNode) | ||
// set instrument | ||
o.type = this.instrument; | ||
// set detune | ||
o.detune.setValueAtTime(this.detune || 0, this.context.currentTime); | ||
// set note frequency | ||
o.frequency.value = f; | ||
oscillators.push(o) | ||
}) | ||
oscillators.push(o); | ||
}); | ||
oscillators.forEach(o => { | ||
o.start(0); | ||
if (nextNote && note.fx.slide) this.slideNote(o, n) | ||
if (nextNote && note.fx.includes('slide')) this.slideNote(o, n) | ||
o.stop(this.context.currentTime + stopTime); | ||
@@ -102,5 +144,3 @@ }) | ||
oscillators.shift().onended = () => { | ||
n < this.notesParsed.length - 1 | ||
? this.play(n += 1) | ||
: this.ended(); | ||
playNext() | ||
}; | ||
@@ -158,3 +198,3 @@ } | ||
//TODO: Brief chords as well | ||
const singleNotes = this.notesParsed.filter(n => !n.fx.poly) | ||
const singleNotes = this.notesParsed.filter(n => !n.fx.includes('poly')) | ||
const origin = this.notesParsed.map(f => Codebeat._originFrequency(f.outputFrequency[0])); | ||
@@ -254,7 +294,7 @@ const notes = origin.filter((note, i) => i === origin.indexOf(note)); | ||
static _expandMotifs(notes, delimiter = ';') { | ||
static _expandMotifs(notes) { | ||
const motifs = {}; | ||
if (notes.includes(delimiter)) { | ||
notes = notes.split(delimiter); | ||
if (notes.includes(';')) { | ||
notes = notes.split(';'); | ||
const motifOccursAt = []; | ||
@@ -283,3 +323,3 @@ const operator = '='; | ||
static _expandMultiples(notes, delimiter = '*') { | ||
static _expandMultiples(notes) { | ||
const multiply = { | ||
@@ -291,3 +331,3 @@ occursAt: [], | ||
notes.forEach((n, i) => { | ||
if (n.includes(delimiter)) { | ||
if (n.includes('*')) { | ||
multiply.occursAt.push(i); | ||
@@ -309,3 +349,3 @@ } | ||
static _expandSlides(notes, delimiter = '-') { | ||
static _expandSlides(notes) { | ||
const slide = { | ||
@@ -317,3 +357,3 @@ occursAt: [], | ||
notes.forEach((n, i) => { | ||
if (n.includes(delimiter)) { | ||
if (n.includes('-')) { | ||
slide.occursAt.push(i); | ||
@@ -333,7 +373,7 @@ } | ||
static _expandPoly(notes, delimiter = '+') { | ||
static _expandPoly(notes) { | ||
notes = notes.map((n, i) => { | ||
if (n.includes(delimiter)) { | ||
if (n.includes('+')) { | ||
const duration = n.shift() | ||
const pitch = n.filter(p => p != delimiter) | ||
const pitch = n.filter(p => p != '+') | ||
.reduce((a,b) => a.concat(' ').concat(b)) | ||
@@ -363,3 +403,3 @@ | ||
const nextNote = this.notesParsed[n + 1]; | ||
if (!nextNote.fx.slide) return | ||
if (!nextNote.fx.includes('slide')) return | ||
@@ -366,0 +406,0 @@ const note = this.notesParsed[n]; |
@@ -8,6 +8,10 @@ const Codebeat = require('./Codebeat'); | ||
@reverb 4/1.5/1.5, | ||
@instrument sine, | ||
t g3 * 3, | ||
first, second, | ||
h c5, q g4, | ||
second, third, | ||
@reverb 1/1/20, | ||
second, | ||
third, | ||
h d4`; | ||
@@ -14,0 +18,0 @@ |
@@ -5,25 +5,70 @@ const Frequency = require('./frequency'); | ||
module.exports = (note) => { | ||
const inputDuration = note[0]; | ||
const inputFrequency = note[1]; | ||
const fx = note[2] || []; | ||
const slide = fx.includes('slide'); | ||
const poly = fx.includes('poly'); | ||
const firstParam = note[0]; | ||
const secondParam = note[1]; | ||
const fx = note[2] || []; | ||
if (firstParam[0] === '@') fx.push(firstParam) | ||
const noteSchema = { | ||
inputDuration, | ||
inputFrequency, | ||
outputDuration: Duration[inputDuration], | ||
outputFrequency: (() => { | ||
return poly | ||
? inputFrequency.split(' ').map(f => Frequency[f]) | ||
: [Frequency[inputFrequency]] | ||
})(), | ||
fx: { | ||
all: fx, | ||
slide, | ||
poly | ||
} | ||
} | ||
let noteSchema = {}; | ||
switch(fx[0]) { | ||
case 'slide': | ||
noteSchema = { | ||
fx, | ||
firstParam, | ||
secondParam, | ||
outputDuration: Duration[firstParam], | ||
outputFrequency: [Frequency[secondParam]], | ||
}; | ||
break; | ||
case 'poly': | ||
noteSchema = { | ||
fx, | ||
firstParam, | ||
secondParam, | ||
outputDuration: Duration[firstParam], | ||
outputFrequency: secondParam.split(' ').map(f => Frequency[f]), | ||
}; | ||
break; | ||
case '@gain': | ||
noteSchema = { | ||
fx, | ||
value: secondParam, | ||
}; | ||
break; | ||
case '@instrument': | ||
noteSchema = { | ||
fx, | ||
value: secondParam, | ||
}; | ||
break; | ||
case '@detune': | ||
noteSchema = { | ||
fx, | ||
value: secondParam, | ||
}; | ||
break; | ||
case '@reverb': | ||
noteSchema = { | ||
fx, | ||
value: secondParam, | ||
}; | ||
break; | ||
default: | ||
noteSchema = { | ||
fx, | ||
firstParam, | ||
secondParam, | ||
outputDuration: Duration[firstParam], | ||
outputFrequency: [Frequency[secondParam]], | ||
}; | ||
break; | ||
} | ||
return noteSchema | ||
} |
29098
43.64%10
25%749
16.12%147
28.95%