noisecraft icon indicating copy to clipboard operation
noisecraft copied to clipboard

Quantizer

Open AlasdairMott opened this issue 2 years ago • 4 comments

As mentioned in the discussions I've been playing around with adding a quantizer node. Snap a frequency to the nearest note in a scale. image

I've made quantizer node branch, but thought that I should check here as per the contribution guidelines before raising the PR. https://github.com/AlasdairMott/noisecraft/tree/feature/quantizer

AlasdairMott avatar Apr 30 '22 08:04 AlasdairMott

Hi Alasdair. Can you explain what the Quantize node does and what kind of inputs it accepts, how the implementation works a little bit, and what the node allows you to do? A video demo might help.

maximecb avatar Apr 30 '22 16:04 maximecb

The quantizer node snaps an incoming value or audio signal to the nearest value in a chosen scale. https://user-images.githubusercontent.com/25563790/166134230-330e6cd8-177b-42fc-902c-159b59798e90.mp4

Background

It works by first converting the incoming frequency to a "1 volt per octave control voltage" (1v/o) value. This scale is used in eurorack modular systems to convert control voltage to pitch. It makes it easy to extract the note and octave from a pitch value. To convert from frequency to 1v/o and back again:

frequencyToCV = (freq) => Math.log2(freq / 55.0);

CVToFrequency = (cv) => Math.pow(2, cv) * 55;

Scales are stored as arrays with numbers from 0 to 1, which represent a single octave in that scale.

How it works

The Quantize AudioNode works as follows:

  1. Convert the incoming frequency value to a cv value:
const cv = this.frequencyToCV(input);
  1. The octave and note (the decimal part of the cv value) are extracted:
const octave = Math.floor(cv);
const decimal = cv - octave;
  1. Map the note (the decimal value) to the nearest note in the selected scale.
const closestNote = this.scale.reduce((prev, curr) => {
   return (Math.abs(curr - decimal) < Math.abs(prev - decimal)) ? curr : prev;
});
  1. Convert the cv value back to a frequency value. Offset according to the current root note.
return this.CVToFrequency(octave + closestNote + this.scaleRoot / 12.0);

What is it useful for?

  • Mapping notes to a given scale
  • Converting signals to something more musical. Works well with S&H noise.
  • Transpose a note a few semitones

AlasdairMott avatar May 01 '22 06:05 AlasdairMott

I can see the use case, that's pretty neat. I wish this were done without the CV conversion though. Seems to me it ought to be possible to go from frequencies to the closest MIDI note number. The logic for that could be implemented in the Note class: https://github.com/maximecb/noisecraft/blob/main/public/music.js#L93

maximecb avatar May 04 '22 03:05 maximecb

I can use the scale generation from music.js

this.midiScale = music.genScale(state.scaleRoot, state.scaleName, 10); 

Then map to the nearest midi note from there

const note = (( 12 * Math.log(input / 220.0) / Math.log(2.0) ) + 57.01);

//find the nearest note number from the midi scale
const nearest = this.midiScale.reduce((prev, curr) => {
    return Math.abs(curr.noteNo - note) < Math.abs(prev.noteNo - note) ? curr : prev;
});

return nearest.getFreq();

This is a fair bit simpler than what I had before and there's no need to define the scales twice. However the midi notes are limited to 10 octaves it seems?

There are a few advantages to v/o cv values I can think of -

  • since they're logarithmic they can be added/subtracted and the results make sense musically
  • unlimited number of octaves
  • non-western scales

Since this isn't already a part of noise craft I understand it's a fairly different way of working.

AlasdairMott avatar May 15 '22 23:05 AlasdairMott