Created
November 25, 2024 05:19
-
-
Save kurtdekker/6efc15a0ca1dbd52da0f3f07139d58e7 to your computer and use it in GitHub Desktop.
Shepard Tones for Unity3D
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Zooming | |
{ | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
// @kurtdekker - Shepard Tones for Unity3D. | |
// | |
// https://en.wikipedia.org/wiki/Shepard_tone | |
// | |
public class ShepardTones : MonoBehaviour | |
{ | |
public class Tone | |
{ | |
public float theta; | |
public float frequency; | |
public float amplitude; | |
} | |
List<Tone> AllTones = new List<Tone>(); | |
// one over sample frequency | |
float sampleK; | |
// base frequency | |
const float F0 = 100; | |
const int octaveCount = 3; | |
const int octaveSpan = 4; | |
AudioSource azz; | |
public static ShepardTones Create() | |
{ | |
GameObject go = new GameObject("ShepherdTones.Create();"); | |
var st = go.AddComponent<ShepardTones>(); | |
var azz = go.AddComponent<AudioSource>(); | |
azz.bypassListenerEffects = true; | |
azz.Play(); | |
st.azz = azz; | |
return st; | |
} | |
const float VolumeSnappiness = 2.0f; | |
float currentVolume; | |
float desiredVolume; | |
float actualVolume; | |
public void SetVolume( float v) | |
{ | |
desiredVolume = Mathf.Clamp01(v); | |
} | |
bool dead; | |
public void SetDead() | |
{ | |
dead = true; | |
} | |
void Start() | |
{ | |
sampleK = 1.0f / 22050; | |
// this detunes the pair frequency by a linear amount, making | |
// for an ever-evolving beat frequency | |
float beatFrequencyOffset = Random.Range(1.0f, 3.0f); | |
// make all the octaves | |
for (int octave = 0; octave < octaveCount; octave++) | |
{ | |
float f = F0 * Mathf.Pow(2, octave); | |
// the core frequency | |
AllTones.Add(new Tone | |
{ | |
frequency = f, | |
}); | |
// the linear detuned frequency | |
AllTones.Add(new Tone | |
{ | |
frequency = f + beatFrequencyOffset, | |
}); | |
} | |
} | |
void Update() | |
{ | |
currentVolume = Mathf.Lerp(currentVolume, desiredVolume, VolumeSnappiness * Time.deltaTime); | |
actualVolume = currentVolume; | |
if (Time.timeScale == 0) actualVolume = 0; | |
// all sounds fall to zero | |
if (dead) | |
{ | |
foreach (var tone in AllTones) | |
{ | |
float f = tone.frequency; | |
// linear - sounds intentionally awful | |
float fx = f / 2 + 250; | |
f -= fx * 0.5f * Time.deltaTime; | |
tone.frequency = f; | |
tone.amplitude -= Time.deltaTime * 0.05f; | |
if (f < 100) | |
{ | |
tone.amplitude = 0; | |
} | |
} | |
return; | |
} | |
// frequency bands: | |
// fade in between these frequencies | |
float F1 = F0; | |
float F2 = F1 * 2; | |
// fade out between these frequencies | |
float F3 = F1 * Mathf.Pow( 2, octaveSpan - 1); | |
float F4 = F3 * 2; | |
foreach (var tone in AllTones) | |
{ | |
float f = tone.frequency; | |
f += f * 0.1f * Time.deltaTime; | |
if (f >= F4) | |
{ | |
f /= (F4 / F1); | |
} | |
tone.frequency = f; | |
float amplitude = 1.0f; | |
// fading in? | |
if (f < F2) | |
{ | |
amplitude = (f - F1) / (F2 - F1); | |
} | |
// fading out? | |
if (f > F3) | |
{ | |
amplitude = 1.0f - (f - F3) / (F4 - F3); | |
} | |
tone.amplitude = amplitude; | |
} | |
} | |
private void OnAudioFilterRead(float[] data, int channels) | |
{ | |
float volume = actualVolume; | |
for (int i = 0; i < data.Length; i += channels) | |
{ | |
for (int channel = 0; channel < channels; channel++) | |
{ | |
int n = i + channel; | |
float d0 = data[n]; | |
foreach (var tone in AllTones) | |
{ | |
float theta = tone.theta; | |
theta += sampleK * tone.frequency; | |
if (theta >= Mathf.PI * 2) theta -= Mathf.PI * 2; | |
tone.theta = theta; | |
float sample = Mathf.Sin(theta); | |
sample *= tone.amplitude; | |
d0 += sample; | |
} | |
data[n] = d0 * volume; | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment