Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save harunpehlivan/2e583b907542a6a15ba54805ab907dca to your computer and use it in GitHub Desktop.
Save harunpehlivan/2e583b907542a6a15ba54805ab907dca to your computer and use it in GitHub Desktop.
Beat Burger (CPC Sandwiches)

Beat Burger (CPC Sandwiches)

Each component of the sandwich (meat, bun, cheese) corresponds to an instrument in the beat. Swap the components around and see what you can make!

A Pen by HARUN PEHLİVAN on CodePen.

License.

<div id="app">
<div class="container">
<h1>BEAT BURGER</h1>
<h2>{{currentBeat}}</h2>
<!-- Sandwich Components -->
<div class="component__container">
<sandwich-component
v-for="component in components"
:key="component.name"
:synth="component.synth"
:type="component.type"
:muted="component.muted"
v-bind="component.variants[component.currentVariant]">
</sandwich-component>
<sandwich-component :type="components.bun.type"
:synth="components.bun.synth"
:muted="components.bun.muted"
v-bind="components.bun.variants[components.bun.currentVariant]">
</sandwich-component>
</div>
<!-- Controls -->
<div class="controls">
<div class="control" v-for="(component, name) in components">
<h3>{{name}}</h3>
<p>({{component.instrument}})</p>
<div class="control__mute" @click="muteComponent(name)">
<ion-icon :name="component.muted ? 'volume-off' : 'volume-high'"></ion-icon>
</div>
<select :size="component.variants.length" v-model="component.currentVariant">
<option v-for="(variant, i) in component.variants"
:value="i">{{variant.name}}</option>
</select>
</div>
</div>
<!-- Play/Pause -->
<div class="toggle">
<ion-icon :name="playing ? 'pause' : 'play'" @click="toggle"></ion-icon>
</div>
</div>
</div>
Vue.component('sandwich-component', {
props: {
synth: Object,
name: String,
muted: Boolean,
color: String,
beat: Object,
type: String
},
data: () => {
return {
loop: null,
buffer: false,
animating: false
}
},
computed: {
style() {
let color = this.color;
return {
backgroundColor: color
}
},
classList() {
return [
//static classes
'component',
//dynamic classes
this.buffer ? 'queued' : '',
this.muted ? 'queued' : '',
`component__${this.type}`,
this.animating ? 'animated pulse' : ''
]
}
},
watch: {
name() {
this.buffer = true;
}
},
methods: {
setLoop() {
this.loop = new Tone.Loop((time) => {
if(this.muted) {
return;
}
this.buffer = false;
for(let i=0; i<this.beat.data.length; i++) {
let currentBeat = this.beat.data[i];
if(currentBeat && this.synth) {
if(this.synth instanceof Tone.NoiseSynth || this.synth instanceof Tone.MetalSynth) {
this.synth.triggerAttackRelease('8n', time + (Tone.Time('16n') * i));
} else if(this.synth instanceof Tone.Synth) {
let notes = ['f2', 'ab2', 'c3', 'f3'];
let pickedNoteIndex = Math.floor(Math.random()*notes.length);
this.synth.triggerAttackRelease(notes[pickedNoteIndex], '16n', time + (Tone.Time('16n') * i));
}
else {
this.synth.triggerAttackRelease('f1', '8n', time + (Tone.Time('16n') * i));
}
//animation
Tone.Draw.schedule(()=>{
this.animating = true;
setTimeout(()=>{
this.animating = false;
}, 100);
}, time + (Tone.Time('16n') * i));
}
}
}, '1m');
this.loop.start(0);
}
},
mounted() {
this.setLoop();
},
template: `
<div :key="name" :style="style" :class="classList">
</div>
`
});
let app = new Vue({
el: '#app',
data: {
//state data
playing: false,
currentLoops: {},
currentBeat: 0,
//Component data
components: {
bun: {
type: 'bun',
currentVariant: 0,
synth: new Tone.MembraneSynth().toMaster(),
instrument: 'bass drum',
muted: false,
variants: [
{
name: 'white',
color: '#eec07b',
beat: {
name: '4 on the Floor',
data: [1,0,0,0,
1,0,0,0,
1,0,0,0,
1,0,0,0]
}
},
{
name: 'wholewheat',
color: '#6d3200',
beat: {
name: 'Syncopation Nation',
data: [1,0,0,1,
0,0,1,0,
0,1,0,0,
1,0,1,0]
}
},
{
name: 'rye',
color: '#ae7646',
beat: {
name: 'Halftime Vibes',
data: [1,0,0,0,
0,0,0,0,
0,0,1,0,
0,1,0,0]
}
},
{
name: 'pumpernickel',
color: '#624f40',
beat: {
name: 'Sick Metal',
data: [1,1,1,1,
1,0,0,0,
0,1,0,1,
0,1,0,1]
}
}
]
},
condiment: {
type: 'condiment',
currentVariant: 0,
synth: new Tone.Synth({
oscillator: {
type: 'fatsawtooth'
},
envelope: {
release: .5
}
}).toMaster(),
muted: false,
instrument: 'bass',
variants: [
{
name: 'lettuce',
color: '#78bf52',
beat: {
name: 'Straight Down the Middle',
data: [1,0,0,0,
1,0,0,0,
1,0,0,0,
1,0,0,0]
}
},
{
name: 'tomato',
color: 'red',
beat: {
name: 'Syncopation Nation',
data: [1,0,0,1,
0,0,1,0,
0,1,0,0,
1,0,1,0]
}
},
{
name: 'ketchup',
color: '#ce2522',
beat: {
name: 'Offbeat 8ths',
data: [0,0,1,0,
0,0,1,0,
0,0,1,0,
0,0,1,0]
}
},
{
name: 'pineapple',
color: '#fee12d',
beat: {
name: 'Offbeat 16th',
data: [1,1,0,1,
0,1,0,1,
0,1,0,1,
0,1,0,1]
}
}
]
},
cheese: {
type: 'cheese',
currentVariant: 0,
synth: new Tone.NoiseSynth.presets.Hats(),
muted: false,
instrument: 'hi hats',
variants: [
{
name: 'cheddar',
color: '#fd941f',
beat: {
name: 'Straight Down the Middle',
data: [1,0,0,0,
1,0,0,0,
1,0,0,0,
1,0,0,0]
}
},
{
name: 'mozzerella',
color: '#FCF3D9',
beat: {
name: 'Offbeat Oddity',
data: [0,1,1,0,
0,1,1,0,
0,1,1,0,
0,1,1,0]
}
},
{
name: 'brie',
color: '#e3dab2',
beat: {
name: 'Offbeat 8ths',
data: [0,0,1,0,
0,0,1,0,
0,0,1,0,
0,0,1,0]
}
},
{
name: 'blue',
color: 'blue',
beat: {
name: '16ths Forever',
data: [1,1,1,1,
1,1,1,1,
1,1,1,1,
1,1,1,1]
}
}
]
},
meat: {
type: 'meat',
currentVariant: 0,
synth: new Tone.NoiseSynth.presets.Snare(),
muted: false,
instrument: 'snare',
variants: [
{
name: 'beef',
color: '#542d2d',
beat: {
name: 'Twos and Fours',
data: [0,0,0,0,
1,0,0,0,
0,0,0,0,
1,0,0,0]
}
},
{
name: 'turkey',
color: '#cab5b2',
beat: {
name: 'The Hot Beat',
data: [0,0,0,1,
0,0,1,0,
0,0,0,1,
0,0,1,0]
}
},
{
name: 'veggie',
color: 'green',
beat: {
name: 'Halftime Vibes',
data: [0,0,0,0,
0,0,0,0,
1,0,0,0,
0,0,0,0]
}
},
{
name: 'salmon',
color: 'salmon',
beat: {
name: 'Hot Beat Part II',
data: [0,0,1,1,
0,0,1,0,
0,0,1,1,
0,0,1,0]
}
}
]
},
}
},
methods: {
regenLoops() {
for(let component in this.components) {
let current = this.components[component];
this.currentLoops[component] = current.variants[current.currentVariant].beat.data;
}
},
muteComponent(component) {
this.components[component].muted = !this.components[component].muted;
},
toggle() {
this.currentBeat = 0;
this.playing = !this.playing;
Tone.Transport.toggle();
}
},
mounted() {
let beatCountLoop = new Tone.Loop((time)=>{
Tone.Draw.schedule(()=>{
this.currentBeat++;
if(this.currentBeat>4) {
this.currentBeat = 1;
}
}, time);
}, '4n');
beatCountLoop.start(0);
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/13.8.12/Tone.min.js"></script>
<script src="https://codepen.io/sparlos/pen/eaqNOK.js"></script>
$font-heading: 'Lobster', cursive;
html, body {
margin: 0;
padding: 0;
box-sizing: border-box;
color: white;
}
h1 {
font-family: $font-heading;
font-size: 60px;
flex: 0 1 100%;
text-align: center;
margin: 0;
margin-top: 100px;
}
h2 {
font-family: $font-heading;
font-size: 40px;
flex: 0 1 100%;
text-align: center;
margin: 20px 0;
}
h3 {
margin: 0;
padding: 0;
font-family: $font-heading;
text-transform: uppercase;
font-size: 25px;
}
.toggle {
font-size: 65px;
cursor: pointer;
margin-top: 30px;
}
.queued {
opacity: .2;
}
.container {
width: 100vw;
min-height: 100vh;
background: linear-gradient(to bottom, #FA8072, #B22222);
display: flex;
justify-content: center;
align-content: flex-start;
flex-wrap: wrap;
}
.component {
$base-height: 10px;
border-radius: 5px;
flex: 0 1 100%;
animation-duration: .1s;
&__container {
display: flex;
align-content: center;
width: 400px;
flex-wrap: wrap;
}
&__bun {
height: $base-height*5;
}
&__meat {
height: $base-height*3;
}
&__cheese {
height: $base-height*1.5;
}
&__condiment {
height: $base-height;
}
}
.controls {
flex: 0 1 100%;
display: flex;
justify-content: center;
margin: 30px 0;
}
.control {
display: flex;
flex-wrap: wrap;
margin-top: 25px;
& * {
flex: 0 1 100%;
text-align: center;
}
&__mute {
font-size: 30px;
cursor: pointer;
margin: 5px 0 10px 0;
}
}
//vue transitions
.fade-leave-active {
transition: opacity .15s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: .5;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment