Files
aibis-dream/Assets/Scripts/FixSystemNew/SynthWaveform.cs
T
2025-05-27 21:16:58 +08:00

159 lines
4.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}