Skip to content

Instantly share code, notes, and snippets.

@sharmer156
Last active August 20, 2018 11:48
Show Gist options
  • Select an option

  • Save sharmer156/99628209cc203323f3760f0cc9d8726d to your computer and use it in GitHub Desktop.

Select an option

Save sharmer156/99628209cc203323f3760f0cc9d8726d to your computer and use it in GitHub Desktop.
Deep Roll
<div id="vis-wrap">
<div id="vis-bg"></div>
<div id="vis"></div>
</div>
<div id="ui">
<div class="button-row">
<button class="key" value="C4">C</button>
<button class="key" value="G3">G</button>
<button class="key" value="D4">D</button>
<button class="key" value="A3">A</button>
<button class="key" value="E4">E</button>
<button class="key" value="B3">B</button>
<button class="key" value="F#4">F&sharp;</button>
<button class="key" value="C#4">D&flat;</button>
<button class="key" value="G#3">A&flat;</button>
<button class="key" value="D#4">E&flat;</button>
<button class="key" value="A#3">B&flat;</button>
<button class="key" value="F4">F</button>
</div>
<div class="button-row">
<button class="mode" value="0">Ionian</button>
<button class="mode" value="1">Dorian</button>
<button class="mode" value="2">Phrygian</button>
<button class="mode" value="3">Lydian</button>
<button class="mode" value="4">Mixolydian</button>
<button class="mode" value="5">Aeolian</button>
<button class="mode" value="6">Locrian</button>
</div>
</div>
<div id="loading">
Loading...
</div>
<footer>
<select id="output">
<option value="internal">Internal output</option>
</select>
<p>
Powered by Magenta's <a href="https://github.com/tensorflow/magenta/tree/master/magenta/models/improv_rnn">Improv RNN</a>,
<a href="https://goo.gl/magenta/js">Magenta.js</a>, <a href="https://js.tensorflow.org/">TensorFlow.js</a>, and <a href="https://tonejs.github.io/">Tone.js</a>.
</p>
<p>
A pen by <a href="https://twitter.com/teropa">@teropa</a>
</p>
</footer>
const MIN_NOTE = 48;
const MAX_NOTE = 83;
const NO_EVENT = -2;
const NOTE_OFF = -1;
const STEPS_PER_CHORD = 16;
const MODES = [
[2, 2, 1, 2, 2, 2, 1],
[2, 1, 2, 2, 2, 1, 2],
[1, 2, 2, 2, 1, 2, 2],
[2, 2, 2, 1, 2, 2, 1],
[2, 2, 1, 2, 2, 1, 2],
[2, 1, 2, 2, 1, 2, 2],
[1, 2, 2, 1, 2, 2, 2]
];
const KEYS = [
'C4',
'G3',
'D4',
'A3',
'E4',
'B3',
'F#4',
'C#4',
'G#3',
'D#4',
'A#3',
'F4'
];
let key = Tone.Frequency(_.sample(KEYS)).toMidi();
let mode = _.sample(MODES);
let melodyLine = [];
let generatedChords = new Map();
let pendingActions = [];
let musicOutput = 'internal';
let currentMIDIOutput;
Tone.Transport.bpm.value = 30;
Tone.context.latencyHint = 'playback';
function buildScale(tonic, mode) {
return mode
.concat(mode)
.reduce((res, interval) => res.concat([_.last(res) + interval]), [tonic]);
}
function getPitchChord(degree, tonic, mode) {
let scale = buildScale(tonic, mode);
let root = scale[degree];
let third = _.includes(scale, root + 4) ? root + 4 : root + 3;
let fifth = _.includes(scale, third + 4) ? third + 4 : third + 3;
return [root % 12, third % 12, fifth % 12];
}
function getChordRootBasedOnLast(degree, tonic, mode, last) {
let rootMid = buildScale(tonic, mode)[degree];
let rootLow = rootMid - 12;
let rootHigh = rootMid + 12;
let options = [rootMid, rootLow, rootHigh].filter(
n => n >= MIN_NOTE && n <= MAX_NOTE
);
return Math.random() < 0.75
? _.minBy(options, r => Math.abs(r - last))
: _.sample(options);
}
// Beethoven's chord progression probabilities
// 0 = I, 1 = ii, etc.
var chordProgressions = new Tone.CtrlMarkov({
0: [
{ value: 1, probability: 0.1 },
{ value: 2, probability: 0.01 },
{ value: 3, probability: 0.13 },
{ value: 4, probability: 0.52 },
{ value: 5, probability: 0.02 },
{ value: 6, probability: 0.22 }
],
1: [
{ value: 0, probability: 0.06 },
{ value: 2, probability: 0.02 },
{ value: 3, probability: 0.0 },
{ value: 4, probability: 0.87 },
{ value: 5, probability: 0.0 },
{ value: 6, probability: 0.05 }
],
2: [
{ value: 0, probability: 0.0 },
{ value: 1, probability: 0.0 },
{ value: 3, probability: 0.0 },
{ value: 4, probability: 0.67 },
{ value: 5, probability: 0.33 },
{ value: 6, probability: 0.0 }
],
3: [
{ value: 0, probability: 0.33 },
{ value: 1, probability: 0.03 },
{ value: 2, probability: 0.07 },
{ value: 4, probability: 0.4 },
{ value: 5, probability: 0.03 },
{ value: 6, probability: 0.13 }
],
4: [
{ value: 0, probability: 0.56 },
{ value: 1, probability: 0.22 },
{ value: 2, probability: 0.01 },
{ value: 3, probability: 0.04 },
{ value: 5, probability: 0.07 },
{ value: 6, probability: 0.11 }
],
5: [
{ value: 0, probability: 0.06 },
{ value: 1, probability: 0.44 },
{ value: 2, probability: 0.0 },
{ value: 3, probability: 0.06 },
{ value: 4, probability: 0.11 },
{ value: 6, probability: 0.33 }
],
6: [
{ value: 0, probability: 0.8 },
{ value: 1, probability: 0.0 },
{ value: 2, probability: 0.0 },
{ value: 3, probability: 0.03 },
{ value: 4, probability: 0.0 },
{ value: 5, probability: 0.0 }
]
});
chordProgressions.value = 0;
let temperature = 1.3;
// Using the Improv RNN pretrained model from https://github.com/tensorflow/magenta/tree/master/magenta/models/improv_rnn
let rnn = new mm.MusicRNN(
'https://storage.googleapis.com/download.magenta.tensorflow.org/tfjs_checkpoints/music_rnn/chord_pitches_improv'
);
function detectChord(notes) {
notes = notes.map(n => Tonal.Note.pc(Tonal.Note.fromMidi(n))).sort();
return Tonal.PcSet.modes(notes)
.map((mode, i) => {
const tonic = Tonal.Note.name(notes[i]);
const names = Tonal.Dictionary.chord.names(mode);
return names.length ? tonic + names[0] : null;
})
.filter(x => x);
}
let lastGenerated;
function generateChord(chordDegree, key, mode) {
let chords = detectChord(getPitchChord(chordDegree, key, mode));
let chord = _.first(chords) || 'Cm';
console.log('chord', chord);
let last = key;
if (lastGenerated) {
for (let i = lastGenerated.length - 1; i > 0; i--) {
if (lastGenerated[i] > 0) {
last = lastGenerated[i];
break;
}
}
}
let seedSeq = toNoteSequence([
getChordRootBasedOnLast(chordDegree, key, mode, last)
]);
return rnn
.continueSequence(seedSeq, STEPS_PER_CHORD, temperature, [chord])
.then(seq => {
lastGenerated = seq.notes.map(n => n.pitch);
let result = [];
let fromChord = { chordDegree, key, mode };
for (let { pitch, quantizedStartStep } of seq.notes) {
while (
result.length === 0 ||
_.last(result).indexInChord < quantizedStartStep - 1
) {
result.push({
note: -2,
indexInChord:
result.length === 0 ? 0 : _.last(result).indexInChord + 1,
fromChord
});
}
result.push({
note: pitch,
indexInChord: quantizedStartStep,
fromChord
});
}
return result;
});
/*
for (let i = 0; i < chords.length - 1; i++) {
let input = dl.Array1D.new(
encodePitchChord(chords[i + 1]).concat(encodeMelodyNote(melody[i]))
);
let nextOutput = math.multiRNNCell(
[lstm1, lstm2, lstm3],
input.as2D(1, -1),
state,
output
);
state.forEach(s => s.dispose());
output.forEach(o => o.dispose());
state = nextOutput[0];
output = nextOutput[1];
state.forEach(s => keep(s));
output.forEach(o => keep(o));
let outputH = output[2];
let weightedResult = math.matMul(outputH, lstm.fullyConnectedWeights);
let logits = math.add(weightedResult, lstm.fullyConnectedBiases);
let softmax = math.softmax(math.divide(logits.as1D(), temperature));
let sampledOutput = math.multinomial(softmax, 1).asScalar();
melody.push(decodeMelodyIndex(await sampledOutput.data()));
}
let fromChord = { chordDegree, key, mode };
lastGenerated = melody;
return melody.map((note, indexInChord) => ({
note,
indexInChord,
fromChord
}));
});*/
}
function toNoteSequence(seq) {
let notes = [];
for (let i = 0; i < seq.length; i++) {
if (seq[i] === -1 && notes.length) {
_.last(notes).endTime = i * 0.5;
} else if (seq[i] !== -2 && seq[i] !== -1) {
if (notes.length && !_.last(notes).endTime) {
_.last(notes).endTime = i * 0.5;
}
notes.push({
pitch: seq[i],
startTime: i * 0.5
});
}
}
if (notes.length && !_.last(notes).endTime) {
_.last(notes).endTime = seq.length * 0.5;
}
return mm.sequences.quantizeNoteSequence(
{
ticksPerQuarter: 220,
totalTime: seq.length * 0.5,
quantizationInfo: {
stepsPerQuarter: 1
},
timeSignatures: [
{
time: 0,
numerator: 4,
denominator: 4
}
],
tempos: [
{
time: 0,
qpm: 120
}
],
notes
},
1
);
}
// Impulse response from Hamilton Mausoleum http://www.openairlib.net/auralizationdb/content/hamilton-mausoleum
let reverb = new Tone.Convolver(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/hm2_000_ortf_48k.mp3'
).toMaster();
reverb.wet = 0.4;
let samples = {
C3: new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-C4.mp3'
),
'D#3': new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-Ds2.mp3'
),
'F#3': new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-Fs2.mp3'
),
A3: new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-A2.mp3'
),
C4: new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-C3.mp3'
),
'D#4': new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-Ds3.mp3'
),
'F#4': new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-Fs3.mp3'
),
A4: new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-A3.mp3'
),
C5: new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-C4.mp3'
),
'D#5': new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-Ds4.mp3'
),
'F#5': new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-Fs4.mp3'
),
A5: new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-A4.mp3'
),
C6: new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-C5.mp3'
),
'D#6': new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-Ds5.mp3'
),
'F#6': new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-Fs5.mp3'
),
A6: new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/harp-A5.mp3'
)
};
let bassSamples = {
C0: new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/bass-C0.mp3'
),
'D#0': new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/bass-Ds0.mp3'
),
'F#0': new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/bass-Fs0.mp3'
),
A0: new Tone.Buffer(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/969699/bass-A0.mp3'
)
};
let sampler = new Tone.Sampler(samples).connect(reverb);
let echoedSampler = new Tone.Sampler(samples)
.connect(new Tone.PingPongDelay('16n', 0.8).connect(reverb))
.connect(reverb);
let bassSampler = new Tone.Sampler(bassSamples).connect(
new Tone.Gain(0.6).connect(reverb)
);
let bassLowSampler = new Tone.Sampler(bassSamples).connect(
new Tone.Gain(0.25).connect(reverb)
);
function generateNext(time) {
while (pendingActions.length) {
let action = pendingActions.shift();
let uiDelay = melodyLine.length * Tone.Time('16n').toSeconds();
switch (action.type) {
case 'keyChange':
key = action.key;
chordProgressions.value = 0;
Tone.Draw.schedule(() => {
setCurrentKeyInUI(key);
action.onDone();
}, time + uiDelay);
break;
case 'modeChange':
mode = action.mode;
chordProgressions.value = 0;
Tone.Draw.schedule(() => {
setCurrentModeInUI(mode);
action.onDone();
}, time + uiDelay);
break;
}
}
let chord = chordProgressions.value;
chordProgressions.next();
let mapKey = `${chord}-${key}-${mode}`;
if (generatedChords.has(mapKey) && Math.random() < 0.6) {
melodyLine = melodyLine.concat(generatedChords.get(mapKey));
return Promise.resolve(true);
} else {
return generateChord(chord, key, mode).then(melody => {
melodyLine = melodyLine.concat(melody);
generatedChords.set(mapKey, melody);
});
}
}
let releasePrev,
timeStep = 0;
function playNext(time) {
if (timeStep++ % STEPS_PER_CHORD === STEPS_PER_CHORD - 5) {
generateNext(time);
}
if (melodyLine.length === 0) {
return;
}
let { fromChord, note, indexInChord } = melodyLine.shift();
if (note !== -2 && note !== -1) {
if (releasePrev) {
releasePrev(time);
releasePrev = null;
}
releasePrev = playNote(note, time);
} else if (note === -1 && releasePrev) {
releasePrev(time);
releasePrev = null;
}
if (indexInChord === 0 || indexInChord === STEPS_PER_CHORD - 2) {
let scale = buildScale(fromChord.key, fromChord.mode);
let root = new Tone.Frequency(
scale[fromChord.chordDegree] % 12 + 12,
'midi'
).toNote();
playBass(root, time, indexInChord === 0);
}
}
function playNote(note, time) {
if (musicOutput === 'internal') {
playInternal(note, time);
} else {
playMIDI(note, time);
}
}
function playBass(note, time, upBeat) {
if (musicOutput === 'internal') {
playInternalBass(note, time, upBeat);
} else {
playMIDIBass(note, time, upBeat);
}
}
function playInternal(note, time) {
let freq = Tone.Frequency(note, 'midi');
let echoed = Math.random() < 0.05;
let smplr = echoed ? echoedSampler : sampler;
smplr.triggerAttack(freq, time);
if (echoed) {
for (let i = 0; i < 10; i++) {
let t = time + Tone.Time('16n').toSeconds() * i;
let amt = 1 / (i + 1);
Tone.Draw.schedule(() => visualizePlay(note, amt), t);
}
} else {
Tone.Draw.schedule(() => visualizePlay(note, 1), time);
}
return t => smplr.triggerRelease(freq, t);
}
function playInternalBass(note, time, upBeat) {
if (upBeat) {
bassSampler.triggerAttack(note, time);
} else {
bassLowSampler.triggerAttack(note, time);
}
}
function playMIDI(note, time) {
let delay = time - Tone.now();
let playAt = delay > 0 ? `+${delay * 1000}` : undefined;
let velocity = 0.8;
currentMIDIOutput.playNote(note, 1, { velocity, time: playAt });
Tone.Draw.schedule(() => visualizePlay(note, 1), time);
return releaseTime => {
let releaseDelay = releaseTime - Tone.now();
let releaseAt = releaseDelay > 0 ? `+${releaseDelay * 1000}` : undefined;
currentMIDIOutput.stopNote(note, 1, { time: releaseAt });
};
}
function playMIDIBass(note, time, upBeat) {
let delay = time - Tone.now();
let playAt = delay > 0 ? `+${delay * 1000}` : undefined;
let velocity = upBeat ? 0.8 : 0.6;
let steps = upBeat ? STEPS_PER_CHORD - 2 : 2;
let duration = steps * Tone.Time('16n').toSeconds() * 1000;
if (currentMIDIOutput) {
currentMIDIOutput.playNote(note, 2, { velocity, duration, time: playAt });
}
}
let vis = document.querySelector('#vis');
let keyButtons = Array.from(document.querySelectorAll('.key'));
let modeButtons = Array.from(document.querySelectorAll('.mode'));
let outputMenu = document.querySelector('#output');
WebMidi.enable(function(err) {
if (!err) {
function syncOutputs() {
let prevOptions = Array.from(outputMenu.querySelectorAll('option'));
prevOptions.forEach(option => {
if (
option.value !== 'internal' &&
!_.find(WebMidi.outputs, { id: option.value })
) {
option.remove();
if (musicOutput === option.value) {
musicOutput = 'internal';
}
}
});
WebMidi.outputs.forEach(output => {
if (!_.find(prevOptions, o => o.value === output.id)) {
let option = document.createElement('option');
option.value = output.id;
option.textContent = `MIDI: ${output.name}`;
outputMenu.appendChild(option);
}
});
}
syncOutputs();
setInterval(syncOutputs, 5000);
outputMenu.addEventListener('change', () => {
musicOutput = outputMenu.value;
if (musicOutput !== 'internal') {
currentMIDIOutput = WebMidi.getOutputById(musicOutput);
} else {
currentMIDIOutput = null;
}
});
}
});
let noteEls = _.range(MIN_NOTE, MAX_NOTE).map(note => {
let el = document.createElement('note');
el.classList.add('note');
vis.appendChild(el);
return el;
});
function visualizePlay(note, amount) {
let noteIdx = note - MIN_NOTE;
if (noteIdx >= 0 && noteIdx < noteEls.length) {
let noteEl = noteEls[noteIdx];
let playEl = document.createElement('div');
let routeLength = vis.offsetHeight + 20;
playEl.classList.add('play');
playEl.style.opacity = amount;
noteEl.appendChild(playEl);
let pathAnimation = playEl.animate(
[
{ transform: 'translateY(0)' },
{ transform: `translateY(-${routeLength}px)` }
],
{
duration: 60000,
easing: 'linear'
}
);
pathAnimation.onfinish = () => playEl.remove();
playEl.animate([{ opacity: amount }, { opacity: 0 }], {
duration: 60000,
easing: 'ease-in',
fill: 'forwards'
});
}
}
function setCurrentKeyInUI(key) {
let keyNote = Tone.Frequency(key, 'midi').toNote();
keyButtons.forEach(
b =>
b.value === keyNote
? b.classList.add('current')
: b.classList.remove('current')
);
document.body.className = `key-${KEYS.indexOf(keyNote)}`;
}
function setCurrentModeInUI(mode) {
let modeIndex = '' + MODES.indexOf(mode);
modeButtons.forEach(
b =>
b.value === modeIndex
? b.classList.add('current')
: b.classList.remove('current')
);
}
keyButtons.forEach(keyButton =>
keyButton.addEventListener('click', evt => {
keyButton.classList.add('pending');
pendingActions.push({
type: 'keyChange',
key: Tone.Frequency(evt.target.value).toMidi(),
onDone: () => keyButton.classList.remove('pending')
});
})
);
modeButtons.forEach(modeButton =>
modeButton.addEventListener('click', evt => {
modeButton.classList.add('pending'),
pendingActions.push({
type: 'modeChange',
mode: MODES[+evt.target.value],
onDone: () => modeButton.classList.remove('pending')
});
})
);
let keyNote = Tone.Frequency(key, 'midi').toNote();
let modeIndex = '' + MODES.indexOf(mode);
keyButtons.find(k => k.value === keyNote).classList.add('current');
modeButtons.find(m => m.value === '' + modeIndex).classList.add('current');
document.body.className = `key-${KEYS.indexOf(keyNote)}`;
let bufferLoadPromise = new Promise(res => Tone.Buffer.on('load', res));
Promise.all([rnn.initialize(), bufferLoadPromise]).then(() => {
document.querySelector('#loading').remove();
generateNext(Tone.now());
Tone.Transport.scheduleRepeat(playNext, '16n', '8n');
Tone.Transport.start();
});
StartAudioContext(Tone.context, '#ui');
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tone@0.12.54/build/Tone.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@magenta/music@0.0.8/dist/magentamusic.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/web-animations-js@2.3.1/web-animations.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/startaudiocontext@1.2.1/StartAudioContext.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babel-regenerator-runtime@6.5.0/runtime.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/webmidi@2.0.0/webmidi.min.js"></script>
<script src="https://cdn.rawgit.com/danigb/tonal/9b6b1663/dist/tonal.min.js"></script>
body, html {
margin: 0;
padding: 0;
width: 100%;
}
body {
perspective: 10px;
font-family: 'Righteous', cursive;
}
/* Backgrounds from https://uigradients.com */
body.key-9 {
background: #005AA7; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #FFFDE4, #005AA7); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #FFFDE4, #005AA7); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
body.key-4 {
background: #D3CCE3; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #E9E4F0, #D3CCE3); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #E9E4F0, #D3CCE3); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
body.key-11 {
background: #ADA996; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #EAEAEA, #DBDBDB, #F2F2F2, #ADA996); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #EAEAEA, #DBDBDB, #F2F2F2, #ADA996); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
body.key-6 {
background: #C9D6FF; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #E2E2E2, #C9D6FF); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #E2E2E2, #C9D6FF); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
body.key-1 {
background: #d9a7c7; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #fffcdc, #d9a7c7); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #fffcdc, #d9a7c7); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
body.key-8 {
background: #1c92d2; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #f2fcfe, #1c92d2); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #f2fcfe, #1c92d2); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
body.key-3 {
background: #4AC29A; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #BDFFF3, #4AC29A); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #BDFFF3, #4AC29A); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
body.key-10 {
background: #A1FFCE; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #FAFFD1, #A1FFCE); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #FAFFD1, #A1FFCE); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
body.key-5 {
background: #E0EAFC; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #CFDEF3, #E0EAFC); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #CFDEF3, #E0EAFC); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
body.key-0 {
background: #8e9eab; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #eef2f3, #8e9eab); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #eef2f3, #8e9eab); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
body.key-7 {
background: #abbaab; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #ffffff, #abbaab); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #ffffff, #abbaab); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
body.key-2 {
background: #C9FFBF; /* fallback for old browsers */
background: -webkit-linear-gradient(to bottom, #FFAFBD, #C9FFBF); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #FFAFBD, #C9FFBF); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
#vis-wrap, #ui {
position: absolute;
left: 0;
right: 0;
top: 0;
height: 100vh;
}
#vis-wrap {
transform-origin: 50% 100%;
transform: rotateX(5deg);
backface-visibility: hidden;
}
#vis {
position: absolute;
left: -200px;
bottom: 0;
right: -200px;
top: -1500px;
overflow: hidden;
display: flex;
box-shadow: 0px 0px 100px rgba(50, 50, 50, 0.1);
}
#vis-bg {
position: absolute;
left: -550px;
bottom: 0;
right: -550px;
top: -500px;
background: linear-gradient(to top, rgba(255, 255, 255, 0.25), rgba(255, 255, 255, 0));
}
.note {
flex: 1;
position: relative;
border-left: 1px solid rgba(34, 34, 34, 0.2);
}
.play {
position: absolute;
bottom: -9px;
left: 0;
width: 100%;
height: 10px;
background-color: #E91E63;
}
#ui {
display: flex;
align-items: stretch;
flex-direction: column;
padding: 10vmin 20vmin;
}
@media (max-width: 1024px) {
#ui {
padding: 10vmin 0vmin;
}
}
.button-row {
display: flex;
padding: 5px;
}
.button-row button {
flex: 1;
}
button {
height: 45px;
margin: 0;
background: none;
border: 1px solid rgba(34, 34, 34, 0.2);
border-right-width: 0;
font-size: 16px;
color: rgba(34, 34, 34, 0.9);
outline: none;
font-family: 'Righteous', cursive;
}
@media (max-width: 768px) {
button {
font-size: 12px;
padding: 0;
}
}
button.current {
background-color: rgba(34, 34, 34, 0.2);
}
button.pending {
animation: pendingFlash linear 1s infinite;
}
button:last-child {
border-right-width: 1px;
}
#loading {
position: absolute;
top: 50vh;
left: 0;
width: 100%;
text-align: center;
font-size: 20px;
}
footer {
padding-top: 120vh;
padding-bottom: 20vh;
line-height: 32px;
text-align: center;
font-size: 16px;
}
footer a, footer a:visited {
color: black;
}
@keyframes pendingFlash {
0% { background-color: rgba(255, 255, 255, 0.8); }
100% { background-color: rgba(255, 255, 255, 0.1); }
}
<link href="https://fonts.googleapis.com/css?family=Righteous" rel="stylesheet" />
@sharmer156
Copy link
Author

Online unlimited light music computer generated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment