0

I'm trying to program an accurate metronome in unity. I know, that there are already some questions for that, but I can't find a solution for my problem. My first naive implementation used Play() or PlayOneShot(). But unfortunately it wasn't very accurate. So I thought, I could implement it with PlayScheduled() and a buffer time. Like in this example: http://www.schmid.dk/talks/2014-05-21-Nordic_Game_Jam/Schmid-2014-05-21-NGJ-140.pdf But this also did not work and either I get no sound at all, or the sound is sometimes cut off, as if the beginning of the sound isn't played. Why doesn't the scheduled sound play?

This is my code so far:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(AudioSource))]

public class inaccurateMetronome : MonoBehaviour {

public double bpm = 140.0F;

public AudioClip sound0;
public AudioSource audio0;

bool running = false;
bool ticked = false;

public double buff = 0.2d;
double timePerTick;
double nextTick;
double dspTime;


void Start () {
    double startTick = AudioSettings.dspTime;
    nextTick = startTick + buff;
    audio0 = GetComponent<AudioSource>();
    audio0.clip = sound0;

}

public void toggleMetronome() {
    if (running)
        stopMetronome();
    else
        startMetronome();
}

public void startMetronome() {
    if (running) {
        Debug.LogError("Metronome: already running");
        return;
    } else {
        running = true;
        timePerTick = 60.0f / bpm;
        nextTick = AudioSettings.dspTime + buff;
        Debug.Log("Metronome started");
    }
}

public void stopMetronome() {
    if (!running) {
        Debug.LogError("Metronome: not yet running");
        return;
    } else {
        running = false;
        Debug.Log("Metronome stopped");
    }
}

public void setBpm(double bpm){
    this.bpm = bpm;
    this.timePerTick = 60.0f / bpm;
}


void FixedUpdate() {
    dspTime = AudioSettings.dspTime;
    if ( running && dspTime + buff >= nextTick) {
        ticked = false;
        nextTick += timePerTick;
    }
    else if ( running && !ticked && nextTick >= AudioSettings.dspTime ) {
                audio0.PlayOneShot(sound0, 1);
                Debug.Log("Tick");
            ticked = true;
        }
    }

}

It would be fantastic, if someone could help me with that. Thanks!

M. Lang
  • 1
  • 2

1 Answers1

0

Use the one directly from the Unity Documentation If you want to play your own sound just play it after the Debug.Log().

using UnityEngine;
    using

 System.Collections;

    // The code example shows how to implement a metronome that procedurally generates the click sounds via the OnAudioFilterRead callback.
    // While the game is paused or the suspended, this time will not be updated and sounds playing will be paused. Therefore developers of music scheduling routines do not have to do any rescheduling after the app is unpaused

    [RequireComponent(typeof(AudioSource))]
    public class ExampleClass : MonoBehaviour
    {
        public double bpm = 140.0F;
        public float gain = 0.5F;
        public int signatureHi = 4;
        public int signatureLo = 4;
        private double nextTick = 0.0F;
        private float amp = 0.0F;
        private float phase = 0.0F;
        private double sampleRate = 0.0F;
        private int accent;
        private bool running = false;
        void Start()
        {
            accent = signatureHi;
            double startTick = AudioSettings.dspTime;
            sampleRate = AudioSettings.outputSampleRate;
            nextTick = startTick * sampleRate;
            running = true;
        }

        void OnAudioFilterRead(float[] data, int channels)
        {
            if (!running)
                return;

            double samplesPerTick = sampleRate * 60.0F / bpm * 4.0F / signatureLo;
            double sample = AudioSettings.dspTime * sampleRate;
            int dataLen = data.Length / channels;
            int n = 0;
            while (n < dataLen)
            {
                float x = gain * amp * Mathf.Sin(phase);
                int i = 0;
                while (i < channels)
                {
                    data[n * channels + i] += x;
                    i++;
                }
                while (sample + n >= nextTick)
                {
                    nextTick += samplesPerTick;
                    amp = 1.0F;
                    if (++accent > signatureHi)
                    {
                        accent = 1;
                        amp *= 2.0F;
                    }
                    Debug.Log("Tick: " + accent + "/" + signatureHi);
                }
                phase += amp * 0.3F;
                amp *= 0.993F;
                n++;
            }
        }
    }
Sandro Figo
  • 183
  • 10
  • Thank you for your answer. I tried playing my own sound like you said, but I get the error "Play can only be called from the main thread." So how can I implement my own sound in OnAudioFilterRead? – M. Lang Nov 20 '17 at 18:47