Skip to content

Commit

Permalink
Add Lullaby
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbainter committed Dec 6, 2020
1 parent 94a701c commit e60815e
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/piece-lullaby/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
access=public
3 changes: 3 additions & 0 deletions packages/piece-lullaby/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# piece-lullaby

Listen at https://generative.fm/music/alex-bainter-lullaby
65 changes: 65 additions & 0 deletions packages/piece-lullaby/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions packages/piece-lullaby/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@generative-music/piece-lullaby",
"main": "dist/cjs.js",
"module": "dist/esm.js",
"version": "0.0.0",
"generativeFmManifest": "piece.gfm.manifest.json",
"repository": {
"type": "git",
"url": "git+https://github.com/generative-music/pieces-alex-bainter.git"
},
"files": [
"dist",
"image.png",
"piece.gfm.manifest.json"
],
"author": "Alex Bainter <[email protected]> (https://alexbainter.com)",
"license": "MIT",
"bugs": {
"url": "https://github.com/generative-music/pieces-alex-bainter/issues"
},
"homepage": "https://github.com/generative-music/pieces-alex-bainter#readme",
"peerDependencies": {
"tone": "14.*.*"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"tone": "^14.7.58"
},
"dependencies": {
"@generative-music/utilities": "^4.2.0"
}
}
16 changes: 16 additions & 0 deletions packages/piece-lullaby/piece.gfm.manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"title": "Lullaby",
"makePiece": "./dist/esm.js",
"image": "./image.png",
"id": "lullaby",
"artistId": "alex-bainter",
"isRecordable": true,
"tags": ["dream", "piano", "melodic", "calm", "electronic"],
"releaseDate": "2020-12-06T00:00:00.000Z",
"visualizationType": "squareCut",
"sampleNames": [
"vsco2-piano-mf",
["lullaby__birds", "birds"],
["lullaby__explosion", "explosion"]
]
}
3 changes: 3 additions & 0 deletions packages/piece-lullaby/src/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}
9 changes: 9 additions & 0 deletions packages/piece-lullaby/src/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"env": {
"node": false,
"browser": true
},
"parserOptions": {
"sourceType": "module"
}
}
182 changes: 182 additions & 0 deletions packages/piece-lullaby/src/piece.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import {
getContext,
Transport,
Reverb,
Gain,
Filter,
ToneBufferSource,
AutoFilter,
} from 'tone';
import {
wrapActivate,
createPitchShiftedSampler,
createPrerenderableBuffers,
} from '@generative-music/utilities';
import { sampleNames } from '../piece.gfm.manifest.json';

const playProgression = piano => {
piano.triggerAttack('C4', `+${1 + Math.random() * 0.1 - 0.05}`);
piano.triggerAttack('G4', `+${1 + Math.random() * 0.1 - 0.05}`);
const t2 = 6 + Math.random() * 6;
piano.triggerAttack('C4', `+${t2 + Math.random() * 0.1 - 0.05}`);
piano.triggerAttack('A4', `+${t2 + Math.random() * 0.1 - 0.05}`);
const t3 = t2 + 1 + Math.random() * 4;

if (Math.random() < 0.9) {
piano.triggerAttack('C4', `+${t3 + Math.random() * 0.1 - 0.05}`);
piano.triggerAttack('F4', `+${t3 + Math.random() * 0.1 - 0.05}`);
}

const now = new Date();
const minutes = now.getMinutes();

if (Math.random() < minutes / 60) {
piano.triggerAttack('C6', `+${1 + Math.random()}`);
}

if (Math.random() < 0.2) {
piano.triggerAttack('E6', `+${1 + Math.random() * t2}`);
}

if (Math.random() < (minutes % 3) / 3) {
piano.triggerAttack('A6', `+${t2 + Math.random()}`);
}

if (Math.random() < (60 - minutes) / 60) {
piano.triggerAttack('C7', `+${t3 + Math.random()}`);
}

Transport.scheduleOnce(() => {
playProgression(piano);
}, `+${t3 + Math.random() * 10 + 5}`);
};

const activate = async ({ destination, sampleLibrary, onProgress }) => {
const samples = await sampleLibrary.request(getContext(), sampleNames);
const pianos = await Promise.all([
createPitchShiftedSampler({
samplesByNote: samples['vsco2-piano-mf'],
pitchShift: -12,
}),
createPitchShiftedSampler({
samplesByNote: samples['vsco2-piano-mf'],
pitchShift: -24,
}),
]);

const birdBuffers = await createPrerenderableBuffers({
samples,
sampleLibrary,
sourceInstrumentName: 'birds',
renderedInstrumentName: 'lullaby__birds',
getDestination: () => new Reverb(15).toDestination().generate(),
onProgress: val => onProgress(val / 2),
});

const birdBuffer = birdBuffers.get(0);

const activeSources = [];

const playBirdSnippet = () => {
const startTime = Math.random() * (birdBuffer.duration - 6);
const duration = Math.max(
6,
Math.random() * (birdBuffer.duration - startTime)
);
const playbackRate = Math.random() * 0.1 + 0.1;
const source = new ToneBufferSource(birdBuffer).set({
fadeIn: 3,
fadeOut: 3,
onended: () => {
const index = activeSources.indexOf(source);
if (index > -1) {
activeSources.splice(index, 1);
}
},
playbackRate,
});
activeSources.push(source);
source.connect(destination);
source.start('+1', startTime, duration / playbackRate, 0.33);
Transport.scheduleOnce(() => {
playBirdSnippet();
}, `+${duration + Math.random() * 5}`);
};

if (samples['explosion']) {
samples['explosion'][0].reverse = true;
}

const explosionBuffers = await createPrerenderableBuffers({
samples,
sampleLibrary,
sourceInstrumentName: 'explosion',
renderedInstrumentName: 'lullaby__explosion',
getDestination: () => new Reverb(15).toDestination().generate(),
onProgress: val => onProgress(val / 2 + 0.5),
});

const explosionGain = new Gain(0.05).connect(destination);
const lowpass = new Filter(200).connect(explosionGain);
const explosionBuffer = explosionBuffers.get(0);

const playReverseExplosion = () => {
const explosionSource = new ToneBufferSource(explosionBuffer)
.set({
playbackRate: Math.random() * 0.1 + 0.05,
fadeOut: 3,
onended: () => {
const index = activeSources.indexOf(explosionSource);
if (index > -1) {
activeSources.splice(index, 1);
}
},
})
.connect(lowpass);
activeSources.push(explosionSource);
explosionSource.start();

Transport.scheduleOnce(() => {
playReverseExplosion();
}, `+${Math.random() * 100 + 60}`);
};

const schedule = () => {
const pianoAutoFilters = pianos.map(piano => {
const autoFilter = new AutoFilter(0.01 * Math.random() + 0.005).connect(
destination
);
autoFilter.start();
piano.connect(autoFilter);
playProgression(piano);
return autoFilter;
});
playBirdSnippet();
playReverseExplosion();

return () => {
pianos.forEach(piano => {
piano.releaseAll(0);
});
activeSources.forEach(source => {
source.stop(0);
});
pianoAutoFilters.forEach(autoFilter => {
autoFilter.dispose();
});
};
};

const deactivate = () => {
pianos
.concat(activeSources)
.concat([birdBuffers, explosionBuffers])
.forEach(node => {
node.dispose();
});
};

return [deactivate, schedule];
};

export default wrapActivate(activate);

0 comments on commit e60815e

Please sign in to comment.