Skip to content

Instantly share code, notes, and snippets.

@lightmnd
Created January 11, 2018 13:48
Show Gist options
  • Save lightmnd/a7cf9d5ff4553215113d5e66c08bdc4d to your computer and use it in GitHub Desktop.
Save lightmnd/a7cf9d5ff4553215113d5e66c08bdc4d to your computer and use it in GitHub Desktop.
My first synthesizer

My first synthesizer

A really simple React.js wrapper around a Tone.js Oscillator.

Monophonic (only plays one note at a time)
Using my synthesizer React.js components for the UI

A Pen by Gregor Adams on CodePen.

License.

//console.clear();
class Oscillator extends React.Component {
constructor(props) {
super(props);
this.env = this.props.envelope;
this.waves = ['sine','square','triangle','sawtooth'];
this.tone = new Tone.Oscillator({
frequency: this.props.frequency,
type: this.waves[this.props.waveform],
volume: this.props.volume
}).connect(this.env).start();
}
componentWillReceiveProps(newProps) {
this.tone.detune.value = newProps.detune;
this.tone.volume.value = newProps.volume;
this.tone.type = this.waves[newProps.waveform];
if (newProps.playing ) {
this.tone.frequency.value = newProps.playing;
}
}
render() {
let ret;
return (<div className="oscillator">
<br/>
{ this.props.children }
</div>);
}
}
class Synth extends React.Component {
constructor(props) {
super(props);
this.envelope = new Tone.AmplitudeEnvelope({
attack : 0.41,
decay : 0.21,
sustain : 0.9,
release : .9
}).toMaster()
this.state = {
frequencies: {
0: 440
},
detunes: {
0: 0
},
volumes: {
0: -20
},
waveforms: {
0: 1
}
};
this.setDetune = this.setDetune.bind(this);
this.setVol = this.setVol.bind(this);
this.setWav = this.setWav.bind(this);
this.startNote = this.startNote.bind(this);
this.stopNote = this.stopNote.bind(this);
}
setDetune(osc, v) {
let detunes = this.state.detunes;
detunes[osc] = v;
this.setState({
detunes: detunes
});
}
setVol(osc, v) {
let volumes = this.state.volumes;
volumes[osc] = v;
this.setState({
volumes: volumes
});
}
setWav(osc, v) {
let waveforms = this.state.waveforms;
waveforms[osc] = v;
console.log(v);
this.setState({
waveforms: waveforms
});
}
startNote(note) {
this.setState({playing: note});
this.envelope.triggerAttack();
}
stopNote(note) {
this.setState({playing: false});
this.envelope.triggerRelease();
}
render() {
return (
<div className='synth'>
<Oscillator frequency={440}
detune={ this.state.detunes[0] }
waveform={ this.state.waveforms[0] }
volume={ this.state.volumes[0] }
type={ 'square' }
envelope={this.envelope}
playing={this.state.playing}>
<Poti className='_colored orange'
range={[-50,50]}
size={60}
label={'detune'}
markers={21}
fullAngle={300}
steps={[{label:-10},{label:-5},{label:'0'},{label:5},{label:10}]}
onUpdate={ this.setDetune.bind(this, 0) }
value={ this.state.detunes[0]} />
<Poti className='_colored yellow'
range={[0,3]}
size={60}
label={'waveform'}
snap={true}
fullAngle={300}
steps={[{label:'sin'},{label:'sqr'},{label:'tri'},{label:'saw'}]}
onUpdate={ this.setWav.bind(this, 0) }
value={ this.state.waveforms[0]} />
<Poti className='_colored red'
range={[-50,20]}
size={60}
label={'volume'}
markers={21}
fullAngle={300}
steps={[{label:'min'},{},{},{},{},{},{},{},{},{},{label:'max'}]}
onUpdate={ this.setVol.bind(this, 0) }
value={ this.state.volumes[0]} />
</Oscillator>
<Keyboard onDown={this.startNote} onUp={this.stopNote}/>
</div>);
}
}
ReactDOM.render(<Synth/>, document.body);
<script src="//cdn.rawgit.com/JedWatson/classnames/master/index.js"></script>
<script src="//cdn.rawgit.com/Tonejs/Tone.js/9bf00eb49be5b97b7446c833657bcd492c04b208/build/Tone.min.js"></script>
<script src="//codepen.io/pixelass/pen/YwvYPL.js"></script>
<script src="//codepen.io/pixelass/pen/VedyyE.js"></script>
<script src="//cdn.rawgit.com/rofrischmann/inline-style-prefixer/43994c348ee869fcb656c4b79873e5605fba2e3e/dist/inline-style-prefixer.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-dom.js"></script>
$stroke: mix(#fefedc, #000, 70%);
$key-height: 105px;
$key-width: 40px;
$roundness: 2px;
$d3-inensity: 20px;
$key-chip: false; // false or px value
.Keyboard {
position: relative;
overflow: hidden;
background: black;
user-select: none;
height: $key-height * 2 + $d3-inensity / 2 + 2;
&:before,
&:after {
content: '';
pointer-events: none;
position: absolute;
left: 0;
width: 100%;
z-index: 2;
}
&:before {
box-shadow: 0 4px 2px #d52, 0 4px 0 #a55;
height: 4px;
top: -4px;
}
&:after {
top: 0;
height: 100%;
border-radius: 5px;
box-shadow: 0 0 0 2px black inset, 0 0 0 4px black;
}
.key {
cursor: pointer;
transform: translate3d(0, 0, 0);
border-style: none !important;
.top {
border-style: none !important;
}
}
.black {
z-index: 0;
background-color: #111;
background-image: linear-gradient(to bottom, rgba(white, 0.1), rgba(black, 0.2)), radial-gradient(ellipse at center, rgba(black, 0.8), rgba(black, 0.1)), linear-gradient(to top, rgba(black, 0.2), rgba(black, 0.1)), linear-gradient(to right, rgba(black, 0.3) 0px, rgba(white, 0.3) 1px, rgba(black, 0) 6px), linear-gradient(to left, rgba(black, 0.3) 0px, rgba(white, 0.3) 1px, rgba(black, 0) 6px);
box-shadow: 0 0 0 1px rgba(black, 0.1) inset, 1px 0 0 0 rgba(#333, 1) inset, 0 1px 2px rgba($stroke, 0.4), 0 0 0 2px #000, 0 ($d3-inensity * 2) 0 2px #333;
margin-top: $d3-inensity * -0.5!important;
&._active {
transform: translate3d(0, $d3-inensity * -1.125, 0);
}
&:before {
content: '';
position: absolute;
top: 100%;
right: 100%;
border-radius: $roundness;
margin-right: -1px;
margin-top: -2px;
border: 10px solid transparent;
border-bottom-color: #000;
border-right-color: #000;
transform-origin: 100% 0%;
transform: rotate(15deg);
}
&:after {
content: '';
position: absolute;
top: 100%;
left: 100%;
border-radius: $roundness;
margin-left: -1px;
margin-top: -2px;
border: 10px solid transparent;
border-bottom-color: #000;
border-left-color: #000;
transform-origin: 0% 0%;
transform: rotate(-15deg);
}
}
$chip: ();
@if $key-chip {
$chip: 0 $key-chip 0 0 mix(#F1F1DA, #000, 80%), 0 ($key-chip + 2) 5px -1px mix(#F1F1DA, #000, 40%);
}
.white {
z-index: 1;
background-color: #EDED68;
background-image: linear-gradient(to bottom, rgba(white, 1), rgba(white, 0.8)), radial-gradient(ellipse at center, rgba(black, 0.5), rgba(black, 0.1)), linear-gradient(to top, rgba(black, 0.2), rgba(black, 0.1)), linear-gradient(to right, rgba(black, 0.1), rgba(white, 0.2), rgba(black, 0.1));
background-position: center center, 15px 50px, center center;
background-size: 100% 100%, 300% 100%, 100% 100%;
box-shadow: 0 0 0 1px rgba(black, 0.1) inset, 1px 0 0 0 rgba(#FFF, 1) inset, 0 0 0 1px rgba($stroke, 1), $chip, 0 $d3-inensity 0 -1px mix(#F1F1DA, #000, 70%), 0 $d3-inensity 0 0 mix(#F1F1DA, #000, 80%), 0 $d3-inensity 0 1px rgba($stroke, 1);
&._active {
background-size: 100% 100%,100% 100%,100% 100%, 300% 100%, 100% 100%;
background-position: center center, center center, center center, 15px 50px, center center;
background-image: linear-gradient(to bottom, rgba(white, 1), rgba(white, 0.7)), linear-gradient(to right, rgba(black, 0.5), rgba(black, 0) 8px), linear-gradient(to left, rgba(black, 0.5), rgba(black, 0) 6px), radial-gradient(ellipse at center, rgba(black, 0.5), rgba(black, 0.1)), linear-gradient(to top, rgba(black, 0.2), rgba(black, 0.1)), linear-gradient(to right, rgba(black, 0.1), rgba(white, 0.2), rgba(black, 0.1));
}
.top {
box-shadow: 1px 0 0 0 rgba(black, 0.1) inset, 1px 0 0 0 rgba(#FFF, 1) inset, 0 -1px 0 0 rgba(#FFF, 1), 0 -1px 0 1px rgba($stroke, 1);
margin-bottom: -1px;
&:before, &:after {
position: absolute;
bottom: 0;
height: $roundness * 2;
width: $roundness * 2;
margin-bottom: 2px;
}
&:before {
left: 100%;
border-radius: 0 0 0 $roundness;
margin-left: 1px;
box-shadow: -1px 0 0 0 $stroke, 0 1px 0 0 $stroke, -3px 3px 0 #fff;
}
&:after {
right: 100%;
margin-right: 1px;
border-radius: 0 0 $roundness 0;
box-shadow: 1px 0 0 0 $stroke, 0 1px 0 0 $stroke, 3px 3px 0 #fff;
}
}
&.left {
.top {
&:before {
content: '';
}
}
}
&.right {
.top {
&:after {
content: '';
}
}
}
&.center {
border-radius: $roundness;
.top {
&:before, &:after {
content: '';
}
}
}
}
}
$colors: (red: red, orange: orange, yellow: yellow, white: white, grey: #ccc, dark-grey: #1E1E1E, black: #111, background: #28282B);
@function color($name) {
@return map-get($colors, $name);
}
@mixin Poti($color: false) {
$className: ".Poti";
@if $color {
$className: $className + "._colored";
$className: $className + ".#{$color}";
}
$className: join($className, ",");
#{$className} {
@content;
}
}
@include Poti {
color: color(white);
.PotiIndicator {
color: transparent;
&:before {
content: '';
position: absolute;
bottom: 0;
left: 0;
height: 2px;
width: 2px;
background: color(white);
margin-bottom: 5px;
border-radius: 100%;
}
.label {
color: color(white);
}
&._numbered {
&:before {
content: normal;
}
}
}
.PotiMarker {
z-index: 2;
color: color(red);
font-size: 6px;
will-change: transform;
}
.PotiKnob {
z-index: 2;
box-shadow: 0 0 2px 1px rgba(color(white), 0.1) inset, 0 0 0 2px color(black) inset;
margin-top: -5px;
&:before {
content: '';
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
height: 100%;
width: 110%;
padding: 4px 0;
border-radius: 100%;
box-shadow: 0 2px 0 1px color(black), 0 2px 0 6px color(dark-grey), 0 2px 0 7px color(black);
background-color: color(dark-grey);
background-image: linear-gradient(to top, rgba(black, 0.5) 0, transparent 10px), linear-gradient(to right, transparent 20%, rgba(white, 0.2) 40%, transparent 70%);
}
&:after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
border-radius: 100%;
background-color: color(dark-grey);
background-image: radial-gradient(closest-corner, rgba(color(black), 0.2) 10%, rgba(color(black), 0.1) 60%, rgba(color(white), 0.1) 80%);
box-shadow: inherit;
}
}
.PotiMarker {
z-index: 2;
color: color(red);
font-size: 6px;
will-change: transform;
}
&._colored {
.PotiIndicator {
&:before {
height: 4px;
margin-bottom: 2px;
border-radius: 0;
}
}
.PotiKnob {
z-index: 2;
box-shadow: 0 0 0 2px color(dark-grey) inset;
margin-top: -5px;
background: currentColor;
border-width: 0;
position: relative;
&:before {
height: 120%;
width: 110%;
padding: 1px;
box-shadow: 0 1px 0 1px color(black);
top: 0;
}
&:after {
background-color: inherit;
}
}
.PotiMarker {
z-index: 2;
color: color(black);
font-size: 6px;
}
}
}
@include Poti(orange) {
.PotiKnob {
color: color(orange);
}
}
@include Poti(red) {
.PotiKnob {
color: color(red);
}
}
@include Poti(yellow) {
.PotiKnob {
color: color(yellow);
}
}
.oscillator {
border-radius: 4px;
display: block;
background: #333;
padding: 10px;
margin: 40px 0;
box-shadow: 0 0 0 1px rgba(black,0.3) inset;
}
.synth {
position: relative;
margin: 50px;
border-radius: 4px;
display: inline-block;
padding: 0 20px;
box-shadow: 0 0 0 2px rgba(black,0.3) inset;
background-color: hsl(20,60%,20%);
background-image:
radial-gradient(10px 90px, 6px 120px, rgba(black,0) 10%,rgba(black,0.05) 10%,rgba(black,0.05) 40%,rgba(white,0) 40%,rgba(white,0.05) 60%,rgba(black,0) 60%),
radial-gradient(70px 130px, 8px 180px, rgba(black,0) 10%,rgba(black,0.05) 10%,rgba(black,0.05) 40%,rgba(white,0) 40%,rgba(white,0.05) 60%,rgba(black,0) 60%),
radial-gradient(20px 120px, 6px 120px, rgba(black,0) 10%,rgba(black,0.05) 10%,rgba(black,0.05) 40%,rgba(white,0) 40%,rgba(white,0.05) 60%,rgba(black,0) 60%),
linear-gradient(to right, rgba(black,0) 10%,rgba(black,0.05) 15%,rgba(black,0) 40%,rgba(white,0.05) 60%,rgba(black,0) 70%),
linear-gradient(to right, rgba(black,0) 10%,rgba(black,0.05) 10%,rgba(black,0) 50%,rgba(white,0.05) 60%,rgba(black,0) 65%),
linear-gradient(to right, rgba(black,0) 10%,rgba(black,0.05) 10%,rgba(black,0) 55%,rgba(white,0.05) 60%,rgba(black,0) 60%),
linear-gradient(to top right, rgba(black,0.2),rgba(black,0) 60%);
background-size: 250px 200px, 120px 300px,100px 500px,8px 100%, 12px 100%, 20px 100%, 100% 100%;
box-shadow: 0 -26px 0 0 rgba(black,0.5) inset,
0 -26px 10px 0 rgba(white,0.2) inset;
&:before, &:after {
content: '';
position: absolute;
top: -3px;
bottom: -3px;
width: 40px;
background-color: hsl(20,60%,25%);
background-image: inherit;
background-size: inherit;
border-radius: 3px;
}
&:before {
left: -30px;
box-shadow: 0 -32px 0 0 rgba(black,0.5) inset,
0 -32px 10px 0 rgba(white,0.2) inset,
10px 0 10px -10px rgba(black,0.6);
}
&:after {
right: -30px;
box-shadow: 0 -32px 0 0 rgba(black,0.5) inset,
0 -32px 10px 0 rgba(white,0.2) inset,
-10px 0 10px -10px rgba(black,0.6);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment