Search code examples
javascriptarraysobjectweb-audio-api

Why is the actual value of this variable not being used as the index number of the object I'm trying to return


This has been driving me bonkers since Monday June 6 2022, I really hope someone can tell me what I am doing wrong.

I have a music machine written (as a hobby/learning experience) in JavaScript using the Web Audio API and I'm trying to do something very simple:- update a variable value via a user interface, then use the new value of the variable to reference an array index.

In English! The user should be able to change the octave via a slider on the UI.

What I have is:

  • a slider to set a value
  • a variable to hold the slider value
  • an array of object literals with key/value pairs that hold note frequencies (each object holds an octave)
  • a programmed keyboard key 'Y'

What should happen:

  • press keyboard key 'Y' to play a note
  • a note should play in a specific octave
  • moving the slider should change the value of the variable
  • the value of the variable should be used to reference the index of an array
  • pressing key 'Y' should play a note in a different octave (or return undefined)

What is actually happening:

  • pressing keyboard key 'Y' plays a note
  • moving the slider changes the value of the variable
  • pressing key 'Y' again plays the same note in the same octave
  • the current value of the variable set by the user is not being used to reference the specific array index

The code for my music machine is pretty chunky, so I've managed to reproduce the problem I'm having in the code below, I was rather hoping that by doing this I would be able to see what is going wrong, but, alas no! :o(

For the sake of brevity, the array holding the scales has just four indexes, 0 & 1 should return undefined, index 2 should return 440 and index 2 should return 880

// get the range slider and label from the document
const slider = document.getElementById("slider");
const labelVal = document.getElementById("label");

// var to hold the value of the slider
// We'll use this as the index value later
let oct = 2;

// add an event listener to the slider
window.addEventListener("change", handelChange, false);

// the change event handler
function handelChange(event) {
  // set the value of var oct to the slider value
  oct = parseFloat(slider.value);
  console.log("oct Value:", oct);

  // set the label content to equal value of `oct`
  labelVal.innerHTML = oct;

  return oct;
}

// Create the audio context
const actx = new AudioContext();

// function to call `function createOsc(freq)`
function playNote(freq) {
  ceateOsc(freq);
}

// function to create an audio graph that
// starts and stops the oscillator
function ceateOsc(freq) {
  // create the audio nodes
  const osc = actx.createOscillator();
  const vol = actx.createGain();

  // set the nodes' values
  osc.frequency.value = freq;
  vol.gain.value = 0.1;

  // connect the nodes to the audio context destintion
  osc.connect(vol).connect(actx.destination);

  // start & stop the oscillator
  osc.start();
  osc.stop(actx.currentTime + 1);
}

// array of objects holding musical note frequencies
let scale = [{}, // return undefined
  {}, // return undefined
  {
    A: 440, // return 440
  },
  {
    A: 880, // return 880
  },
];

// map keyboard to notes using var `oct`
// for the index number of array `scale`
const notes = {
  // scale[0].A should undefined
  // scale[1].A should undefined
  // scale[2].A should return 440
  // scale[3].A should return 880
  y: scale[oct].A,
};

// ************* Listen For Keyboard Input START ************ \\
window.addEventListener("keydown", keydownHandler, false);

function keydownHandler(event) {
  const key = event.key;
  const freq = notes[key];
  console.log("keydown event: oct val =", oct, "frequency =", freq);
  // if our notes object has this keyboard key defined
  // play the note:
  if (freq) {
    playNote(freq);
  } else {
    console.log("Only key 'Y' can play a note");
  }
}
// ************* Listen For Keyboard Input END ************ \\
<h3>Test To Set Index Via User Interface</h3>

<p>Press key 'Y' to play note 'A'</p>
<p>Set slider to value 2 or 3 to change the octave</p>
<p>
  <!-- Range input: value to be used as the index number -->
  <input type="range" id="slider" min="0" max="3" value="2" /><label id="label" for="setOctave">2</label
      >
    </p>

What I've tried:

  • If I manually set the index like this: scale[3].A key 'Y' plays note A @ 880hz
  • If I manually set the index like this: scale[2].A key 'Y' plays note A @ 440hz
  • If I manually set the index like this: scale[0].A key 'Y' returns undefined

Also, if I manually set the initial value of the var oct key 'Y' returns the correct note/octave.

I've included console.log(oct) at various points in the code including the point of keydown and at each point we can see that the value of oct is equal to the value of the slider. In fact, the value of oct is actually being used to update the UI 'slider text value' shown to the user, however, the current value of oct is not being used at this point in the code scale[oct].A

Am I missing something completely obvious here or is there something going on that I am just not aware of?

I much would much appreciate any feedback on this at all.

Thanks.


Solution

  • When you initialize notes, the value of oct is only accessed once at the point that object is created. Subsequent changes to oct will not have any effect on the value of the "y" property.

    One option to fix that would be to have the value of "y" be a getter function:

    const notes = {
      // scale[0].A should undefined
      // scale[1].A should undefined
      // scale[2].A should return 440
      // scale[3].A should return 880
      get y() { return scale[oct].A; }
    };