Fix typescript errors
This commit is contained in:
		
							parent
							
								
									aefd2135f5
								
							
						
					
					
						commit
						9d968bc9ec
					
				
					 13 changed files with 493 additions and 465 deletions
				
			
		| 
						 | 
				
			
			@ -1813,6 +1813,15 @@ let
 | 
			
		|||
        sha512 = "YRRv9DNZhaVTVRh9Wmmit7Y0UFhEVqXqCSw3uazRWMxa2x85hWQZ5BN24i7GXZbaclaLXEcodEeIHsjBA8eAMw==";
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
    "@types/react-dom-17.0.3" = {
 | 
			
		||||
      name = "_at_types_slash_react-dom";
 | 
			
		||||
      packageName = "@types/react-dom";
 | 
			
		||||
      version = "17.0.3";
 | 
			
		||||
      src = fetchurl {
 | 
			
		||||
        url = "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.3.tgz";
 | 
			
		||||
        sha512 = "4NnJbCeWE+8YBzupn/YrJxZ8VnjcJq5iR1laqQ1vkpQgBiA7bwk0Rp24fxsdNinzJY2U+HHS4dJJDPdoMjdJ7w==";
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
    "@types/react-redux-7.1.16" = {
 | 
			
		||||
      name = "_at_types_slash_react-redux";
 | 
			
		||||
      packageName = "@types/react-redux";
 | 
			
		||||
| 
						 | 
				
			
			@ -1840,6 +1849,15 @@ let
 | 
			
		|||
        sha512 = "7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==";
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
    "@types/three-0.127.0" = {
 | 
			
		||||
      name = "_at_types_slash_three";
 | 
			
		||||
      packageName = "@types/three";
 | 
			
		||||
      version = "0.127.0";
 | 
			
		||||
      src = fetchurl {
 | 
			
		||||
        url = "https://registry.npmjs.org/@types/three/-/three-0.127.0.tgz";
 | 
			
		||||
        sha512 = "4Q33L6PzzxCXm0VdUv4x1/4VBnwWgCS7Ui6WpRh88GVIUUYsg5qU2GVzva14hDJbtfyNBxey7UcdInR4RkKPeQ==";
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
    "abab-2.0.5" = {
 | 
			
		||||
      name = "abab";
 | 
			
		||||
      packageName = "abab";
 | 
			
		||||
| 
						 | 
				
			
			@ -9992,9 +10010,11 @@ let
 | 
			
		|||
      sources."@types/prop-types-15.7.3"
 | 
			
		||||
      sources."@types/q-1.5.4"
 | 
			
		||||
      sources."@types/react-16.14.5"
 | 
			
		||||
      sources."@types/react-dom-17.0.3"
 | 
			
		||||
      sources."@types/react-redux-7.1.16"
 | 
			
		||||
      sources."@types/scheduler-0.16.1"
 | 
			
		||||
      sources."@types/sizzle-2.3.2"
 | 
			
		||||
      sources."@types/three-0.127.0"
 | 
			
		||||
      sources."abab-2.0.5"
 | 
			
		||||
      sources."abortcontroller-polyfill-1.7.1"
 | 
			
		||||
      sources."acorn-3.3.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -10398,6 +10418,7 @@ let
 | 
			
		|||
      (sources."favicons-6.2.1" // {
 | 
			
		||||
        dependencies = [
 | 
			
		||||
          sources."semver-7.3.5"
 | 
			
		||||
          sources."sharp-0.26.3"
 | 
			
		||||
        ];
 | 
			
		||||
      })
 | 
			
		||||
      sources."file-type-9.0.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -11153,11 +11174,6 @@ let
 | 
			
		|||
      })
 | 
			
		||||
      sources."setimmediate-1.0.5"
 | 
			
		||||
      sources."sha.js-2.4.11"
 | 
			
		||||
      (sources."sharp-0.26.3" // {
 | 
			
		||||
        dependencies = [
 | 
			
		||||
          sources."semver-7.3.5"
 | 
			
		||||
        ];
 | 
			
		||||
      })
 | 
			
		||||
      sources."shebang-command-1.2.0"
 | 
			
		||||
      sources."shebang-regex-1.0.0"
 | 
			
		||||
      sources."signal-exit-3.0.3"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										6
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -3191,6 +3191,12 @@
 | 
			
		|||
      "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "@types/three": {
 | 
			
		||||
      "version": "0.127.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/three/-/three-0.127.0.tgz",
 | 
			
		||||
      "integrity": "sha512-4Q33L6PzzxCXm0VdUv4x1/4VBnwWgCS7Ui6WpRh88GVIUUYsg5qU2GVzva14hDJbtfyNBxey7UcdInR4RkKPeQ==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "abab": {
 | 
			
		||||
      "version": "2.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,7 @@
 | 
			
		|||
    "@types/react": "^16.9.41",
 | 
			
		||||
    "@types/react-dom": "^17.0.3",
 | 
			
		||||
    "@types/react-redux": "^7.1.9",
 | 
			
		||||
    "@types/three": "^0.127.0",
 | 
			
		||||
    "jstransformer-markdown-it": "^2.1.0",
 | 
			
		||||
    "parcel": "2.0.0-beta.2",
 | 
			
		||||
    "posthtml-favicons": "^1.3.0",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										196
									
								
								src/index.ts
									
										
									
									
									
								
							
							
						
						
									
										196
									
								
								src/index.ts
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
import $ from "jquery";
 | 
			
		||||
import jQuery from "jquery";
 | 
			
		||||
 | 
			
		||||
// Helpers
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6,114 +6,114 @@ import $ from "jquery";
 | 
			
		|||
 * "Types" out a DOM element, emulating the way a human might.
 | 
			
		||||
 */
 | 
			
		||||
class Typer {
 | 
			
		||||
  private element: JQuery;
 | 
			
		||||
  private text: string;
 | 
			
		||||
  private cursor: boolean;
 | 
			
		||||
  private typed: number;
 | 
			
		||||
  private min: number;
 | 
			
		||||
  private max: number;
 | 
			
		||||
  private blink_tick: number;
 | 
			
		||||
  private blink_timeout: number;
 | 
			
		||||
  private end?: number;
 | 
			
		||||
    private element: JQuery;
 | 
			
		||||
    private text: string;
 | 
			
		||||
    private cursor: boolean;
 | 
			
		||||
    private typed: number;
 | 
			
		||||
    private min: number;
 | 
			
		||||
    private max: number;
 | 
			
		||||
    private blink_tick: number;
 | 
			
		||||
    private blink_timeout: number;
 | 
			
		||||
    private end?: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create the typer.
 | 
			
		||||
   * @param {HTMLElement} element - The element to type.
 | 
			
		||||
   * @param {number} blink - The time between cursor blinks.
 | 
			
		||||
   * @param {number} blink_timeout - How long the cursor should keep
 | 
			
		||||
   *                                 blinking for after the text
 | 
			
		||||
   *                                 finishes typing.
 | 
			
		||||
   */
 | 
			
		||||
  constructor(element: HTMLElement, blink: number, blink_timeout: number) {
 | 
			
		||||
    // Retrieve the current content and wipe it. We also make the
 | 
			
		||||
    // element visible if it was hidden.
 | 
			
		||||
    this.element = $(element);
 | 
			
		||||
    this.text = this.element.html();
 | 
			
		||||
    this.element.html("");
 | 
			
		||||
    this.element.css("visibility", "visible");
 | 
			
		||||
    /**
 | 
			
		||||
     * Create the typer.
 | 
			
		||||
     * @param {HTMLElement} element - The element to type.
 | 
			
		||||
     * @param {number} blink - The time between cursor blinks.
 | 
			
		||||
     * @param {number} blink_timeout - How long the cursor should keep
 | 
			
		||||
     *                                 blinking for after the text
 | 
			
		||||
     *                                 finishes typing.
 | 
			
		||||
     */
 | 
			
		||||
    constructor(element: HTMLElement, blink: number, blink_timeout: number) {
 | 
			
		||||
        // Retrieve the current content and wipe it. We also make the
 | 
			
		||||
        // element visible if it was hidden.
 | 
			
		||||
        this.element = $(element);
 | 
			
		||||
        this.text = this.element.html();
 | 
			
		||||
        this.element.html("");
 | 
			
		||||
        this.element.css("visibility", "visible");
 | 
			
		||||
 | 
			
		||||
    this.cursor = false;
 | 
			
		||||
    this.typed = 0;
 | 
			
		||||
        this.cursor = false;
 | 
			
		||||
        this.typed = 0;
 | 
			
		||||
 | 
			
		||||
    this.min = 20;
 | 
			
		||||
    this.max = 70;
 | 
			
		||||
    this.blink_tick = blink;
 | 
			
		||||
    this.blink_timeout = blink_timeout;
 | 
			
		||||
        this.min = 20;
 | 
			
		||||
        this.max = 70;
 | 
			
		||||
        this.blink_tick = blink;
 | 
			
		||||
        this.blink_timeout = blink_timeout;
 | 
			
		||||
 | 
			
		||||
    this.end = null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Start typing.
 | 
			
		||||
   */
 | 
			
		||||
  type() {
 | 
			
		||||
    this._type();
 | 
			
		||||
    this._blink();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Draw the current text line, i.e., anything that has been typed
 | 
			
		||||
   * so far, and a cursor if it is currently supposed to be on.
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  _draw() {
 | 
			
		||||
    let text = this.text.slice(0, this.typed);
 | 
			
		||||
 | 
			
		||||
    if (this.cursor) {
 | 
			
		||||
      text += "\u2588";
 | 
			
		||||
        this.end = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    window.requestAnimationFrame(() => this.element.html(text));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Type the next character, and prepare to draw the next one. If
 | 
			
		||||
   * no new characters are to be drawn, set the end timestamp.
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  _type() {
 | 
			
		||||
    this.typed += 1;
 | 
			
		||||
    this._draw();
 | 
			
		||||
 | 
			
		||||
    if (this.typed != this.text.length)
 | 
			
		||||
      setTimeout(this._type.bind(this), this._type_tick());
 | 
			
		||||
    else {
 | 
			
		||||
      this.end = Date.now();
 | 
			
		||||
    /**
 | 
			
		||||
     * Start typing.
 | 
			
		||||
     */
 | 
			
		||||
    type() {
 | 
			
		||||
        this._type();
 | 
			
		||||
        this._blink();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Make the cursor change blink status, and prepare for the next
 | 
			
		||||
   * blink.
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  _blink() {
 | 
			
		||||
    this.cursor = !this.cursor;
 | 
			
		||||
    this._draw();
 | 
			
		||||
    /**
 | 
			
		||||
     * Draw the current text line, i.e., anything that has been typed
 | 
			
		||||
     * so far, and a cursor if it is currently supposed to be on.
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _draw() {
 | 
			
		||||
        let text = this.text.slice(0, this.typed);
 | 
			
		||||
 | 
			
		||||
    // As long as we are typing, keep blinking
 | 
			
		||||
    if (this.typed != this.text.length)
 | 
			
		||||
      setTimeout(this._blink.bind(this), this.blink_tick);
 | 
			
		||||
    // Once typing ends, keep going for a little bit
 | 
			
		||||
    else if (Date.now() - this.end < this.blink_timeout)
 | 
			
		||||
      setTimeout(this._blink.bind(this), this.blink_tick);
 | 
			
		||||
    // Make sure we get rid of the cursor in the end
 | 
			
		||||
    else {
 | 
			
		||||
      this.cursor = true;
 | 
			
		||||
      setTimeout(this._blink.bind(this), this.blink_tick);
 | 
			
		||||
        if (this.cursor) {
 | 
			
		||||
            text += "\u2588";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        window.requestAnimationFrame(() => this.element.html(text));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Calculate a "human" time for the next character to type.
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  _type_tick() {
 | 
			
		||||
    return Math.round(Math.random() * this.max) + this.min;
 | 
			
		||||
  }
 | 
			
		||||
    /**
 | 
			
		||||
     * Type the next character, and prepare to draw the next one. If
 | 
			
		||||
     * no new characters are to be drawn, set the end timestamp.
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _type() {
 | 
			
		||||
        this.typed += 1;
 | 
			
		||||
        this._draw();
 | 
			
		||||
 | 
			
		||||
        if (this.typed != this.text.length)
 | 
			
		||||
            setTimeout(this._type.bind(this), this._type_tick());
 | 
			
		||||
        else {
 | 
			
		||||
            this.end = Date.now();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Make the cursor change blink status, and prepare for the next
 | 
			
		||||
     * blink.
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _blink() {
 | 
			
		||||
        this.cursor = !this.cursor;
 | 
			
		||||
        this._draw();
 | 
			
		||||
 | 
			
		||||
        // As long as we are typing, keep blinking
 | 
			
		||||
        if (this.typed != this.text.length)
 | 
			
		||||
            setTimeout(this._blink.bind(this), this.blink_tick);
 | 
			
		||||
        // Once typing ends, keep going for a little bit
 | 
			
		||||
        else if (Date.now() - this.end < this.blink_timeout)
 | 
			
		||||
            setTimeout(this._blink.bind(this), this.blink_tick);
 | 
			
		||||
        // Make sure we get rid of the cursor in the end
 | 
			
		||||
        else {
 | 
			
		||||
            this.cursor = true;
 | 
			
		||||
            setTimeout(this._blink.bind(this), this.blink_tick);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculate a "human" time for the next character to type.
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _type_tick() {
 | 
			
		||||
        return Math.round(Math.random() * this.max) + this.min;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$(document).ready(() => {
 | 
			
		||||
  let typer = new Typer($(".head-line .typed").get(0), 500, 3000);
 | 
			
		||||
  typer.type();
 | 
			
		||||
jQuery(($) => {
 | 
			
		||||
    let typer = new Typer($(".head-line .typed").get(0), 500, 3000);
 | 
			
		||||
    typer.type();
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
import $ from "jquery";
 | 
			
		||||
import jQuery from "jquery";
 | 
			
		||||
 | 
			
		||||
$(document).ready(() => $("html").removeClass("no-js"));
 | 
			
		||||
jQuery(($) => {
 | 
			
		||||
    $("html").removeClass("no-js")
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,89 +6,89 @@ import Visualizer from "./components/visualizer";
 | 
			
		|||
import { State } from "./store";
 | 
			
		||||
 | 
			
		||||
type AudioState = {
 | 
			
		||||
  audioContext: AudioContext;
 | 
			
		||||
  audioSource: HTMLAudioElement;
 | 
			
		||||
  audioSourceNode: MediaElementAudioSourceNode;
 | 
			
		||||
  audioVolume: GainNode;
 | 
			
		||||
    audioContext: AudioContext;
 | 
			
		||||
    audioSource: HTMLAudioElement;
 | 
			
		||||
    audioSourceNode: MediaElementAudioSourceNode;
 | 
			
		||||
    audioVolume: GainNode;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type MusicPlayerProps = {
 | 
			
		||||
  playing: boolean;
 | 
			
		||||
  muted: boolean;
 | 
			
		||||
  source?: string;
 | 
			
		||||
    playing: boolean;
 | 
			
		||||
    muted: boolean;
 | 
			
		||||
    source?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MusicPlayer extends React.Component<MusicPlayerProps, {}> {
 | 
			
		||||
  private audioState: AudioState;
 | 
			
		||||
    private audioState: AudioState;
 | 
			
		||||
 | 
			
		||||
  constructor(props: MusicPlayerProps) {
 | 
			
		||||
    super(props);
 | 
			
		||||
    constructor(props: MusicPlayerProps) {
 | 
			
		||||
        super(props);
 | 
			
		||||
 | 
			
		||||
    let context = new AudioContext();
 | 
			
		||||
    let source = new Audio();
 | 
			
		||||
    let sourceNode = context.createMediaElementSource(source);
 | 
			
		||||
    let volume = context.createGain();
 | 
			
		||||
        let context = new AudioContext();
 | 
			
		||||
        let source = new Audio();
 | 
			
		||||
        let sourceNode = context.createMediaElementSource(source);
 | 
			
		||||
        let volume = context.createGain();
 | 
			
		||||
 | 
			
		||||
    sourceNode.connect(volume);
 | 
			
		||||
    volume.connect(context.destination);
 | 
			
		||||
        sourceNode.connect(volume);
 | 
			
		||||
        volume.connect(context.destination);
 | 
			
		||||
 | 
			
		||||
    this.audioState = {
 | 
			
		||||
      audioContext: context,
 | 
			
		||||
      audioSourceNode: sourceNode,
 | 
			
		||||
      audioSource: source,
 | 
			
		||||
      audioVolume: volume,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    return (
 | 
			
		||||
      <div id="player" style={{ height: "100%", width: "100%" }}>
 | 
			
		||||
        <Visualizer
 | 
			
		||||
          audioContext={this.audioState.audioContext}
 | 
			
		||||
          audioSource={this.audioState.audioSourceNode}
 | 
			
		||||
        />
 | 
			
		||||
        <Controls />
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentDidUpdate() {
 | 
			
		||||
    let context = this.audioState.audioContext;
 | 
			
		||||
    let source = this.audioState.audioSource;
 | 
			
		||||
    let volume = this.audioState.audioVolume;
 | 
			
		||||
 | 
			
		||||
    // First, set the audio source (if it changed)
 | 
			
		||||
    if (this.props.source && source.src != this.props.source) {
 | 
			
		||||
      source.src = this.props.source;
 | 
			
		||||
        this.audioState = {
 | 
			
		||||
            audioContext: context,
 | 
			
		||||
            audioSourceNode: sourceNode,
 | 
			
		||||
            audioSource: source,
 | 
			
		||||
            audioVolume: volume,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.props.playing) {
 | 
			
		||||
      source
 | 
			
		||||
        .play()
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          console.info("Started playing audio");
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error(`Could not play audio: ${error}`);
 | 
			
		||||
        });
 | 
			
		||||
    } else {
 | 
			
		||||
      source.pause();
 | 
			
		||||
    render() {
 | 
			
		||||
        return (
 | 
			
		||||
            <div id="player" style={{ height: "100%", width: "100%" }}>
 | 
			
		||||
                <Visualizer
 | 
			
		||||
                    audioContext={this.audioState.audioContext}
 | 
			
		||||
                    audioSource={this.audioState.audioSourceNode}
 | 
			
		||||
                />
 | 
			
		||||
                <Controls />
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!this.props.muted) {
 | 
			
		||||
      volume.gain.setValueAtTime(1, context.currentTime);
 | 
			
		||||
    } else {
 | 
			
		||||
      volume.gain.setValueAtTime(0, context.currentTime);
 | 
			
		||||
    componentDidUpdate() {
 | 
			
		||||
        let context = this.audioState.audioContext;
 | 
			
		||||
        let source = this.audioState.audioSource;
 | 
			
		||||
        let volume = this.audioState.audioVolume;
 | 
			
		||||
 | 
			
		||||
        // First, set the audio source (if it changed)
 | 
			
		||||
        if (this.props.source && source.src != this.props.source) {
 | 
			
		||||
            source.src = this.props.source;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.props.playing) {
 | 
			
		||||
            source
 | 
			
		||||
                .play()
 | 
			
		||||
                .then(() => {
 | 
			
		||||
                    console.info("Started playing audio");
 | 
			
		||||
                })
 | 
			
		||||
                .catch((error) => {
 | 
			
		||||
                    console.error(`Could not play audio: ${error}`);
 | 
			
		||||
                });
 | 
			
		||||
        } else {
 | 
			
		||||
            source.pause();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.props.muted) {
 | 
			
		||||
            volume.gain.setValueAtTime(1, context.currentTime);
 | 
			
		||||
        } else {
 | 
			
		||||
            volume.gain.setValueAtTime(0, context.currentTime);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function mapStateToProps(state: State): MusicPlayerProps {
 | 
			
		||||
  return {
 | 
			
		||||
    playing: state.musicState.playing,
 | 
			
		||||
    muted: state.musicState.muted,
 | 
			
		||||
    source: state.musicState.source,
 | 
			
		||||
  };
 | 
			
		||||
    return {
 | 
			
		||||
        playing: state.musicState.playing,
 | 
			
		||||
        muted: state.musicState.muted,
 | 
			
		||||
        source: state.musicState.source,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default connect(mapStateToProps)(MusicPlayer);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,50 +1,49 @@
 | 
			
		|||
import React from "react";
 | 
			
		||||
import Redux from "redux";
 | 
			
		||||
import { connect } from "react-redux";
 | 
			
		||||
 | 
			
		||||
import { State } from "../store";
 | 
			
		||||
import { Title, togglePlay } from "../store/music/types";
 | 
			
		||||
import { Title } from "../store/music/types";
 | 
			
		||||
import Indicator from "./indicator";
 | 
			
		||||
 | 
			
		||||
type ControlProps = {
 | 
			
		||||
  title: Title;
 | 
			
		||||
    title: Title;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Controls extends React.Component<ControlProps, {}> {
 | 
			
		||||
  render() {
 | 
			
		||||
    return (
 | 
			
		||||
      <div id="playerControls" className="container-fluid fixed-bottom">
 | 
			
		||||
        <div className="align-items-center row p-2">
 | 
			
		||||
          <Indicator></Indicator>
 | 
			
		||||
          <div
 | 
			
		||||
            id="playerText"
 | 
			
		||||
            className="text-justify text-truncate col-6 playerControlsContent"
 | 
			
		||||
          >
 | 
			
		||||
            {this.props.title.name} - {this.props.title.album}
 | 
			
		||||
          </div>
 | 
			
		||||
    render() {
 | 
			
		||||
        return (
 | 
			
		||||
            <div id="playerControls" className="container-fluid fixed-bottom">
 | 
			
		||||
                <div className="align-items-center row p-2">
 | 
			
		||||
                    <Indicator></Indicator>
 | 
			
		||||
                    <div
 | 
			
		||||
                        id="playerText"
 | 
			
		||||
                        className="text-justify text-truncate col-6 playerControlsContent"
 | 
			
		||||
                    >
 | 
			
		||||
                        {this.props.title.name} - {this.props.title.album}
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
          {this.props.title.name === "Journey" &&
 | 
			
		||||
          this.props.title.artist === "Mseq" ? (
 | 
			
		||||
            <div id="copyrightNotice" className="col text-center">
 | 
			
		||||
              <a href="http://dig.ccmixter.org/files/Mseq/54702">Journey</a>
 | 
			
		||||
                    {this.props.title.name === "Journey" &&
 | 
			
		||||
                        this.props.title.artist === "Mseq" ? (
 | 
			
		||||
                            <div id="copyrightNotice" className="col text-center">
 | 
			
		||||
                                <a href="http://dig.ccmixter.org/files/Mseq/54702">Journey</a>
 | 
			
		||||
               by Mseq (c) copyright 2016 Licensed under a Creative
 | 
			
		||||
              Commons 
 | 
			
		||||
              <a href="http://creativecommons.org/licenses/by-nc/3.0/">
 | 
			
		||||
                Attribution Noncommercial (3.0)
 | 
			
		||||
                                <a href="http://creativecommons.org/licenses/by-nc/3.0/">
 | 
			
		||||
                                    Attribution Noncommercial (3.0)
 | 
			
		||||
              </a>
 | 
			
		||||
                license. Ft: Admiral Bob,Texas Radio Fish
 | 
			
		||||
                            </div>
 | 
			
		||||
                        ) : null}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          ) : null}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function mapStateToProps(state: State): ControlProps {
 | 
			
		||||
  return {
 | 
			
		||||
    title: state.musicState.title,
 | 
			
		||||
  };
 | 
			
		||||
    return {
 | 
			
		||||
        title: state.musicState.title,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default connect(mapStateToProps)(Controls);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,59 +2,59 @@ import React from "react";
 | 
			
		|||
import { connect } from "react-redux";
 | 
			
		||||
import classNames from "classnames";
 | 
			
		||||
 | 
			
		||||
import { State } from "../store";
 | 
			
		||||
import { MusicState, togglePlay } from "../store/music/types";
 | 
			
		||||
import { State, Dispatch } from "../store";
 | 
			
		||||
import { togglePlay } from "../store/music/types";
 | 
			
		||||
 | 
			
		||||
type IndicatorProps = {
 | 
			
		||||
  muted: boolean;
 | 
			
		||||
  playing: boolean;
 | 
			
		||||
    muted: boolean;
 | 
			
		||||
    playing: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type IndicatorDispatch = {
 | 
			
		||||
  play: () => void;
 | 
			
		||||
    play: () => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type Props = IndicatorProps & IndicatorDispatch;
 | 
			
		||||
 | 
			
		||||
class Indicator extends React.Component<Props, {}> {
 | 
			
		||||
  click() {
 | 
			
		||||
    this.props.play();
 | 
			
		||||
  }
 | 
			
		||||
    click() {
 | 
			
		||||
        this.props.play();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    let classes = classNames({
 | 
			
		||||
      btn: true,
 | 
			
		||||
      "col-auto": true,
 | 
			
		||||
      fas: true,
 | 
			
		||||
      "fa-muted": this.props.muted,
 | 
			
		||||
      "fa-play": this.props.playing,
 | 
			
		||||
      "fa-pause": !this.props.playing,
 | 
			
		||||
    });
 | 
			
		||||
    render() {
 | 
			
		||||
        let classes = classNames({
 | 
			
		||||
            btn: true,
 | 
			
		||||
            "col-auto": true,
 | 
			
		||||
            fas: true,
 | 
			
		||||
            "fa-muted": this.props.muted,
 | 
			
		||||
            "fa-play": this.props.playing,
 | 
			
		||||
            "fa-pause": !this.props.playing,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <button
 | 
			
		||||
        type="button"
 | 
			
		||||
        id="playerIndicator"
 | 
			
		||||
        onClick={this.click.bind(this)}
 | 
			
		||||
        className={classes}
 | 
			
		||||
      ></button>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
        return (
 | 
			
		||||
            <button
 | 
			
		||||
                type="button"
 | 
			
		||||
                id="playerIndicator"
 | 
			
		||||
                onClick={this.click.bind(this)}
 | 
			
		||||
                className={classes}
 | 
			
		||||
            ></button>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function mapStateToProps(state: State): IndicatorProps {
 | 
			
		||||
  return {
 | 
			
		||||
    muted: state.musicState.muted,
 | 
			
		||||
    playing: state.musicState.playing,
 | 
			
		||||
  };
 | 
			
		||||
    return {
 | 
			
		||||
        muted: state.musicState.muted,
 | 
			
		||||
        playing: state.musicState.playing,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function mapDispatchToProps(dispatch: Redux.Dispatch<any>): IndicatorDispatch {
 | 
			
		||||
  return {
 | 
			
		||||
    play: () => {
 | 
			
		||||
      dispatch(togglePlay());
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
function mapDispatchToProps(dispatch: Dispatch): IndicatorDispatch {
 | 
			
		||||
    return {
 | 
			
		||||
        play: () => {
 | 
			
		||||
            dispatch(togglePlay());
 | 
			
		||||
        },
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default connect(mapStateToProps, mapDispatchToProps)(Indicator);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,217 +2,217 @@ import React from "react";
 | 
			
		|||
import * as three from "three";
 | 
			
		||||
 | 
			
		||||
type VisualizerProps = {
 | 
			
		||||
  audioContext: AudioContext;
 | 
			
		||||
  audioSource: AudioNode;
 | 
			
		||||
    audioContext: AudioContext;
 | 
			
		||||
    audioSource: AudioNode;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class CanvasDrawer {
 | 
			
		||||
  private analyser: AnalyserNode;
 | 
			
		||||
  private canvas: HTMLCanvasElement;
 | 
			
		||||
    private analyser: AnalyserNode;
 | 
			
		||||
    private canvas: HTMLCanvasElement;
 | 
			
		||||
 | 
			
		||||
  private analyserData: Float32Array;
 | 
			
		||||
    private analyserData: Float32Array;
 | 
			
		||||
 | 
			
		||||
  private boxes: Array<three.Mesh>;
 | 
			
		||||
  private camera: three.Camera;
 | 
			
		||||
  private renderer: three.Renderer;
 | 
			
		||||
  private scene: three.Scene;
 | 
			
		||||
    private boxes: Array<three.Mesh>;
 | 
			
		||||
    private camera: three.PerspectiveCamera;
 | 
			
		||||
    private renderer: three.WebGLRenderer;
 | 
			
		||||
    private scene: three.Scene;
 | 
			
		||||
 | 
			
		||||
  private angle: number;
 | 
			
		||||
    private angle: number;
 | 
			
		||||
 | 
			
		||||
  private animationFrame: number;
 | 
			
		||||
  private lastTime: number;
 | 
			
		||||
    private animationFrame: number;
 | 
			
		||||
    private lastTime: number;
 | 
			
		||||
 | 
			
		||||
  constructor(analyser: AnalyserNode, canvas: HTMLCanvasElement) {
 | 
			
		||||
    this.analyser = analyser;
 | 
			
		||||
    this.canvas = canvas;
 | 
			
		||||
    constructor(analyser: AnalyserNode, canvas: HTMLCanvasElement) {
 | 
			
		||||
        this.analyser = analyser;
 | 
			
		||||
        this.canvas = canvas;
 | 
			
		||||
 | 
			
		||||
    // Set up analyser data storage
 | 
			
		||||
    this.analyserData = new Float32Array(analyser.frequencyBinCount);
 | 
			
		||||
        // Set up analyser data storage
 | 
			
		||||
        this.analyserData = new Float32Array(analyser.frequencyBinCount);
 | 
			
		||||
 | 
			
		||||
    // Initialize the scene
 | 
			
		||||
    this.scene = new three.Scene();
 | 
			
		||||
        // Initialize the scene
 | 
			
		||||
        this.scene = new three.Scene();
 | 
			
		||||
 | 
			
		||||
    // Make a bunch of boxes to represent the bars
 | 
			
		||||
    this.boxes = Array(analyser.frequencyBinCount);
 | 
			
		||||
    let width = 2 / analyser.frequencyBinCount;
 | 
			
		||||
    for (let freq = 0; freq < analyser.frequencyBinCount; freq++) {
 | 
			
		||||
      let geometry = new three.BoxGeometry(1, 1, 1);
 | 
			
		||||
      let material = new three.MeshLambertMaterial({
 | 
			
		||||
        color: new three.Color(0x99d1ce),
 | 
			
		||||
      });
 | 
			
		||||
      let cube = new three.Mesh(geometry, material);
 | 
			
		||||
        // Make a bunch of boxes to represent the bars
 | 
			
		||||
        this.boxes = Array(analyser.frequencyBinCount);
 | 
			
		||||
        let width = 2 / analyser.frequencyBinCount;
 | 
			
		||||
        for (let freq = 0; freq < analyser.frequencyBinCount; freq++) {
 | 
			
		||||
            let geometry = new three.BoxGeometry(1, 1, 1);
 | 
			
		||||
            let material = new three.MeshLambertMaterial({
 | 
			
		||||
                color: new three.Color(0x99d1ce),
 | 
			
		||||
            });
 | 
			
		||||
            let cube = new three.Mesh(geometry, material);
 | 
			
		||||
 | 
			
		||||
      cube.scale.set(width, 1e-6, width);
 | 
			
		||||
      cube.position.set(-1 + freq * width, 0, 0);
 | 
			
		||||
            cube.scale.set(width, 1e-6, width);
 | 
			
		||||
            cube.position.set(-1 + freq * width, 0, 0);
 | 
			
		||||
 | 
			
		||||
      this.scene.add(cube);
 | 
			
		||||
      this.boxes[freq] = cube;
 | 
			
		||||
            this.scene.add(cube);
 | 
			
		||||
            this.boxes[freq] = cube;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add lights for shadowing
 | 
			
		||||
        let ambientLight = new three.AmbientLight(0xffffff, 0.4);
 | 
			
		||||
        this.scene.add(ambientLight);
 | 
			
		||||
 | 
			
		||||
        let directionalLight = new three.DirectionalLight(0xffffff, 1);
 | 
			
		||||
        directionalLight.position.set(-1, 0.3, -1);
 | 
			
		||||
        directionalLight.castShadow = true;
 | 
			
		||||
        this.scene.add(directionalLight);
 | 
			
		||||
 | 
			
		||||
        // Add a camera
 | 
			
		||||
        this.angle = 3;
 | 
			
		||||
        this.camera = new three.PerspectiveCamera(
 | 
			
		||||
            70,
 | 
			
		||||
            canvas.width / canvas.height,
 | 
			
		||||
            0.01,
 | 
			
		||||
            10
 | 
			
		||||
        );
 | 
			
		||||
        this.camera.lookAt(0, 0, 0);
 | 
			
		||||
        this.scene.add(this.camera);
 | 
			
		||||
        this.rotateCamera(1);
 | 
			
		||||
 | 
			
		||||
        // Add a renderer
 | 
			
		||||
        this.renderer = new three.WebGLRenderer({
 | 
			
		||||
            antialias: true,
 | 
			
		||||
            canvas: canvas,
 | 
			
		||||
            powerPreference: "low-power",
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.renderer.setClearColor(new three.Color(0x0f0f0f), 1.0);
 | 
			
		||||
        this.renderer.setSize(canvas.width, canvas.height);
 | 
			
		||||
 | 
			
		||||
        // Set up canvas resizing
 | 
			
		||||
        window.addEventListener("resize", this.resize.bind(this));
 | 
			
		||||
 | 
			
		||||
        // Run the first, set the first animation frame time and start requesting
 | 
			
		||||
        // animation frames
 | 
			
		||||
        this.resize();
 | 
			
		||||
        this.lastTime = 0;
 | 
			
		||||
        this.animationFrame = requestAnimationFrame(this.render.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add lights for shadowing
 | 
			
		||||
    let ambientLight = new three.AmbientLight(0xffffff, 0.4);
 | 
			
		||||
    this.scene.add(ambientLight);
 | 
			
		||||
    render(time: number) {
 | 
			
		||||
        // Set our animation frame to 0, so that if we stop, we don't try to cancel a past animation frame
 | 
			
		||||
        this.animationFrame = 0;
 | 
			
		||||
        // Update elapsed time
 | 
			
		||||
        let elapsed = time - this.lastTime;
 | 
			
		||||
        this.lastTime = time;
 | 
			
		||||
 | 
			
		||||
    let directionalLight = new three.DirectionalLight(0xffffff, 1);
 | 
			
		||||
    directionalLight.position.set(-1, 0.3, -1);
 | 
			
		||||
    directionalLight.castShadow = true;
 | 
			
		||||
    this.scene.add(directionalLight);
 | 
			
		||||
        let camera = this.camera;
 | 
			
		||||
        let renderer = this.renderer;
 | 
			
		||||
        let scene = this.scene;
 | 
			
		||||
 | 
			
		||||
    // Add a camera
 | 
			
		||||
    this.angle = 3;
 | 
			
		||||
    this.camera = new three.PerspectiveCamera(
 | 
			
		||||
      70,
 | 
			
		||||
      canvas.width / canvas.height,
 | 
			
		||||
      0.01,
 | 
			
		||||
      10
 | 
			
		||||
    );
 | 
			
		||||
    this.camera.lookAt(0, 0, 0);
 | 
			
		||||
    this.scene.add(this.camera);
 | 
			
		||||
    this.rotateCamera(1);
 | 
			
		||||
        this.scaleBoxes();
 | 
			
		||||
        this.rotateCamera(elapsed);
 | 
			
		||||
 | 
			
		||||
    // Add a renderer
 | 
			
		||||
    this.renderer = new three.WebGLRenderer({
 | 
			
		||||
      antialias: true,
 | 
			
		||||
      canvas: canvas,
 | 
			
		||||
      powerPreference: "low-power",
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.renderer.setClearColor(new three.Color(0x0f0f0f));
 | 
			
		||||
    this.renderer.setSize(canvas.width, canvas.height);
 | 
			
		||||
 | 
			
		||||
    // Set up canvas resizing
 | 
			
		||||
    window.addEventListener("resize", this.resize.bind(this));
 | 
			
		||||
 | 
			
		||||
    // Run the first, set the first animation frame time and start requesting
 | 
			
		||||
    // animation frames
 | 
			
		||||
    this.resize();
 | 
			
		||||
    this.lastTime = 0;
 | 
			
		||||
    this.animationFrame = requestAnimationFrame(this.render.bind(this));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render(time: number) {
 | 
			
		||||
    // Set our animation frame to 0, so that if we stop, we don't try to cancel a past animation frame
 | 
			
		||||
    this.animationFrame = 0;
 | 
			
		||||
    // Update elapsed time
 | 
			
		||||
    let elapsed = time - this.lastTime;
 | 
			
		||||
    this.lastTime = time;
 | 
			
		||||
 | 
			
		||||
    let camera = this.camera;
 | 
			
		||||
    let renderer = this.renderer;
 | 
			
		||||
    let scene = this.scene;
 | 
			
		||||
 | 
			
		||||
    this.scaleBoxes();
 | 
			
		||||
    this.rotateCamera(elapsed);
 | 
			
		||||
 | 
			
		||||
    renderer.render(scene, camera);
 | 
			
		||||
    this.animationFrame = requestAnimationFrame(this.render.bind(this));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  scaleBoxes() {
 | 
			
		||||
    let analyser = this.analyser;
 | 
			
		||||
 | 
			
		||||
    analyser.getFloatFrequencyData(this.analyserData);
 | 
			
		||||
 | 
			
		||||
    for (let freq = 0; freq < analyser.frequencyBinCount; freq++) {
 | 
			
		||||
      let height = analyser.maxDecibels / this.analyserData[freq];
 | 
			
		||||
 | 
			
		||||
      if (height > 0.3) {
 | 
			
		||||
        height -= 0.3;
 | 
			
		||||
      } else {
 | 
			
		||||
        height = 1e-6;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.boxes[freq].scale.y = height;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  rotateCamera(elapsed: number) {
 | 
			
		||||
    if (this.angle >= Math.PI * 2) {
 | 
			
		||||
      this.angle = 0;
 | 
			
		||||
    } else {
 | 
			
		||||
      this.angle += 0.1 * (elapsed / 1000);
 | 
			
		||||
        renderer.render(scene, camera);
 | 
			
		||||
        this.animationFrame = requestAnimationFrame(this.render.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let camera = this.camera;
 | 
			
		||||
    let angle = this.angle;
 | 
			
		||||
    scaleBoxes() {
 | 
			
		||||
        let analyser = this.analyser;
 | 
			
		||||
 | 
			
		||||
    camera.position.x = 1.01 * Math.sin(angle);
 | 
			
		||||
    camera.position.z = 1.01 * Math.cos(angle);
 | 
			
		||||
        analyser.getFloatFrequencyData(this.analyserData);
 | 
			
		||||
 | 
			
		||||
    /* camera.position.y = (1 - Math.abs(angle - 0.5) / 0.5); */
 | 
			
		||||
    camera.lookAt(0, 0, 0);
 | 
			
		||||
  }
 | 
			
		||||
        for (let freq = 0; freq < analyser.frequencyBinCount; freq++) {
 | 
			
		||||
            let height = analyser.maxDecibels / this.analyserData[freq];
 | 
			
		||||
 | 
			
		||||
  resize() {
 | 
			
		||||
    let canvas = this.canvas;
 | 
			
		||||
    if (canvas.parentElement === null) {
 | 
			
		||||
      throw Error("Could not access canvas parent for size calculation");
 | 
			
		||||
            if (height > 0.3) {
 | 
			
		||||
                height -= 0.3;
 | 
			
		||||
            } else {
 | 
			
		||||
                height = 1e-6;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.boxes[freq].scale.y = height;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Compute the height of all our siblings
 | 
			
		||||
    let combinedHeight = 0;
 | 
			
		||||
    for (let i = 0; i < canvas.parentElement.children.length; i++) {
 | 
			
		||||
      const child = canvas.parentElement.children[i];
 | 
			
		||||
    rotateCamera(elapsed: number) {
 | 
			
		||||
        if (this.angle >= Math.PI * 2) {
 | 
			
		||||
            this.angle = 0;
 | 
			
		||||
        } else {
 | 
			
		||||
            this.angle += 0.1 * (elapsed / 1000);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
      if (child != canvas) {
 | 
			
		||||
        combinedHeight += child.clientHeight;
 | 
			
		||||
      }
 | 
			
		||||
        let camera = this.camera;
 | 
			
		||||
        let angle = this.angle;
 | 
			
		||||
 | 
			
		||||
        camera.position.x = 1.01 * Math.sin(angle);
 | 
			
		||||
        camera.position.z = 1.01 * Math.cos(angle);
 | 
			
		||||
 | 
			
		||||
        /* camera.position.y = (1 - Math.abs(angle - 0.5) / 0.5); */
 | 
			
		||||
        camera.lookAt(0, 0, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The remaining space we want to fill
 | 
			
		||||
    let remainingHeight = canvas.parentElement.clientHeight - combinedHeight;
 | 
			
		||||
    canvas.height = remainingHeight;
 | 
			
		||||
    canvas.width = canvas.parentElement.clientWidth;
 | 
			
		||||
    resize() {
 | 
			
		||||
        let canvas = this.canvas;
 | 
			
		||||
        if (canvas.parentElement === null) {
 | 
			
		||||
            throw Error("Could not access canvas parent for size calculation");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    this.camera.aspect = canvas.width / remainingHeight;
 | 
			
		||||
    this.camera.updateProjectionMatrix();
 | 
			
		||||
    this.renderer.setSize(canvas.width, remainingHeight);
 | 
			
		||||
  }
 | 
			
		||||
        // Compute the height of all our siblings
 | 
			
		||||
        let combinedHeight = 0;
 | 
			
		||||
        for (let i = 0; i < canvas.parentElement.children.length; i++) {
 | 
			
		||||
            const child = canvas.parentElement.children[i];
 | 
			
		||||
 | 
			
		||||
  stop() {
 | 
			
		||||
    if (this.animationFrame != 0) {
 | 
			
		||||
      cancelAnimationFrame(this.animationFrame);
 | 
			
		||||
            if (child != canvas) {
 | 
			
		||||
                combinedHeight += child.clientHeight;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The remaining space we want to fill
 | 
			
		||||
        let remainingHeight = canvas.parentElement.clientHeight - combinedHeight;
 | 
			
		||||
        canvas.height = remainingHeight;
 | 
			
		||||
        canvas.width = canvas.parentElement.clientWidth;
 | 
			
		||||
 | 
			
		||||
        this.camera.aspect = canvas.width / remainingHeight;
 | 
			
		||||
        this.camera.updateProjectionMatrix();
 | 
			
		||||
        this.renderer.setSize(canvas.width, remainingHeight);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    stop() {
 | 
			
		||||
        if (this.animationFrame != 0) {
 | 
			
		||||
            cancelAnimationFrame(this.animationFrame);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Visualizer extends React.Component<VisualizerProps, {}> {
 | 
			
		||||
  private analyser: AnalyserNode;
 | 
			
		||||
  private canvas: React.RefObject<HTMLCanvasElement>;
 | 
			
		||||
  private drawer: CanvasDrawer;
 | 
			
		||||
    private analyser: AnalyserNode;
 | 
			
		||||
    private canvas: React.RefObject<HTMLCanvasElement>;
 | 
			
		||||
    private drawer: CanvasDrawer;
 | 
			
		||||
 | 
			
		||||
  constructor(props: VisualizerProps) {
 | 
			
		||||
    super(props);
 | 
			
		||||
    this.canvas = React.createRef();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    return (
 | 
			
		||||
      <canvas
 | 
			
		||||
        id="visualizer"
 | 
			
		||||
        ref={this.canvas}
 | 
			
		||||
        style={{ width: "100%", height: "100%" }}
 | 
			
		||||
      ></canvas>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentDidMount() {
 | 
			
		||||
    if (this.canvas.current === null) {
 | 
			
		||||
      throw Error("Failed to create canvas; aborting");
 | 
			
		||||
    constructor(props: VisualizerProps) {
 | 
			
		||||
        super(props);
 | 
			
		||||
        this.canvas = React.createRef();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.analyser = this.props.audioContext.createAnalyser();
 | 
			
		||||
    this.analyser.fftSize = 2048;
 | 
			
		||||
    this.analyser.smoothingTimeConstant = 0.8;
 | 
			
		||||
    this.props.audioSource.connect(this.analyser);
 | 
			
		||||
    render() {
 | 
			
		||||
        return (
 | 
			
		||||
            <canvas
 | 
			
		||||
                id="visualizer"
 | 
			
		||||
                ref={this.canvas}
 | 
			
		||||
                style={{ width: "100%", height: "100%" }}
 | 
			
		||||
            ></canvas>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.drawer = new CanvasDrawer(this.analyser, this.canvas.current);
 | 
			
		||||
  }
 | 
			
		||||
    componentDidMount() {
 | 
			
		||||
        if (this.canvas.current === null) {
 | 
			
		||||
            throw Error("Failed to create canvas; aborting");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
  componentWillUnmount() {
 | 
			
		||||
    this.drawer.stop();
 | 
			
		||||
    this.props.audioSource.disconnect(this.analyser);
 | 
			
		||||
  }
 | 
			
		||||
        this.analyser = this.props.audioContext.createAnalyser();
 | 
			
		||||
        this.analyser.fftSize = 2048;
 | 
			
		||||
        this.analyser.smoothingTimeConstant = 0.8;
 | 
			
		||||
        this.props.audioSource.connect(this.analyser);
 | 
			
		||||
 | 
			
		||||
        this.drawer = new CanvasDrawer(this.analyser, this.canvas.current);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentWillUnmount() {
 | 
			
		||||
        this.drawer.stop();
 | 
			
		||||
        this.props.audioSource.disconnect(this.analyser);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Visualizer;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,23 +5,24 @@ import { Provider } from "react-redux";
 | 
			
		|||
import { store } from "./store";
 | 
			
		||||
import MusicPlayer from "./MusicPlayer";
 | 
			
		||||
import { setSource, setTitle } from "./store/music/types";
 | 
			
		||||
// @ts-ignore Can't find module - this is an mp3 file, so...
 | 
			
		||||
import mseq from "./Mseq_-_Journey.mp3";
 | 
			
		||||
 | 
			
		||||
const rootElement = document.getElementById("playerUI");
 | 
			
		||||
 | 
			
		||||
ReactDOM.render(
 | 
			
		||||
  <Provider store={store}>
 | 
			
		||||
    <MusicPlayer />
 | 
			
		||||
  </Provider>,
 | 
			
		||||
  rootElement
 | 
			
		||||
    <Provider store={store}>
 | 
			
		||||
        <MusicPlayer />
 | 
			
		||||
    </Provider>,
 | 
			
		||||
    rootElement
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
store.dispatch(setSource(mseq));
 | 
			
		||||
store.dispatch(
 | 
			
		||||
  setTitle({
 | 
			
		||||
    name: "Journey",
 | 
			
		||||
    artist: "Mseq",
 | 
			
		||||
    album: "Unknown album",
 | 
			
		||||
    length: 192052244,
 | 
			
		||||
  })
 | 
			
		||||
    setTitle({
 | 
			
		||||
        name: "Journey",
 | 
			
		||||
        artist: "Mseq",
 | 
			
		||||
        album: "Unknown album",
 | 
			
		||||
        length: 192052244,
 | 
			
		||||
    })
 | 
			
		||||
);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,14 +4,17 @@ import { MusicState } from "./music/types";
 | 
			
		|||
import { musicStateReducer } from "./music/reducers";
 | 
			
		||||
 | 
			
		||||
export interface State {
 | 
			
		||||
  musicState: MusicState;
 | 
			
		||||
    musicState: MusicState;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const rootReducer = combineReducers<State>({
 | 
			
		||||
  musicState: musicStateReducer,
 | 
			
		||||
    musicState: musicStateReducer,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const store = createStore(
 | 
			
		||||
  rootReducer,
 | 
			
		||||
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
 | 
			
		||||
    rootReducer,
 | 
			
		||||
    // @ts-ignore Missing property - this will only exist if the devtools extension is installed, and is actually what we're checking for
 | 
			
		||||
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export type Dispatch = typeof store.dispatch;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,44 +2,44 @@ import { createReducer } from "redux-act";
 | 
			
		|||
import update from "immutability-helper";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  Title,
 | 
			
		||||
  MusicState,
 | 
			
		||||
  setTitle,
 | 
			
		||||
  toggleMute,
 | 
			
		||||
  togglePlay,
 | 
			
		||||
  setSource,
 | 
			
		||||
    Title,
 | 
			
		||||
    MusicState,
 | 
			
		||||
    setTitle,
 | 
			
		||||
    toggleMute,
 | 
			
		||||
    togglePlay,
 | 
			
		||||
    setSource,
 | 
			
		||||
} from "./types";
 | 
			
		||||
 | 
			
		||||
const defaultTitle: Title = {
 | 
			
		||||
  name: "Untitled",
 | 
			
		||||
  artist: "Unknown Artist",
 | 
			
		||||
  album: "Unknown Album",
 | 
			
		||||
  length: 0,
 | 
			
		||||
    name: "Untitled",
 | 
			
		||||
    artist: "Unknown Artist",
 | 
			
		||||
    album: "Unknown Album",
 | 
			
		||||
    length: 0,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const initialState: MusicState = {
 | 
			
		||||
  muted: false,
 | 
			
		||||
  playing: false,
 | 
			
		||||
  title: defaultTitle,
 | 
			
		||||
  playTime: 0,
 | 
			
		||||
    muted: false,
 | 
			
		||||
    playing: false,
 | 
			
		||||
    title: defaultTitle,
 | 
			
		||||
    playTime: 0,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const musicStateReducer = createReducer<MusicState>(
 | 
			
		||||
  {
 | 
			
		||||
    [setTitle]: (state: MusicState, title: Title): MusicState => {
 | 
			
		||||
      return update(state, {
 | 
			
		||||
        title: { $set: title },
 | 
			
		||||
      });
 | 
			
		||||
    {
 | 
			
		||||
        [setTitle]: (state: MusicState, title: Title): MusicState => {
 | 
			
		||||
            return update(state, {
 | 
			
		||||
                title: { $set: title },
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        [togglePlay]: (state: MusicState): MusicState => {
 | 
			
		||||
            return update(state, { $toggle: ["playing"] });
 | 
			
		||||
        },
 | 
			
		||||
        [toggleMute]: (state: MusicState): MusicState => {
 | 
			
		||||
            return update(state, { $toggle: ["muted"] });
 | 
			
		||||
        },
 | 
			
		||||
        [setSource]: (state: MusicState, source: string): MusicState => {
 | 
			
		||||
            return update(state, { source: { $set: source } });
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    [togglePlay]: (state: MusicState): MusicState => {
 | 
			
		||||
      return update(state, { $toggle: ["playing"] });
 | 
			
		||||
    },
 | 
			
		||||
    [toggleMute]: (state: MusicState): MusicState => {
 | 
			
		||||
      return update(state, { $toggle: ["muted"] });
 | 
			
		||||
    },
 | 
			
		||||
    [setSource]: (state: MusicState, source: string): MusicState => {
 | 
			
		||||
      return update(state, { source: { $set: source } });
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  initialState
 | 
			
		||||
    initialState
 | 
			
		||||
);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,29 +1,29 @@
 | 
			
		|||
import { Action, createAction } from "redux-act";
 | 
			
		||||
 | 
			
		||||
export interface Title {
 | 
			
		||||
  name: string;
 | 
			
		||||
  artist: string;
 | 
			
		||||
  album: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * The length of the title in nanoseconds.
 | 
			
		||||
   */
 | 
			
		||||
  length: number;
 | 
			
		||||
    name: string;
 | 
			
		||||
    artist: string;
 | 
			
		||||
    album: string;
 | 
			
		||||
    /**
 | 
			
		||||
     * The length of the title in nanoseconds.
 | 
			
		||||
     */
 | 
			
		||||
    length: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface MusicState {
 | 
			
		||||
  muted: boolean;
 | 
			
		||||
  playing: boolean;
 | 
			
		||||
  title: Title;
 | 
			
		||||
  playTime: number;
 | 
			
		||||
  source?: string;
 | 
			
		||||
    muted: boolean;
 | 
			
		||||
    playing: boolean;
 | 
			
		||||
    title: Title;
 | 
			
		||||
    playTime: number;
 | 
			
		||||
    source?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const setTitle: (title: Title) => Action<null, null> = createAction(
 | 
			
		||||
  "set currently playing title"
 | 
			
		||||
    "set currently playing title"
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const setPlayTime: (time: number) => Action<null, null> = createAction(
 | 
			
		||||
  "set the play time"
 | 
			
		||||
    "set the play time"
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const toggleMute: () => Action<null, null> = createAction("toggle mute");
 | 
			
		||||
| 
						 | 
				
			
			@ -31,5 +31,5 @@ export const toggleMute: () => Action<null, null> = createAction("toggle mute");
 | 
			
		|||
export const togglePlay: () => Action<null, null> = createAction("toggle play");
 | 
			
		||||
 | 
			
		||||
export const setSource: (source: string) => Action<null, null> = createAction(
 | 
			
		||||
  "set the title"
 | 
			
		||||
    "set the title"
 | 
			
		||||
);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Reference in a new issue