repo_description: A graph node engine and editor written in Javascript similar to PD or UDK Blueprints, comes with its own editor in HTML5 Canvas2D. The engine can run client side or server side using Node. It allows to export graphs as JSONs to be included in applications independently.
repo_topics: blueprints canvas2d editor graph graphs javascript modular nocode node-graph nodejs nodes visual visual-programming workflow
code: (function(global) {
      var LiteGraph = global.LiteGraph;
  
      var LGAudio = {};
      global.LGAudio = LGAudio;
  
      LGAudio.getAudioContext = function() {
          if (!this._audio_context) {
              window.AudioContext =
                  window.AudioContext || window.webkitAudioContext;
              if (!window.AudioContext) {
                  console.error("AudioContext not supported by browser");
                  return null;
              }
              this._audio_context = new AudioContext();
              this._audio_context.onmessage = function(msg) {
                  console.log("msg", msg);
              };
              this._audio_context.onended = function(msg) {
                  console.log("ended", msg);
              };
              this._audio_context.oncomplete = function(msg) {
                  console.log("complete", msg);
              };
          }
  
          //in case it crashes
          //if(this._audio_context.state == "suspended")
          //	this._audio_context.resume();
          return this._audio_context;
      };
  
      LGAudio.connect = function(audionodeA, audionodeB) {
          try {
              audionodeA.connect(audionodeB);
          } catch (err) {
              console.warn("LGraphAudio:", err);
          }
      };
  
      LGAudio.disconnect = function(audionodeA, audionodeB) {
          try {
              audionodeA.disconnect(audionodeB);
          } catch (err) {
              console.warn("LGraphAudio:", err);
          }
      };
  
      LGAudio.changeAllAudiosConnections = function(node, connect) {
          if (node.inputs) {
              for (var i = 0; i < node.inputs.length; ++i) {
                  var input = node.inputs[i];
                  var link_info = node.graph.links[input.link];
                  if (!link_info) {
                      continue;
                  }
  
                  var origin_node = node.graph.getNodeById(link_info.origin_id);
                  var origin_audionode = null;
                  if (origin_node.getAudioNodeInOutputSlot) {
                      origin_audionode = origin_node.getAudioNodeInOutputSlot(
                          link_info.origin_slot
                      );
                  } else {
                      origin_audionode = origin_node.audionode;
                  }
  
                  var target_audionode = null;
                  if (node.getAudioNodeInInputSlot) {
                      target_audionode = node.getAudioNodeInInputSlot(i);
                  } else {
                      target_audionode = node.audionode;
                  }
  
                  if (connect) {
                      LGAudio.connect(origin_audionode, target_audionode);
                  } else {
                      LGAudio.disconnect(origin_audionode, target_audionode);
                  }
              }
          }
  
          if (node.outputs) {
              for (var i = 0; i < node.outputs.length; ++i) {
                  var output = node.outputs[i];
                  for (var j = 0; j < output.links.length; ++j) {
                      var link_info = node.graph.links[output.links[j]];
                      if (!link_info) {
                          continue;
                      }
  
                      var origin_audionode = null;
                      if (node.getAudioNodeInOutputSlot) {
                          origin_audionode = node.getAudioNodeInOutputSlot(i);
                      } else {
                          origin_audionode = node.audionode;
                      }
  
                      var target_node = node.graph.getNodeById(
                          link_info.target_id
                      );
                      var target_audionode = null;
                      if (target_node.getAudioNodeInInputSlot) {
                          target_audionode = target_node.getAudioNodeInInputSlot(
                              link_info.target_slot
                          );
                      } else {
                          target_audionode = target_node.audionode;
                      }
  
                      if (connect) {
                          LGAudio.connect(origin_audionode, target_audionode);
                      } else {
                          LGAudio.disconnect(origin_audionode, target_audionode);
                      }
                  }
              }
          }
      };
  
      //used by many nodes
      LGAudio.onConnectionsChange = function(
          connection,
          slot,
          connected,
          link_info
      ) {
          //only process the outputs events
          if (connection != LiteGraph.OUTPUT) {
              return;
          }
  
          var target_node = null;
          if (link_info) {
              target_node = this.graph.getNodeById(link_info.target_id);
          }
  
          if (!target_node) {
              return;
          }
  
          //get origin audionode
          var local_audionode = null;
          if (this.getAudioNodeInOutputSlot) {
              local_audionode = this.getAudioNodeInOutputSlot(slot);
          } else {
              local_audionode = this.audionode;
          }
  
          //get target audionode
          var target_audionode = null;
          if (target_node.getAudioNodeInInputSlot) {
              target_audionode = target_node.getAudioNodeInInputSlot(
                  link_info.target_slot
              );
          } else {
              target_audionode = target_node.audionode;
          }
  
          //do the connection/disconnection
          if (connected) {
              LGAudio.connect(local_audionode, target_audionode);
          } else {
              LGAudio.disconnect(local_audionode, target_audionode);
          }
      };
  
      //this function helps creating wrappers to existing classes
      LGAudio.createAudioNodeWrapper = function(class_object) {
          var old_func = class_object.prototype.onPropertyChanged;
  
          class_object.prototype.onPropertyChanged = function(name, value) {
              if (old_func) {
                  old_func.call(this, name, value);
              }
  
              if (!this.audionode) {
                  return;
              }
  
              if (this.audionode[name] === undefined) {
                  return;
              }
  
              if (this.audionode[name].value !== undefined) {
                  this.audionode[name].value = value;
              } else {
                  this.audionode[name] = value;
              }
          };
  
          class_object.prototype.onConnectionsChange =
              LGAudio.onConnectionsChange;
      };
  
      //contains the samples decoded of the loaded audios in AudioBuffer format
      LGAudio.cached_audios = {};
  
      LGAudio.loadSound = function(url, on_complete, on_error) {
          if (LGAudio.cached_audios[url] && url.indexOf("blob:") == -1) {
              if (on_complete) {
                  on_complete(LGAudio.cached_audios[url]);
              }
              return;
          }
  
          if (LGAudio.onProcessAudioURL) {
              url = LGAudio.onProcessAudioURL(url);
          }
  
          //load new sample
          var request = new XMLHttpRequest();
          request.open("GET", url, true);
          request.responseType = "arraybuffer";
  
          var context = LGAudio.getAudioContext();
  
          // Decode asynchronously
          request.onload = function() {
              console.log("AudioSource loaded");
              context.decodeAudioData(
                  request.response,
                  function(buffer) {
                      console.log("AudioSource decoded");
                      LGAudio.cached_audios[url] = buffer;
                      if (on_complete) {
                          on_complete(buffer);
                      }
                  },
                  onError
              );
          };
          request.send();
  
          function onError(err) {
              console.log("Audio loading sample error:", err);
              if (on_error) {
                  on_error(err);
              }
          }
  
          return request;
      };
  
      //****************************************************
  
      function LGAudioSource() {
          this.properties = {
              src: "",
              gain: 0.5,
              loop: true,
              autoplay: true,
              playbackRate: 1
          };
  
          this._loading_audio = false;
          this._audiobuffer = null; //points to AudioBuffer with the audio samples decoded
          this._audionodes = [];
          this._last_sourcenode = null; //the last AudioBufferSourceNode (there could be more if there are several sounds playing)
  
          this.addOutput("out", "audio");
          this.addInput("gain", "number");
  
          //init context
          var context = LGAudio.getAudioContext();
  
          //create gain node to control volume
          this.audionode = context.createGain();
          this.audionode.graphnode = this;
          this.audionode.gain.value = this.properties.gain;
  
          //debug
          if (this.properties.src) {
              this.loadSound(this.properties.src);
          }
      }
  
  	LGAudioSource.desc = "Plays an audio file";
      LGAudioSource["@src"] = { widget: "resource" };
      LGAudioSource.supported_extensions = ["wav", "ogg", "mp3"];
  
      LGAudioSource.prototype.onAdded = function(graph) {
          if (graph.status === LGraph.STATUS_RUNNING) {
              this.onStart();
          }
      };
  
      LGAudioSource.prototype.onStart = function() {
          if (!this._audiobuffer) {
              return;
          }
  
          if (this.properties.autoplay) {
  			this.playBuffer(this._audiobuffer);
          }
      };
  
      LGAudioSource.prototype.onStop = function() {
          this.stopAllSounds();
      };
  
      LGAudioSource.prototype.onPause = function() {
          this.pauseAllSounds();
      };
  
      LGAudioSource.prototype.onUnpause = function() {
          this.unpauseAllSounds();
          //this.onStart();
      };
  
      LGAudioSource.prototype.onRemoved = function() {
          this.stopAllSounds();
          if (this._dropped_url) {
              URL.revokeObjectURL(this._url);
          }
      };
  
      LGAudioSource.prototype.stopAllSounds = function() {
          //iterate and stop
          for (var i = 0; i < this._audionodes.length; ++i) {
              if (this._audionodes[i].started) {
                  this._audionodes[i].started = false;
                  this._audionodes[i].stop();
              }
              //this._audionodes[i].disconnect( this.audionode );
          }
          this._audionodes.length = 0;
      };
  
      LGAudioSource.prototype.pauseAllSounds = function() {
          LGAudio.getAudioContext().suspend();
      };
  
      LGAudioSource.prototype.unpauseAllSounds = function() {
          LGAudio.getAudioContext().resume();
      };
  
      LGAudioSource.prototype.onExecute = function() {
          if (this.inputs) {
              for (var i = 0; i < this.inputs.length; ++i) {
                  var input = this.inputs[i];
                  if (input.link == null) {
                      continue;
                  }
                  var v = this.getInputData(i);
                  if (v === undefined) {
                      continue;
                  }
                  if (input.name == "gain")
                      this.audionode.gain.value = v;
                  else if (input.name == "src") {
                      this.setProperty("src",v);
                  } else if (input.name == "playbackRate") {
                      this.properties.playbackRate = v;
                      for (var j = 0; j < this._audionodes.length; ++j) {
                          this._audionodes[j].playbackRate.value = v;
                      }
                  }
              }
          }
  
          if (this.outputs) {
              for (var i = 0; i < this.outputs.length; ++i) {
                  var output = this.outputs[i];
                  if (output.name == "buffer" && this._audiobuffer) {
                      this.setOutputData(i, this._audiobuffer);
                  }
              }
          }
      };
  
      LGAudioSource.prototype.onAction = function(event) {
          if (this._audiobuffer) {
              if (event == "Play") {
                  this.playBuffer(this._audiobuffer);
              } else if (event == "Stop") {
                  this.stopAllSounds();
              }
          }
      };
  
      LGAudioSource.prototype.onPropertyChanged = function(name, value) {
          if (name == "src") {
              this.loadSound(value);
          } else if (name == "gain") {
              this.audionode.gain.value = value;
          } else if (name == "playbackRate") {
              for (var j = 0; j < this._audionodes.length; ++j) {
                  this._audionodes[j].playbackRate.value = value;
              }
          }
      };
  
      LGAudioSource.prototype.playBuffer = function(buffer) {
          var that = this;
          var context = LGAudio.getAudioContext();
  
          //create a new audionode (this is mandatory, AudioAPI doesnt like to reuse old ones)
          var audionode = context.createBufferSource(); //create a AudioBufferSourceNode
          this._last_sourcenode = audionode;
          audionode.graphnode = this;
          audionode.buffer = buffer;
          audionode.loop = this.properties.loop;
          audionode.playbackRate.value = this.properties.playbackRate;
          this._audionodes.push(audionode);
          audionode.connect(this.audionode); //connect to gain
  
  		this._audionodes.push(audionode);
  
  		this.trigger("start");
  
          audionode.onended = function() {
              //console.log("ended!");
              that.trigger("ended");
              //remove
              var index = that._audionodes.indexOf(audionode);
              if (index != -1) {
                  that._audionodes.splice(index, 1);
              }
          };
  
          if (!audionode.started) {
              audionode.started = true;
              audionode.start();
          }
          return audionode;
      };
  
      LGAudioSource.prototype.loadSound = function(url) {
          var that = this;
  
          //kill previous load
          if (this._request) {
              this._request.abort();
              this._request = null;
          }
  
          this._audiobuffer = null; //points to the audiobuffer once the audio is loaded
          this._loading_audio = false;
  
          if (!url) {
              return;
          }
  
          this._request = LGAudio.loadSound(url, inner);
  
          this._loading_audio = true;
          this.boxcolor = "#AA4";
  
          function inner(buffer) {
              this.boxcolor = LiteGraph.NODE_DEFAULT_BOXCOLOR;
              that._audiobuffer = buffer;
              that._loading_audio = false;
              //if is playing, then play it
              if (that.graph && that.graph.status === LGraph.STATUS_RUNNING) {
                  that.onStart();
              } //this controls the autoplay already
          }
      };
  
      //Helps connect/disconnect AudioNodes when new connections are made in the node
      LGAudioSource.prototype.onConnectionsChange = LGAudio.onConnectionsChange;
  
      LGAudioSource.prototype.onGetInputs = function() {
          return [
              ["playbackRate", "number"],
  			["src","string"],
              ["Play", LiteGraph.ACTION],
              ["Stop", LiteGraph.ACTION]
          ];
      };
  
      LGAudioSource.prototype.onGetOutputs = function() {
          return [["buffer", "audiobuffer"], ["start", LiteGraph.EVENT], ["ended", LiteGraph.EVENT]];
      };
  
      LGAudioSource.prototype.onDropFile = function(file) {
          if (this._dropped_url) {
              URL.revokeObjectURL(this._dropped_url);
          }
          var url = URL.createObjectURL(file);
          this.properties.src = url;
          this.loadSound(url);
          this._dropped_url = url;
      };
  
      LGAudioSource.title = "Source";
      LGAudioSource.desc = "Plays audio";
      LiteGraph.registerNodeType("audio/source", LGAudioSource);
  
      //****************************************************
  
      function LGAudioMediaSource() {
          this.properties = {
              gain: 0.5
          };
  
          this._audionodes = [];
          this._media_stream = null;
  
          this.addOutput("out", "audio");
          this.addInput("gain", "number");
  
          //create gain node to control volume
          var context = LGAudio.getAudioContext();
          this.audionode = context.createGain();
          this.audionode.graphnode = this;
          this.audionode.gain.value = this.properties.gain;
      }
  
      LGAudioMediaSource.prototype.onAdded = function(graph) {
          if (graph.status === LGraph.STATUS_RUNNING) {
              this.onStart();
          }
      };
  
      LGAudioMediaSource.prototype.onStart = function() {
          if (this._media_stream == null && !this._waiting_confirmation) {
              this.openStream();
          }
      };
  
      LGAudioMediaSource.prototype.onStop = function() {
          this.audionode.gain.value = 0;
      };
  
      LGAudioMediaSource.prototype.onPause = function() {
          this.audionode.gain.value = 0;
      };
  
      LGAudioMediaSource.prototype.onUnpause = function() {
          this.audionode.gain.value = this.properties.gain;
      };
  
      LGAudioMediaSource.prototype.onRemoved = function() {
          this.audionode.gain.value = 0;
          if (this.audiosource_node) {
              this.audiosource_node.disconnect(this.audionode);
              this.audiosource_node = null;
          }
          if (this._media_stream) {
              var tracks = this._media_stream.getTracks();
              if (tracks.length) {
                  tracks[0].stop();
              }
          }
      };
  
      LGAudioMediaSource.prototype.openStream = function() {
          if (!navigator.mediaDevices) {
              console.log(
                  "getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags"
              );
              return;
          }
  
          this._waiting_confirmation = true;
  
          // Not showing vendor prefixes.
          navigator.mediaDevices
              .getUserMedia({ audio: true, video: false })
              .then(this.streamReady.bind(this))
              .catch(onFailSoHard);
  
          var that = this;
          function onFailSoHard(err) {
              console.log("Media rejected", err);
              that._media_stream = false;
              that.boxcolor = "red";
          }
      };
  
      LGAudioMediaSource.prototype.streamReady = function(localMediaStream) {
          this._media_stream = localMediaStream;
          //this._waiting_confirmation = false;
  
          //init context
          if (this.audiosource_node) {
              this.audiosource_node.disconnect(this.audionode);
          }
          var context = LGAudio.getAudioContext();
          this.audiosource_node = context.createMediaStreamSource(
              localMediaStream
          );
          this.audiosource_node.graphnode = this;
          this.audiosource_node.connect(this.audionode);
          this.boxcolor = "white";
      };
  
      LGAudioMediaSource.prototype.onExecute = function() {
          if (this._media_stream == null && !this._waiting_confirmation) {
              this.openStream();
          }
  
          if (this.inputs) {
              for (var i = 0; i < this.inputs.length; ++i) {
                  var input = this.inputs[i];
                  if (input.link == null) {
                      continue;
                  }
                  var v = this.getInputData(i);
                  if (v === undefined) {
                      continue;
                  }
                  if (input.name == "gain") {
                      this.audionode.gain.value = this.properties.gain = v;
                  }
              }
          }
      };
  
      LGAudioMediaSource.prototype.onAction = function(event) {
          if (event == "Play") {
              this.audionode.gain.value = this.properties.gain;
          } else if (event == "Stop") {
              this.audionode.gain.value = 0;
          }
      };
  
      LGAudioMediaSource.prototype.onPropertyChanged = function(name, value) {
          if (name == "gain") {
              this.audionode.gain.value = value;
          }
      };
  
      //Helps connect/disconnect AudioNodes when new connections are made in the node
      LGAudioMediaSource.prototype.onConnectionsChange =
          LGAudio.onConnectionsChange;
  
      LGAudioMediaSource.prototype.onGetInputs = function() {
          return [
              ["playbackRate", "number"],
              ["Play", LiteGraph.ACTION],
              ["Stop", LiteGraph.ACTION]
          ];
      };
  
      LGAudioMediaSource.title = "MediaSource";
      LGAudioMediaSource.desc = "Plays microphone";
      LiteGraph.registerNodeType("audio/media_source", LGAudioMediaSource);
  
      //*****************************************************
  
      function LGAudioAnalyser() {
          this.properties = {
              fftSize: 2048,
              minDecibels: -100,
              maxDecibels: -10,
              smoothingTimeConstant: 0.5
          };
  
          var context = LGAudio.getAudioContext();
  
          this.audionode = context.createAnalyser();
          this.audionode.graphnode = this;
          this.audionode.fftSize = this.properties.fftSize;
          this.audionode.minDecibels = this.properties.minDecibels;
          this.audionode.maxDecibels = this.properties.maxDecibels;
          this.audionode.smoothingTimeConstant = this.properties.smoothingTimeConstant;
  
          this.addInput("in", "audio");
          this.addOutput("freqs", "array");
          this.addOutput("samples", "array");
  
          this._freq_bin = null;
          this._time_bin = null;
      }
  
      LGAudioAnalyser.prototype.onPropertyChanged = function(name, value) {
          this.audionode[name] = value;
      };
  
      LGAudioAnalyser.prototype.onExecute = function() {
          if (this.isOutputConnected(0)) {
              //send FFT
              var bufferLength = this.audionode.frequencyBinCount;
              if (!this._freq_bin || this._freq_bin.length != bufferLength) {
                  this._freq_bin = new Uint8Array(bufferLength);
              }
              this.audionode.getByteFrequencyData(this._freq_bin);
              this.setOutputData(0, this._freq_bin);
          }
  
          //send analyzer
          if (this.isOutputConnected(1)) {
              //send Samples
              var bufferLength = this.audionode.frequencyBinCount;
              if (!this._time_bin || this._time_bin.length != bufferLength) {
                  this._time_bin = new Uint8Array(bufferLength);
              }
              this.audionode.getByteTimeDomainData(this._time_bin);
              this.setOutputData(1, this._time_bin);
          }
  
          //properties
          for (var i = 1; i < this.inputs.length; ++i) {
              var input = this.inputs[i];
              if (input.link == null) {
                  continue;
              }
              var v = this.getInputData(i);
              if (v !== undefined) {
                  this.audionode[input.name].value = v;
              }
          }
  
          //time domain
          //this.audionode.getFloatTimeDomainData( dataArray );
      };
  
      LGAudioAnalyser.prototype.onGetInputs = function() {
          return [
              ["minDecibels", "number"],
              ["maxDecibels", "number"],
              ["smoothingTimeConstant", "number"]
          ];
      };
  
      LGAudioAnalyser.prototype.onGetOutputs = function() {
          return [["freqs", "array"], ["samples", "array"]];
      };
  
      LGAudioAnalyser.title = "Analyser";
      LGAudioAnalyser.desc = "Audio Analyser";
      LiteGraph.registerNodeType("audio/analyser", LGAudioAnalyser);
  
      //*****************************************************
  
      function LGAudioGain() {
          //default
          this.properties = {
              gain: 1
          };
  
          this.audionode = LGAudio.getAudioContext().createGain();
          this.addInput("in", "audio");
          this.addInput("gain", "number");
          this.addOutput("out", "audio");
      }
  
      LGAudioGain.prototype.onExecute = function() {
          if (!this.inputs || !this.inputs.length) {
              return;
          }
  
          for (var i = 1; i < this.inputs.length; ++i) {
              var input = this.inputs[i];
              var v = this.getInputData(i);
              if (v !== undefined) {
                  this.audionode[input.name].value = v;
              }
          }
      };
  
      LGAudio.createAudioNodeWrapper(LGAudioGain);
  
      LGAudioGain.title = "Gain";
      LGAudioGain.desc = "Audio gain";
      LiteGraph.registerNodeType("audio/gain", LGAudioGain);
  
      function LGAudioConvolver() {
          //default
          this.properties = {
              impulse_src: "",
              normalize: true
          };
  
          this.audionode = LGAudio.getAudioContext().createConvolver();
          this.addInput("in", "audio");
          this.addOutput("out", "audio");
      }
  
      LGAudio.createAudioNodeWrapper(LGAudioConvolver);
  
      LGAudioConvolver.prototype.onRemove = function() {
          if (this._dropped_url) {
              URL.revokeObjectURL(this._dropped_url);
          }
      };
  
      LGAudioConvolver.prototype.onPropertyChanged = function(name, value) {
          if (name == "impulse_src") {
              this.loadImpulse(value);
          } else if (name == "normalize") {
              this.audionode.normalize = value;
          }
      };
  
      LGAudioConvolver.prototype.onDropFile = function(file) {
          if (this._dropped_url) {
              URL.revokeObjectURL(this._dropped_url);
          }
          this._dropped_url = URL.createObjectURL(file);
          this.properties.impulse_src = this._dropped_url;
          this.loadImpulse(this._dropped_url);
      };
  
      LGAudioConvolver.prototype.loadImpulse = function(url) {
          var that = this;
  
          //kill previous load
          if (this._request) {
              this._request.abort();
              this._request = null;
          }
  
          this._impulse_buffer = null;
          this._loading_impulse = false;
  
          if (!url) {
              return;
          }
  
          //load new sample
          this._request = LGAudio.loadSound(url, inner);
          this._loading_impulse = true;
  
          // Decode asynchronously
          function inner(buffer) {
              that._impulse_buffer = buffer;
              that.audionode.buffer = buffer;
              console.log("Impulse signal set");
              that._loading_impulse = false;
          }
      };
  
      LGAudioConvolver.title = "Convolver";
      LGAudioConvolver.desc = "Convolves the signal (used for reverb)";
      LiteGraph.registerNodeType("audio/convolver", LGAudioConvolver);
  
      function LGAudioDynamicsCompressor() {
          //default
          this.properties = {
              threshold: -50,
              knee: 40,
              ratio: 12,
              reduction: -20,
              attack: 0,
              release: 0.25
          };
  
          this.audionode = LGAudio.getAudioContext().createDynamicsCompressor();
          this.addInput("in", "audio");
          this.addOutput("out", "audio");
      }
  
      LGAudio.createAudioNodeWrapper(LGAudioDynamicsCompressor);
  
      LGAudioDynamicsCompressor.prototype.onExecute = function() {
          if (!this.inputs || !this.inputs.length) {
              return;
          }
          for (var i = 1; i < this.inputs.length; ++i) {
              var input = this.inputs[i];
              if (input.link == null) {
                  continue;
              }
              var v = this.getInputData(i);
              if (v !== undefined) {
                  this.audionode[input.name].value = v;
              }
          }
      };
  
      LGAudioDynamicsCompressor.prototype.onGetInputs = function() {
          return [
              ["threshold", "number"],
              ["knee", "number"],
              ["ratio", "number"],
              ["reduction", "number"],
              ["attack", "number"],
              ["release", "number"]
          ];
      };
  
      LGAudioDynamicsCompressor.title = "DynamicsCompressor";
      LGAudioDynamicsCompressor.desc = "Dynamics Compressor";
      LiteGraph.registerNodeType(
          "audio/dynamicsCompressor",
          LGAudioDynamicsCompressor
      );
  
      function LGAudioWaveShaper() {
          //default
          this.properties = {};
  
          this.audionode = LGAudio.getAudioContext().createWaveShaper();
          this.addInput("in", "audio");
          this.addInput("shape", "waveshape");
          this.addOutput("out", "audio");
      }
  
      LGAudioWaveShaper.prototype.onExecute = function() {
          if (!this.inputs || !this.inputs.length) {
              return;
          }
          var v = this.getInputData(1);
          if (v === undefined) {
              return;
          }
          this.audionode.curve = v;
      };
  
      LGAudioWaveShaper.prototype.setWaveShape = function(shape) {
          this.audionode.curve = shape;
      };
  
      LGAudio.createAudioNodeWrapper(LGAudioWaveShaper);
  
      /* disabled till I dont find a way to do a wave shape
  LGAudioWaveShaper.title = "WaveShaper";
  LGAudioWaveShaper.desc = "Distortion using wave shape";
  LiteGraph.registerNodeType("audio/waveShaper", LGAudioWaveShaper);
  */
  
      function LGAudioMixer() {
          //default
          this.properties = {
              gain1: 0.5,
              gain2: 0.5
          };
  
          this.audionode = LGAudio.getAudioContext().createGain();
  
          this.audionode1 = LGAudio.getAudioContext().createGain();
          this.audionode1.gain.value = this.properties.gain1;
          this.audionode2 = LGAudio.getAudioContext().createGain();
          this.audionode2.gain.value = this.properties.gain2;
  
          this.audionode1.connect(this.audionode);
          this.audionode2.connect(this.audionode);
  
          this.addInput("in1", "audio");
          this.addInput("in1 gain", "number");
          this.addInput("in2", "audio");
          this.addInput("in2 gain", "number");
  
          this.addOutput("out", "audio");
      }
  
      LGAudioMixer.prototype.getAudioNodeInInputSlot = function(slot) {
          if (slot == 0) {
              return this.audionode1;
          } else if (slot == 2) {
              return this.audionode2;
          }
      };
  
      LGAudioMixer.prototype.onPropertyChanged = function(name, value) {
          if (name == "gain1") {
              this.audionode1.gain.value = value;
          } else if (name == "gain2") {
              this.audionode2.gain.value = value;
          }
      };
  
      LGAudioMixer.prototype.onExecute = function() {
          if (!this.inputs || !this.inputs.length) {
              return;
          }
  
          for (var i = 1; i < this.inputs.length; ++i) {
              var input = this.inputs[i];
  
              if (input.link == null || input.type == "audio") {
                  continue;
              }
  
              var v = this.getInputData(i);
              if (v === undefined) {
                  continue;
              }
  
              if (i == 1) {
                  this.audionode1.gain.value = v;
              } else if (i == 3) {
                  this.audionode2.gain.value = v;
              }
          }
      };
  
      LGAudio.createAudioNodeWrapper(LGAudioMixer);
  
      LGAudioMixer.title = "Mixer";
      LGAudioMixer.desc = "Audio mixer";
      LiteGraph.registerNodeType("audio/mixer", LGAudioMixer);
  
      function LGAudioADSR() {
          //default
          this.properties = {
              A: 0.1,
              D: 0.1,
              S: 0.1,
              R: 0.1
          };
  
          this.audionode = LGAudio.getAudioContext().createGain();
          this.audionode.gain.value = 0;
          this.addInput("in", "audio");
          this.addInput("gate", "boolean");
          this.addOutput("out", "audio");
          this.gate = false;
      }
  
      LGAudioADSR.prototype.onExecute = function() {
          var audioContext = LGAudio.getAudioContext();
          var now = audioContext.currentTime;
          var node = this.audionode;
          var gain = node.gain;
          var current_gate = this.getInputData(1);
  
          var A = this.getInputOrProperty("A");
          var D = this.getInputOrProperty("D");
          var S = this.getInputOrProperty("S");
          var R = this.getInputOrProperty("R");
  
          if (!this.gate && current_gate) {
              gain.cancelScheduledValues(0);
              gain.setValueAtTime(0, now);
              gain.linearRampToValueAtTime(1, now + A);
              gain.linearRampToValueAtTime(S, now + A + D);
          } else if (this.gate && !current_gate) {
              gain.cancelScheduledValues(0);
              gain.setValueAtTime(gain.value, now);
              gain.linearRampToValueAtTime(0, now + R);
          }
  
          this.gate = current_gate;
      };
  
      LGAudioADSR.prototype.onGetInputs = function() {
          return [
              ["A", "number"],
              ["D", "number"],
              ["S", "number"],
              ["R", "number"]
          ];
      };
  
      LGAudio.createAudioNodeWrapper(LGAudioADSR);
  
      LGAudioADSR.title = "ADSR";
      LGAudioADSR.desc = "Audio envelope";
      LiteGraph.registerNodeType("audio/adsr", LGAudioADSR);
  
      function LGAudioDelay() {
          //default
          this.properties = {
              delayTime: 0.5
          };
  
          this.audionode = LGAudio.getAudioContext().createDelay(10);
          this.audionode.delayTime.value = this.properties.delayTime;
          this.addInput("in", "audio");
          this.addInput("time", "number");
          this.addOutput("out", "audio");
      }
  
      LGAudio.createAudioNodeWrapper(LGAudioDelay);
  
      LGAudioDelay.prototype.onExecute = function() {
          var v = this.getInputData(1);
          if (v !== undefined) {
              this.audionode.delayTime.value = v;
          }
      };
  
      LGAudioDelay.title = "Delay";
      LGAudioDelay.desc = "Audio delay";
      LiteGraph.registerNodeType("audio/delay", LGAudioDelay);
  
      function LGAudioBiquadFilter() {
          //default
          this.properties = {
              frequency: 350,
              detune: 0,
              Q: 1
          };
          this.addProperty("type", "lowpass", "enum", {
              values: [
                  "lowpass",
                  "highpass",
                  "bandpass",
                  "lowshelf",
                  "highshelf",
                  "peaking",
                  "notch",
                  "allpass"
              ]
          });
  
          //create node
          this.audionode = LGAudio.getAudioContext().createBiquadFilter();
  
          //slots
          this.addInput("in", "audio");
          this.addOutput("out", "audio");
      }
  
      LGAudioBiquadFilter.prototype.onExecute = function() {
          if (!this.inputs || !this.inputs.length) {
              return;
          }
  
          for (var i = 1; i < this.inputs.length; ++i) {
              var input = this.inputs[i];
              if (input.link == null) {
                  continue;
              }
              var v = this.getInputData(i);
              if (v !== undefined) {
                  this.audionode[input.name].value = v;
              }
          }
      };
  
      LGAudioBiquadFilter.prototype.onGetInputs = function() {
          return [["frequency", "number"], ["detune", "number"], ["Q", "number"]];
      };
  
      LGAudio.createAudioNodeWrapper(LGAudioBiquadFilter);
  
      LGAudioBiquadFilter.title = "BiquadFilter";
      LGAudioBiquadFilter.desc = "Audio filter";
      LiteGraph.registerNodeType("audio/biquadfilter", LGAudioBiquadFilter);
  
      function LGAudioOscillatorNode() {
          //default
          this.properties = {
              frequency: 440,
              detune: 0,
              type: "sine"
          };
          this.addProperty("type", "sine", "enum", {
              values: ["sine", "square", "sawtooth", "triangle", "custom"]
          });
  
          //create node
          this.audionode = LGAudio.getAudioContext().createOscillator();
  
          //slots
          this.addOutput("out", "audio");
      }
  
      LGAudioOscillatorNode.prototype.onStart = function() {
          if (!this.audionode.started) {
              this.audionode.started = true;
              try {
                  this.audionode.start();
              } catch (err) {}
          }
      };
  
      LGAudioOscillatorNode.prototype.onStop = function() {
          if (this.audionode.started) {
              this.audionode.started = false;
              this.audionode.stop();
          }
      };
  
      LGAudioOscillatorNode.prototype.onPause = function() {
          this.onStop();
      };
  
      LGAudioOscillatorNode.prototype.onUnpause = function() {
          this.onStart();
      };
  
      LGAudioOscillatorNode.prototype.onExecute = function() {
          if (!this.inputs || !this.inputs.length) {
              return;
          }
  
          for (var i = 0; i < this.inputs.length; ++i) {
              var input = this.inputs[i];
              if (input.link == null) {
                  continue;
              }
              var v = this.getInputData(i);
              if (v !== undefined) {
                  this.audionode[input.name].value = v;
              }
          }
      };
  
      LGAudioOscillatorNode.prototype.onGetInputs = function() {
          return [
              ["frequency", "number"],
              ["detune", "number"],
              ["type", "string"]
          ];
      };
  
      LGAudio.createAudioNodeWrapper(LGAudioOscillatorNode);
  
      LGAudioOscillatorNode.title = "Oscillator";
      LGAudioOscillatorNode.desc = "Oscillator";
      LiteGraph.registerNodeType("audio/oscillator", LGAudioOscillatorNode);
  
      //*****************************************************
  
      //EXTRA
  
      function LGAudioVisualization() {
          this.properties = {
              continuous: true,
              mark: -1
          };
  
          this.addInput("data", "array");
          this.addInput("mark", "number");
          this.size = [300, 200];
          this._last_buffer = null;
      }
  
      LGAudioVisualization.prototype.onExecute = function() {
          this._last_buffer = this.getInputData(0);
          var v = this.getInputData(1);
          if (v !== undefined) {
              this.properties.mark = v;
          }
          this.setDirtyCanvas(true, false);
      };
  
      LGAudioVisualization.prototype.onDrawForeground = function(ctx) {
          if (!this._last_buffer) {
              return;
          }
  
          var buffer = this._last_buffer;
  
          //delta represents how many samples we advance per pixel
          var delta = buffer.length / this.size[0];
          var h = this.size[1];
  
          ctx.fillStyle = "black";
          ctx.fillRect(0, 0, this.size[0], this.size[1]);
          ctx.strokeStyle = "white";
          ctx.beginPath();
          var x = 0;
  
          if (this.properties.continuous) {
              ctx.moveTo(x, h);
              for (var i = 0; i < buffer.length; i += delta) {
                  ctx.lineTo(x, h - (buffer[i | 0] / 255) * h);
                  x++;
              }
          } else {
              for (var i = 0; i < buffer.length; i += delta) {
                  ctx.moveTo(x + 0.5, h);
                  ctx.lineTo(x + 0.5, h - (buffer[i | 0] / 255) * h);
                  x++;
              }
          }
          ctx.stroke();
  
          if (this.properties.mark >= 0) {
              var samplerate = LGAudio.getAudioContext().sampleRate;
              var binfreq = samplerate / buffer.length;
              var x = (2 * (this.properties.mark / binfreq)) / delta;
              if (x >= this.size[0]) {
                  x = this.size[0] - 1;
              }
              ctx.strokeStyle = "red";
              ctx.beginPath();
              ctx.moveTo(x, h);
              ctx.lineTo(x, 0);
              ctx.stroke();
          }
      };
  
      LGAudioVisualization.title = "Visualization";
      LGAudioVisualization.desc = "Audio Visualization";
      LiteGraph.registerNodeType("audio/visualization", LGAudioVisualization);
  
      function LGAudioBandSignal() {
          //default
          this.properties = {
              band: 440,
              amplitude: 1
          };
  
          this.addInput("freqs", "array");
          this.addOutput("signal", "number");
      }
  
      LGAudioBandSignal.prototype.onExecute = function() {
          this._freqs = this.getInputData(0);
          if (!this._freqs) {
              return;
          }
  
          var band = this.properties.band;
          var v = this.getInputData(1);
          if (v !== undefined) {
              band = v;
          }
  
          var samplerate = LGAudio.getAudioContext().sampleRate;
          var binfreq = samplerate / this._freqs.length;
          var index = 2 * (band / binfreq);
          var v = 0;
          if (index < 0) {
              v = this._freqs[0];
          }
          if (index >= this._freqs.length) {
              v = this._freqs[this._freqs.length - 1];
          } else {
              var pos = index | 0;
              var v0 = this._freqs[pos];
              var v1 = this._freqs[pos + 1];
              var f = index - pos;
              v = v0 * (1 - f) + v1 * f;
          }
  
          this.setOutputData(0, (v / 255) * this.properties.amplitude);
      };
  
      LGAudioBandSignal.prototype.onGetInputs = function() {
          return [["band", "number"]];
      };
  
      LGAudioBandSignal.title = "Signal";
      LGAudioBandSignal.desc = "extract the signal of some frequency";
      LiteGraph.registerNodeType("audio/signal", LGAudioBandSignal);
  
      function LGAudioScript() {
          if (!LGAudioScript.default_code) {
              var code = LGAudioScript.default_function.toString();
              var index = code.indexOf("{") + 1;
              var index2 = code.lastIndexOf("}");
              LGAudioScript.default_code = code.substr(index, index2 - index);
          }
  
          //default
          this.properties = {
              code: LGAudioScript.default_code
          };
  
          //create node
          var ctx = LGAudio.getAudioContext();
          if (ctx.createScriptProcessor) {
              this.audionode = ctx.createScriptProcessor(4096, 1, 1);
          }
          //buffer size, input channels, output channels
          else {
              console.warn("ScriptProcessorNode deprecated");
              this.audionode = ctx.createGain(); //bypass audio
          }
  
          this.processCode();
          if (!LGAudioScript._bypass_function) {
              LGAudioScript._bypass_function = this.audionode.onaudioprocess;
          }
  
          //slots
          this.addInput("in", "audio");
          this.addOutput("out", "audio");
      }
  
      LGAudioScript.prototype.onAdded = function(graph) {
          if (graph.status == LGraph.STATUS_RUNNING) {
              this.audionode.onaudioprocess = this._callback;
          }
      };
  
      LGAudioScript["@code"] = { widget: "code", type: "code" };
  
      LGAudioScript.prototype.onStart = function() {
          this.audionode.onaudioprocess = this._callback;
      };
  
      LGAudioScript.prototype.onStop = function() {
          this.audionode.onaudioprocess = LGAudioScript._bypass_function;
      };
  
      LGAudioScript.prototype.onPause = function() {
          this.audionode.onaudioprocess = LGAudioScript._bypass_function;
      };
  
      LGAudioScript.prototype.onUnpause = function() {
          this.audionode.onaudioprocess = this._callback;
      };
  
      LGAudioScript.prototype.onExecute = function() {
          //nothing! because we need an onExecute to receive onStart... fix that
      };
  
      LGAudioScript.prototype.onRemoved = function() {
          this.audionode.onaudioprocess = LGAudioScript._bypass_function;
      };
  
      LGAudioScript.prototype.processCode = function() {
          try {
              var func = new Function("properties", this.properties.code);
              this._script = new func(this.properties);
              this._old_code = this.properties.code;
              this._callback = this._script.onaudioprocess;
          } catch (err) {
              console.error("Error in onaudioprocess code", err);
              this._callback = LGAudioScript._bypass_function;
              this.audionode.onaudioprocess = this._callback;
          }
      };
  
      LGAudioScript.prototype.onPropertyChanged = function(name, value) {
          if (name == "code") {
              this.properties.code = value;
              this.processCode();
              if (this.graph && this.graph.status == LGraph.STATUS_RUNNING) {
                  this.audionode.onaudioprocess = this._callback;
              }
          }
      };
  
      LGAudioScript.default_function = function() {
          this.onaudioprocess = function(audioProcessingEvent) {
              // The input buffer is the song we loaded earlier
              var inputBuffer = audioProcessingEvent.inputBuffer;
  
              // The output buffer contains the samples that will be modified and played
              var outputBuffer = audioProcessingEvent.outputBuffer;
  
              // Loop through the output channels (in this case there is only one)
              for (
                  var channel = 0;
                  channel < outputBuffer.numberOfChannels;
                  channel++
              ) {
                  var inputData = inputBuffer.getChannelData(channel);
                  var outputData = outputBuffer.getChannelData(channel);
  
                  // Loop through the 4096 samples
                  for (var sample = 0; sample < inputBuffer.length; sample++) {
                      // make output equal to the same as the input
                      outputData[sample] = inputData[sample];
                  }
              }
          };
      };
  
      LGAudio.createAudioNodeWrapper(LGAudioScript);
  
      LGAudioScript.title = "Script";
      LGAudioScript.desc = "apply script to signal";
      LiteGraph.registerNodeType("audio/script", LGAudioScript);
  
      function LGAudioDestination() {
          this.audionode = LGAudio.getAudioContext().destination;
          this.addInput("in", "audio");
      }
  
      LGAudioDestination.title = "Destination";
      LGAudioDestination.desc = "Audio output";
      LiteGraph.registerNodeType("audio/destination", LGAudioDestination);
  })(this);
  