/*
 * +================================================================================+
 *      __  ___  ______________  __  __  ______________   ____
 *     / / / / |/ /  _/_  __/\ \/ / / / / /_  __/  _/ /  / __/
 *    / /_/ /    // /  / /    \  / / /_/ / / / _/ // /___\ \
 *    \____/_/|_/___/ /_/     /_/  \____/ /_/ /___/____/___/
 *
 * +================================================================================+
 *
 *    "THE BEER-WARE LICENSE"
 *    -----------------------
 *
 *    As long as you retain this notice you can do whatever you want with this
 *    stuff. However it is expressly forbidden to sell or commercially distribute
 *    Unity Utils or parts of it apart from your games.
 *    If you meet any of the authors some day, and you think this stuff is
 *    worth it, you can buy them a beer in return.
 *
 *    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *    THE SOFTWARE.
 *
 * +================================================================================+
 *
 *    Authors:
 *    David Krummenacher (dave@timbookedtwo.com)
 *
 *    Partially based on
 *    http://www.kaappine.fi/tutorials/fundamental-frequencies-and-detecting-notes/
 * 
 */

using UnityEngine;

namespace Utils.Helpers {

	/// <summary>
	/// Audio helper methods.
	/// </summary>
	public abstract class AudioHelper {

		/// <summary>
		/// Randomizes the pitch of a given audio source.
		/// </summary>
		/// <param name="audioSource">Audio source.</param>
		/// <param name="subtract">Subtract.</param>
		/// <param name="add">Add.</param>
		public static void RandomizePitch(AudioSource audioSource, float subtract = -0.2f, float add = 0.2f) {
			audioSource.pitch += Random.Range(subtract, add);
		}

		/// <summary>
		/// Randimizes the volume of a given audio source.
		/// </summary>
		/// <param name="audioSource">Audio source.</param>
		/// <param name="subtract">Subtract.</param>
		/// <param name="add">Add.</param>
		public static void RandimizeVolume(AudioSource audioSource, float subtract = -0.2f, float add = 0.2f) {
			audioSource.volume += Random.Range(subtract, add);
		}

		/// <summary>
		/// Calculates the average volume at current moment.
		/// </summary>
		/// <returns>The averaged volume.</returns>
		/// <param name="source">The audio source.</param>
		public static float GetAveragedVolume(AudioSource source) {
			float[] data = new float[256];
			float a = 0;
			source.GetOutputData(data, 0);
			foreach (float s in data) {
				a += Mathf.Abs (s);
			}
			return a / 256;
		}
		
		/// <summary>
		/// Gets the fundamental frequency of an audio source at the current moment.
		/// </summary>
		/// <returns>The fundamental frequency.</returns>
		/// <param name="source">The audio source.</param>
		/// <param name="samplerate">The samplerate. Default is default microphone samplerate.</param>
		/// <param name="FFTsize">The FFT size. Needs to be power of 2. Min value is 64, max is 8192.</param>
		public static float GetFundamentalFrequency(AudioSource source, int samplerate = 48000, int FFTsize = 8192) {
			float fundamentalFrequency = 0.0f;
			float[] data = new float[FFTsize];
			source.GetSpectrumData (data, 0, FFTWindow.BlackmanHarris);
			float s = 0.0f;
			int i = 0;
			for (int j = 1; j < FFTsize; j++) {
				if (s < data [j]) {
					s = data [j];
					i = j;
				}
			}
			fundamentalFrequency = i * samplerate / FFTsize;
			return fundamentalFrequency;
		}

		/// <summary>
		/// Finds the note based on a frequency.
		/// </summary>
		/// <returns>The note.</returns>
		/// <param name="frequency">The frequency.</param>
		public static NoteData GetNote(float frequency) {
			// Init empty note
			NoteData note = new NoteData(Notes.None, -1);

			// Get fundamental frequency
			int f = (int)frequency;
			// Frequency 0
			if (f <= 0) return note;
			
			// 440 => A4
			// 880 => A5
			note.Octave = 4;
			while (f < 440) {
				f *= 2;
				note.Octave--;
			}
			while (f >= 880) {
				f /= 2;
				note.Octave++;
			}
			// 440 < f <= 880
			// We want:  f=440 => 0
			//           f=880 => 12
			float scaled = 12 * ((Mathf.Log(f) - Mathf.Log(440)) / Mathf.Log(2));
			if (scaled < 0) { scaled += 12; }

			// Set note
			note.Note = (Notes)((int)Mathf.Round(scaled) % 12 + 1);

			// Octave is based on c so increment if necessairy
			if ((int)note.Note > (int)Notes.B) note.Octave++;

			return note;
		}
	}

	/// <summary>
	/// Note data representation.
	/// </summary>
	public struct NoteData {
		/// <summary>
		/// The note.
		/// </summary>
		public Notes Note;
		/// <summary>
		/// The octave.
		/// </summary>
		public int Octave;

		/// <summary>
		/// Initializes a new instance of the <see cref="Utils.Helpers.NoteData"/> struct.
		/// </summary>
		/// <param name="note">The note.</param>
		/// <param name="octave">The octave.</param>
		public NoteData(Notes note, int octave) {
			Note = note;
			Octave = octave;
		}
	}

	/// <summary>
	/// Notes.
	/// </summary>
	public enum Notes {
		/// <summary>No note.</summary>
		None,
		/// <summary>A</summary>
		A,
		/// <summary>A#</summary>
		ASharp,
		/// <summary>B</summary>
		B,
		/// <summary>C</summary>
		C,
		/// <summary>C#</summary>
		CSharp,
		/// <summary>D</summary>
		D,
		/// <summary>D#</summary>
		DSharp,
		/// <summary>E</summary>
		E,
		/// <summary>F</summary>
		F,
		/// <summary>F#</summary>
		FSharp,
		/// <summary>G</summary>
		G,
		/// <summary>G#</summary>
		GSharp
	}
}
