Skip to content

Instantly share code, notes, and snippets.

@harunpehlivan
Created May 30, 2021 17:03
Show Gist options
  • Select an option

  • Save harunpehlivan/3474a5cc8d1f4ab29b4e576369ec94d1 to your computer and use it in GitHub Desktop.

Select an option

Save harunpehlivan/3474a5cc8d1f4ab29b4e576369ec94d1 to your computer and use it in GitHub Desktop.
Chance the Sampler

Chance the Sampler

Yet another ridiculous Chance the Rapper thing. Works best in Chrome or Safari. Firefox is a slightly subpar experience. It hardly works on mobile but it is somehow better than Firefox.

chano

A Pen by HARUN PEHLİVAN on CodePen.

License.

<main>
<section id="entertainment">
<div id="faces"></div>
<div id="chano"><div><div><div></div></div></div></div>
</section>
<section id="sequencer">
<div id="sequence"></div>
<nav class="controls">
<ul>
<li>
<input id="bpm" type="range" min="80" max="140" />
</li>
<li>
<button id="go">Start</button>
</li>
</ul>
</nav>
</section>
</main>
console.clear();
/********************
Instantiate Chano
********************/
var chance = new Chano({
bpm: 90,
beats: 16,
selectors: {
bpm: "#bpm",
go: "#go"
}
});
chance.load.then(function(chano) {
//chano.play();
var sequence = new Sequence({
sequence: "#sequence",
faces: "#faces",
chano: chano
});
var animation = new Animation({
chano: "#chano",
frames: 13
});
chano.update = function(time, col) {
sequence.$seq.className = "current-" + col;
sequence.$faces.className = "current-" + chano.actives.join("");
setTimeout(function() {
sequence.$faces.className = "";
}, 100 / chano.bpm * 64);
animation.update();
};
});
/********************
Chance the Sampler: Player
********************/
function Chano(params) {
var C = {};
init();
return C;
/***********
initializer
***********/
function init() {
drawPlaceholder(); // better screengrab
setParams();
/*********
instance.load.then(function(chano) {
// do whatever with chano
});
*********/
C.is_mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
C.click_event = C.is_mobile ? "touchstart" : "click";
if("-webkit-box-reflect" in document.body.style) {
document.querySelector("html").className += "box-reflect ";
}
C.load = new Promise(function(resolve, reject) {
// if mobile, this needs to be instantiated by a click.
setMobile(go);
function go() {
var set_synth = setSynth();
set_synth.then(function() {
removePlaceholder();
setDomValue();
setMethods();
setMatrix();
setTransport();
setEvents();
resolve(C);
});
}
});
}
/***********
placeholder
***********/
function drawPlaceholder() {
C.placeholder = document.createElement("div");
C.placeholder.style.backgroundImage = "url('https://res.cloudinary.com/tercuman-b-l-m-merkez/image/upload/v1606077366/logo_transparent_zkii7j.png')";
C.placeholder.style.backgroundPosition = "center";
C.placeholder.style.backgroundSize = "cover";
C.placeholder.style.position = "fixed";
C.placeholder.style.top = 0;
C.placeholder.style.left = 0;
C.placeholder.style.right = 0;
C.placeholder.style.bottom = 0;
document.body.appendChild(C.placeholder);
}
function removePlaceholder() {
C.placeholder.remove();
}
/***********
setters
***********/
function setMobile(callback) {
// if mobile, we need to instantiate with a click
if(C.is_mobile) {
document.querySelector("html").className += "mobile ";
var $element = document.createElement("div");
$element.className = "mobile-start";
document.body.appendChild($element);
var $mobile_start = document.createElement("button");
$mobile_start.innerHTML = "Launch";
$mobile_start.addEventListener("touchend", function(e) {
e.preventDefault();
Tone.startMobile();
$element.remove();
callback();
});
$element.appendChild($mobile_start);
} else {
callback();
}
}
function setParams() {
C._set = {
bpm: params.bpm || 90,
beats: params.beats || 16
}
C._el = {
$bpm: document.querySelector(params.selectors.bpm),
$go: document.querySelector(params.selectors.go)
}
}
function setDomValue() {
C._el.$bpm.value = C._set.bpm;
}
function setSynth() {
return new Promise(function(resolve, reject) {
var root = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/111863/chance-the-sampler-";
C.keys = new Tone.PolySynth(4, Tone.Sampler, {
sd1: root + "01.mp3",
hh1: root + "02.mp3",
sd2: root + "03.mp3",
kd1: root + "04.mp3",
}, {
"volume" : -5,
}).toMaster();
C.note_names = ["sd1", "hh1", "sd2", "kd1"];
var count = 4;
var loaded = 1;
for(var i = 0; i < count; i++) {
var buffer = new Tone.Buffer(root + "0" + (i+1) + ".mp3", function(){
loaded++;
if(loaded === count) {
resolve("Audios Loaded");
}
});
}
});
}
function setMatrix() {
C.matrix = [
[1,0,0,1],
[0,0,0,0],
[1,0,0,0],
[0,0,0,0],
[1,1,0,0],
[0,1,0,1],
[1,1,0,0],
[0,0,0,0],
[0,0,0,1],
[1,0,1,0],
[0,0,0,1],
[1,0,0,0],
[0,0,0,0],
[1,0,1,0],
[0,0,0,1],
[1,0,1,0]
];
}
function setTransport() {
var arr = [];
for(var i = 0; i < C._set.beats; i++) arr.push(i);
C.loop = new Tone.Sequence(function(time, col) {
C.actives = [];
var column = C.matrix[col];
for (var i = 0; i < 4; i++) {
if (column[i] === 1) {
C.actives.push(i);
C.keys.triggerAttackRelease(C.note_names[i], "8n", time);
}
}
C.update(time, col);
}, arr, "16n");
C.updateBpm(C._set.bpm);
Tone.Transport.start();
}
function setMethods() {
C.updateBpm = updateBpm;
C.updateMatrix = updateMatrix;
C.play = play;
C.pause = pause;
// provide update function
C.update = function(time, col) {};
}
function setEvents() {
C._el.$bpm.addEventListener("change", function(e) {
C.updateBpm(e.target.value);
});
C._el.$go.addEventListener(C.click_event, togglePlay);
}
/***********
events
***********/
function updateMatrix(col, row, value) {
C.matrix[col][row] = value;
}
function updateBpm(bpm) {
Tone.Transport.bpm.value = bpm;
var $html = document.querySelector("html");
var bpm_class = $html.className.match("bpm-");
$html.className = bpm_class ? $html.className.replace(/bpm-\d+/g,"bpm-"+bpm) : $html.className + "bpm-" + bpm;
C.bpm = bpm;
}
function play() {
C.loop.start();
document.body.className = "playing";
C.playing = true;
}
function pause() {
C.loop.stop();
document.body.className = "";
C.playing = false;
}
function togglePlay(e) {
if(C.playing) {
C.pause();
if(e && e.target) e.target.innerHTML = "Start";
} else {
C.play();
if(e && e.target) e.target.innerHTML = "Stop";
}
}
}
/********************
Chance the Sampler: Sequencer
********************/
function Sequence(params) {
var S = {};
init();
return S;
/***********
initializer
***********/
function init() {
setData();
createElements();
}
/***********
private
***********/
function setData() {
S.chano = params.chano;
S.$seq = document.querySelector(params.sequence);
S.$faces = document.querySelector(params.faces);
}
function createElements() {
S.nodes = [];
S.rows = [];
for(var r = 0; r < S.chano.matrix[0].length; r++) {
S.nodes.push([]);
var $row = document.createElement("div");
$row.className = "row-" + r;
S.$seq.appendChild($row);
S.rows.push($row);
}
for(var r = 0; r < S.chano.matrix[0].length; r++) {
var $face = document.createElement("div");
$face.className = "face-" + r;
$face.setAttribute("data-key", r);
S.$faces.appendChild($face);
$face.addEventListener(S.chano.click_event, function(e) {
var el = e.target;
var key = parseInt(el.getAttribute("data-key"));
S.$faces.className = "face-" + key;
if(!S.chano.playing) {
setTimeout(function() {
S.$faces.className = "";
}, 100);
}
var note = S.chano.note_names[key];
S.chano.keys.triggerAttackRelease(note, "8n");
});
}
for(var c = 0; c < S.chano.matrix.length; c++) {
var col = S.chano.matrix[c];
for(var r = 0; r < col.length; r++) {
var el = document.createElement("span");
el.setAttribute("data-row", r);
el.setAttribute("data-col", c);
el.setAttribute("data-val", col[r]);
el.addEventListener(S.chano.click_event, handleNodeClick);
S.nodes[r].push(el);
S.rows[r].appendChild(el);
}
}
}
function handleNodeClick(e) {
var el = e.target,
col = el.getAttribute("data-col"),
row = el.getAttribute("data-row"),
val = el.getAttribute("data-val");
val = val === "1" ? 0 : 1;
el.setAttribute("data-val", val);
S.chano.updateMatrix(col, row, val);
}
}
/********************
Chance the Sampler: Sprite Animation
********************/
function Animation(params) {
var A = {};
init();
return A;
/***********
initialize
***********/
function init() {
setData();
setUpdate();
}
/***********
private
***********/
function setData() {
A.frame = 1;
A.direction = "up";
A.max_frame = params.frames;
A.$chano = document.querySelector(params.chano);
}
function setUpdate() {
A.update = function() {
A.$chano.className = "frame-" + A.frame;
if(A.frame === 1) {
A.direction = "up";
A.frame++;
} else if(A.frame === A.max_frame) {
A.direction = "down";
A.frame--;
} else if(A.direction === "up") {
A.frame++;
} else {
A.frame--;
}
}
}
}
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/111863/Tone.min.js"></script>
$cols: 16;
$rows: 4;
$frame-h: 463px;
$frame-w: 300px;
$frame-count: 13;
$node-bg-di: 118px;
$row-h: $node-bg-di;
$asset-root: "https://s3-us-west-2.amazonaws.com/s.cdpn.io/111863/";
$mobile-w: 668px;
$c-bg: #101010;
$c-primary: #D79C57;
$c-primary-d: darken($c-primary, 10%);
$c-secondary: #996888;
$c-material: #444;
$c-material-xl: lighten($c-material, 15%);
$c-material-l: lighten($c-material, 5%);
$c-material-d: darken($c-material, 5%);
$c-material-xd: darken($c-material, 10%);
$c-material-xxd: darken($c-material, 15%);
$c-border: rgba(0,0,0,0.35);
$c-border: $c-material-xxd;
html, body {
height: 100%;
min-height: 422px;
}
main {
width: 100%;
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
}
section {
margin: 0 auto;
}
button {
appearance: none;
color: white;
background: $c-primary;
border: none;
border-radius: 4px;
text-shadow: -1px -1px 0px $c-primary-d;
border: 1px solid $c-primary-d;
&:hover {
background: $c-primary-d;
}
}
.mobile-start {
position: fixed;
top: 0; right: 0; bottom: 0; left: 0;
background: rgba(0,0,0,0.9);
button {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
font-size: 2rem;
padding: 2rem 2rem;
}
}
#entertainment {
padding: 0 0 1rem 0;
width: 150px;
height: 150px;
@media (min-width: $mobile-w) {
width: 300px;
height: 300px;
}
position: relative;
#chano, #faces {
position: absolute;
}
}
#sequencer {
width: 90%;
max-width: 1200px;
padding: 0.25rem;
background: $c-material-xd;
border-radius: 4px;
box-shadow: 0px 2px 0px 2px $c-material-xxd;
ul {
border-radius: 4px;
list-style: none;
margin: 0.25rem 0 0;
padding: 0.5rem;
text-align: center;
background: $c-material-d;
border: 1px solid $c-border;
li {
display: inline-block;
margin: 0;
+ li { margin-left: 1rem; }
label {
display: block;
}
}
}
button {
box-sizing: border-box;
padding: 0.5rem 0;
}
button, input {
width: 100px;
}
}
#faces {
display: flex;
flex-wrap: wrap;
width: 100%;
position: relative;
box-sizing: border-box;
background: $c-material-xd;
box-shadow: 0px 2px 0px 2px $c-material-xxd;
border-radius: 4px;
padding: 0.5rem;
div:nth-child(1) { border-radius: 4px 0 0 0; }
div:nth-child(2) { border-radius: 0 4px 0 0; }
div:nth-child(3) { border-radius: 0 0 0 4px; }
div:nth-child(4) { border-radius: 0 0 4px 0; }
[class^="face-"] {
width: 50%;
position: relative;
cursor: pointer;
overflow: hidden;
&::before {
display: block;
content: "";
width: 100%;
padding-top: 100%;
}
&::after {
content: "";
cursor: pointer;
position: absolute;
top: 0; left: 0; bottom: 0;
width: 100%;
background-size: cover;
filter: grayscale(1);
}
&:hover::after {
filter: grayscale(0);
}
}
}
// the sequencer rows
[class^="row-"] {
width: 100%;
display: flex;
box-sizing: border-box;
border-bottom: 1px solid $c-border;
border-right: 1px solid $c-border;
border-left: 1px solid $c-border;
overflow: hidden;
transform: translateZ(0);
&:first-child {
border-top: 1px solid $c-border;
border-radius: 4px 4px 0 0;
}
&:last-child {
border-radius: 0 0 4px 4px;
}
span {
position: relative;
display: block;
box-sizing: border-box;
width: 1 / $cols * 100%;
cursor: pointer;
overflow: hidden;
background: $c-material-d;
transform: translateZ(0);
&::before {
content: "";
display: block;
width: 100%;
padding-top: 100%;
}
+ span {
border-left: 1px solid $c-border;
}
&:nth-child(n+5):not(:nth-child(n+9)),
&:nth-child(n+13) {
background: $c-material-l;
}
&::after {
content: "";
position: absolute;
top: 0; left: 0; bottom: 0;
width: 100%;
background-size: cover;
border: 1px solid $c-border;
transform-origin: 50% 50%;
@media (min-width: $mobile-w) {
transition: transform 70ms ease-in-out,
filter 70ms ease-in-out,
border-radius 100ms ease-in-out;
}
}
&[data-val="1"] {
&::after {
opacity: 0.8;
transform: scale(1.05) translateZ(0);
filter: grayscale(0);
border: none;
border-radius: 0px;
}
}
&[data-val="0"] {
&::after {
opacity: 0.6;
transform: scale(0.8) translateZ(0);
filter: grayscale(1);
border-radius: 50%;
}
}
&:hover {
background: $c-material-xl!important;
&::after {
opacity: 1;
}
}
}
}
@for $row from 1 through $rows {
[class^="face-#{$row - 1}"]::after,
[class^="row-#{$row - 1}"] span::after {
background-image: url(#{$asset-root}chance-the-sampler-face-#{$row}.jpg);
}
}
@for $col from 1 through $cols {
#faces[class*="#{$col - 1}"] .face-#{$col - 1}::after {
background-position: -100% center;
filter: grayscale(0);
}
.current-#{$col - 1} [data-col="#{$col - 1}"] {
@media (min-width: $mobile-w) {
&, &:hover { background: rgba(255,255,255,0.4)!important; }
}
&::after {
opacity: 1!important;
}
&[data-val="1"] {
&::after {
background-position: -100% center;
}
}
}
}
#chano div {
display: none;
@media (min-width: $mobile-w) {
.box-reflect & {
display: block;
position: absolute;
transform: translate(-100%, -50%) scale(0.5) rotate(15deg);
-webkit-box-reflect: above;
}
}
}
#chano {
transform: translate(-50%, -50%);
@media (min-width: $mobile-w) {
html:not(.box-reflect) & {
display: none;
}
.box-reflect & {
transform: translate(-300px, -50%);
-webkit-box-reflect: right 300px;
}
}
}
:not(.mobile) #chano div,
#chano {
top: 50%;
left: 50%;
opacity: 0;
pointer-events: none;
transition: opacity 1000ms;
.playing & { opacity: 1; }
&::after {
content: "";
display: block;
background-image: url("#{$asset-root}chance-the-sprite.png");
height: $frame-h / 4;
width: $frame-w / 4;
background-size: ($frame-w/4) * $frame-count ($frame-h/4);
@media (min-width: $mobile-w) {
height: $frame-h / 2;
width: $frame-w / 2;
background-size: ($frame-w/2) * $frame-count ($frame-h/2);
}
}
}
@for $i from 1 through $frame-count {
#chano.frame-#{$i} div::after,
#chano.frame-#{$i}::after {
$x: ($i - 1) / ($frame-count) * ($frame-w / 4 * -$frame-count);
background-position: $x center;
@media (min-width: $mobile-w) {
$x: ($i - 1) / ($frame-count) * ($frame-w / 2 * -$frame-count);
background-position: $x center;
}
}
}
body {
color: white;
background: $c-bg;
// overflow: hidden;
&::before {
content: "";
position: fixed;
top: 0; right: 0; bottom: 0; left: 0;
background-size: cover;
background-position: center;
background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/111863/chance-the-sampler-bg.jpg);
opacity: 0.1;
filter: grayscale(1);
}
.box-reflect &.playing::before {
animation: bg 1s ease-in-out alternate infinite;
}
}
@for $i from 80 through 140 {
html.bpm-#{$i}.box-reflect body::before {
animation-duration: (1 / ($i / 60)) * 1s;
}
}
@keyframes bg {
from { transform: scale(1); }
to { transform: scale(1.05); }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment