PageSourceSearch

https://codelamp.co.uk/js/audio-landscape.js?build=BUILDTOKEN

js codelamp.co.uk collected 2026-10-04 13:34:38 UTC 9,459 bytes, 320 lines download raw bytes

1(function(){
2
3  var inst;
4
5  /**
6   * AudioLandscape is a layer that appears as part of the parallax when the 
7   * play music icon is clicked.
8   *
9   * The audio is realtime processed by JavaScript to produce a visulaisation 
10   * of the audio, using the AudioContext API.
11   *
12   * @namespace audioLandscape
13   * @author Phil Glanville
14   */
15  var audioLandscape = {
16    /**
17     * Create an instance of `audioLandscape`
18     *
19     * @memberof! audioLandscape
20     */
21    create: function(options){
22      if ( !audioLandscape.AudioContext ) {
23        audioLandscape.AudioContext
24          = window.AudioContext ||
25            window.webkitAudioContext;
26      }
27      return audioLandscape.AudioContext &&
28        this.prep.apply(Object.create(this), arguments);
29    },
30    /**
31     * Bind methods to this instance, or a passed in context
32     *
33     * @memberof! audioLandscape
34     */
35    bind: function(method, context){
36      var self = this;
37      return function(){
38        return method.apply(context||self, arguments);
39      };
40    },
41    /**
42     * Internal function to prep a new instance of `audioLandscape`
43     *
44     * @memberof! audioLandscape
45     * @private
46     */
47    prep: function(options){
48      this.options = options;
49      this.console = window.console;
50      this.canvas = jQuery(options.canvas);
51      this.window = jQuery(window);
52      this.ctx = this.canvas.get(0).getContext("2d");
53      this.gradient = this.ctx.createLinearGradient(0, 0, 0, 300);
54      this.gradient.addColorStop(1,'#ffffff');
55      this.gradient.addColorStop(0,'#ffffff');
56      /// if we have optional beats json, load it now
57      if ( options.beats ) {
58        this.loadBeats(options.beats, function(beats){
59          this.beats = beats;
60          this.loadAudio(this.options.audio);
61        });
62      }
63      else {
64        this.loadAudio(options.audio);
65      }
66      return this;
67    },
68    /**
69     * Event handler collection
70     *
71     * @memberof! audioLandscape
72     * @namespace audioLandscape.events
73     */
74    events: {
75      /**
76       * Triggered on each processing of audio
77       *
78       * @memberof! audioLandscape.events
79       */
80      audioprocess: function() {
81        // get the average for the first channel
82        var array =  new Uint8Array(this.analyser.frequencyBinCount);
83        this.analyser.getByteFrequencyData(array);
84        // clear the current state
85        this.ctx.clearRect(0, 0, 1000, 325);
86        // set the fill style
87        this.ctx.fillStyle = this.gradient;
88        this.draw(array);
89      },
90      /**
91       * Triggered when the audio first loads
92       *
93       * @memberof! audioLandscape.events
94       */
95      audioload: function(){
96        this.context.decodeAudioData(
97          this.request.response,
98          this.bind(this.events.audiodecoded),
99          this.bind(this.events.audioerror)
100        );
101      },
102      /**
103       * Triggered when a buffered part of the audio has been decoded
104       *
105       * @memberof! audioLandscape.events
106       */
107      audiodecoded: function( buffer ){
108        this.playBuffer(buffer);
109      },
110      /**
111       * Triggered when an error occurs
112       *
113       * @memberof! audioLandscape.events
114       */
115      audioerror: function( e ){
116        this.console.log(e);
117      }
118    },
119    /**
120     * "Beats" is work-in-progress. The idea is to have a JSON file twinned with each
121     * audio file. This JSON tracks key beats in the music for when events should occur.
122     * This would then be tied into the UI, by way of a visual demo or something.
123     *
124     * My plan is that the website would be "navigated" by the music.
125     *
126     * @memberof! audioLandscape
127     */
128    loadBeats: function( url, callback ){
129      jQuery.getJSON(url, jQuery.proxy(function(data){
130        var beats = data;
131        var first = beats.list[0];
132            beats.watchForOffset = first.offset;
133            beats.index = 0;
134        callback.call(this, beats);
135      }, this));
136      return this;
137    },
138    /**
139     * load a particular file by URL
140     *
141     * @memberof! audioLandscape
142     */
143    loadAudio: function( url ){
144      /// create a new audio context
145      this.context = new audioLandscape.AudioContext();
146      this.javascriptNode = this.context.createScriptProcessor(2048, 1, 1);
147      this.javascriptNode.connect(this.context.destination);
148      /// setup a analyzer
149      this.analyser = this.context.createAnalyser();
150      this.analyser.smoothingTimeConstant = 0.3;
151      this.analyser.fftSize = 512;
152      /// create a buffer source node
153      this.sourceNode = this.context.createBufferSource();
154      this.sourceNode.connect(this.analyser);
155      this.analyser.connect(this.javascriptNode);
156      this.sourceNode.connect(this.context.destination);
157      /// listen out for the changes
158      this.javascriptNode.onaudioprocess = this.bind(this.events.audioprocess);
159      ///
160      this.request = new XMLHttpRequest();
161      this.request.open('GET', url, true);
162      this.request.responseType = 'arraybuffer';
163      this.request.onload = this.bind(this.events.audioload);
164      this.request.send();
165    },
166    /**
167     * Player a buffer of audio, at a particular offset
168     *
169     * @memberof! audioLandscape
170     */
171    playBuffer: function( buffer, offset ){
172      this.sourceNode.buffer = buffer;
173      this.sourceNode.start(0, offset || this.playFrom || 0);
174    },
175    /**
176     * Stop the current audio playing
177     *
178     * @memberof! audioLandscape
179     */
180    stop: function(){
181      delete this.pauseAt;
182      delete this.playFrom;
183      this.sourceNode.stop();
184    },
185    /**
186     * Pause the current audio playing
187     *
188     * @memberof! audioLandscape
189     */
190    pause: function(){
191      if ( this.context && this.sourceNode ) {
192        this.pausedAt = this.context.currentTime;
193        this.sourceNode.stop();
194      }
195    },
196    /**
197     * Start again after a pause
198     *
199     * @memberof! audioLandscape
200     */
201    play: function(){
202      if ( this.pausedAt ) {
203        this.playFrom = this.pausedAt;
204        this.loadAudio( this.options.audio );
205        delete this.pausedAt;
206      }
207    },
208    /**
209     * Tied into the "Beats" idea. Each time the dispaly is redrawn, we check
210     * to see if the current played audio has reached the next beat time offset.
211     * This is not the ideal approach, as things would break down if the
212     * audio jumps ahead for any reason... hence why "Beats" is still a work in progress.
213     *
214     * @memberof! audioLandscape
215     * @see audioLandscape.loadBeats
216     */
217    triggerBeats: function( sum ){
218      if ( this.beats.watchForOffset ) {
219        if ( this.context.currentTime >= this.beats.watchForOffset ) {
220          var index = this.beats.index,
221              item = this.beats.list[index],
222              next = this.beats.list[index+1]
223          ;
224          this.beats.index++;
225          this.beats.watchForOffset = next ? next.offset : false;
226          try {
227            this.window.triggerHandler('audiolandscapebeat', {
228              offset: item.offset,
229              action: item.action,
230              power: item.power,
231              sum: sum,
232              next: next
233            });
234          }
235          catch (ex) {
236            console.warn(ex);
237          }
238        }
239      }
240    },
241
242    /**
243     * Redraw the currently calculated visulisation data (in terms of VU bars)
244     *
245     * @memberof! audioLandscape
246     */
247    draw: function( array ){
248      if ( this.context ) {
249        var sum = 0, ctx = this.ctx, i = 0, l = array.length;
250        for ( i; i<l; i++ ){
251          sum += array[i];
252          ctx.fillRect(i*5, 325-array[i], 3, 325);
253        }
254        this.beats && this.triggerBeats(sum);
255        this.lastSum = sum;
256      }
257    }
258  };
259
260  var tracks = [
261    '/audio/wind-mark-diangelo-long.ogg',
262    '/audio/gone-ft-mona-moua.ogg',
263    '/audio/tove-lo-habits-hippie-sabotage-mono.ogg'    /*,
264    '/audio/a-perfect-circle_weak-and-powerless.mp3',
265    '/audio/a-perfect-cirlce_3-libras.mp3',
266    '/audio/brothomstates_adozenaday.mp3',
267    '/audio/bush_swallowed.mp3',
268    '/audio/gone-ft-mona-moua.ogg',
269    '/audio/john-tejada_young.mp3',
270    '/audio/jurassic-5_high-fidelity.mp3',
271    '/audio/telefon-tel-aviv_sound-in-a-dark-room.mp3'*/
272  ];
273
274  ///  || tracks[Math.floor(Math.random() * tracks.length)]
275
276  0 && tabVisibilityChange && tabVisibilityChange.on('change', function(e, tab){
277    if ( tab.hidden ) {
278      inst && inst.pause();
279    }
280    else {
281      inst && inst.play();
282    }
283  });
284
285  jQuery(window)
286    .on('click', function(){
287      if ( inst && inst.context ) {
288        console.log(Math.round(inst.context.currentTime * 100)/100, inst.lastSum);
289      }
290    })
291    .on('audiolandscapebeat', function(e, beat){
292      if ( beat && beat.action ) {
293        var type = beat.action.type.split('.'), subtype = type[1];
294        switch ( type[0] ) {
295          case 'site':
296            if ( site[subtype] ) {
297              site[subtype].apply(site, beat.action.args || []);
298            }
299          break;
300        }
301      }
302    })
303    .on('audiolandscapestart', function(){
304      inst = audioLandscape.create({
305        canvas: '#audio-canvas',
306        audio: tracks[1],
307        beats: '/audio/tove-lo-habits.beats.json'
308      });
309    })
310    .on('audiolandscapestop', function(){
311      inst && inst.stop();
312    })
313  ;
314  /*
315  setTimeout(function(){
316    site.window.triggerHandler('randomtrack');
317  }, site.html.is('.pan-down') ? 10000 : 5000);
318  */
319
320})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.