Skip to content

clairewild/Chimes

Repository files navigation

Chimes

Live demo

Chimes is a generative music synthesizer based on the Otomata app. Sounds are generated by blocks that move back and forth across a 9 by 9 grid. When a block hits a wall, it plays a note based on the position hit, and reverses direction. When two blocks collide (move to the same grid square at the same time), they both rotate 90 degrees and keep moving. When two blocks are in adjacent squares, they do not collide but pass through each other.

Otomata

Instructions

  • Click on a grid square to add a block
  • Click on a block to change it's direction
  • Click the play button to start
  • Add blocks while the program is running

Technologies

This project is built using React.js, HTML5 canvas, and React Konva, a JavaScript library that integrates the HTML5 canvas with React. It also makes use of HTML audio elements.

screenshot screenshot

Features and Implementation

Stage and Grid

The Grid component renders a konva Stage, handling all click and hover events. Nested under the stage is a Layer element, containing all the other konva elements - cells, blocks, hover, collisions, and ripples. The global state keeps track of the positions of all cells, blocks, hover, collisions, and ripples.

Block Logic

The Sidebar component (where the play button is located) manages the logic for moving, rotating, and reversing blocks. Clicking play installs an intervalHandler which calls the oneStep function. This function checks if blocks are about to collide, have collided, or are hitting a wall, and dispatches the appropriate action:

blockKeys.forEach(key => {
  let block = blocks[key];

  if (this.isCollided(blocks, blockKeys, block)) {
    this.props.rotateBlock(block.id);
  }
  else if (this.isHittingWall(block)) {
    this.playSound(block.pos);
    this.props.addRipple(block.pos);
    this.props.reverseBlock(block.id);
  }
  this.props.moveBlock(block.id);
});

The rotateBlock, reverseBlock, and moveBlock actions all hit the BlockReducer, which updates the global state. Blocks are stored in the state with an id, position, direction, and the reducer uses the constant objects rotated, reversed, and offsets to convert current direction or position to the new direction or position.

const rotated = {
  "up": "right",
  "right": "down",
  "down": "left",
  "left": "up"
};

The playSound function creates a new HTML Audio element each time it is called. SOUNDS is an object containing 9 different file paths for audio files I created using GarageBand, and playSound decides which file to use for the let note = new Audio(file) by checking the position it is passed. playSound finally calls note.play().

Ripple Animations

The ripple effect when a block plays a note is created by a Ripple component that renders three konva Circle elements. The position of a Ripple is added to the state by the Sidebar component (which checks when a block is hitting the wall). Ripple components then delete themselves from the state after a setTimeout.

I used Konva.Easings to animate the ripples. In componentDidMount(), each circle starts expanding to a larger size over a set duration. The largest circle has a set final size, and the smaller circles grow to a random size less than the largest circle. The code looks something like this:

componentDidMount() {
  const maxSize = 700;

  this.refs.ripple.to({
    width: maxSize,
    height: maxSize,
    easing: Konva.Easings.EaseInOut,
    duration: 1.2
  });
  window.setTimeout(() => this.props.deleteRipple(this.props.pos), 1200);
}

render() {
  const size = 70;

  return(
    <Circle
      ref="ripple"
      x={ this.props.pos[0] * size + size / 2 }
      y={ this.props.pos[1] * size + size / 2 }
      radius={ 5 }
      stroke="white"
      strokeWidth={ 1 }>
    </Circle>
  );
};

Future Features

  • Ability to record compositions
  • Controls for adjusting tempo
  • Alternative color schemes
  • Alternative scales/instruments

About

Generative musical sequencer based on Otomata app

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages