159 lines
4.5 KiB
C#
159 lines
4.5 KiB
C#
using UnityEngine;
|
||
|
||
[ExecuteInEditMode]
|
||
[RequireComponent(typeof(LineRenderer))]
|
||
public class SynthWaveform : MonoBehaviour
|
||
{
|
||
[System.Serializable]
|
||
public class SynthVoice
|
||
{
|
||
public bool enabled = true; // 是否启用该波形
|
||
[Range(0f, 1f)]
|
||
public float waveMorph = 0f; // 0~1 控制波形从三角到脉冲环形渐变
|
||
public float frequency = 1f; // 基础频率
|
||
public int octave = 0; // 八度调整(+/-)
|
||
[Range(0f, 1f)]
|
||
public float amplitude = 1f; // 振幅
|
||
[Range(0.1f, 0.9f)]
|
||
public float pulseWidth = 0.5f; // 脉冲宽度,仅在Pulse阶段有效
|
||
}
|
||
|
||
public SynthVoice[] voices = new SynthVoice[3];
|
||
|
||
[Header("绘图设置")]
|
||
public int resolution = 300;
|
||
public float waveWidth = 10f;
|
||
public float waveHeight = 1f;
|
||
public float speed = 2f;
|
||
|
||
[Header("周期设置")]
|
||
[Range(1, 20)]
|
||
public int cycles = 4; // 在waveWidth内显示几个波周期
|
||
|
||
[Header("跳动点")]
|
||
public Transform glowPointInstance; // 小球跳动点物体
|
||
|
||
private LineRenderer lineRenderer;
|
||
private Vector3[] points;
|
||
private float phaseOffset = 0f;
|
||
|
||
void OnEnable()
|
||
{
|
||
lineRenderer = GetComponent<LineRenderer>();
|
||
lineRenderer.positionCount = resolution;
|
||
points = new Vector3[resolution];
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
phaseOffset += Time.deltaTime * speed;
|
||
UpdateWave();
|
||
UpdateGlowPoint();
|
||
}
|
||
|
||
void UpdateWave()
|
||
{
|
||
Vector3 origin = transform.position;
|
||
Quaternion rotation = transform.rotation;
|
||
|
||
for (int i = 0; i < resolution; i++)
|
||
{
|
||
float x = ((float)i / (resolution - 1)) * waveWidth;
|
||
float time = (x / waveWidth) * cycles + phaseOffset;
|
||
|
||
float sample = 0f;
|
||
foreach (var voice in voices)
|
||
{
|
||
if (!voice.enabled) continue;
|
||
|
||
float freq = voice.frequency * Mathf.Pow(2, voice.octave);
|
||
float wavePos = (time * freq) % 1f;
|
||
|
||
float morphValue = voice.waveMorph * 20f; // 0~20两圈循环
|
||
float val = MorphWaveCircular(morphValue, wavePos, voice.pulseWidth);
|
||
|
||
sample += val * voice.amplitude;
|
||
}
|
||
|
||
sample = Mathf.Clamp(sample, -1f, 1f);
|
||
Vector3 localPos = new Vector3(x, sample * waveHeight, 0f);
|
||
points[i] = origin + rotation * localPos;
|
||
}
|
||
|
||
lineRenderer.SetPositions(points);
|
||
}
|
||
|
||
void UpdateGlowPoint()
|
||
{
|
||
if (glowPointInstance == null || points == null || points.Length == 0) return;
|
||
|
||
// phaseOffset在0~1之间循环,映射points索引
|
||
float t = phaseOffset % 1f;
|
||
int idx = Mathf.RoundToInt(t * (resolution - 1));
|
||
idx = Mathf.Clamp(idx, 0, resolution - 1);
|
||
|
||
// 小球X固定为最右端点X
|
||
float fixedX = points[resolution - 1].x;
|
||
// 小球Y跟随索引对应的波形Y
|
||
float y = points[idx].y;
|
||
|
||
glowPointInstance.position = new Vector3(fixedX, y, points[idx].z);
|
||
}
|
||
|
||
// 环形渐变波形 morphValue 0~20,循环两圈
|
||
float MorphWaveCircular(float morphValue, float time, float pulseWidth)
|
||
{
|
||
float val = morphValue % 20f;
|
||
|
||
float[] keyPoints = { 0f, 4f, 7f, 10f, 14f, 17f, 20f };
|
||
int[] waveIDs = { 0, 1, 2, 3, 3, 0, 1 };
|
||
|
||
int idx = 0;
|
||
for (int i = 0; i < keyPoints.Length - 1; i++)
|
||
{
|
||
if (val >= keyPoints[i] && val < keyPoints[i + 1])
|
||
{
|
||
idx = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
float t = (val - keyPoints[idx]) / (keyPoints[idx + 1] - keyPoints[idx]);
|
||
float waveA = GetWaveByID(waveIDs[idx], time, pulseWidth);
|
||
float waveB = GetWaveByID(waveIDs[idx + 1], time, pulseWidth);
|
||
return Mathf.Lerp(waveA, waveB, t);
|
||
}
|
||
|
||
float GetWaveByID(int id, float t, float pulseWidth)
|
||
{
|
||
switch (id)
|
||
{
|
||
case 0: return Triangle(t);
|
||
case 1: return Sawtooth(t);
|
||
case 2: return Square(t);
|
||
case 3: return Pulse(t, pulseWidth);
|
||
default: return 0f;
|
||
}
|
||
}
|
||
|
||
float Sawtooth(float t)
|
||
{
|
||
return 2f * t - 1f;
|
||
}
|
||
|
||
float Triangle(float t)
|
||
{
|
||
return 1f - 4f * Mathf.Abs(Mathf.Round(t - 0.25f) - (t - 0.25f));
|
||
}
|
||
|
||
float Square(float t)
|
||
{
|
||
return t < 0.5f ? 1f : -1f;
|
||
}
|
||
|
||
float Pulse(float t, float pulseWidth)
|
||
{
|
||
return t < pulseWidth ? 1f : -1f;
|
||
}
|
||
}
|