From f02f4b40d568e80dc8edc0e724554ed605610f17 Mon Sep 17 00:00:00 2001 From: "nis.wegmann" Date: Fri, 11 Sep 2026 10:39:26 +0200 Subject: [PATCH] Add the scriptable processor effect rack example project. --- README.md | 1 + .../Assets/AudioClips.meta | 8 + .../Assets/AudioClips/Count1To10.wav | 3 + .../Assets/AudioClips/Count1To10.wav.meta | 23 + .../Assets/Scenes.meta | 8 + .../Assets/Scenes/EffectRack.unity | 361 +++++++ .../Assets/Scenes/EffectRack.unity.meta | 7 + .../Assets/Scripts.meta | 8 + .../Assets/Scripts/EffectRack.cs | 686 ++++++++++++ .../Assets/Scripts/EffectRack.cs.meta | 11 + .../Assets/Scripts/Effects.meta | 8 + .../Assets/Scripts/Effects/BandPassEffect.cs | 249 +++++ .../Scripts/Effects/BandPassEffect.cs.meta | 11 + .../Assets/Scripts/Effects/DriveEffect.cs | 157 +++ .../Scripts/Effects/DriveEffect.cs.meta | 11 + .../Assets/Scripts/Effects/HissEffect.cs | 224 ++++ .../Assets/Scripts/Effects/HissEffect.cs.meta | 11 + .../Assets/Scripts/Effects/NoiseGateEffect.cs | 295 ++++++ .../Scripts/Effects/NoiseGateEffect.cs.meta | 11 + .../Assets/Scripts/Effects/SpectrumEffect.cs | 330 ++++++ .../Scripts/Effects/SpectrumEffect.cs.meta | 11 + .../Scripts/Effects/WhiteNoiseGenerator.cs | 67 ++ .../Effects/WhiteNoiseGenerator.cs.meta | 11 + .../Packages/manifest.json | 41 + .../ProjectSettings/AudioManager.asset | 19 + .../ProjectSettings/ClusterInputManager.asset | 6 + .../ProjectSettings/DynamicsManager.asset | 36 + .../ProjectSettings/EditorBuildSettings.asset | 11 + .../ProjectSettings/EditorSettings.asset | 52 + .../ProjectSettings/GraphicsSettings.asset | 66 ++ .../ProjectSettings/InputManager.asset | 487 +++++++++ .../ProjectSettings/MemorySettings.asset | 35 + .../ProjectSettings/MultiplayerManager.asset | 7 + .../ProjectSettings/NavMeshAreas.asset | 91 ++ .../PackageManagerSettings.asset | 44 + .../ProjectSettings/Physics2DSettings.asset | 56 + .../PhysicsCoreProjectSettings2D.asset | 6 + .../ProjectSettings/PresetManager.asset | 7 + .../ProjectSettings/ProjectSettings.asset | 987 ++++++++++++++++++ .../ProjectSettings/ProjectVersion.txt | 2 + .../ProjectSettings/QualitySettings.asset | 135 +++ .../SceneTemplateSettings.json | 121 +++ .../ProjectSettings/ShaderGraphSettings.asset | 19 + .../ProjectSettings/TagManager.asset | 76 ++ .../ProjectSettings/TimeManager.asset | 9 + .../ProjectSettings/URPProjectSettings.asset | 15 + .../UnityConnectSettings.asset | 42 + .../ProjectSettings/VFXManager.asset | 12 + .../VersionControlSettings.asset | 8 + .../ProjectSettings/XRSettings.asset | 10 + scriptable-processor-effect-rack/README.md | 69 ++ 51 files changed, 4981 insertions(+) create mode 100644 scriptable-processor-effect-rack/Assets/AudioClips.meta create mode 100644 scriptable-processor-effect-rack/Assets/AudioClips/Count1To10.wav create mode 100644 scriptable-processor-effect-rack/Assets/AudioClips/Count1To10.wav.meta create mode 100644 scriptable-processor-effect-rack/Assets/Scenes.meta create mode 100644 scriptable-processor-effect-rack/Assets/Scenes/EffectRack.unity create mode 100644 scriptable-processor-effect-rack/Assets/Scenes/EffectRack.unity.meta create mode 100644 scriptable-processor-effect-rack/Assets/Scripts.meta create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/EffectRack.cs create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/EffectRack.cs.meta create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/Effects.meta create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/Effects/BandPassEffect.cs create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/Effects/BandPassEffect.cs.meta create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/Effects/DriveEffect.cs create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/Effects/DriveEffect.cs.meta create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/Effects/HissEffect.cs create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/Effects/HissEffect.cs.meta create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/Effects/NoiseGateEffect.cs create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/Effects/NoiseGateEffect.cs.meta create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/Effects/SpectrumEffect.cs create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/Effects/SpectrumEffect.cs.meta create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/Effects/WhiteNoiseGenerator.cs create mode 100644 scriptable-processor-effect-rack/Assets/Scripts/Effects/WhiteNoiseGenerator.cs.meta create mode 100644 scriptable-processor-effect-rack/Packages/manifest.json create mode 100644 scriptable-processor-effect-rack/ProjectSettings/AudioManager.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/ClusterInputManager.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/DynamicsManager.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/EditorBuildSettings.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/EditorSettings.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/GraphicsSettings.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/InputManager.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/MemorySettings.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/MultiplayerManager.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/NavMeshAreas.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/PackageManagerSettings.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/Physics2DSettings.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/PhysicsCoreProjectSettings2D.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/PresetManager.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/ProjectSettings.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/ProjectVersion.txt create mode 100644 scriptable-processor-effect-rack/ProjectSettings/QualitySettings.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/SceneTemplateSettings.json create mode 100644 scriptable-processor-effect-rack/ProjectSettings/ShaderGraphSettings.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/TagManager.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/TimeManager.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/URPProjectSettings.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/UnityConnectSettings.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/VFXManager.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/VersionControlSettings.asset create mode 100644 scriptable-processor-effect-rack/ProjectSettings/XRSettings.asset create mode 100644 scriptable-processor-effect-rack/README.md diff --git a/README.md b/README.md index 5f054e5..9314e4a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Each project is a standalone Unity project; open its folder in the required Unit | Project | Description | Required Unity version | | ------- | ----------- | ---------------------- | | [Scriptable Processor Random Container](scriptable-processor-random-container) | Implements a simplified version of the Audio Random Container using nested generators. | 6.5 | +| [Scriptable Processor Effect Rack](scriptable-processor-effect-rack) | Builds a rack of scriptable effects on one AudioSource at runtime, turning a spoken clip into radio chatter. | 6.7 | ## License diff --git a/scriptable-processor-effect-rack/Assets/AudioClips.meta b/scriptable-processor-effect-rack/Assets/AudioClips.meta new file mode 100644 index 0000000..48e5967 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/AudioClips.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b3a98323df9248bc8a9032c6ed5fb4a4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scriptable-processor-effect-rack/Assets/AudioClips/Count1To10.wav b/scriptable-processor-effect-rack/Assets/AudioClips/Count1To10.wav new file mode 100644 index 0000000..245e966 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/AudioClips/Count1To10.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b6b2e5cbac5bdf5effcd056450e1c0d7cc7e97cb63fd6613bfaa7512434eb04 +size 882078 diff --git a/scriptable-processor-effect-rack/Assets/AudioClips/Count1To10.wav.meta b/scriptable-processor-effect-rack/Assets/AudioClips/Count1To10.wav.meta new file mode 100644 index 0000000..fb6020b --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/AudioClips/Count1To10.wav.meta @@ -0,0 +1,23 @@ +fileFormatVersion: 2 +guid: c1d7ad1ffa34a421a94d5602f74625b2 +AudioImporter: + externalObjects: {} + serializedVersion: 8 + defaultSettings: + serializedVersion: 2 + loadType: 0 + sampleRateSetting: 0 + sampleRateOverride: 44100 + compressionFormat: 1 + quality: 1 + conversionMode: 0 + preloadAudioData: 0 + platformSettingOverrides: {} + forceToMono: 0 + normalize: 1 + loadInBackground: 0 + ambisonic: 0 + 3D: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/scriptable-processor-effect-rack/Assets/Scenes.meta b/scriptable-processor-effect-rack/Assets/Scenes.meta new file mode 100644 index 0000000..434512d --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dc139e75749643399e1ddfe4bd9f4bd5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scriptable-processor-effect-rack/Assets/Scenes/EffectRack.unity b/scriptable-processor-effect-rack/Assets/Scenes/EffectRack.unity new file mode 100644 index 0000000..09025bb --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scenes/EffectRack.unity @@ -0,0 +1,361 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &852000000 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 852000001} + - component: {fileID: 852000002} + - component: {fileID: 852000007} + m_Layer: 0 + m_Name: Radio + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &852000001 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 852000000} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!82 &852000002 +AudioSource: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 852000000} + m_Enabled: 1 + serializedVersion: 4 + OutputAudioMixerGroup: {fileID: 0} + m_audioClip: {fileID: 0} + m_Resource: {fileID: 8300000, guid: c1d7ad1ffa34a421a94d5602f74625b2, type: 3} + m_PlayOnAwake: 0 + m_Volume: 1 + m_Pitch: 1 + Loop: 1 + Mute: 0 + Spatialize: 0 + SpatializePostEffects: 0 + Priority: 128 + DopplerLevel: 1 + MinDistance: 1 + MaxDistance: 500 + Pan2D: 0 + rolloffMode: 0 + BypassEffects: 0 + BypassListenerEffects: 0 + BypassReverbZones: 0 + rolloffCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 1 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + panLevelCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + spreadCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + reverbZoneMixCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!114 &852000007 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 852000000} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0f8a87d0f2e143d582f69b7456e456bd, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::RadioEffectRack.EffectRack +--- !u!1 &330585543 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 330585546} + - component: {fileID: 330585545} + - component: {fileID: 330585544} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &330585544 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + m_Enabled: 1 +--- !u!20 &330585545 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &330585546 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 852000001} + - {fileID: 330585546} diff --git a/scriptable-processor-effect-rack/Assets/Scenes/EffectRack.unity.meta b/scriptable-processor-effect-rack/Assets/Scenes/EffectRack.unity.meta new file mode 100644 index 0000000..3a28089 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scenes/EffectRack.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ec8fa406414942d1aa410329eca4e73d +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scriptable-processor-effect-rack/Assets/Scripts.meta b/scriptable-processor-effect-rack/Assets/Scripts.meta new file mode 100644 index 0000000..d9b3155 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ce8692097fae4eae987e5d52f94edd6a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scriptable-processor-effect-rack/Assets/Scripts/EffectRack.cs b/scriptable-processor-effect-rack/Assets/Scripts/EffectRack.cs new file mode 100644 index 0000000..bf08240 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/EffectRack.cs @@ -0,0 +1,686 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Audio; + +namespace RadioEffectRack +{ + /// + /// Transport and effect rack. Drives the AudioSource, lists the effect chain in the order the audio system sees it, + /// edits each effect, and rebuilds the rack during playback. + /// + [RequireComponent(typeof(AudioSource))] + public class EffectRack : MonoBehaviour + { + /// What an effect's header row was asked to do this frame. + enum EffectAction + { + None, + Remove, + MoveUp, + MoveDown, + } + + /// A named rack, built by adding effects in the order they should run. + readonly struct Preset + { + public string label { get; } + public Action build { get; } + + public Preset(string label, Action build) + { + this.label = label; + this.build = build; + } + } + + static readonly Type[] k_EffectTypes = + { + typeof(BandPassEffect), + typeof(DriveEffect), + typeof(NoiseGateEffect), + typeof(HissEffect), + typeof(SpectrumEffect), + }; + + // Added in the order they run in, so a rack reads top to bottom like the chain it builds. + static readonly Preset[] k_Presets = + { + new Preset("Handheld radio", BuildHandheldRadio), + new Preset("Blown speaker", BuildBlownSpeaker), + new Preset("Spectrum only", BuildSpectrumOnly), + }; + + const float k_PanelWidth = 440f; + const float k_PanelHeight = 760f; + const float k_PanelMargin = 4f; + + // Three across. A fourth starts a new row rather than squeezing the labels. + const int k_ButtonsPerRow = 3; + const float k_ButtonWidth = 128f; + + AudioSource m_Source; + + // Applied once the outgoing effects are actually gone. + Action m_PendingRack; + int m_PendingKeepCount; + + readonly List m_Reordered = new List(); + + readonly List m_AddableTypes = new List(); + readonly List m_AddableLabels = new List(); + + readonly List m_PresetLabels = new List(); + + // Reused, so the per-frame refresh allocates nothing. + readonly List m_Components = new List(); + readonly List m_Effects = new List(); + + // Sampled once per frame, not from OnGUI: IMGUI needs the same controls in its layout and + // repaint passes, and OnGUI runs several times per frame. + bool m_HasEnvelope; + float m_EnvelopeDb; + bool m_GateIsOpen; + + SpectrumEffect m_Spectrum; + bool m_HasSpectrum; + + void Start() + { + m_Source = GetComponent(); + + foreach (var preset in k_Presets) + { + m_PresetLabels.Add(preset.label); + } + + // Clear rides along as the last button. + m_PresetLabels.Add("Clear"); + } + + void Update() + { + RefreshEffects(); + + m_HasEnvelope = false; + + foreach (var effect in m_Effects) + { + if (effect != null && effect is NoiseGateEffect gate) + m_HasEnvelope = gate.TryGetEnvelope(out m_EnvelopeDb, out m_GateIsOpen); + } + + m_HasSpectrum = m_Spectrum != null && m_Spectrum.TryReadLevels(); + + // Destroy is deferred to end of frame, AddComponent is immediate. Build only once the old + // effects have gone, or the dying ones sit in the chain ahead of the new ones. + if (m_PendingRack != null && m_Effects.Count == m_PendingKeepCount) + { + var build = m_PendingRack; + m_PendingRack = null; + + build(gameObject); + RefreshEffects(); + } + } + + /// + /// Rebuilds the chain from the components on this GameObject. Reading it every frame is what + /// keeps the panel honest when an effect is added or removed, whether from here or the Inspector. + /// + void RefreshEffects() + { + GetComponents(m_Components); + + m_Effects.Clear(); + m_Spectrum = null; + + foreach (var component in m_Components) + { + // A destroyed component can still be listed. Unity's == operator is what reports that. + if (component == null) + continue; + + if (component is IAudioEffect && component is Behaviour behaviour) + m_Effects.Add(behaviour); + + if (component is SpectrumEffect spectrum) + m_Spectrum = spectrum; + } + + m_AddableTypes.Clear(); + m_AddableLabels.Clear(); + + foreach (var type in k_EffectTypes) + { + if (HasEffect(type)) + continue; + + m_AddableTypes.Add(type); + m_AddableLabels.Add(Label(type)); + } + } + + void OnGUI() + { + // The editor repaints the game view on its own schedule, so OnGUI can run after an effect was + // destroyed and before Update rebuilt the list. Rebuild on Layout, reuse for Repaint. + if (Event.current.type == EventType.Layout) + RefreshEffects(); + + // Fit the view rather than assume there is room, and sit off the corner. + var width = Mathf.Min(k_PanelWidth, Screen.width - 2f * k_PanelMargin); + var height = Mathf.Min(k_PanelHeight, Screen.height - 2f * k_PanelMargin); + + GUILayout.BeginArea(new Rect(k_PanelMargin, k_PanelMargin, width, height), GUI.skin.box); + + var clip = m_Source.clip; + GUILayout.Label(clip != null + ? $"Clip: {clip.name} ({clip.length:0.0}s @ {clip.frequency} Hz)" + : "Assign a clip on the AudioSource."); + + GUILayout.BeginHorizontal(); + if (GUILayout.Button(m_Source.isPlaying ? "Stop" : "Play")) + { + if (m_Source.isPlaying) + m_Source.Stop(); + else + m_Source.Play(); + } + + m_Source.loop = GUILayout.Toggle(m_Source.loop, "Loop"); + GUILayout.EndHorizontal(); + + m_Source.bypassEffects = GUILayout.Toggle(m_Source.bypassEffects, "Bypass Effects (hear the dry clip)"); + + GUILayout.Space(6); + + var presetClicked = WrappedButtons(m_PresetLabels, k_ButtonWidth, k_ButtonsPerRow); + + if (presetClicked >= 0) + { + if (presetClicked < k_Presets.Length) + LoadPreset(k_Presets[presetClicked]); + else + ClearRack(); + } + + GUILayout.Space(6); + GUILayout.Label(m_Effects.Count > 0 + ? "Chain, in component order:" + : "The rack is empty, so the clip plays dry. Load a preset or add an effect below."); + + Behaviour pendingRemoval = null; + var pendingMove = -1; + var pendingMoveDelta = 0; + + for (var index = 0; index < m_Effects.Count; index++) + { + var effect = m_Effects[index]; + + if (effect == null) + continue; + + GUILayout.BeginVertical(GUI.skin.box); + + switch (Header(index, m_Effects.Count, Label(effect.GetType()), effect)) + { + case EffectAction.Remove: + pendingRemoval = effect; + break; + + case EffectAction.MoveUp: + pendingMove = index; + pendingMoveDelta = -1; + break; + + case EffectAction.MoveDown: + pendingMove = index; + pendingMoveDelta = 1; + break; + } + + switch (effect) + { + case BandPassEffect bandPass: + GUILayout.Label($"Low cut: {bandPass.lowCutHz:0} Hz"); + bandPass.lowCutHz = GUILayout.HorizontalSlider(bandPass.lowCutHz, 50f, 2000f); + GUILayout.Label($"High cut: {bandPass.highCutHz:0} Hz"); + bandPass.highCutHz = GUILayout.HorizontalSlider(bandPass.highCutHz, 800f, 8000f); + break; + + case DriveEffect drive: + GUILayout.Label($"Drive: {drive.drive:0.0}"); + drive.drive = GUILayout.HorizontalSlider(drive.drive, 1f, 40f); + GUILayout.Label($"Output: {drive.outputDb:0.0} dB"); + drive.outputDb = GUILayout.HorizontalSlider(drive.outputDb, -40f, 6f); + break; + + case NoiseGateEffect gate: + GUILayout.Label($"Threshold: {gate.thresholdDb:0.0} dB"); + gate.thresholdDb = GUILayout.HorizontalSlider(gate.thresholdDb, -60f, 0f); + GUILayout.Label($"Attack: {gate.attackMs:0.0} ms"); + gate.attackMs = GUILayout.HorizontalSlider(gate.attackMs, 0.5f, 50f); + GUILayout.Label($"Release: {gate.releaseMs:0} ms"); + gate.releaseMs = GUILayout.HorizontalSlider(gate.releaseMs, 10f, 500f); + DrawGateMeter(); + break; + + case HissEffect hiss: + GUILayout.Label($"Static: {hiss.levelDb:0.0} dB"); + hiss.levelDb = GUILayout.HorizontalSlider(hiss.levelDb, -80f, -12f); + break; + + case SpectrumEffect: + DrawSpectrum(); + break; + } + + GUILayout.EndVertical(); + } + + Type pendingAddition = null; + + GUILayout.Space(4); + + if (m_AddableLabels.Count > 0) + { + GUILayout.Label("Add an effect while it plays:"); + + var addClicked = WrappedButtons(m_AddableLabels, k_ButtonWidth, k_ButtonsPerRow); + + if (addClicked >= 0) + pendingAddition = m_AddableTypes[addClicked]; + } + + GUILayout.EndArea(); + + // After the layout, so the set of controls cannot change mid-frame. The audio source + // rediscovers its effects when the components change. + if (pendingRemoval != null) + { + // Forget it first: nothing drawn later this cycle should reach a component on its way out. + m_Effects.Remove(pendingRemoval); + + if (pendingRemoval is SpectrumEffect) + { + m_Spectrum = null; + m_HasSpectrum = false; + } + + Destroy(pendingRemoval); + } + + if (pendingMove >= 0) + MoveEffect(pendingMove, pendingMoveDelta); + + if (pendingAddition != null) + gameObject.AddComponent(pendingAddition); + } + + /// Effect name, position in the chain, reorder buttons, bypass toggle, remove button. + static EffectAction Header(int index, int count, string label, Behaviour effect) + { + var action = EffectAction.None; + + GUILayout.BeginHorizontal(); + GUILayout.Label($"{index + 1}. {label}"); + GUILayout.FlexibleSpace(); + + var wasEnabled = GUI.enabled; + + GUI.enabled = wasEnabled && index > 0; + if (GUILayout.Button("^", GUILayout.Width(24))) + action = EffectAction.MoveUp; + + GUI.enabled = wasEnabled && index < count - 1; + if (GUILayout.Button("v", GUILayout.Width(24))) + action = EffectAction.MoveDown; + + GUI.enabled = wasEnabled; + + // Disabling bypasses and keeps the instance. Removing destroys it. + effect.enabled = GUILayout.Toggle(effect.enabled, effect.enabled ? "on" : "bypassed", + GUILayout.Width(78)); + + if (GUILayout.Button("x", GUILayout.Width(22))) + action = EffectAction.Remove; + + GUILayout.EndHorizontal(); + + return action; + } + + /// + /// Shows the envelope the audio thread last reported, and whether the gate is open. The value + /// comes back through a query message, so nothing here touches the audio thread directly. + /// + void DrawGateMeter() + { + GUILayout.Label(m_HasEnvelope + ? $"Envelope: {m_EnvelopeDb:0.0} dB gate {(m_GateIsOpen ? "OPEN" : "closed")}" + : "Envelope: (not running)"); + + var rect = GUILayoutUtility.GetRect(1f, 10f, GUILayout.ExpandWidth(true)); + GUI.Box(rect, GUIContent.none); + + if (!m_HasEnvelope) + return; + + var normalized = Mathf.InverseLerp(NoiseGateEffect.k_SilenceDb, 0f, m_EnvelopeDb); + var fill = new Rect(rect.x + 1f, rect.y + 1f, (rect.width - 2f) * normalized, rect.height - 2f); + + var previousColor = GUI.color; + GUI.color = m_GateIsOpen ? Color.green : Color.grey; + GUI.DrawTexture(fill, Texture2D.whiteTexture); + GUI.color = previousColor; + } + + /// Draws the band levels as a histogram, lowest frequency on the left. + void DrawSpectrum() + { + var bands = m_Spectrum != null ? m_Spectrum.bandCount : 0; + + GUILayout.Label(m_HasSpectrum + ? $"Spectrum, {m_Spectrum.lowestHz:0} Hz to {m_Spectrum.highestHz / 1000f:0.#} kHz:" + : "Spectrum: (not running)"); + + var rect = GUILayoutUtility.GetRect(1f, 58f, GUILayout.ExpandWidth(true)); + GUI.Box(rect, GUIContent.none); + + if (!m_HasSpectrum || bands <= 0) + return; + + var inner = new Rect(rect.x + 2f, rect.y + 2f, rect.width - 4f, rect.height - 4f); + var barWidth = inner.width / bands; + + var previousColor = GUI.color; + GUI.color = new Color(0.45f, 0.85f, 0.5f); + + for (var band = 0; band < bands; band++) + { + var level = m_Spectrum.Level(band); + var decibels = level > 0.00001f ? 20f * Mathf.Log10(level) : k_SpectrumFloorDb; + var height = Mathf.InverseLerp(k_SpectrumFloorDb, k_SpectrumCeilingDb, decibels) * inner.height; + + if (height <= 0f) + continue; + + var bar = new Rect(inner.x + band * barWidth, inner.yMax - height, + Mathf.Max(barWidth - 1f, 1f), height); + + GUI.DrawTexture(bar, Texture2D.whiteTexture); + } + + GUI.color = previousColor; + } + + // Each band carries part of the energy, so the top of the scale sits below full scale. + const float k_SpectrumFloorDb = -60f; + const float k_SpectrumCeilingDb = -6f; + + /// + /// Draws labels as buttons of one width, starting a new row every , so + /// a long row is never squeezed until its text is clipped. Returns the index clicked, or -1. + /// + static int WrappedButtons(List labels, float width, int perRow) + { + var clicked = -1; + + for (var index = 0; index < labels.Count; index++) + { + if (index % perRow == 0) + GUILayout.BeginHorizontal(); + + if (GUILayout.Button(labels[index], GUILayout.Width(width))) + clicked = index; + + if (index % perRow == perRow - 1 || index == labels.Count - 1) + GUILayout.EndHorizontal(); + } + + return clicked; + } + + bool HasEffect(Type type) + { + foreach (var effect in m_Effects) + { + if (effect.GetType() == type) + return true; + } + + return false; + } + + static string Label(Type type) + { + if (type == typeof(BandPassEffect)) + return "Band pass"; + if (type == typeof(DriveEffect)) + return "Drive"; + if (type == typeof(NoiseGateEffect)) + return "Noise gate"; + if (type == typeof(HissEffect)) + return "Hiss"; + if (type == typeof(SpectrumEffect)) + return "Spectrum"; + + return type.Name; + } + + /// Clears the rack, then rebuilds it from the preset once the old effects are gone. + void LoadPreset(Preset preset) + { + ClearRack(); + + m_PendingRack = preset.build; + m_PendingKeepCount = 0; + } + + /// + /// Removes every effect. Destroying a component destroys its effect instance, so the whole + /// chain and all of its state goes with it. + /// + void ClearRack() + { + foreach (var effect in m_Effects) + { + if (effect != null) + Destroy(effect); + } + + m_Effects.Clear(); + m_Spectrum = null; + m_HasSpectrum = false; + m_HasEnvelope = false; + m_PendingRack = null; + m_PendingKeepCount = 0; + } + + /// + /// Moves an effect one place along the chain. + /// + /// Chain order is component order, and at runtime a component can only be appended: the + /// reorder API is editor-only. So everything from the swap onwards is destroyed and added + /// again in the new order, carrying its current settings across. Effects before the swap are + /// left alone and keep their instances, and their filter state with them. + /// + void MoveEffect(int index, int delta) + { + var target = index + delta; + + if (index < 0 || index >= m_Effects.Count || target < 0 || target >= m_Effects.Count) + return; + + var first = Mathf.Min(index, target); + + // Reorder the tail before destroying anything. Either direction swaps the first two. + m_Reordered.Clear(); + + for (var i = first; i < m_Effects.Count; i++) + { + m_Reordered.Add(m_Effects[i]); + } + + (m_Reordered[0], m_Reordered[1]) = (m_Reordered[1], m_Reordered[0]); + + // Capture settings while the components exist. Allocating on a click is fine; this never runs + // while audio is being processed. + var builders = new List>(m_Reordered.Count); + + foreach (var effect in m_Reordered) + { + var build = Capture(effect); + + if (build != null) + builders.Add(build); + } + + for (var i = first; i < m_Effects.Count; i++) + { + if (m_Effects[i] != null) + Destroy(m_Effects[i]); + } + + // Forget the tail now, rebuild once the destroys land. + m_Effects.RemoveRange(first, m_Effects.Count - first); + + if (m_Spectrum != null && !m_Effects.Contains(m_Spectrum)) + { + m_Spectrum = null; + m_HasSpectrum = false; + } + + m_PendingRack = host => + { + foreach (var build in builders) + { + build(host); + } + }; + + m_PendingKeepCount = first; + } + + /// + /// Returns a step that adds one effect of the same type with the settings it has right now. + /// This is what lets an effect survive being destroyed and added again in a new position. + /// + static Action Capture(Behaviour effect) + { + switch (effect) + { + case BandPassEffect band: + { + var lowCutHz = band.lowCutHz; + var highCutHz = band.highCutHz; + + return host => + { + var added = host.AddComponent(); + added.lowCutHz = lowCutHz; + added.highCutHz = highCutHz; + }; + } + + case DriveEffect drive: + { + var amount = drive.drive; + var outputDb = drive.outputDb; + + return host => + { + var added = host.AddComponent(); + added.drive = amount; + added.outputDb = outputDb; + }; + } + + case NoiseGateEffect gate: + { + var thresholdDb = gate.thresholdDb; + var attackMs = gate.attackMs; + var releaseMs = gate.releaseMs; + + return host => + { + var added = host.AddComponent(); + added.thresholdDb = thresholdDb; + added.attackMs = attackMs; + added.releaseMs = releaseMs; + }; + } + + case HissEffect hiss: + { + var levelDb = hiss.levelDb; + + return host => + { + var added = host.AddComponent(); + added.levelDb = levelDb; + }; + } + + case SpectrumEffect: + return host => host.AddComponent(); + + default: + return null; + } + } + + // Each effect is configured as it is added: the audio source reads those values when it + // discovers the component. + + static void BuildHandheldRadio(GameObject host) + { + var band = host.AddComponent(); + band.lowCutHz = 400f; + band.highCutHz = 2600f; + + var drive = host.AddComponent(); + drive.drive = 9f; + drive.outputDb = -3f; + + var gate = host.AddComponent(); + gate.thresholdDb = -34f; + gate.attackMs = 3f; + gate.releaseMs = 140f; + + var hiss = host.AddComponent(); + hiss.levelDb = -44f; + + host.AddComponent(); + } + + // Two drives, band pass between them, no gate. The duplicate is deliberate: an effect is + // addressed by its component, not its type. + static void BuildBlownSpeaker(GameObject host) + { + var first = host.AddComponent(); + first.drive = 14f; + first.outputDb = -4f; + + var band = host.AddComponent(); + band.lowCutHz = 700f; + band.highCutHz = 1800f; + + var second = host.AddComponent(); + second.drive = 24f; + second.outputDb = -10f; + + var hiss = host.AddComponent(); + hiss.levelDb = -34f; + + host.AddComponent(); + } + + // Changes no audio. A way to watch the untouched signal. + static void BuildSpectrumOnly(GameObject host) + { + host.AddComponent(); + } + } +} diff --git a/scriptable-processor-effect-rack/Assets/Scripts/EffectRack.cs.meta b/scriptable-processor-effect-rack/Assets/Scripts/EffectRack.cs.meta new file mode 100644 index 0000000..83326c0 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/EffectRack.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0f8a87d0f2e143d582f69b7456e456bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scriptable-processor-effect-rack/Assets/Scripts/Effects.meta b/scriptable-processor-effect-rack/Assets/Scripts/Effects.meta new file mode 100644 index 0000000..fb101e6 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/Effects.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f35132a2a52c47b4b402810abf694a9c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scriptable-processor-effect-rack/Assets/Scripts/Effects/BandPassEffect.cs b/scriptable-processor-effect-rack/Assets/Scripts/Effects/BandPassEffect.cs new file mode 100644 index 0000000..87a3016 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/Effects/BandPassEffect.cs @@ -0,0 +1,249 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Audio; +using static UnityEngine.Audio.ProcessorInstance; + +namespace RadioEffectRack +{ + /// + /// Band-limits the signal to a narrow speech band. Throwing away the lowest and highest octaves is + /// most of what makes a voice sound like it came out of a small radio speaker. + /// + /// This effect is the example of a filter whose coefficients depend on the sample rate: they are + /// computed on the control side, in Configure and whenever a parameter changes, and the audio + /// thread only ever sees finished numbers. + /// + [BurstCompile(CompileSynchronously = true)] + struct BandPassProcessor : EffectInstance.IRealtime + { + // AudioSpeakerMode tops out at 7.1. Sized for the widest layout, so a device change to more + // channels never needs a bigger allocation. + internal const int k_MaxChannels = 8; + + /// Cutoffs in Hz. Sent from the component with ControlContext.SendMessage. + internal struct Cutoffs + { + internal float lowCutHz; + internal float highCutHz; + + internal Cutoffs(float lowCutHz, float highCutHz) + { + this.lowCutHz = lowCutHz; + this.highCutHz = highCutHz; + } + } + + /// + /// One-pole coefficients, which is what the audio thread actually needs. Sent over the pipe by + /// the control part, so the conversion from Hz never happens in Process. + /// + internal struct Coefficients + { + internal float lowPass; + internal float highPass; + } + + Coefficients m_Coefficients; + internal NativeArray m_LowPassState; + internal NativeArray m_HighPassState; + + /// Zeroes the filter memory. Called from Configure, where the realtime part is idle. + internal void ClearState() + { + for (var channel = 0; channel < m_LowPassState.Length; channel++) + { + m_LowPassState[channel] = 0f; + m_HighPassState[channel] = 0f; + } + } + + public void Update(UpdatedDataContext context, Pipe pipe) + { + foreach (var element in pipe.GetAvailableData(context)) + { + if (element.TryGetData(out Coefficients coefficients)) + m_Coefficients = coefficients; + } + } + + public EffectInstance.Result Process(in RealtimeContext context, ChannelBuffer inputBuffer, + ChannelBuffer outputBuffer, EffectInstance.Arguments args) + { + // Configure allocates the state, so this only guards the gap before the first Configure. + var filteredChannels = m_LowPassState.IsCreated + ? math.min(inputBuffer.channelCount, k_MaxChannels) + : 0; + + for (var channel = 0; channel < filteredChannels; channel++) + { + // Into locals, so the inner loop stays in registers. + var lowPass = m_LowPassState[channel]; + var highPass = m_HighPassState[channel]; + + for (var frame = 0; frame < inputBuffer.frameCount; frame++) + { + // Read before writing: input and output can be the same memory. + var input = inputBuffer[channel, frame]; + + // A one-pole lowpass minus a slower one leaves the band between the two cutoffs. + lowPass += m_Coefficients.lowPass * (input - lowPass); + highPass += m_Coefficients.highPass * (lowPass - highPass); + + outputBuffer[channel, frame] = lowPass - highPass; + } + + m_LowPassState[channel] = lowPass; + m_HighPassState[channel] = highPass; + } + + // Channels past the state pass through. The output buffer starts undefined, so write every + // sample on every path, including this one. + for (var channel = filteredChannels; channel < inputBuffer.channelCount; channel++) + { + for (var frame = 0; frame < inputBuffer.frameCount; frame++) + { + outputBuffer[channel, frame] = inputBuffer[channel, frame]; + } + } + + return default; + } + + internal struct Control : EffectInstance.IControl + { + Cutoffs m_Cutoffs; + int m_SampleRate; + + internal Control(Cutoffs cutoffs) + { + m_Cutoffs = cutoffs; + m_SampleRate = 0; + } + + public void Configure(ControlContext context, ref BandPassProcessor processor, + in AudioConfiguration configuration, out EffectInstance.Setup setup) + { + setup = default; + + m_SampleRate = configuration.sampleRate; + + // The realtime part is suspended here, so its fields can be written directly and this is where + // to allocate. Everywhere else the values travel over the pipe. + processor.m_Coefficients = ComputeCoefficients(); + + if (!processor.m_LowPassState.IsCreated) + { + // Already zeroed. + processor.m_LowPassState = new NativeArray(k_MaxChannels, Allocator.Persistent); + processor.m_HighPassState = new NativeArray(k_MaxChannels, Allocator.Persistent); + } + else + { + // Configure runs again on a device change, and the memory belongs to the old sample rate. + processor.ClearState(); + } + } + + public void Dispose(ControlContext context, ref BandPassProcessor processor) + { + if (processor.m_LowPassState.IsCreated) + processor.m_LowPassState.Dispose(); + + if (processor.m_HighPassState.IsCreated) + processor.m_HighPassState.Dispose(); + } + + public void Update(ControlContext context, Pipe pipe) { } + + public Response OnMessage(ControlContext context, Pipe pipe, Message message) + { + if (!message.Is()) + return Response.Unhandled; + + var cutoffs = message.Get(); + + // Clamp before the audio thread sees it. A one-pole coefficient means nothing above Nyquist, + // and the band has to stay the right way round. + var nyquist = m_SampleRate > 0 ? m_SampleRate * 0.5f : 24000f; + + m_Cutoffs.lowCutHz = math.clamp(cutoffs.lowCutHz, 20f, nyquist - 1f); + m_Cutoffs.highCutHz = math.clamp(cutoffs.highCutHz, m_Cutoffs.lowCutHz + 50f, nyquist - 1f); + + pipe.SendData(context, ComputeCoefficients()); + + return Response.Handled; + } + + Coefficients ComputeCoefficients() => new Coefficients + { + lowPass = OnePole(m_Cutoffs.highCutHz, m_SampleRate), + highPass = OnePole(m_Cutoffs.lowCutHz, m_SampleRate), + }; + + /// The usual one-pole coefficient, 1 - exp(-2*pi*f/sampleRate). + static float OnePole(float cutoffHz, int sampleRate) + { + if (sampleRate <= 0) + return 1f; + + return math.saturate(1f - math.exp(-2f * math.PI * cutoffHz / sampleRate)); + } + } + } + + /// + /// Add this next to an AudioSource to band-limit whatever the source plays. Unity finds the + /// component on its own: there is no field to assign. + /// + [RequireComponent(typeof(AudioSource))] + public class BandPassEffect : MonoBehaviour, IAudioEffect + { + [Tooltip("Everything below this rolls off.")] + [Range(50f, 2000f)] public float lowCutHz = 400f; + + [Tooltip("Everything above this rolls off.")] + [Range(800f, 8000f)] public float highCutHz = 2600f; + + AudioSource m_Source; + float m_SentLowCutHz; + float m_SentHighCutHz; + + public EffectInstance CreateInstance(ControlContext context, AudioFormat? nestedFormat, + EffectInstance.CreationParameters creationParameters) + { + return context.AllocateEffect( + new BandPassProcessor(), + new BandPassProcessor.Control(new BandPassProcessor.Cutoffs(lowCutHz, highCutHz)), + nestedFormat, + creationParameters); + } + + void Awake() + { + m_Source = GetComponent(); + m_SentLowCutHz = lowCutHz; + m_SentHighCutHz = highCutHz; + } + + void Update() + { + if (Mathf.Approximately(lowCutHz, m_SentLowCutHz) && Mathf.Approximately(highCutHz, m_SentHighCutHz)) + return; + + // An effect is addressed by its component, since a GameObject can carry several. The instance + // only exists while the source plays, so nothing counts as sent until it goes out. + var instance = m_Source.GetEffectInstance(this); + + if (!ControlContext.builtIn.Exists(instance)) + return; + + var message = new BandPassProcessor.Cutoffs(lowCutHz, highCutHz); + ControlContext.builtIn.SendMessage(instance, ref message); + + m_SentLowCutHz = lowCutHz; + m_SentHighCutHz = highCutHz; + } + } +} diff --git a/scriptable-processor-effect-rack/Assets/Scripts/Effects/BandPassEffect.cs.meta b/scriptable-processor-effect-rack/Assets/Scripts/Effects/BandPassEffect.cs.meta new file mode 100644 index 0000000..1f900e9 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/Effects/BandPassEffect.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c1d93a71313045d8ad42e117c88c290a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scriptable-processor-effect-rack/Assets/Scripts/Effects/DriveEffect.cs b/scriptable-processor-effect-rack/Assets/Scripts/Effects/DriveEffect.cs new file mode 100644 index 0000000..b90f0fa --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/Effects/DriveEffect.cs @@ -0,0 +1,157 @@ +using Unity.Burst; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Audio; +using static UnityEngine.Audio.ProcessorInstance; + +namespace RadioEffectRack +{ + /// + /// Pushes the signal into a soft clipper, then trims the result back down. Cheap transmitter + /// distortion, and the simplest complete effect in this project: no state, no allocation. + /// + /// The clipper normalizes its own peak, but clipping raises the average level far more than the + /// peak, so a driven signal still sounds much louder. The output trim is what makes it possible to + /// match levels while dialling the drive around. + /// + [BurstCompile(CompileSynchronously = true)] + struct DriveProcessor : EffectInstance.IRealtime + { + /// + /// Pre-gain into the clipper, and the linear gain applied after it. Travels from the component + /// and over the pipe unchanged. + /// + internal struct Settings + { + internal float drive; + internal float output; + + internal Settings(float drive, float output) + { + this.drive = drive; + this.output = output; + } + } + + float m_Drive; + float m_Output; + + internal DriveProcessor(float drive, float output) + { + m_Drive = drive; + m_Output = output; + } + + public void Update(UpdatedDataContext context, Pipe pipe) + { + foreach (var element in pipe.GetAvailableData(context)) + { + if (element.TryGetData(out Settings settings)) + { + m_Drive = settings.drive; + m_Output = settings.output; + } + } + } + + public EffectInstance.Result Process(in RealtimeContext context, ChannelBuffer inputBuffer, + ChannelBuffer outputBuffer, EffectInstance.Arguments args) + { + // One factor for two jobs: normalizing the clipper's peak, and the output trim. + var scale = m_Output / math.tanh(m_Drive); + + for (var frame = 0; frame < inputBuffer.frameCount; frame++) + { + for (var channel = 0; channel < inputBuffer.channelCount; channel++) + { + // Read before writing: input and output can be the same memory. + var input = inputBuffer[channel, frame]; + + outputBuffer[channel, frame] = math.tanh(input * m_Drive) * scale; + } + } + + return default; + } + + internal struct Control : EffectInstance.IControl + { + public void Configure(ControlContext context, ref DriveProcessor processor, + in AudioConfiguration configuration, out EffectInstance.Setup setup) + { + setup = default; + } + + public void Dispose(ControlContext context, ref DriveProcessor processor) { } + + public void Update(ControlContext context, Pipe pipe) { } + + public Response OnMessage(ControlContext context, Pipe pipe, Message message) + { + if (!message.Is()) + return Response.Unhandled; + + // Clamp before forwarding. A drive of 0 divides by zero in the normalizer. + var settings = message.Get(); + + pipe.SendData(context, new Settings( + math.clamp(settings.drive, 1f, 60f), + math.clamp(settings.output, 0f, 4f))); + + return Response.Handled; + } + } + } + + /// Add this next to an AudioSource to overdrive whatever the source plays. + [RequireComponent(typeof(AudioSource))] + public class DriveEffect : MonoBehaviour, IAudioEffect + { + [Tooltip("Pre-gain into the soft clipper. Higher values sound more crushed.")] + [Range(1f, 40f)] public float drive = 9f; + + [Tooltip("Gain applied after the clipper, in dB. Use it to match levels as you change the drive.")] + [Range(-40f, 6f)] public float outputDb; + + AudioSource m_Source; + float m_SentDrive; + float m_SentOutputDb; + + public EffectInstance CreateInstance(ControlContext context, AudioFormat? nestedFormat, + EffectInstance.CreationParameters creationParameters) + { + // The values that bypass OnMessage's clamp. + return context.AllocateEffect( + new DriveProcessor(Mathf.Max(drive, 1f), DecibelToLinear(outputDb)), + new DriveProcessor.Control(), + nestedFormat, + creationParameters); + } + + void Awake() + { + m_Source = GetComponent(); + m_SentDrive = drive; + m_SentOutputDb = outputDb; + } + + void Update() + { + if (Mathf.Approximately(drive, m_SentDrive) && Mathf.Approximately(outputDb, m_SentOutputDb)) + return; + + var instance = m_Source.GetEffectInstance(this); + + if (!ControlContext.builtIn.Exists(instance)) + return; + + var message = new DriveProcessor.Settings(drive, DecibelToLinear(outputDb)); + ControlContext.builtIn.SendMessage(instance, ref message); + + m_SentDrive = drive; + m_SentOutputDb = outputDb; + } + + static float DecibelToLinear(float decibels) => Mathf.Pow(10f, decibels / 20f); + } +} diff --git a/scriptable-processor-effect-rack/Assets/Scripts/Effects/DriveEffect.cs.meta b/scriptable-processor-effect-rack/Assets/Scripts/Effects/DriveEffect.cs.meta new file mode 100644 index 0000000..cae9588 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/Effects/DriveEffect.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8ba4de1e54874822aace85f016bf494d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scriptable-processor-effect-rack/Assets/Scripts/Effects/HissEffect.cs b/scriptable-processor-effect-rack/Assets/Scripts/Effects/HissEffect.cs new file mode 100644 index 0000000..7c60214 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/Effects/HissEffect.cs @@ -0,0 +1,224 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Audio; +using static UnityEngine.Audio.ProcessorInstance; + +namespace RadioEffectRack +{ + /// + /// Mixes a bed of static under the signal, so the channel sounds open even between phrases. + /// + /// The noise comes from a nested . An effect that owns a nested + /// processor owns all of it: create it with the format it will run at, tick it from the control + /// part, render it from Process inside the same mix cycle, and destroy it in Dispose. + /// + [BurstCompile(CompileSynchronously = true)] + struct HissProcessor : EffectInstance.IRealtime + { + // Sized for the widest speaker layout, so a device change cannot outgrow it. AudioSpeakerMode + // tops out at 7.1. + internal const int k_MaxChannels = 8; + + /// Static level, linear. Sent from the component and forwarded over the pipe. + internal struct Level + { + internal float level; + } + + GeneratorInstance m_Noise; + internal NativeArray m_Scratch; + internal float m_Level; + + internal HissProcessor(GeneratorInstance noise, float level) + { + m_Noise = noise; + m_Scratch = default; + m_Level = level; + } + + public void Update(UpdatedDataContext context, Pipe pipe) + { + foreach (var element in pipe.GetAvailableData(context)) + { + if (element.TryGetData(out Level level)) + m_Level = level.level; + } + } + + public EffectInstance.Result Process(in RealtimeContext context, ChannelBuffer inputBuffer, + ChannelBuffer outputBuffer, EffectInstance.Arguments args) + { + var sampleCount = inputBuffer.frameCount * inputBuffer.channelCount; + + // If the host asks for more than Configure sized for, pass through: nothing on the audio thread + // may allocate, and every output sample still has to be written. + if (!m_Scratch.IsCreated || m_Scratch.Length < sampleCount) + { + for (var channel = 0; channel < inputBuffer.channelCount; channel++) + { + for (var frame = 0; frame < inputBuffer.frameCount; frame++) + { + outputBuffer[channel, frame] = inputBuffer[channel, frame]; + } + } + + return default; + } + + // A nested processor runs when its owner runs it, from inside the owner's Process. + var scratch = new ChannelBuffer(m_Scratch.AsSpan().Slice(0, sampleCount), inputBuffer.channelCount); + var written = context.Process(m_Noise, scratch, default).processedFrames; + + for (var channel = 0; channel < inputBuffer.channelCount; channel++) + { + for (var frame = 0; frame < inputBuffer.frameCount; frame++) + { + // Read before writing: input and output can be the same memory. + var input = inputBuffer[channel, frame]; + var noise = frame < written ? scratch[channel, frame] : 0f; + + outputBuffer[channel, frame] = input + noise * m_Level; + } + } + + return default; + } + + internal struct Control : EffectInstance.IControl + { + GeneratorInstance m_Noise; + float m_Level; + + internal Control(GeneratorInstance noise, float level) + { + m_Noise = noise; + m_Level = level; + } + + public void Configure(ControlContext context, ref HissProcessor processor, + in AudioConfiguration configuration, out EffectInstance.Setup setup) + { + setup = default; + + // Control side, realtime suspended: the place to allocate. + var sampleCount = configuration.dspBufferSize * k_MaxChannels; + + if (processor.m_Scratch.IsCreated && processor.m_Scratch.Length != sampleCount) + processor.m_Scratch.Dispose(); + + if (!processor.m_Scratch.IsCreated) + processor.m_Scratch = new NativeArray(sampleCount, Allocator.Persistent); + + processor.m_Level = m_Level; + + // A child may only be reconfigured during a system-wide reconfiguration, which is the case that + // matters: a new output device. Not so on the Configure that runs at creation, where the child + // was just built with this format, and asking again is a recursive configuration change that + // the audio system rejects. + if (context.IsSystemWideReconfiguring) + { + context.Configure(m_Noise, new AudioFormat(configuration.speakerMode, + configuration.sampleRate, configuration.dspBufferSize)); + } + } + + public void Dispose(ControlContext context, ref HissProcessor processor) + { + // The nested generator and the scratch buffer both belong to this effect. + context.Destroy(m_Noise); + + if (processor.m_Scratch.IsCreated) + processor.m_Scratch.Dispose(); + } + + public void Update(ControlContext context, Pipe pipe) + { + // Nothing updates a nested processor on its own. Hence UpdateSetting.UpdateAlways. + context.Update(m_Noise); + } + + public Response OnMessage(ControlContext context, Pipe pipe, Message message) + { + if (!message.Is()) + return Response.Unhandled; + + m_Level = math.clamp(message.Get().level, 0f, 1f); + pipe.SendData(context, new Level { level = m_Level }); + + return Response.Handled; + } + } + } + + /// Add this next to an AudioSource to mix static under whatever the source plays. + [RequireComponent(typeof(AudioSource))] + public class HissEffect : MonoBehaviour, IAudioEffect + { + [Tooltip("Level of the static bed, in dB.")] + [Range(-80f, -12f)] public float levelDb = -44f; + + AudioSource m_Source; + AudioFormat m_Format; + uint m_Seed; + float m_SentLevelDb; + + public EffectInstance CreateInstance(ControlContext context, AudioFormat? nestedFormat, + EffectInstance.CreationParameters creationParameters) + { + // A child is created with the format it will be rendered at: the owner's when nested, otherwise + // the system configuration. + var format = nestedFormat ?? m_Format; + var level = DecibelToLinear(levelDb); + + var noise = context.AllocateGenerator(new WhiteNoiseGenerator(m_Seed), + new WhiteNoiseGenerator.Control(), format); + + // CreationParameters is one flags word, and the defaults are only substituted when the whole + // word is unset. Setting one side turns the other off, so always spell out both. + // Control every tick, because that is where the nested generator is updated. Realtime only when + // a parameter arrives. + creationParameters.controlUpdateSetting = UpdateSetting.UpdateAlways; + creationParameters.realtimeUpdateSetting = UpdateSetting.UpdateIfDataIsAvailable; + + return context.AllocateEffect( + new HissProcessor(noise, level), + new HissProcessor.Control(noise, level), + nestedFormat, + creationParameters); + } + + void Awake() + { + m_Source = GetComponent(); + m_SentLevelDb = levelDb; + + // Both are main-thread APIs, and CreateInstance runs whenever the audio system wants an + // instance. Configure keeps the child in step if the configuration changes later. + m_Seed = (uint)UnityEngine.Random.Range(1, int.MaxValue); + + var configuration = AudioSettings.GetConfiguration(); + m_Format = new AudioFormat(configuration.speakerMode, configuration.sampleRate, + configuration.dspBufferSize); + } + + void Update() + { + if (Mathf.Approximately(levelDb, m_SentLevelDb)) + return; + + var instance = m_Source.GetEffectInstance(this); + + if (!ControlContext.builtIn.Exists(instance)) + return; + + var message = new HissProcessor.Level { level = DecibelToLinear(levelDb) }; + ControlContext.builtIn.SendMessage(instance, ref message); + + m_SentLevelDb = levelDb; + } + + static float DecibelToLinear(float decibels) => Mathf.Pow(10f, decibels / 20f); + } +} diff --git a/scriptable-processor-effect-rack/Assets/Scripts/Effects/HissEffect.cs.meta b/scriptable-processor-effect-rack/Assets/Scripts/Effects/HissEffect.cs.meta new file mode 100644 index 0000000..19f02fe --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/Effects/HissEffect.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0bb408417c1548cd9ebfd9d7272f2172 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scriptable-processor-effect-rack/Assets/Scripts/Effects/NoiseGateEffect.cs b/scriptable-processor-effect-rack/Assets/Scripts/Effects/NoiseGateEffect.cs new file mode 100644 index 0000000..4e29cd9 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/Effects/NoiseGateEffect.cs @@ -0,0 +1,295 @@ +using Unity.Burst; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Audio; +using static UnityEngine.Audio.ProcessorInstance; + +namespace RadioEffectRack +{ + /// + /// Silences the signal between phrases, the way a squelch circuit does. An envelope follower tracks + /// the loudest channel, and the gate opens while that envelope sits above the threshold. + /// + /// This effect is the example of data travelling the other way: the audio thread posts the envelope + /// to the control side over the pipe, and the control side hands it back to the UI when asked. + /// + [BurstCompile(CompileSynchronously = true)] + struct NoiseGateProcessor : EffectInstance.IRealtime + { + /// Gate parameters in the units a person thinks in. Sent from the component. + internal struct Settings + { + internal float thresholdDb; + internal float attackMs; + internal float releaseMs; + } + + /// The same settings converted for the audio thread. Sent over the pipe. + internal struct Tuning + { + internal float threshold; + internal float attack; + internal float release; + } + + /// What the audio thread reports back, once per real-time update. + internal struct Status + { + internal float envelope; + internal bool isOpen; + } + + /// + /// Sent by the UI to read the newest back out. Messages are passed by + /// reference, so the control part answers by writing into the message. + /// + internal struct StatusQuery + { + internal Status status; + internal bool hasStatus; + } + + Tuning m_Tuning; + float m_Envelope; + float m_Gain; + + public void Update(UpdatedDataContext context, Pipe pipe) + { + foreach (var element in pipe.GetAvailableData(context)) + { + if (element.TryGetData(out Tuning tuning)) + m_Tuning = tuning; + } + + // Post the gate state. Update is where the realtime part is handed the pipe; an effect's + // Process is not, unlike a generator's. SendData returns false when the pipe is full, which + // here only means nobody has read the meter for a while. + pipe.SendData(context, new Status { envelope = m_Envelope, isOpen = m_Gain > 0.5f }); + } + + public EffectInstance.Result Process(in RealtimeContext context, ChannelBuffer inputBuffer, + ChannelBuffer outputBuffer, EffectInstance.Arguments args) + { + for (var frame = 0; frame < inputBuffer.frameCount; frame++) + { + // Follow the loudest channel, so the gate opens and closes for all of them together. Every + // input for a frame is read before any output is written: they can be the same memory. + var peak = 0f; + + for (var channel = 0; channel < inputBuffer.channelCount; channel++) + { + peak = math.max(peak, math.abs(inputBuffer[channel, frame])); + } + + // Rise on attack, fall on release. + m_Envelope += (peak > m_Envelope ? m_Tuning.attack : m_Tuning.release) * (peak - m_Envelope); + + // Smooth the decision too, so the gate fades rather than clicks. + var target = m_Envelope >= m_Tuning.threshold ? 1f : 0f; + m_Gain += (target > m_Gain ? m_Tuning.attack : m_Tuning.release) * (target - m_Gain); + + for (var channel = 0; channel < inputBuffer.channelCount; channel++) + { + outputBuffer[channel, frame] = inputBuffer[channel, frame] * m_Gain; + } + } + + return default; + } + + internal struct Control : EffectInstance.IControl + { + Settings m_Settings; + int m_SampleRate; + + // The audio thread posts one reading per mix block, slower than a UI polls, so the newest is + // kept and every query answered from it. + Status m_LastStatus; + bool m_HasStatus; + + internal Control(Settings settings) + { + m_Settings = settings; + m_SampleRate = 0; + m_LastStatus = default; + m_HasStatus = false; + } + + public void Configure(ControlContext context, ref NoiseGateProcessor processor, + in AudioConfiguration configuration, out EffectInstance.Setup setup) + { + setup = default; + + // Times, so the coefficients depend on the sample rate. + m_SampleRate = configuration.sampleRate; + processor.m_Tuning = ComputeTuning(); + } + + public void Dispose(ControlContext context, ref NoiseGateProcessor processor) { } + + public void Update(ControlContext context, Pipe pipe) + { + // Consume as they arrive, so the pipe does not back up while nothing reads the meter. + Drain(context, pipe); + } + + /// Takes every reading the audio thread has posted and keeps the newest. + void Drain(ControlContext context, Pipe pipe) + { + foreach (var element in pipe.GetAvailableData(context)) + { + if (element.TryGetData(out Status status)) + { + m_LastStatus = status; + m_HasStatus = true; + } + } + } + + public Response OnMessage(ControlContext context, Pipe pipe, Message message) + { + if (message.Is()) + { + var settings = message.Get(); + + m_Settings.thresholdDb = math.clamp(settings.thresholdDb, -80f, 0f); + m_Settings.attackMs = math.clamp(settings.attackMs, 0.1f, 1000f); + m_Settings.releaseMs = math.clamp(settings.releaseMs, 1f, 5000f); + + pipe.SendData(context, ComputeTuning()); + + return Response.Handled; + } + + if (message.Is()) + { + // Get returns a reference to the caller's own message, so writing to it is the reply. + ref var query = ref message.Get(); + + // hasStatus means the effect has reported at least once, not that a reading arrived since you + // last asked. A caller polling faster than the mix rate gets the most recent value, not a gap. + Drain(context, pipe); + + query.status = m_LastStatus; + query.hasStatus = m_HasStatus; + + return Response.Handled; + } + + return Response.Unhandled; + } + + Tuning ComputeTuning() => new Tuning + { + threshold = math.pow(10f, m_Settings.thresholdDb / 20f), + attack = TimeConstant(m_Settings.attackMs, m_SampleRate), + release = TimeConstant(m_Settings.releaseMs, m_SampleRate), + }; + + /// One-pole coefficient for a time constant given in milliseconds. + static float TimeConstant(float milliseconds, int sampleRate) + { + if (sampleRate <= 0) + return 1f; + + var samples = math.max(1f, milliseconds * 0.001f * sampleRate); + + return math.saturate(1f - math.exp(-1f / samples)); + } + } + } + + /// Add this next to an AudioSource to gate whatever the source plays. + [RequireComponent(typeof(AudioSource))] + public class NoiseGateEffect : MonoBehaviour, IAudioEffect + { + [Tooltip("The gate opens while the signal sits above this level.")] + [Range(-60f, 0f)] public float thresholdDb = -34f; + + [Tooltip("How fast the gate opens.")] + [Range(0.5f, 50f)] public float attackMs = 3f; + + [Tooltip("How fast the gate closes again.")] + [Range(10f, 500f)] public float releaseMs = 140f; + + AudioSource m_Source; + NoiseGateProcessor.Settings m_Sent; + + public EffectInstance CreateInstance(ControlContext context, AudioFormat? nestedFormat, + EffectInstance.CreationParameters creationParameters) + { + // Both spelled out together: see HissEffect for why setting one alone turns the other off. + // Realtime every mix cycle, because that is where the envelope is posted. + creationParameters.realtimeUpdateSetting = UpdateSetting.UpdateAlways; + creationParameters.controlUpdateSetting = UpdateSetting.UpdateIfDataIsAvailable; + + return context.AllocateEffect( + new NoiseGateProcessor(), + new NoiseGateProcessor.Control(CurrentSettings()), + nestedFormat, + creationParameters); + } + + /// + /// Reads the newest envelope back out of the running effect. Returns false when the effect is + /// not running, or has not reported anything yet. + /// + public bool TryGetEnvelope(out float envelopeDb, out bool isOpen) + { + envelopeDb = k_SilenceDb; + isOpen = false; + + var instance = m_Source.GetEffectInstance(this); + + if (!ControlContext.builtIn.Exists(instance)) + return false; + + var query = new NoiseGateProcessor.StatusQuery(); + + // Evaluated straight away, so the answer is in the message when it returns. + if (ControlContext.builtIn.SendMessage(instance, ref query) != Response.Handled || !query.hasStatus) + return false; + + envelopeDb = query.status.envelope > 0.0001f + ? 20f * Mathf.Log10(query.status.envelope) + : k_SilenceDb; + isOpen = query.status.isOpen; + + return true; + } + + internal const float k_SilenceDb = -80f; + + void Awake() + { + m_Source = GetComponent(); + m_Sent = CurrentSettings(); + } + + void Update() + { + var settings = CurrentSettings(); + + if (Mathf.Approximately(settings.thresholdDb, m_Sent.thresholdDb) + && Mathf.Approximately(settings.attackMs, m_Sent.attackMs) + && Mathf.Approximately(settings.releaseMs, m_Sent.releaseMs)) + return; + + var instance = m_Source.GetEffectInstance(this); + + if (!ControlContext.builtIn.Exists(instance)) + return; + + ControlContext.builtIn.SendMessage(instance, ref settings); + + m_Sent = settings; + } + + NoiseGateProcessor.Settings CurrentSettings() => new NoiseGateProcessor.Settings + { + thresholdDb = thresholdDb, + attackMs = attackMs, + releaseMs = releaseMs, + }; + } +} diff --git a/scriptable-processor-effect-rack/Assets/Scripts/Effects/NoiseGateEffect.cs.meta b/scriptable-processor-effect-rack/Assets/Scripts/Effects/NoiseGateEffect.cs.meta new file mode 100644 index 0000000..cd47e99 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/Effects/NoiseGateEffect.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf6e0826d7304b13a88822172ca84e97 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scriptable-processor-effect-rack/Assets/Scripts/Effects/SpectrumEffect.cs b/scriptable-processor-effect-rack/Assets/Scripts/Effects/SpectrumEffect.cs new file mode 100644 index 0000000..2c42fab --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/Effects/SpectrumEffect.cs @@ -0,0 +1,330 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Audio; +using static UnityEngine.Audio.ProcessorInstance; + +namespace RadioEffectRack +{ + /// + /// Measures the signal without changing it, and reports the level in each of sixteen frequency + /// bands so the panel can draw them as a histogram. + /// + /// The frequency split is a bank of two-pole state-variable band-pass filters rather than an FFT, + /// which keeps a reading down to sixteen numbers. That fits in a single , so + /// it rides the pipe as one message per mix block and the audio thread and the UI share no memory. + /// + /// The filters need to be selective to be worth drawing. A pair of one-poles, as BandPassEffect + /// uses to shape audio, only rejects about 6 dB two octaves from its centre, so every band would + /// mostly report the overall level. These reject about 22 dB, which is enough to see a spectrum. + /// + /// It is an ordinary effect component, so where it sits in the chain decides what it measures. + /// + [BurstCompile(CompileSynchronously = true)] + struct SpectrumProcessor : EffectInstance.IRealtime + { + internal const int k_BandCount = 16; + + internal const float k_LowestHz = 100f; + internal const float k_HighestHz = 8000f; + const float k_AttackMs = 5f; + const float k_ReleaseMs = 90f; + + // Matched to the band spacing: no gaps, little overlap. + const float k_Q = 3.5f; + + // Damping. The band-pass output peaks at Q, so the same number scales it back to unity. + const float k_Damping = 1f / k_Q; + + /// + /// One band: a topology-preserving state-variable filter and the envelope of its band-pass + /// output. The three coefficients come from the centre frequency and Q; the two state values + /// are the integrators. + /// + internal struct Band + { + internal float a1; + internal float a2; + internal float a3; + internal float ic1eq; + internal float ic2eq; + internal float envelope; + } + + /// One reading of every band, posted once per mix block. + internal struct Reading + { + internal float4x4 levels; + } + + /// + /// Sent by the UI to read the newest levels out. The caller owns the destination array; the + /// control part fills one entry per band, lowest first. + /// + internal struct LevelQuery + { + internal NativeArray destination; + internal bool hasLevels; + } + + internal NativeArray m_Bands; + float m_AttackCoefficient; + float m_ReleaseCoefficient; + Reading m_Reading; + + public void Update(UpdatedDataContext context, Pipe pipe) + { + // Update is where the realtime part is handed the pipe; an effect's Process is not. + pipe.SendData(context, m_Reading); + } + + public EffectInstance.Result Process(in RealtimeContext context, ChannelBuffer inputBuffer, + ChannelBuffer outputBuffer, EffectInstance.Arguments args) + { + if (!m_Bands.IsCreated) + { + // Guards the gap before the first Configure. + for (var channel = 0; channel < inputBuffer.channelCount; channel++) + { + for (var frame = 0; frame < inputBuffer.frameCount; frame++) + { + outputBuffer[channel, frame] = inputBuffer[channel, frame]; + } + } + + return default; + } + + var scale = 1f / inputBuffer.channelCount; + + for (var frame = 0; frame < inputBuffer.frameCount; frame++) + { + // Sum to mono for the analysis, pass every channel through untouched. + // Read before writing: input and output can be the same memory. + var mono = 0f; + + for (var channel = 0; channel < inputBuffer.channelCount; channel++) + { + var input = inputBuffer[channel, frame]; + + mono += input; + outputBuffer[channel, frame] = input; + } + + mono *= scale; + + for (var index = 0; index < k_BandCount; index++) + { + var band = m_Bands[index]; + + // One filter step. v1 is the band-pass output, peaking at Q, so scale it back before measuring. + var v3 = mono - band.ic2eq; + var v1 = band.a1 * band.ic1eq + band.a2 * v3; + var v2 = band.ic2eq + band.a2 * band.ic1eq + band.a3 * v3; + + band.ic1eq = 2f * v1 - band.ic1eq; + band.ic2eq = 2f * v2 - band.ic2eq; + + var magnitude = math.abs(v1 * k_Damping); + var coefficient = magnitude > band.envelope ? m_AttackCoefficient : m_ReleaseCoefficient; + + band.envelope += coefficient * (magnitude - band.envelope); + + m_Bands[index] = band; + } + } + + m_Reading = new Reading { levels = new float4x4(Group(0), Group(4), Group(8), Group(12)) }; + + return default; + } + + /// Four band envelopes, so a whole column packs into one matrix. + float4 Group(int first) => new float4( + m_Bands[first].envelope, + m_Bands[first + 1].envelope, + m_Bands[first + 2].envelope, + m_Bands[first + 3].envelope); + + internal struct Control : EffectInstance.IControl + { + Reading m_Latest; + bool m_HasReading; + + public void Configure(ControlContext context, ref SpectrumProcessor processor, + in AudioConfiguration configuration, out EffectInstance.Setup setup) + { + setup = default; + + if (!processor.m_Bands.IsCreated) + processor.m_Bands = new NativeArray(k_BandCount, Allocator.Persistent); + + // Log spaced. The coefficients depend on the sample rate, so the bank is rebuilt here and its + // state cleared: the old memory belongs to the old rate. + var sampleRate = configuration.sampleRate; + var octaves = math.log2(k_HighestHz / k_LowestHz); + + for (var index = 0; index < k_BandCount; index++) + { + var centre = k_LowestHz * math.pow(2f, octaves * index / (k_BandCount - 1f)); + + processor.m_Bands[index] = Coefficients(centre, sampleRate); + } + + processor.m_AttackCoefficient = TimeConstant(k_AttackMs, sampleRate); + processor.m_ReleaseCoefficient = TimeConstant(k_ReleaseMs, sampleRate); + } + + public void Dispose(ControlContext context, ref SpectrumProcessor processor) + { + if (processor.m_Bands.IsCreated) + processor.m_Bands.Dispose(); + } + + public void Update(ControlContext context, Pipe pipe) + { + Drain(context, pipe); + } + + /// Keeps the newest reading the audio thread has posted. + void Drain(ControlContext context, Pipe pipe) + { + foreach (var element in pipe.GetAvailableData(context)) + { + if (element.TryGetData(out Reading reading)) + { + m_Latest = reading; + m_HasReading = true; + } + } + } + + public Response OnMessage(ControlContext context, Pipe pipe, Message message) + { + if (!message.Is()) + return Response.Unhandled; + + Drain(context, pipe); + + ref var query = ref message.Get(); + var destination = query.destination; + + if (!destination.IsCreated || !m_HasReading || destination.Length < k_BandCount) + { + query.hasLevels = false; + + return Response.Handled; + } + + // One entry per band, lowest first. + for (var band = 0; band < k_BandCount; band++) + { + destination[band] = m_Latest.levels[band >> 2][band & 3]; + } + + query.hasLevels = true; + + return Response.Handled; + } + + /// + /// Prewarped coefficients for one band, with its integrator state left at zero. + /// + static Band Coefficients(float centreHz, int sampleRate) + { + if (sampleRate <= 0) + return default; + + // Clear of Nyquist, where the prewarping runs away. + var centre = math.min(centreHz, sampleRate * 0.45f); + var g = math.tan(math.PI * centre / sampleRate); + var a1 = 1f / (1f + g * (g + k_Damping)); + var a2 = g * a1; + + return new Band + { + a1 = a1, + a2 = a2, + a3 = g * a2, + }; + } + + /// One-pole coefficient for a time constant given in milliseconds. + static float TimeConstant(float milliseconds, int sampleRate) + { + if (sampleRate <= 0) + return 1f; + + var samples = math.max(1f, milliseconds * 0.001f * sampleRate); + + return math.saturate(1f - math.exp(-1f / samples)); + } + } + } + + /// Add this next to an AudioSource to watch the spectrum at that point in the chain. + [RequireComponent(typeof(AudioSource))] + public class SpectrumEffect : MonoBehaviour, IAudioEffect + { + AudioSource m_Source; + NativeArray m_Levels; + + /// How many bands a reading holds, lowest first. + public int bandCount => SpectrumProcessor.k_BandCount; + + /// Centre frequency of the lowest band. + public float lowestHz => SpectrumProcessor.k_LowestHz; + + /// Centre frequency of the highest band. + public float highestHz => SpectrumProcessor.k_HighestHz; + + /// The level of one band from the last read. + public float Level(int band) => m_Levels.IsCreated ? m_Levels[band] : 0f; + + public EffectInstance CreateInstance(ControlContext context, AudioFormat? nestedFormat, + EffectInstance.CreationParameters creationParameters) + { + // Both spelled out together: see HissEffect for why setting one alone turns the other off. + creationParameters.realtimeUpdateSetting = UpdateSetting.UpdateAlways; + creationParameters.controlUpdateSetting = UpdateSetting.UpdateIfDataIsAvailable; + + return context.AllocateEffect( + new SpectrumProcessor(), + new SpectrumProcessor.Control(), + nestedFormat, + creationParameters); + } + + /// Reads the newest levels out of the running effect. False when it isn't running. + public bool TryReadLevels() + { + if (!m_Levels.IsCreated) + return false; + + var instance = m_Source.GetEffectInstance(this); + + if (!ControlContext.builtIn.Exists(instance)) + return false; + + var query = new SpectrumProcessor.LevelQuery { destination = m_Levels }; + + if (ControlContext.builtIn.SendMessage(instance, ref query) != Response.Handled) + return false; + + return query.hasLevels; + } + + void Awake() + { + m_Source = GetComponent(); + m_Levels = new NativeArray(SpectrumProcessor.k_BandCount, Allocator.Persistent); + } + + void OnDestroy() + { + if (m_Levels.IsCreated) + m_Levels.Dispose(); + } + } +} diff --git a/scriptable-processor-effect-rack/Assets/Scripts/Effects/SpectrumEffect.cs.meta b/scriptable-processor-effect-rack/Assets/Scripts/Effects/SpectrumEffect.cs.meta new file mode 100644 index 0000000..a36efab --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/Effects/SpectrumEffect.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 12db624b6dd54df0b6dd5b01479ddbfa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scriptable-processor-effect-rack/Assets/Scripts/Effects/WhiteNoiseGenerator.cs b/scriptable-processor-effect-rack/Assets/Scripts/Effects/WhiteNoiseGenerator.cs new file mode 100644 index 0000000..4c07e46 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/Effects/WhiteNoiseGenerator.cs @@ -0,0 +1,67 @@ +using Unity.Burst; +using Unity.IntegerTime; +using Unity.Mathematics; +using UnityEngine.Audio; +using static UnityEngine.Audio.ProcessorInstance; + +namespace RadioEffectRack +{ + /// + /// White noise, used by as its static bed. + /// + /// Nothing in here knows that it runs nested inside an effect: a generator is written the same way + /// whether an AudioSource drives it or another processor does. The difference is all on the owning + /// side, which creates it, updates it, renders it, and destroys it. + /// + [BurstCompile(CompileSynchronously = true)] + struct WhiteNoiseGenerator : GeneratorInstance.IRealtime + { + Random m_Random; + + // Never ends, no timeline to seek along. + public bool isFinite => false; + public bool isRealtime => false; + public DiscreteTime? length => null; + + internal WhiteNoiseGenerator(uint seed) + { + // Random rejects a zero seed, and throwing here would surface while an effect is being set up. + m_Random = new Random(seed == 0u ? 1u : seed); + } + + public void Update(UpdatedDataContext context, Pipe pipe) { } + + public GeneratorInstance.Result Process(in RealtimeContext context, Pipe pipe, ChannelBuffer buffer, + GeneratorInstance.Arguments args) + { + for (var frame = 0; frame < buffer.frameCount; frame++) + { + // One sample to every channel: correlated noise, like a single small speaker. + var sample = m_Random.NextFloat(-1f, 1f); + + for (var channel = 0; channel < buffer.channelCount; channel++) + { + buffer[channel, frame] = sample; + } + } + + return buffer.frameCount; + } + + internal struct Control : GeneratorInstance.IControl + { + public void Configure(ControlContext context, ref WhiteNoiseGenerator realtime, in AudioFormat format, + out GeneratorInstance.Setup setup, ref GeneratorInstance.Properties properties) + { + // Noise sounds the same at any rate, so match the owner and leave nothing to convert. + setup = new GeneratorInstance.Setup(format.speakerMode, format.sampleRate); + } + + public void Dispose(ControlContext context, ref WhiteNoiseGenerator realtime) { } + + public void Update(ControlContext context, Pipe pipe) { } + + public Response OnMessage(ControlContext context, Pipe pipe, Message message) => Response.Unhandled; + } + } +} diff --git a/scriptable-processor-effect-rack/Assets/Scripts/Effects/WhiteNoiseGenerator.cs.meta b/scriptable-processor-effect-rack/Assets/Scripts/Effects/WhiteNoiseGenerator.cs.meta new file mode 100644 index 0000000..b621e47 --- /dev/null +++ b/scriptable-processor-effect-rack/Assets/Scripts/Effects/WhiteNoiseGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3b2c45e3c6a4a74a27e1a2610aef6e4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scriptable-processor-effect-rack/Packages/manifest.json b/scriptable-processor-effect-rack/Packages/manifest.json new file mode 100644 index 0000000..be2cdd1 --- /dev/null +++ b/scriptable-processor-effect-rack/Packages/manifest.json @@ -0,0 +1,41 @@ +{ + "dependencies": { + "com.unity.ide.rider": "3.0.38", + "com.unity.ide.visualstudio": "2.0.26", + "com.unity.modules.accessibility": "1.0.0", + "com.unity.modules.adaptiveperformance": "1.0.0", + "com.unity.modules.ai": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.physicscore2d": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.smartstrings": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vectorgraphics": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } +} diff --git a/scriptable-processor-effect-rack/ProjectSettings/AudioManager.asset b/scriptable-processor-effect-rack/ProjectSettings/AudioManager.asset new file mode 100644 index 0000000..27287fe --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/AudioManager.asset @@ -0,0 +1,19 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!11 &1 +AudioManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Volume: 1 + Rolloff Scale: 1 + Doppler Factor: 1 + Default Speaker Mode: 2 + m_SampleRate: 0 + m_DSPBufferSize: 1024 + m_VirtualVoiceCount: 512 + m_RealVoiceCount: 32 + m_SpatializerPlugin: + m_AmbisonicDecoderPlugin: + m_DisableAudio: 0 + m_VirtualizeEffects: 1 + m_RequestedDSPBufferSize: 0 diff --git a/scriptable-processor-effect-rack/ProjectSettings/ClusterInputManager.asset b/scriptable-processor-effect-rack/ProjectSettings/ClusterInputManager.asset new file mode 100644 index 0000000..e7886b2 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/ClusterInputManager.asset @@ -0,0 +1,6 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!236 &1 +ClusterInputManager: + m_ObjectHideFlags: 0 + m_Inputs: [] diff --git a/scriptable-processor-effect-rack/ProjectSettings/DynamicsManager.asset b/scriptable-processor-effect-rack/ProjectSettings/DynamicsManager.asset new file mode 100644 index 0000000..fc90ab9 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/DynamicsManager.asset @@ -0,0 +1,36 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!55 &1 +PhysicsManager: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_Gravity: {x: 0, y: -9.81, z: 0} + m_DefaultMaterial: {fileID: 0} + m_BounceThreshold: 2 + m_SleepThreshold: 0.005 + m_DefaultContactOffset: 0.01 + m_DefaultSolverIterations: 6 + m_DefaultSolverVelocityIterations: 1 + m_QueriesHitBackfaces: 0 + m_QueriesHitTriggers: 1 + m_EnableAdaptiveForce: 0 + m_ClothInterCollisionDistance: 0.1 + m_ClothInterCollisionStiffness: 0.2 + m_ContactsGeneration: 1 + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + m_AutoSimulation: 1 + m_AutoSyncTransforms: 0 + m_ReuseCollisionCallbacks: 1 + m_ClothInterCollisionSettingsToggle: 0 + m_ClothGravity: {x: 0, y: -9.81, z: 0} + m_ContactPairsMode: 0 + m_BroadphaseType: 0 + m_WorldBounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 250, y: 250, z: 250} + m_WorldSubdivisions: 8 + m_FrictionType: 0 + m_EnableEnhancedDeterminism: 0 + m_EnableUnifiedHeightmaps: 1 + m_SolverType: 0 + m_DefaultMaxAngularSpeed: 50 diff --git a/scriptable-processor-effect-rack/ProjectSettings/EditorBuildSettings.asset b/scriptable-processor-effect-rack/ProjectSettings/EditorBuildSettings.asset new file mode 100644 index 0000000..08eb3c3 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/EditorBuildSettings.asset @@ -0,0 +1,11 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1045 &1 +EditorBuildSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Scenes: + - enabled: 1 + path: Assets/Scenes/EffectRack.unity + guid: ec8fa406414942d1aa410329eca4e73d + m_configObjects: {} diff --git a/scriptable-processor-effect-rack/ProjectSettings/EditorSettings.asset b/scriptable-processor-effect-rack/ProjectSettings/EditorSettings.asset new file mode 100644 index 0000000..327f9e4 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/EditorSettings.asset @@ -0,0 +1,52 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!159 &1 +EditorSettings: + m_ObjectHideFlags: 0 + serializedVersion: 16 + m_SerializationMode: 2 + m_LineEndingsForNewScripts: 0 + m_DefaultBehaviorMode: 0 + m_PrefabRegularEnvironment: {fileID: 0} + m_PrefabUIEnvironment: {fileID: 0} + m_SpritePackerMode: 0 + m_SpritePackerCacheSize: 10 + m_SpritePackerPaddingPower: 1 + m_Bc7TextureCompressor: 0 + m_EtcTextureCompressorBehavior: 1 + m_EtcTextureFastCompressor: 1 + m_EtcTextureNormalCompressor: 2 + m_EtcTextureBestCompressor: 4 + m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp;asmref + m_ProjectGenerationRootNamespace: + m_EnableTextureStreamingInEditMode: 1 + m_EnableTextureStreamingInPlayMode: 1 + m_EnableEditorAsyncCPUTextureLoading: 0 + m_AsyncShaderCompilation: 1 + m_BlockShaders: 0 + m_UnlockBlockShaders: 0 + m_PrefabModeAllowAutoSave: 1 + m_EnterPlayModeOptionsEnabled: 1 + m_EnterPlayModeOptions: 0 + m_GameObjectNamingDigits: 1 + m_GameObjectNamingScheme: 0 + m_AssetNamingUsesSpace: 1 + m_InspectorUseIMGUIDefaultInspector: 0 + m_UseLegacyProbeSampleCount: 0 + m_DisableCookiesInLightmapper: 0 + m_ShadowmaskStitching: 0 + m_AssetPipelineMode: 1 + m_RefreshImportMode: 0 + m_CacheServerMode: 0 + m_CacheServerEndpoint: + m_CacheServerNamespacePrefix: default + m_CacheServerEnableDownload: 1 + m_CacheServerEnableUpload: 1 + m_CacheServerEnableTls: 0 + m_CacheServerImportResultCachingEnabled: 0 + m_CacheServerValidationMode: 2 + m_CacheServerDownloadBatchSize: 128 + m_EnableEnlightenBakedGI: 0 + m_ReferencedClipsExactNaming: 1 + m_ForceAssetUnloadAndGCOnSceneLoad: 1 + m_HideBuildProfileClassicPlatforms: 1 diff --git a/scriptable-processor-effect-rack/ProjectSettings/GraphicsSettings.asset b/scriptable-processor-effect-rack/ProjectSettings/GraphicsSettings.asset new file mode 100644 index 0000000..3dc8c28 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/GraphicsSettings.asset @@ -0,0 +1,66 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!30 &1 +GraphicsSettings: + m_ObjectHideFlags: 0 + serializedVersion: 16 + m_Deferred: + m_Mode: 1 + m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} + m_DeferredReflections: + m_Mode: 1 + m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} + m_ScreenSpaceShadows: + m_Mode: 1 + m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} + m_DepthNormals: + m_Mode: 1 + m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} + m_MotionVectors: + m_Mode: 1 + m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} + m_LightHalo: + m_Mode: 1 + m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} + m_LensFlare: + m_Mode: 1 + m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} + m_VideoShadersIncludeMode: 2 + m_AlwaysIncludedShaders: + - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10783, guid: 0000000000000000f000000000000000, type: 0} + m_PreloadedShaders: [] + m_PreloadShadersBatchTimeLimit: -1 + m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0} + m_CustomRenderPipeline: {fileID: 0} + m_TransparencySortMode: 0 + m_TransparencySortAxis: {x: 0, y: 0, z: 1} + m_DefaultRenderingPath: 1 + m_DefaultMobileRenderingPath: 1 + m_TierSettings: [] + m_LightmapStripping: 0 + m_FogStripping: 0 + m_InstancingStripping: 0 + m_BrgStripping: 2 + m_LightmapKeepPlain: 1 + m_LightmapKeepDirCombined: 1 + m_LightmapKeepDynamicPlain: 1 + m_LightmapKeepDynamicDirCombined: 1 + m_LightmapKeepShadowMask: 1 + m_LightmapKeepSubtractive: 1 + m_FogKeepLinear: 1 + m_FogKeepExp: 1 + m_FogKeepExp2: 1 + m_AlbedoSwatchInfos: [] + m_RenderPipelineGlobalSettingsMap: {} + m_LightsUseLinearIntensity: 1 + m_LightsUseColorTemperature: 1 + m_LogWhenShaderIsCompiled: 0 + m_LightProbeOutsideHullStrategy: 0 + m_CameraRelativeLightCulling: 0 + m_CameraRelativeShadowCulling: 0 diff --git a/scriptable-processor-effect-rack/ProjectSettings/InputManager.asset b/scriptable-processor-effect-rack/ProjectSettings/InputManager.asset new file mode 100644 index 0000000..b16147e --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/InputManager.asset @@ -0,0 +1,487 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!13 &1 +InputManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Axes: + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: a + altPositiveButton: d + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: s + altPositiveButton: w + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left ctrl + altNegativeButton: + altPositiveButton: mouse 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left alt + altNegativeButton: + altPositiveButton: mouse 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left shift + altNegativeButton: + altPositiveButton: mouse 2 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: space + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse X + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse Y + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse ScrollWheel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 2 + joyNum: 0 + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 0 + type: 2 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 1 + type: 2 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 0 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 1 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 2 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 3 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: return + altNegativeButton: + altPositiveButton: joystick button 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: enter + altNegativeButton: + altPositiveButton: space + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Cancel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: escape + altNegativeButton: + altPositiveButton: joystick button 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Enable Debug Button 1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left ctrl + altNegativeButton: + altPositiveButton: joystick button 8 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Enable Debug Button 2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: backspace + altNegativeButton: + altPositiveButton: joystick button 9 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Reset + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left alt + altNegativeButton: + altPositiveButton: joystick button 1 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Next + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: page down + altNegativeButton: + altPositiveButton: joystick button 5 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Previous + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: page up + altNegativeButton: + altPositiveButton: joystick button 4 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Validate + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: return + altNegativeButton: + altPositiveButton: joystick button 0 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Persistent + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: right shift + altNegativeButton: + altPositiveButton: joystick button 2 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Multiplier + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left shift + altNegativeButton: + altPositiveButton: joystick button 3 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 2 + axis: 6 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 2 + axis: 5 + joyNum: 0 diff --git a/scriptable-processor-effect-rack/ProjectSettings/MemorySettings.asset b/scriptable-processor-effect-rack/ProjectSettings/MemorySettings.asset new file mode 100644 index 0000000..5b5face --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/MemorySettings.asset @@ -0,0 +1,35 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!387306366 &1 +MemorySettings: + m_ObjectHideFlags: 0 + m_EditorMemorySettings: + m_MainAllocatorBlockSize: -1 + m_ThreadAllocatorBlockSize: -1 + m_MainGfxBlockSize: -1 + m_ThreadGfxBlockSize: -1 + m_CacheBlockSize: -1 + m_TypetreeBlockSize: -1 + m_ProfilerBlockSize: -1 + m_ProfilerEditorBlockSize: -1 + m_BucketAllocatorGranularity: -1 + m_BucketAllocatorBucketsCount: -1 + m_BucketAllocatorBlockSize: -1 + m_BucketAllocatorBlockCount: -1 + m_ProfilerBucketAllocatorGranularity: -1 + m_ProfilerBucketAllocatorBucketsCount: -1 + m_ProfilerBucketAllocatorBlockSize: -1 + m_ProfilerBucketAllocatorBlockCount: -1 + m_TempAllocatorSizeMain: -1 + m_JobTempAllocatorBlockSize: -1 + m_BackgroundJobTempAllocatorBlockSize: -1 + m_JobTempAllocatorReducedBlockSize: -1 + m_TempAllocatorSizeGIBakingWorker: -1 + m_TempAllocatorSizeNavMeshWorker: -1 + m_TempAllocatorSizeAudioWorker: -1 + m_TempAllocatorSizeCloudWorker: -1 + m_TempAllocatorSizeGfx: -1 + m_TempAllocatorSizeJobWorker: -1 + m_TempAllocatorSizeBackgroundWorker: -1 + m_TempAllocatorSizePreloadManager: -1 + m_PlatformMemorySettings: {} diff --git a/scriptable-processor-effect-rack/ProjectSettings/MultiplayerManager.asset b/scriptable-processor-effect-rack/ProjectSettings/MultiplayerManager.asset new file mode 100644 index 0000000..2a93664 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/MultiplayerManager.asset @@ -0,0 +1,7 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!655991488 &1 +MultiplayerManager: + m_ObjectHideFlags: 0 + m_EnableMultiplayerRoles: 0 + m_StrippingTypes: {} diff --git a/scriptable-processor-effect-rack/ProjectSettings/NavMeshAreas.asset b/scriptable-processor-effect-rack/ProjectSettings/NavMeshAreas.asset new file mode 100644 index 0000000..3b0b7c3 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/NavMeshAreas.asset @@ -0,0 +1,91 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!126 &1 +NavMeshProjectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + areas: + - name: Walkable + cost: 1 + - name: Not Walkable + cost: 1 + - name: Jump + cost: 2 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + m_LastAgentTypeID: -887442657 + m_Settings: + - serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.75 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_SettingNames: + - Humanoid diff --git a/scriptable-processor-effect-rack/ProjectSettings/PackageManagerSettings.asset b/scriptable-processor-effect-rack/ProjectSettings/PackageManagerSettings.asset new file mode 100644 index 0000000..7b2d426 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/PackageManagerSettings.asset @@ -0,0 +1,44 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_EnablePreReleasePackages: 0 + m_AdvancedSettingsExpanded: 1 + m_ScopedRegistriesSettingsExpanded: 1 + m_SeeAllPackageVersions: 0 + m_DismissPreviewPackagesInUse: 0 + oneTimeWarningShown: 0 + oneTimePackageErrorsPopUpShown: 0 + m_MainRegistry: + m_Id: main + m_Name: + m_Url: https://packages.unity.com + m_Scopes: [] + m_IsDefault: 1 + m_IsUnityRegistry: 1 + m_Capabilities: 7 + m_ConfigSource: 0 + m_Compliance: + m_Status: 0 + m_Violations: [] + m_ScopedRegistries: [] + m_UserSelectedRegistryName: + m_UserAddingNewScopedRegistry: 0 + m_RegistryInfoDraft: + m_Modified: 0 + m_ErrorMessage: + m_UserModificationsEntityId: + m_rawData: 1099511629084 + m_OriginalEntityId: + m_rawData: 1099511629085 + m_LoadAssets: 0 diff --git a/scriptable-processor-effect-rack/ProjectSettings/Physics2DSettings.asset b/scriptable-processor-effect-rack/ProjectSettings/Physics2DSettings.asset new file mode 100644 index 0000000..47880b1 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/Physics2DSettings.asset @@ -0,0 +1,56 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!19 &1 +Physics2DSettings: + m_ObjectHideFlags: 0 + serializedVersion: 4 + m_Gravity: {x: 0, y: -9.81} + m_DefaultMaterial: {fileID: 0} + m_VelocityIterations: 8 + m_PositionIterations: 3 + m_VelocityThreshold: 1 + m_MaxLinearCorrection: 0.2 + m_MaxAngularCorrection: 8 + m_MaxTranslationSpeed: 100 + m_MaxRotationSpeed: 360 + m_BaumgarteScale: 0.2 + m_BaumgarteTimeOfImpactScale: 0.75 + m_TimeToSleep: 0.5 + m_LinearSleepTolerance: 0.01 + m_AngularSleepTolerance: 2 + m_DefaultContactOffset: 0.01 + m_JobOptions: + serializedVersion: 2 + useMultithreading: 0 + useConsistencySorting: 0 + m_InterpolationPosesPerJob: 100 + m_NewContactsPerJob: 30 + m_CollideContactsPerJob: 100 + m_ClearFlagsPerJob: 200 + m_ClearBodyForcesPerJob: 200 + m_SyncDiscreteFixturesPerJob: 50 + m_SyncContinuousFixturesPerJob: 50 + m_FindNearestContactsPerJob: 100 + m_UpdateTriggerContactsPerJob: 100 + m_IslandSolverCostThreshold: 100 + m_IslandSolverBodyCostScale: 1 + m_IslandSolverContactCostScale: 10 + m_IslandSolverJointCostScale: 10 + m_IslandSolverBodiesPerJob: 50 + m_IslandSolverContactsPerJob: 50 + m_AutoSimulation: 1 + m_QueriesHitTriggers: 1 + m_QueriesStartInColliders: 1 + m_CallbacksOnDisable: 1 + m_ReuseCollisionCallbacks: 1 + m_AutoSyncTransforms: 0 + m_AlwaysShowColliders: 0 + m_ShowColliderSleep: 1 + m_ShowColliderContacts: 0 + m_ShowColliderAABB: 0 + m_ContactArrowScale: 0.2 + m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} + m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} + m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} + m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/scriptable-processor-effect-rack/ProjectSettings/PhysicsCoreProjectSettings2D.asset b/scriptable-processor-effect-rack/ProjectSettings/PhysicsCoreProjectSettings2D.asset new file mode 100644 index 0000000..049c745 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/PhysicsCoreProjectSettings2D.asset @@ -0,0 +1,6 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!176606843 &1 +PhysicsCoreProjectSettings2D: + m_ObjectHideFlags: 0 + m_PhysicsCoreSettings: {fileID: 0} diff --git a/scriptable-processor-effect-rack/ProjectSettings/PresetManager.asset b/scriptable-processor-effect-rack/ProjectSettings/PresetManager.asset new file mode 100644 index 0000000..67a94da --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/PresetManager.asset @@ -0,0 +1,7 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1386491679 &1 +PresetManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_DefaultPresets: {} diff --git a/scriptable-processor-effect-rack/ProjectSettings/ProjectSettings.asset b/scriptable-processor-effect-rack/ProjectSettings/ProjectSettings.asset new file mode 100644 index 0000000..a0340c5 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/ProjectSettings.asset @@ -0,0 +1,987 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!129 &1 +PlayerSettings: + m_ObjectHideFlags: 0 + m_BuildProfilePlayerSettings: {fileID: 0} + serializedVersion: 31 + productGUID: 84c4238c0ec8d4d92a67b28a5c830b5a + AndroidProfiler: 0 + AndroidFilterTouchesWhenObscured: 0 + AndroidEnableSustainedPerformanceMode: 0 + defaultScreenOrientation: 4 + targetDevice: 2 + useOnDemandResources: 0 + accelerometerFrequency: 60 + companyName: DefaultCompany + productName: scriptable-processor-effect-rack + defaultCursor: {fileID: 0} + cursorHotspot: {x: 0, y: 0} + m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} + m_ShowUnitySplashScreen: 1 + m_ShowUnitySplashLogo: 1 + m_SplashScreenOverlayOpacity: 1 + m_SplashScreenAnimation: 1 + m_SplashScreenLogoStyle: 1 + m_SplashScreenDrawMode: 0 + m_SplashScreenBackgroundAnimationZoom: 1 + m_SplashScreenLogoAnimationZoom: 1 + m_SplashScreenBackgroundLandscapeAspect: 1 + m_SplashScreenBackgroundPortraitAspect: 1 + m_SplashScreenBackgroundLandscapeUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenBackgroundPortraitUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenLogos: [] + m_VirtualRealitySplashScreen: {fileID: 0} + defaultScreenWidth: 1024 + defaultScreenHeight: 768 + defaultScreenWidthWeb: 960 + defaultScreenHeightWeb: 600 + m_StereoRenderingPath: 0 + m_ActiveColorSpace: 1 + unsupportedMSAAFallback: 0 + m_SpriteBatchMaxVertexCount: 65535 + m_SpriteBatchVertexThreshold: 300 + m_MTRendering: 1 + mipStripping: 0 + numberOfMipsStripped: 0 + numberOfMipsStrippedPerMipmapLimitGroup: {} + m_StackTraceTypes: 000000004082c90201000000010000000100000001000000i + iosShowActivityIndicatorOnLoading: -1 + androidShowActivityIndicatorOnLoading: -1 + iosUseCustomAppBackgroundBehavior: 0 + allowedAutorotateToPortrait: 1 + allowedAutorotateToPortraitUpsideDown: 1 + allowedAutorotateToLandscapeRight: 1 + allowedAutorotateToLandscapeLeft: 1 + useOSAutorotation: 1 + use32BitDisplayBuffer: 1 + preserveFramebufferAlpha: 0 + adjustIOSFPSUsingThermalState: 1 + thermalStateSeriousIOSFPS: 30 + thermalStateCriticalIOSFPS: 15 + disableDepthAndStencilBuffers: 0 + androidStartInFullscreen: 1 + androidRenderOutsideSafeArea: 1 + androidUseSwappy: 0 + androidRequestedVisibleInsets: 0 + androidSystemBarsBehavior: 2 + androidDisplayOptions: 1 + androidBlitType: 0 + androidResizeableActivity: 1 + androidDefaultWindowWidth: 1920 + androidDefaultWindowHeight: 1080 + androidMinimumWindowWidth: 400 + androidMinimumWindowHeight: 300 + androidFullscreenMode: 1 + androidAutoRotationBehavior: 1 + androidPredictiveBackSupport: 1 + androidApplicationEntry: 2 + defaultIsNativeResolution: 1 + macRetinaSupport: 1 + runInBackground: 0 + callOnDisableOnAssetBundleUnload: 1 + muteOtherAudioSources: 0 + Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + audioSpatialExperience: 0 + deferSystemGesturesMode: 0 + hideHomeButton: 0 + submitAnalytics: 1 + usePlayerLog: 1 + dedicatedServerOptimizations: 1 + bakeCollisionMeshes: 0 + forceSingleInstance: 0 + useFlipModelSwapchain: 1 + resizableWindow: 0 + useMacAppStoreValidation: 0 + macAppStoreCategory: public.app-category.games + gpuSkinning: 1 + meshDeformation: 2 + xboxPIXTextureCapture: 0 + xboxEnableAvatar: 0 + xboxEnableKinect: 0 + xboxEnableKinectAutoTracking: 0 + xboxEnableFitness: 0 + visibleInBackground: 1 + allowFullscreenSwitch: 1 + fullscreenMode: 1 + xboxSpeechDB: 0 + xboxEnableHeadOrientation: 0 + xboxEnableGuest: 0 + xboxEnablePIXSampling: 0 + metalFramebufferOnly: 0 + metalUseMetalDisplayLink: 0 + xboxOneResolution: 0 + xboxOneSResolution: 0 + xboxOneXResolution: 3 + xboxOneMonoLoggingLevel: 0 + xboxOneLoggingLevel: 1 + xboxOneDisableEsram: 0 + xboxOneEnableTypeOptimization: 0 + xboxOnePresentImmediateThreshold: 0 + switchQueueCommandMemory: 1048576 + switchQueueControlMemory: 16384 + switchQueueComputeMemory: 262144 + switchNVNShaderPoolsGranularity: 33554432 + switchNVNDefaultPoolsGranularity: 16777216 + switchNVNOtherPoolsGranularity: 16777216 + switchGpuScratchPoolGranularity: 2097152 + switchAllowGpuScratchShrinking: 0 + switchNVNMaxPublicTextureIDCount: 0 + switchNVNMaxPublicSamplerIDCount: 0 + switchMaxWorkerMultiple: 8 + switchNVNGraphicsFirmwareMemory: 32 + switchGraphicsJobsSyncAfterKick: 1 + vulkanNumSwapchainBuffers: 3 + vulkanEnableSetSRGBWrite: 0 + vulkanEnablePreTransform: 1 + vulkanEnableLateAcquireNextImage: 0 + vulkanEnableCommandBufferRecycling: 1 + loadStoreDebugModeEnabled: 0 + visionOSBundleVersion: 1.0 + tvOSBundleVersion: 1.0 + bundleVersion: 0.1.0 + preloadedAssets: [] + metroInputSource: 0 + wsaTransparentSwapchain: 0 + xboxOneDisableKinectGpuReservation: 1 + xboxOneEnable7thCore: 1 + vrSettings: + enable360StereoCapture: 0 + enableFrameTimingStats: 0 + enableOpenGLProfilerGPURecorders: 1 + allowHDRDisplaySupport: 0 + useHDRDisplay: 0 + hdrBitDepth: 0 + m_ColorGamuts: 0000000000000000i + targetPixelDensity: 30 + resolutionScalingMode: 0 + resetResolutionOnWindowResize: 0 + androidSupportedAspectRatio: 1 + androidMaxAspectRatio: 2.4 + androidMinAspectRatio: 1 + applicationIdentifier: + Android: com.UnityTechnologies.com.unity.template.urpblank + Standalone: com.Unity-Technologies.com.unity.template.urp-blank + iPhone: com.Unity-Technologies.com.unity.template.urp-blank + buildNumber: + Standalone: 0 + VisionOS: 0 + iPhone: 0 + tvOS: 0 + overrideDefaultApplicationIdentifier: 1 + AndroidBundleVersionCode: 1 + AndroidMinSdkVersion: 26 + AndroidTargetSdkVersion: 0 + AndroidPreferredInstallLocation: 1 + AndroidPreferredDataLocation: 1 + aotOptions: + stripEngineCode: 1 + iPhoneStrippingLevel: 0 + iPhoneScriptCallOptimization: 0 + ForceInternetPermission: 0 + ForceSDCardPermission: 0 + CreateWallpaper: 0 + androidSplitApplicationBinary: 0 + keepLoadedShadersAlive: 0 + StripUnusedMeshComponents: 0 + strictShaderVariantMatching: 0 + VertexChannelCompressionMask: 4054 + iPhoneSdkVersion: 988 + iOSSimulatorArchitecture: 1 + iOSTargetOSVersionString: 15.0 + tvOSSdkVersion: 0 + tvOSSimulatorArchitecture: 1 + tvOSRequireExtendedGameController: 0 + tvOSTargetOSVersionString: 15.0 + VisionOSSdkVersion: 0 + VisionOSTargetOSVersionString: 1.0 + xcodeProjectType: 0 + uIPrerenderedIcon: 0 + uIRequiresPersistentWiFi: 0 + uIRequiresFullScreen: 1 + uIStatusBarHidden: 1 + uIExitOnSuspend: 0 + uIStatusBarStyle: 0 + appleTVSplashScreen: {fileID: 0} + appleTVSplashScreen2x: {fileID: 0} + tvOSSmallIconLayers: [] + tvOSSmallIconLayers2x: [] + tvOSLargeIconLayers: [] + tvOSLargeIconLayers2x: [] + tvOSTopShelfImageLayers: [] + tvOSTopShelfImageLayers2x: [] + tvOSTopShelfImageWideLayers: [] + tvOSTopShelfImageWideLayers2x: [] + iOSLaunchScreenType: 0 + iOSLaunchScreenPortrait: {fileID: 0} + iOSLaunchScreenLandscape: {fileID: 0} + iOSLaunchScreenBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreenFillPct: 100 + iOSLaunchScreenSize: 100 + iOSLaunchScreeniPadType: 0 + iOSLaunchScreeniPadImage: {fileID: 0} + iOSLaunchScreeniPadBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreeniPadFillPct: 100 + iOSLaunchScreeniPadSize: 100 + iOSLaunchScreenCustomStoryboardPath: + iOSLaunchScreeniPadCustomStoryboardPath: + iOSDeviceRequirements: [] + iOSURLSchemes: [] + macOSURLSchemes: [] + iOSBackgroundModes: 0 + iOSMetalForceHardShadows: 0 + metalEditorSupport: 1 + metalAPIValidation: 1 + metalCompileShaderBinary: 0 + iOSRenderExtraFrameOnPause: 0 + iosCopyPluginsCodeInsteadOfSymlink: 0 + appleDeveloperTeamID: + iOSManualSigningProvisioningProfileID: + tvOSManualSigningProvisioningProfileID: + VisionOSManualSigningProvisioningProfileID: + iOSManualSigningProvisioningProfileType: 0 + tvOSManualSigningProvisioningProfileType: 0 + VisionOSManualSigningProvisioningProfileType: 0 + appleEnableAutomaticSigning: 0 + iOSRequireARKit: 0 + iOSAutomaticallyDetectAndAddCapabilities: 1 + appleEnableProMotion: 0 + shaderPrecisionModel: 0 + clonedFromGUID: 3c72c65a16f0acb438eed22b8b16c24a + templatePackageId: com.unity.template.urp-blank@17.1.0 + templateDefaultScene: Assets/Scenes/SampleScene.unity + useCustomMainManifest: 0 + useCustomLauncherManifest: 0 + useCustomMainGradleTemplate: 0 + useCustomLauncherGradleManifest: 0 + useCustomBaseGradleTemplate: 0 + useCustomGradlePropertiesTemplate: 0 + useCustomGradleSettingsTemplate: 0 + useCustomProguardFile: 0 + AndroidTargetArchitectures: 2 + AndroidSplashScreenScale: 0 + androidSplashScreen: {fileID: 0} + AndroidKeystoreName: + AndroidKeyaliasName: + AndroidEnableArmv9SecurityFeatures: 0 + AndroidEnableArm64MTE: 0 + AndroidBuildApkPerCpuArchitecture: 0 + AndroidTVCompatibility: 0 + AndroidIsGame: 1 + androidAppCategory: 3 + useAndroidAppCategory: 1 + androidAppCategoryOther: + AndroidEnableTango: 0 + androidEnableBanner: 1 + androidUseLowAccuracyLocation: 0 + androidUseCustomKeystore: 0 + m_AndroidBanners: + - width: 320 + height: 180 + banner: {fileID: 0} + androidGamepadSupportLevel: 0 + AndroidMinifyRelease: 0 + AndroidMinifyDebug: 0 + AndroidValidateAppBundleSize: 1 + AndroidAppBundleSizeToValidate: 150 + AndroidReportGooglePlayAppDependencies: 1 + androidSymbolsSizeThreshold: 800 + m_BuildTargetIcons: [] + m_BuildTargetPlatformIcons: + - m_BuildTarget: iPhone + m_Icons: + - m_Textures: [] + m_Width: 180 + m_Height: 180 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 167 + m_Height: 167 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 152 + m_Height: 152 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 76 + m_Height: 76 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 87 + m_Height: 87 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 60 + m_Height: 60 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 20 + m_Height: 20 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 1024 + m_Height: 1024 + m_Kind: 4 + m_SubKind: App Store + - m_BuildTarget: Android + m_Icons: + - m_Textures: [] + m_Width: 432 + m_Height: 432 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 324 + m_Height: 324 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 216 + m_Height: 216 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 162 + m_Height: 162 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 108 + m_Height: 108 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 81 + m_Height: 81 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 640 + m_Height: 360 + m_Kind: 3 + m_SubKind: + - m_Textures: [] + m_Width: 480 + m_Height: 270 + m_Kind: 3 + m_SubKind: + - m_Textures: [] + m_Width: 320 + m_Height: 180 + m_Kind: 3 + m_SubKind: + - m_Textures: [] + m_Width: 240 + m_Height: 135 + m_Kind: 3 + m_SubKind: + - m_Textures: [] + m_Width: 160 + m_Height: 90 + m_Kind: 3 + m_SubKind: + - m_BuildTarget: tvOS + m_Icons: + - m_Textures: [] + m_Width: 1280 + m_Height: 768 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 800 + m_Height: 480 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 400 + m_Height: 240 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 4640 + m_Height: 1440 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 2320 + m_Height: 720 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 3840 + m_Height: 1440 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 1920 + m_Height: 720 + m_Kind: 1 + m_SubKind: + - m_BuildTarget: VisionOS + m_Icons: + - m_Textures: [] + m_Width: 1024 + m_Height: 1024 + m_Kind: 0 + m_SubKind: + m_BuildTargetBatching: + - m_BuildTarget: Standalone + m_StaticBatching: 1 + m_DynamicBatching: 0 + m_BuildTargetShaderSettings: [] + m_BuildTargetGraphicsJobs: [] + m_BuildTargetGraphicsJobMode: [] + m_BuildTargetGraphicsAPIs: + - m_BuildTarget: iOSSupport + m_APIs: 10000000i + m_Automatic: 1 + - m_BuildTarget: AndroidPlayer + m_APIs: 150000000b000000i + m_Automatic: 1 + m_DefaultShaderChunkSizeInMB: 16 + m_DefaultShaderChunkCount: 0 + openGLRequireES31: 0 + openGLRequireES31AEP: 0 + openGLRequireES32: 0 + m_TemplateCustomTags: {} + mobileMTRendering: + Android: 1 + iPhone: 1 + tvOS: 1 + m_BuildTargetGroupLightmapEncodingQuality: + - serializedVersion: 2 + m_BuildTarget: Android + m_EncodingQuality: 1 + m_BuildTargetGroupHDRCubemapEncodingQuality: [] + m_BuildTargetGroupLightmapSettings: [] + m_BuildTargetGroupLoadStoreDebugModeSettings: [] + m_BuildTargetNormalMapEncoding: + - m_BuildTarget: Android + m_Encoding: 1 + m_BuildTargetDefaultTextureCompressionFormat: + - serializedVersion: 3 + m_BuildTarget: Android + m_Formats: 00000000c0c62d00i + playModeTestRunnerEnabled: 0 + runPlayModeTestAsEditModeTest: 0 + actionOnDotNetUnhandledException: 1 + editorGfxJobOverride: 1 + enableInternalProfiler: 0 + logObjCUncaughtExceptions: 1 + enableCrashReportAPI: 0 + cameraUsageDescription: + locationUsageDescription: + microphoneUsageDescription: + bluetoothUsageDescription: + macOSTargetOSVersion: 12.0 + switchNMETAOverride: + switchNetLibKey: + switchSocketMemoryPoolSize: 6144 + switchSocketAllocatorPoolSize: 128 + switchSocketConcurrencyLimit: 14 + switchScreenResolutionBehavior: 2 + switchUseCPUProfiler: 0 + switchEnableFileSystemTrace: 0 + switchLTOSetting: 0 + switchApplicationID: 0x01004b9000490000 + switchNSODependencies: + switchCompilerFlags: + switchTitleNames_0: + switchTitleNames_1: + switchTitleNames_2: + switchTitleNames_3: + switchTitleNames_4: + switchTitleNames_5: + switchTitleNames_6: + switchTitleNames_7: + switchTitleNames_8: + switchTitleNames_9: + switchTitleNames_10: + switchTitleNames_11: + switchTitleNames_12: + switchTitleNames_13: + switchTitleNames_14: + switchTitleNames_15: + switchPublisherNames_0: + switchPublisherNames_1: + switchPublisherNames_2: + switchPublisherNames_3: + switchPublisherNames_4: + switchPublisherNames_5: + switchPublisherNames_6: + switchPublisherNames_7: + switchPublisherNames_8: + switchPublisherNames_9: + switchPublisherNames_10: + switchPublisherNames_11: + switchPublisherNames_12: + switchPublisherNames_13: + switchPublisherNames_14: + switchPublisherNames_15: + switchIcons_0: {fileID: 0} + switchIcons_1: {fileID: 0} + switchIcons_2: {fileID: 0} + switchIcons_3: {fileID: 0} + switchIcons_4: {fileID: 0} + switchIcons_5: {fileID: 0} + switchIcons_6: {fileID: 0} + switchIcons_7: {fileID: 0} + switchIcons_8: {fileID: 0} + switchIcons_9: {fileID: 0} + switchIcons_10: {fileID: 0} + switchIcons_11: {fileID: 0} + switchIcons_12: {fileID: 0} + switchIcons_13: {fileID: 0} + switchIcons_14: {fileID: 0} + switchIcons_15: {fileID: 0} + switchSmallIcons_0: {fileID: 0} + switchSmallIcons_1: {fileID: 0} + switchSmallIcons_2: {fileID: 0} + switchSmallIcons_3: {fileID: 0} + switchSmallIcons_4: {fileID: 0} + switchSmallIcons_5: {fileID: 0} + switchSmallIcons_6: {fileID: 0} + switchSmallIcons_7: {fileID: 0} + switchSmallIcons_8: {fileID: 0} + switchSmallIcons_9: {fileID: 0} + switchSmallIcons_10: {fileID: 0} + switchSmallIcons_11: {fileID: 0} + switchSmallIcons_12: {fileID: 0} + switchSmallIcons_13: {fileID: 0} + switchSmallIcons_14: {fileID: 0} + switchSmallIcons_15: {fileID: 0} + switchManualHTML: + switchAccessibleURLs: + switchLegalInformation: + switchMainThreadStackSize: 1048576 + switchPresenceGroupId: + switchLogoHandling: 0 + switchReleaseVersion: 0 + switchDisplayVersion: 1.0.0 + switchStartupUserAccount: 0 + switchSupportedLanguagesMask: 0 + switchLogoType: 0 + switchApplicationErrorCodeCategory: + switchUserAccountSaveDataSize: 0 + switchUserAccountSaveDataJournalSize: 0 + switchApplicationAttribute: 0 + switchCardSpecSize: -1 + switchCardSpecClock: -1 + switchRatingsMask: 0 + switchRatingsInt_0: 0 + switchRatingsInt_1: 0 + switchRatingsInt_2: 0 + switchRatingsInt_3: 0 + switchRatingsInt_4: 0 + switchRatingsInt_5: 0 + switchRatingsInt_6: 0 + switchRatingsInt_7: 0 + switchRatingsInt_8: 0 + switchRatingsInt_9: 0 + switchRatingsInt_10: 0 + switchRatingsInt_11: 0 + switchRatingsInt_12: 0 + switchLocalCommunicationIds_0: + switchLocalCommunicationIds_1: + switchLocalCommunicationIds_2: + switchLocalCommunicationIds_3: + switchLocalCommunicationIds_4: + switchLocalCommunicationIds_5: + switchLocalCommunicationIds_6: + switchLocalCommunicationIds_7: + switchParentalControl: 0 + switchAllowsScreenshot: 1 + switchAllowsVideoCapturing: 1 + switchAllowsRuntimeAddOnContentInstall: 0 + switchDataLossConfirmation: 0 + switchUserAccountLockEnabled: 0 + switchSystemResourceMemory: 16777216 + switchSupportedNpadStyles: 22 + switchNativeFsCacheSize: 32 + switchIsHoldTypeHorizontal: 0 + switchSupportedNpadCount: 8 + switchEnableTouchScreen: 1 + switchSocketConfigEnabled: 0 + switchTcpInitialSendBufferSize: 32 + switchTcpInitialReceiveBufferSize: 64 + switchTcpAutoSendBufferSizeMax: 256 + switchTcpAutoReceiveBufferSizeMax: 256 + switchUdpSendBufferSize: 9 + switchUdpReceiveBufferSize: 42 + switchSocketBufferEfficiency: 4 + switchSocketInitializeEnabled: 1 + switchNetworkInterfaceManagerInitializeEnabled: 1 + switchDisableHTCSPlayerConnection: 0 + switchUseNewStyleFilepaths: 0 + switchUseLegacyFmodPriorities: 0 + switchUseMicroSleepForYield: 1 + switchEnableRamDiskSupport: 0 + switchMicroSleepForYieldTime: 25 + switchRamDiskSpaceSize: 12 + switchUpgradedPlayerSettingsToNMETA: 1 + switchCaStoreSource: 0 + switchCaStoreFilePath: + ps4NPAgeRating: 12 + ps4NPTitleSecret: + ps4NPTrophyPackPath: + ps4ParentalLevel: 11 + ps4ContentID: ED1633-NPXX51362_00-0000000000000000 + ps4Category: 0 + ps4MasterVersion: 01.00 + ps4AppVersion: 01.00 + ps4AppType: 0 + ps4ParamSfxPath: + ps4VideoOutPixelFormat: 0 + ps4VideoOutInitialWidth: 1920 + ps4VideoOutBaseModeInitialWidth: 1920 + ps4VideoOutReprojectionRate: 60 + ps4PronunciationXMLPath: + ps4PronunciationSIGPath: + ps4BackgroundImagePath: + ps4StartupImagePath: + ps4StartupImagesFolder: + ps4IconImagesFolder: + ps4SaveDataImagePath: + ps4SdkOverride: + ps4BGMPath: + ps4ShareFilePath: + ps4ShareOverlayImagePath: + ps4PrivacyGuardImagePath: + ps4ExtraSceSysFile: + ps4NPtitleDatPath: + ps4RemotePlayKeyAssignment: -1 + ps4RemotePlayKeyMappingDir: + ps4PlayTogetherPlayerCount: 0 + ps4EnterButtonAssignment: 2 + ps4ApplicationParam1: 0 + ps4ApplicationParam2: 0 + ps4ApplicationParam3: 0 + ps4ApplicationParam4: 0 + ps4DownloadDataSize: 0 + ps4GarlicHeapSize: 2048 + ps4ProGarlicHeapSize: 2560 + playerPrefsMaxSize: 32768 + ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ + ps4pnSessions: 1 + ps4pnPresence: 1 + ps4pnFriends: 1 + ps4pnGameCustomData: 1 + playerPrefsSupport: 0 + enableApplicationExit: 0 + resetTempFolder: 1 + restrictedAudioUsageRights: 0 + ps4UseResolutionFallback: 0 + ps4ReprojectionSupport: 0 + ps4UseAudio3dBackend: 0 + ps4UseLowGarlicFragmentationMode: 1 + ps4SocialScreenEnabled: 0 + ps4ScriptOptimizationLevel: 2 + ps4Audio3dVirtualSpeakerCount: 14 + ps4attribCpuUsage: 0 + ps4PatchPkgPath: + ps4PatchLatestPkgPath: + ps4PatchChangeinfoPath: + ps4PatchDayOne: 0 + ps4attribUserManagement: 0 + ps4attribMoveSupport: 0 + ps4attrib3DSupport: 0 + ps4attribShareSupport: 0 + ps4attribExclusiveVR: 0 + ps4disableAutoHideSplash: 0 + ps4videoRecordingFeaturesUsed: 0 + ps4contentSearchFeaturesUsed: 0 + ps4CompatibilityPS5: 0 + ps4AllowPS5Detection: 0 + ps4GPU800MHz: 1 + ps4attribEyeToEyeDistanceSettingVR: 0 + ps4IncludedModules: [] + ps4attribVROutputEnabled: 0 + monoEnv: + splashScreenBackgroundSourceLandscape: {fileID: 0} + splashScreenBackgroundSourcePortrait: {fileID: 0} + blurSplashScreenBackground: 1 + spritePackerPolicy: + webGLMemorySize: 32 + webGLExceptionSupport: 1 + webGLNameFilesAsHashes: 0 + webGLShowDiagnostics: 0 + webGLDataCaching: 1 + webGLDebugSymbols: 0 + webGLEmscriptenArgs: + webGLModulesDirectory: + webGLTemplate: APPLICATION:Default + webGLAnalyzeBuildSize: 0 + webGLUseEmbeddedResources: 0 + webGLCompressionFormat: 0 + webGLWasmArithmeticExceptions: 0 + webGLLinkerTarget: 1 + webGLThreadsSupport: 0 + webGLDecompressionFallback: 0 + webGLInitialMemorySize: 32 + webGLMaximumMemorySize: 2048 + webGLMemoryGrowthMode: 2 + webGLMemoryLinearGrowthStep: 16 + webGLMemoryGeometricGrowthStep: 0.2 + webGLMemoryGeometricGrowthCap: 96 + webGLPowerPreference: 2 + webGLWebAssemblyTable: 0 + webGLWebAssemblyBigInt: 0 + webGLCloseOnQuit: 0 + webWasm2023: 1 + webEnableSubmoduleStrippingCompatibility: 0 + webProgressiveAssetLoading: 0 + scriptingDefineSymbols: {} + additionalCompilerArguments: {} + platformArchitecture: {} + scriptingBackend: + Android: 1 + il2cppCompilerConfiguration: {} + il2cppCodeGeneration: {} + il2cppLTOMode: {} + il2cppStacktraceInformation: {} + managedStrippingLevel: {} + incrementalIl2cppBuild: {} + suppressCommonWarnings: 1 + allowUnsafeCode: 0 + useDeterministicCompilation: 1 + additionalIl2CppArgs: + scriptingRuntimeVersion: 1 + gcIncremental: 1 + gcWBarrierValidation: 0 + managedCodeVariant: {} + apiCompatibilityLevelPerPlatform: {} + editorAssembliesCompatibilityLevel: 1 + m_RenderingPath: 1 + m_MobileRenderingPath: 1 + metroPackageName: scriptable-processor-random-container + metroPackageVersion: + metroCertificatePath: + metroCertificatePassword: + metroCertificateSubject: + metroCertificateIssuer: + metroCertificateNotAfter: 0000000000000000 + metroApplicationDescription: scriptable-processor-random-container + wsaImages: {} + metroTileShortName: + metroTileShowName: 0 + metroMediumTileShowName: 0 + metroLargeTileShowName: 0 + metroWideTileShowName: 0 + metroSupportStreamingInstall: 0 + metroLastRequiredScene: 0 + metroDefaultTileSize: 1 + metroTileForegroundText: 2 + metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} + metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} + metroSplashScreenUseBackgroundColor: 0 + syncCapabilities: 0 + platformCapabilities: {} + metroTargetDeviceFamilies: {} + metroFTAName: + metroFTAFileTypes: [] + metroProtocolName: + vcxProjDefaultLanguage: + XboxOneProductId: + XboxOneUpdateKey: + XboxOneSandboxId: + XboxOneContentId: + XboxOneTitleId: + XboxOneSCId: + XboxOneGameOsOverridePath: + XboxOnePackagingOverridePath: + XboxOneAppManifestOverridePath: + XboxOneVersion: 1.0.0.0 + XboxOnePackageEncryption: 0 + XboxOnePackageUpdateGranularity: 2 + XboxOneDescription: + XboxOneLanguage: + - enus + XboxOneCapability: [] + XboxOneGameRating: {} + XboxOneIsContentPackage: 0 + XboxOneEnhancedXboxCompatibilityMode: 0 + XboxOneEnableGPUVariability: 1 + XboxOneSockets: {} + XboxOneSplashScreen: {fileID: 0} + XboxOneAllowedProductIds: [] + XboxOnePersistentLocalStorageSize: 0 + XboxOneXTitleMemory: 8 + XboxOneOverrideIdentityName: + XboxOneOverrideIdentityPublisher: + cloudServicesEnabled: {} + luminIcon: + m_Name: + m_ModelFolderPath: + m_PortalFolderPath: + luminCert: + m_CertPath: + m_SignPackage: 1 + luminIsChannelApp: 0 + luminVersion: + m_VersionCode: 1 + m_VersionName: + hmiPlayerDataPath: + hmiForceSRGBBlit: 1 + embeddedLinuxEnableGamepadInput: 0 + hmiCpuConfiguration: 0000000000000000i + hmiLogStartupTiming: 0 + qnxGraphicConfPath: + apiCompatibilityLevel: 6 + captureStartupLogs: {} + activeInputHandler: 0 + windowsGamepadBackendHint: 0 + enableDirectStorage: 0 + cloudProjectId: + framebufferDepthMemorylessMode: 0 + qualitySettingsNames: [] + projectName: + organizationId: + cloudEnabled: 0 + legacyClampBlendShapeWeights: 0 + hmiLoadingImage: {fileID: 0} + platformRequiresReadableAssets: 0 + virtualTexturingSupportEnabled: 0 + insecureHttpOption: 0 + androidVulkanDenyFilterList: [] + androidVulkanAllowFilterList: [] + androidVulkanDeviceFilterListAsset: {fileID: 0} + d3d12DeviceFilterListAsset: {fileID: 0} + webGPUDeviceFilterListAsset: {fileID: 0} + allowedHttpConnections: 3 diff --git a/scriptable-processor-effect-rack/ProjectSettings/ProjectVersion.txt b/scriptable-processor-effect-rack/ProjectSettings/ProjectVersion.txt new file mode 100644 index 0000000..7492f6e --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/ProjectVersion.txt @@ -0,0 +1,2 @@ +m_EditorVersion: 6000.7.0b1 +m_EditorVersionWithRevision: 6000.7.0b1 (02f4c115e67d) diff --git a/scriptable-processor-effect-rack/ProjectSettings/QualitySettings.asset b/scriptable-processor-effect-rack/ProjectSettings/QualitySettings.asset new file mode 100644 index 0000000..06e9a34 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/QualitySettings.asset @@ -0,0 +1,135 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!47 &1 +QualitySettings: + m_ObjectHideFlags: 0 + serializedVersion: 5 + m_CurrentQuality: 1 + m_QualitySettings: + - serializedVersion: 4 + name: Mobile + pixelLightCount: 2 + shadows: 2 + shadowResolution: 1 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 40 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + skinWeights: 2 + globalTextureMipmapLimit: 0 + textureMipmapLimitSettings: [] + anisotropicTextures: 1 + antiAliasing: 0 + softParticles: 0 + softVegetation: 1 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 1 + useLegacyDetailDistribution: 1 + adaptiveVsync: 0 + vSyncCount: 0 + realtimeGICPUUsage: 100 + adaptiveVsyncExtraA: 0 + adaptiveVsyncExtraB: 0 + lodBias: 1 + maximumLODLevel: 0 + enableLODCrossFade: 1 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 11400000, guid: 5e6cbd92db86f4b18aec3ed561671858, type: 2} + terrainQualityOverrides: 0 + terrainPixelError: 1 + terrainDetailDensityScale: 1 + terrainBasemapDistance: 1000 + terrainDetailDistance: 80 + terrainTreeDistance: 5000 + terrainBillboardStart: 50 + terrainFadeLength: 5 + terrainMaxTrees: 50 + excludedTargetPlatforms: + - Standalone + - serializedVersion: 4 + name: PC + pixelLightCount: 2 + shadows: 2 + shadowResolution: 1 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 40 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + skinWeights: 4 + globalTextureMipmapLimit: 0 + textureMipmapLimitSettings: [] + anisotropicTextures: 2 + antiAliasing: 0 + softParticles: 0 + softVegetation: 1 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 1 + useLegacyDetailDistribution: 1 + adaptiveVsync: 0 + vSyncCount: 0 + realtimeGICPUUsage: 100 + adaptiveVsyncExtraA: 0 + adaptiveVsyncExtraB: 0 + lodBias: 2 + maximumLODLevel: 0 + enableLODCrossFade: 1 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 11400000, guid: 4b83569d67af61e458304325a23e5dfd, type: 2} + terrainQualityOverrides: 0 + terrainPixelError: 1 + terrainDetailDensityScale: 1 + terrainBasemapDistance: 1000 + terrainDetailDistance: 80 + terrainTreeDistance: 5000 + terrainBillboardStart: 50 + terrainFadeLength: 5 + terrainMaxTrees: 50 + excludedTargetPlatforms: + - Android + - iPhone + m_TextureMipmapLimitGroupNames: [] + m_PerPlatformDefaultQuality: + Android: 0 + AndroidXR: 0 + GameCoreScarlett: 1 + GameCoreXboxOne: 1 + Lumin: 0 + MetaQuest: 0 + Nintendo Switch: 1 + Nintendo Switch 2: 1 + PS4: 1 + PS5: 1 + Server: 0 + Stadia: 0 + Standalone: 1 + WebGL: 0 + Windows Store Apps: 0 + XboxOne: 0 + iPhone: 0 + tvOS: 0 diff --git a/scriptable-processor-effect-rack/ProjectSettings/SceneTemplateSettings.json b/scriptable-processor-effect-rack/ProjectSettings/SceneTemplateSettings.json new file mode 100644 index 0000000..ede5887 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/SceneTemplateSettings.json @@ -0,0 +1,121 @@ +{ + "templatePinStates": [], + "dependencyTypeInfos": [ + { + "userAdded": false, + "type": "UnityEngine.AnimationClip", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Animations.AnimatorController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.AnimatorOverrideController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Audio.AudioMixerController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.ComputeShader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Cubemap", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.GameObject", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.LightingDataAsset", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.LightingSettings", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Material", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.MonoScript", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.VolumeProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.SceneAsset", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Shader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.ShaderVariantCollection", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Timeline.TimelineAsset", + "defaultInstantiationMode": 0 + } + ], + "defaultDependencyTypeInfo": { + "userAdded": false, + "type": "", + "defaultInstantiationMode": 1 + }, + "newSceneOverride": 0 +} \ No newline at end of file diff --git a/scriptable-processor-effect-rack/ProjectSettings/ShaderGraphSettings.asset b/scriptable-processor-effect-rack/ProjectSettings/ShaderGraphSettings.asset new file mode 100644 index 0000000..ce8c243 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/ShaderGraphSettings.asset @@ -0,0 +1,19 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de02f9e1d18f588468e474319d09a723, type: 3} + m_Name: + m_EditorClassIdentifier: + shaderVariantLimit: 128 + overrideShaderVariantLimit: 0 + customInterpolatorErrorThreshold: 32 + customInterpolatorWarningThreshold: 16 + customHeatmapValues: {fileID: 0} diff --git a/scriptable-processor-effect-rack/ProjectSettings/TagManager.asset b/scriptable-processor-effect-rack/ProjectSettings/TagManager.asset new file mode 100644 index 0000000..6413d11 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/TagManager.asset @@ -0,0 +1,76 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!78 &1 +TagManager: + serializedVersion: 2 + tags: [] + layers: + - Default + - TransparentFX + - Ignore Raycast + - + - Water + - UI + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + m_SortingLayers: + - name: Default + uniqueID: 0 + locked: 0 + m_RenderingLayers: + - Default + - Light Layer 1 + - Light Layer 2 + - Light Layer 3 + - Light Layer 4 + - Light Layer 5 + - Light Layer 6 + - Light Layer 7 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - diff --git a/scriptable-processor-effect-rack/ProjectSettings/TimeManager.asset b/scriptable-processor-effect-rack/ProjectSettings/TimeManager.asset new file mode 100644 index 0000000..558a017 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/TimeManager.asset @@ -0,0 +1,9 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!5 &1 +TimeManager: + m_ObjectHideFlags: 0 + Fixed Timestep: 0.02 + Maximum Allowed Timestep: 0.33333334 + m_TimeScale: 1 + Maximum Particle Timestep: 0.03 diff --git a/scriptable-processor-effect-rack/ProjectSettings/URPProjectSettings.asset b/scriptable-processor-effect-rack/ProjectSettings/URPProjectSettings.asset new file mode 100644 index 0000000..64a8674 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/URPProjectSettings.asset @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 247994e1f5a72c2419c26a37e9334c01, type: 3} + m_Name: + m_EditorClassIdentifier: + m_LastMaterialVersion: 10 diff --git a/scriptable-processor-effect-rack/ProjectSettings/UnityConnectSettings.asset b/scriptable-processor-effect-rack/ProjectSettings/UnityConnectSettings.asset new file mode 100644 index 0000000..72f48ef --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/UnityConnectSettings.asset @@ -0,0 +1,42 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!310 &1 +UnityConnectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 1 + m_Enabled: 0 + m_TestMode: 0 + m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events + m_EventUrl: https://cdp.cloud.unity3d.com/v1/events + m_ConfigUrl: https://config.uca.cloud.unity3d.com + m_DashboardUrl: https://dashboard.unity3d.com + m_TestInitMode: 0 + InsightsSettings: + m_EventUrl: + m_StackTraceUrl: + m_EngineDiagnosticsEnabled: 1 + m_Enabled: 0 + CrashReportingSettings: + serializedVersion: 2 + m_EventUrl: https://perf-events.cloud.unity3d.com + m_EnableCloudDiagnosticsReporting: 0 + m_LogBufferSize: 10 + m_CaptureEditorExceptions: 1 + UnityPurchasingSettings: + m_Enabled: 0 + m_TestMode: 0 + UnityAnalyticsSettings: + m_Enabled: 0 + m_TestMode: 0 + m_InitializeOnStartup: 1 + m_PackageRequiringCoreStatsPresent: 0 + UnityAdsSettings: + m_Enabled: 0 + m_InitializeOnStartup: 1 + m_TestMode: 0 + m_IosGameId: + m_AndroidGameId: + m_GameIds: {} + m_GameId: + PerformanceReportingSettings: + m_Enabled: 0 diff --git a/scriptable-processor-effect-rack/ProjectSettings/VFXManager.asset b/scriptable-processor-effect-rack/ProjectSettings/VFXManager.asset new file mode 100644 index 0000000..3a95c98 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/VFXManager.asset @@ -0,0 +1,12 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!937362698 &1 +VFXManager: + m_ObjectHideFlags: 0 + m_IndirectShader: {fileID: 0} + m_CopyBufferShader: {fileID: 0} + m_SortShader: {fileID: 0} + m_StripUpdateShader: {fileID: 0} + m_RenderPipeSettingsPath: + m_FixedTimeStep: 0.016666668 + m_MaxDeltaTime: 0.05 diff --git a/scriptable-processor-effect-rack/ProjectSettings/VersionControlSettings.asset b/scriptable-processor-effect-rack/ProjectSettings/VersionControlSettings.asset new file mode 100644 index 0000000..dca2881 --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/VersionControlSettings.asset @@ -0,0 +1,8 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!890905787 &1 +VersionControlSettings: + m_ObjectHideFlags: 0 + m_Mode: Visible Meta Files + m_CollabEditorSettings: + inProgressEnabled: 1 diff --git a/scriptable-processor-effect-rack/ProjectSettings/XRSettings.asset b/scriptable-processor-effect-rack/ProjectSettings/XRSettings.asset new file mode 100644 index 0000000..482590c --- /dev/null +++ b/scriptable-processor-effect-rack/ProjectSettings/XRSettings.asset @@ -0,0 +1,10 @@ +{ + "m_SettingKeys": [ + "VR Device Disabled", + "VR Device User Alert" + ], + "m_SettingValues": [ + "False", + "False" + ] +} \ No newline at end of file diff --git a/scriptable-processor-effect-rack/README.md b/scriptable-processor-effect-rack/README.md new file mode 100644 index 0000000..47df69f --- /dev/null +++ b/scriptable-processor-effect-rack/README.md @@ -0,0 +1,69 @@ +# Effect Rack Example Project + +This example demonstrates **scriptable effects**: custom audio processors that transform the signal an `AudioSource` plays. A rack of small effects turns speech into radio chatter. It covers the following: + +- A chain of effect components on a single `AudioSource`, processed in component order, assembled at runtime, while the audio source plays. +- Addressing one effect out of several with `AudioSource.GetEffectInstance`, and bypassing it by disabling its component. +- Filter coefficients derived from the audio configuration in `Configure`, and rebuilt when the output device changes. +- Data travelling out of the audio thread: the noise gate reports its envelope over the pipe, and the control part hands it back to the UI through a query message. +- A nested generator owned by an effect: the hiss effect creates a white noise generator, renders it into a scratch buffer from its own `Process`, and destroys it when the effect goes away. + +The project targets Unity 6.7 and is built on the [scriptable audio pipeline][manual] APIs, specifically the part covered in the [effects][effects] section. + +[manual]: https://docs.unity3d.com/6000.7/Documentation/Manual/audio-scriptable-processors.html +[effects]: https://docs.unity3d.com/6000.7/Documentation/Manual/audio-scriptable-processors-effects.html + +## Run the demo + +Open the project in Unity 6.7, open `Assets/Scenes/EffectRack.unity`, and enter Play Mode. + +The scene contains a single **Radio** GameObject holding an `AudioSource` with `Count1To10.wav` and the `EffectRack` panel, plus a camera that provides the `AudioListener`. The rack starts empty and the clip plays dry until you build one. A GUI panel appears in the top left: + +- Playback starts **stopped**. Use the **Play/Stop** button. The clip loops by default. +- **Bypass Effects** takes every effect out at once, which is the quickest way to hear the dry clip. +- Each effect has an **on/bypassed** toggle and its own parameters. Everything is live: drag a slider while the clip plays and you hear it immediately. +- **Handheld radio**, **Blown speaker** and **Spectrum only** each clear the rack and rebuild it. They differ in what they contain and in what order, not just in their settings, so loading one is an audible teardown and rebuild rather than a parameter change. **Clear** empties the rack. +- Each effect row has **^** and **v** buttons that move it up/down the chain, and an **x** button that removes it mid-playback. The audio source rediscovers its effects when the components change, so the chain rebuilds while the clip keeps playing. + +## The effects + +Effects run in the order their components were added, which the panel numbers top to bottom: + +| Effect | What it does | +|--------------|-----------------------------------------------------------------------------------------------| +| Band pass | Rolls off everything outside a speech band, roughly 400 Hz to 2.6 kHz. | +| Drive | Pushes the signal into a soft clipper, for cheap transmitter distortion, then trims the output back down. | +| Noise gate | Silences the signal between phrases, and reports its envelope back for the meter. | +| Hiss | Mixes a bed of white noise under the signal, from a generator nested inside the effect. | +| Spectrum | Measures the signal without changing it, and reports the level in each of sixteen frequency bands for the panel to draw as a histogram. A bank of two-pole state-variable filters, not a transform. | + +## Implementation + +The implementation is in `Assets/Scripts/`. Each effect is one file holding the whole effect: the component, its real-time part, its control part, and the messages they exchange. The split follows the [control vs realtime model][concepts] described in the manual. + +[concepts]: https://docs.unity3d.com/6000.7/Documentation/Manual/audio-scriptable-processors-concepts.html + +| File | Role | +|---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------| +| `Effects/BandPassEffect.cs` | Two one-pole filters. Shows coefficients computed on the control side, per-channel state allocated in `Configure`, and that state cleared when the device changes. | +| `Effects/DriveEffect.cs` | A stateless soft clipper with an output trim. The simplest complete effect here: no state, no allocation, and both parameters folded into one per-block multiply. | +| `Effects/NoiseGateEffect.cs` | An envelope follower and gate. Shows the pipe used in both directions, and a query message answered by writing into the caller's own message. | +| `Effects/HissEffect.cs` | A noise bed. Shows an effect owning a nested processor, and a scratch buffer allocated in `Configure` and released in `Dispose`. | +| `Effects/WhiteNoiseGenerator.cs` | The generator the hiss effect nests. Written exactly like a generator attached to an `AudioSource`. | +| `Effects/SpectrumEffect.cs` | A pass-through effect that measures. A filterbank keeps a reading down to sixteen numbers, which packs into one `float4x4` and rides the pipe as a single message per block, so no shared memory is needed between the audio thread and the UI. | +| `EffectRack.cs` | The demo panel. Reads the chain back off the GameObject in component order, edits the effects while they play, and builds, reorders or clears the rack. Each preset is an ordered list of `AddComponent` calls, and a reorder rebuilds the chain from the swap onwards. | + +Two rules shape all of the `Process` methods: + +- **Read the input sample before writing the output sample.** When Unity runs an attached effect, the input and output buffers can reference the same memory. +- **Write every output sample on every code path**, including the paths that pass audio through untouched. The output buffer starts undefined, and Unity does not clear it. + +## Notes + +This is meant as a teaching example. A few things are intentionally limited: + +- **One-pole filters**: The band pass is two first-order sections, so its slopes are gentle. A real radio effect would use steeper filters, at the cost of a longer example. +- **No parameter smoothing**: Parameters jump to their new value when a message arrives. Dragging a slider fast can produce a small click. Production effects ramp between values across a buffer. +- **Fixed channel budget**: The band pass and the hiss effect size their buffers for eight channels, which covers every `AudioSpeakerMode`. Anything wider passes through untouched. +- **Coarse spectrum**: The analysis splits the signal into sixteen log-spaced bands, which is enough to see the band pass and the drive at work but far short of a transform. A real analyser would use an FFT, at the cost of moving readings through shared memory rather than the pipe. +- **Correlated noise**: The hiss effect writes the same noise sample to every channel, so the static is mono. Decorrelated noise would need one random stream per channel.