Flash Part of screen With Sound created using web audio api for Morse code(audio context)

313 views Asked by At

I have created code to generate morse code sound using web audio api. Morse code sound is working perfect. I wanted to flash part of the screen with that sound.There are only two sounds dot(.) and dash(-). I wanted to show message by flashing part of the screen. I tried to set the background color of div as black and then hide/show that div to give flash effect. but it's not working as expected.please help me....Thanks in advance... I tried this :

$(document).ready(function() {
 var context = new (window.AudioContext || window.webkitAudioContext());
 var O= new MorseNode(context,20);
 O.connect(context.destination);
 O.playString(1,'.-- -..');

});

function MorseNode(ac, rate) {
    // ac is an audio context.
    this._oscillator = ac.createOscillator();
    this._gain = ac.createGain();

    this._gain.gain.value = 0;
    this._oscillator.frequency.value = 550;

    this._oscillator.connect(this._gain);

    if(rate == undefined)
        rate = 20;
    this._dot = 1.2 / rate; // formula from Wikipedia.

    this._oscillator.start(0);
}

MorseNode.prototype.connect = function(target) {
    return this._gain.connect(target);
}

MorseNode.prototype.playChar = function(t, c) {
    for(var i = 0; i < c.length; i++) {
        switch(c[i]) {
        case '.':
            $('#flashBlock').hide(); //I tried this to flash the screen.
            this._gain.gain.setValueAtTime(1.0, t);
            t += this._dot;
            this._gain.gain.setValueAtTime(0.0, t);
            $('#flashBlock').show();
            break;
        case '-':
            $('#flashBlock').hide();
            this._gain.gain.setValueAtTime(1.0, t);
            t += 3 * this._dot;
            this._gain.gain.setValueAtTime(0.0, t);
            $('#flashBlock').show();
            break;          
        }
        t += this._dot;
    }
    return t;
}

MorseNode.prototype.playString = function(t, w) {
    w = w.toUpperCase();
    for(var i = 0; i < w.length; i++) {
        if(w[i] == ' ') {
            t += 3 * this._dot; // 3 dots from before, three here, and
                                // 1 from the ending letter before.
        }
        else if(w[i] != undefined) {
            t = this.playChar(t, w[i]);
            t += 2 * this._dot;
        }
    }
    return t;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<html>
   <div id="flashBlock" style="Background:black;display:none;height:100px;width:100px">
  </div>
</html>

1

There are 1 answers

4
Patrick Evans On BEST ANSWER

The problem is the place where you are doing the element manipulation is going to be run before the audio context plays the sounds. setValueAtTime() sets up an event, in this case a gain change, to happen at a specific time. Your hide/show calls do not sync up with these event setups, since they are run right away, and so they run before the audio.

You also have another problem that you ran hide() and show() nearly right after each other, this is going to basically cancel each other out. There needs to be a certain amount of time to have lapsed before calling the other method in order for the animation to render properly.

What you will need to do is setup a timing system to execute the hide / show at the right times.

One way you can achieve this by creating an array that has objects detailing when to start and end a hide / show operation.

var flashBlock = $('#flashBlock'); //cache this so you dont create it each time

//in constructor
this.flashTimes = [];

//in playChar()
this.flashTimes.push({
  timestamp:t,
  end:t+this._dot // or t+(3*this._dot)
});

Then do a constant check of the audio stream to check its time, and if it is at the right time start the operation.

MorseNode.prototype.flashLoop = function(){
  var ct = ac.currentTime;
  var currentFlash = this.flashTimes[0];
  if(ct >= currentFlash.timestamp && ct < currentFlash.end){
     // remove it from the queued
     this.flashTimes.shift(); 
     // determine how much time the animation can 
     // last between now and when it is supposed to end.
     let duration = ac.currentTime - currentFlash.end;

     // first argument to hide / show is duration of animation,
     // the second is a callback to be called when animation is done
     flashBlock.hide(duration-100,()=>{
       flashBlock.show(100);
     });
  }
  requestAnimationFrame(()=>this.flashLoop());
}

requestAnimationFrame(()=>this.flashLoop());

var flashBlock = null;
$(document).ready(function() {
  flashBlock = $('#flashBlock');
  var context = new(window.AudioContext || window.webkitAudioContext());
  var O = new MorseNode(context, 20);
  O.connect(context.destination);
  O.playString(1, '.-- -.. ... - . --- .-. --.');
});

function MorseNode(ac, rate) {
  this.flashTimes = [];
  this.ac = ac;
  
  // ac is an audio context.
  this._oscillator = ac.createOscillator();
  this._gain = ac.createGain();

  this._gain.gain.value = 0;
  this._oscillator.frequency.value = 550;

  this._oscillator.connect(this._gain);

  if (rate == undefined)
    rate = 20;
  this._dot = 1.2 / rate; // formula from Wikipedia.

  this._oscillator.start(0);
}

MorseNode.prototype.connect = function(target) {
  return this._gain.connect(target);
}

MorseNode.prototype.playChar = function(t, c) {
  switch (c) {
    case '.':
      this.flashTimes.push({
        timestamp: t,
        end: t + this._dot
      });
      this._gain.gain.setValueAtTime(1.0, t);
      t += this._dot;
      this._gain.gain.setValueAtTime(0.0, t);
      break;
    case '-':
      this.flashTimes.push({
        timestamp: t,
        end: t + (3 * this._dot)
      });
      this._gain.gain.setValueAtTime(1.0, t);
      t += 3 * this._dot;
      this._gain.gain.setValueAtTime(0.0, t);
      break;
  }
  t += this._dot;

  return t;
}

MorseNode.prototype.playString = function(t, w) {
  w = w.toUpperCase();
  for (var i = 0; i < w.length; i++) {
    if (w[i] == ' ') {
      t += 3 * this._dot;
    } else if (w[i] != undefined) {
      t = this.playChar(t, w[i]);
      t += 2 * this._dot;
    }
  }
  requestAnimationFrame(() => this.flashLoop());
  return t;
}

MorseNode.prototype.flashLoop = function() {
  var ct = this.ac.currentTime;
  var currentFlash = this.flashTimes[0];
  if (!currentFlash) return;

  if (ct >= currentFlash.timestamp && ct < currentFlash.end) {
    this.flashTimes.shift(); // remove it from the queued actions
    let duration = this.ac.currentTime - currentFlash.end;
    flashBlock.hide(duration - 100, () => {
      flashBlock.show(100);
    });
  }
  requestAnimationFrame(() => this.flashLoop());
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<html>
<div id="flashBlock" style="Background:black;display:none;height:100px;width:100px">
</div>

</html>