├── .gitignore ├── AboutMusicEngineJP.pdf ├── Classes ├── MusicDebugText.cs ├── MusicMeter.cs ├── MusicMode.cs ├── MusicSection.cs └── Timing.cs ├── Editor ├── MusicMeterPropertyDrawer.cs ├── MusicUnityEditor.cs └── TimingPropertyDrawer.cs ├── Music.cs ├── MusicADX2.cs ├── MusicBase.cs ├── MusicUnity.cs ├── MusicWwise.cs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | *.publishproj 131 | 132 | # NuGet Packages Directory 133 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 134 | #packages/ 135 | 136 | # Windows Azure Build Output 137 | csx 138 | *.build.csdef 139 | 140 | # Windows Store app package directory 141 | AppPackages/ 142 | 143 | # Others 144 | sql/ 145 | *.Cache 146 | ClientBin/ 147 | [Ss]tyle[Cc]op.* 148 | ~$* 149 | *~ 150 | *.dbmdl 151 | *.[Pp]ublish.xml 152 | *.pfx 153 | *.publishsettings 154 | 155 | # RIA/Silverlight projects 156 | Generated_Code/ 157 | 158 | # Backup & report files from converting an old project file to a newer 159 | # Visual Studio version. Backup files are not needed, because we have git ;-) 160 | _UpgradeReport_Files/ 161 | Backup*/ 162 | UpgradeLog*.XML 163 | UpgradeLog*.htm 164 | 165 | # SQL Server files 166 | App_Data/*.mdf 167 | App_Data/*.ldf 168 | 169 | ############# 170 | ## Windows detritus 171 | ############# 172 | 173 | # Windows image file caches 174 | Thumbs.db 175 | ehthumbs.db 176 | 177 | # Folder config file 178 | Desktop.ini 179 | 180 | # Recycle Bin used on file shares 181 | $RECYCLE.BIN/ 182 | 183 | # Mac crap 184 | .DS_Store 185 | 186 | 187 | ############# 188 | ## Python 189 | ############# 190 | 191 | *.py[cod] 192 | 193 | # Packages 194 | *.egg 195 | *.egg-info 196 | dist/ 197 | build/ 198 | eggs/ 199 | parts/ 200 | var/ 201 | sdist/ 202 | develop-eggs/ 203 | .installed.cfg 204 | 205 | # Installer logs 206 | pip-log.txt 207 | 208 | # Unit test / coverage reports 209 | .coverage 210 | .tox 211 | 212 | #Translations 213 | *.mo 214 | 215 | #Mr Developer 216 | .mr.developer.cfg 217 | 218 | *.meta -------------------------------------------------------------------------------- /AboutMusicEngineJP.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geekdrums/MusicEngine/b202a37bbee460d59b88b3b3e6b08b4988f6da9b/AboutMusicEngineJP.pdf -------------------------------------------------------------------------------- /Classes/MusicDebugText.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.UI; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | [RequireComponent(typeof(MusicBase))] 8 | public class MusicDebugText : MonoBehaviour 9 | { 10 | public TextMesh TextMesh; 11 | public Text TextUI; 12 | 13 | MusicBase music_; 14 | 15 | void Awake() 16 | { 17 | music_ = GetComponent(); 18 | UpdateText(); 19 | } 20 | 21 | void Update() 22 | { 23 | UpdateText(); 24 | } 25 | 26 | void UpdateText() 27 | { 28 | string debugText = music_.ToString(); 29 | if( TextMesh != null ) 30 | { 31 | TextMesh.text = debugText; 32 | } 33 | if( TextUI != null ) 34 | { 35 | TextUI.text = debugText; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Classes/MusicMeter.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | [Serializable] 7 | public class MusicMeter 8 | { 9 | public int StartBar = 0; 10 | public double Tempo = 120.0f; 11 | public int UnitPerBeat = 4; 12 | public int UnitPerBar = 16; 13 | public int Numerator = 4; 14 | public int Denominator = 4; 15 | 16 | 17 | #region properties 18 | 19 | public double StartSec { get; protected set; } 20 | public double SecPerBar { get; protected set; } 21 | public double SecPerBeat { get; protected set; } 22 | public double SecPerUnit { get; protected set; } 23 | 24 | public double StartMSec { get; protected set; } 25 | public double MSecPerBar { get; protected set; } 26 | public double MSecPerBeat { get; protected set; } 27 | public double MSecPerUnit { get; protected set; } 28 | 29 | #endregion 30 | 31 | 32 | public MusicMeter(int startBar, int unitPerBeat = 4, int unitPerBar = 16, double tempo = 120) 33 | { 34 | StartBar = startBar; 35 | Tempo = tempo; 36 | 37 | UnitPerBeat = unitPerBeat; 38 | UnitPerBar = unitPerBar; 39 | 40 | CalcMeterByUnits(UnitPerBeat, UnitPerBar, out Numerator, out Denominator); 41 | } 42 | 43 | public override string ToString() 44 | { 45 | return string.Format("({0}/{1}, {2:F2})", Numerator, Denominator, Tempo); 46 | } 47 | 48 | 49 | #region validate 50 | 51 | public static void CalcMeterByUnits(int unitPerBeat, int unitPerBar, out int numerator, out int denominator) 52 | { 53 | if( unitPerBar % unitPerBeat == 0 ) 54 | { 55 | denominator = unitPerBeat == 2 ? 8 : 4; 56 | numerator = unitPerBar / unitPerBeat; 57 | } 58 | else 59 | { 60 | int commonDividor = Euclid(unitPerBar, unitPerBeat * 4); 61 | numerator = unitPerBar / commonDividor; 62 | denominator = unitPerBeat * 4 / commonDividor; 63 | } 64 | } 65 | 66 | public static void CalcMeterByFraction(int numerator, int denominator, out int unitPerBeat, out int unitPerBar) 67 | { 68 | unitPerBeat = 4 * Mathf.Max(1, (denominator / 16)); 69 | unitPerBar = numerator * ((unitPerBeat * 4) / denominator); 70 | } 71 | 72 | public static int Euclid(int small, int big) 73 | { 74 | if( big % small == 0 ) 75 | return small; 76 | return Euclid(big % small, small); 77 | } 78 | 79 | public void Validate(double startSec) 80 | { 81 | StartSec = startSec; 82 | SecPerBeat = (60.0 / Tempo); 83 | SecPerUnit = SecPerBeat / UnitPerBeat; 84 | SecPerBar = UnitPerBar * (SecPerBeat / UnitPerBeat); 85 | 86 | StartMSec = StartSec * 1000.0; 87 | MSecPerBeat = SecPerBeat * 1000.0; 88 | MSecPerUnit = SecPerUnit * 1000.0; 89 | MSecPerBar = SecPerBar * 1000.0; 90 | } 91 | 92 | #endregion 93 | 94 | 95 | #region convert 96 | 97 | public float GetMusicalTime(Timing just, float fractionFromJust) 98 | { 99 | return (just.GetTotalUnits(this) + fractionFromJust) / UnitPerBar; 100 | } 101 | 102 | public double GetSecondsFromTiming(Timing timing) 103 | { 104 | return StartSec + (timing.Bar - StartBar) * SecPerBar + timing.Beat * SecPerBeat + timing.Unit * SecPerUnit; 105 | } 106 | 107 | public double GetMilliSecondsFromTiming(Timing timing) 108 | { 109 | return StartMSec + (timing.Bar - StartBar) * MSecPerBar + timing.Beat * MSecPerBeat + timing.Unit * MSecPerUnit; 110 | } 111 | 112 | public Timing GetTimingFromSeconds(double sec) 113 | { 114 | if( sec < StartSec ) 115 | { 116 | return new Timing(StartBar); 117 | } 118 | else 119 | { 120 | double meterSec = sec - StartSec; 121 | int bar = (int)(meterSec / SecPerBar); 122 | int beat = (int)((meterSec - bar * SecPerBar) / SecPerBeat); 123 | int unit = (int)(((meterSec - bar * SecPerBar) - beat * SecPerBeat) / SecPerUnit); 124 | return new Timing(bar + StartBar, beat, unit); 125 | } 126 | } 127 | 128 | public Timing GetTimingFromMilliSeconds(double msec) 129 | { 130 | return GetTimingFromSeconds(msec/1000.0); 131 | } 132 | 133 | #endregion 134 | 135 | } 136 | 137 | [Serializable] 138 | public class MusicMeterBySample : MusicMeter 139 | { 140 | public MusicMeterBySample(int startBar, int unitPerBeat = 4, int unitPerBar = 16, double tempo = 120) 141 | : base(startBar, unitPerBeat, unitPerBar, tempo) 142 | { 143 | 144 | } 145 | 146 | public int SampleRate { get; protected set; } 147 | public int StartSamples { get; protected set; } 148 | public int SamplesPerUnit { get; protected set; } 149 | public int SamplesPerBeat { get; protected set; } 150 | public int SamplesPerBar { get; protected set; } 151 | 152 | public void OnValidate(int sampleRate = 44100, int startTimeSample = 0) 153 | { 154 | SampleRate = sampleRate; 155 | 156 | Validate((double)startTimeSample / SampleRate); 157 | 158 | StartSamples = startTimeSample; 159 | SamplesPerBeat = (int)(SampleRate * SecPerBeat); 160 | SamplesPerUnit = (int)(SampleRate * (SecPerBeat / UnitPerBeat)); 161 | SamplesPerBar = (int)(SampleRate * UnitPerBar * (SecPerBeat / UnitPerBeat)); 162 | } 163 | 164 | public int GetSampleFromTiming(Timing timing) 165 | { 166 | return StartSamples + (timing.Bar - StartBar) * SamplesPerBar + timing.Beat * SamplesPerBeat + timing.Unit * SamplesPerUnit; 167 | } 168 | 169 | public Timing GetTimingFromSample(int sample) 170 | { 171 | if( sample < StartSamples ) 172 | { 173 | return new Timing(StartBar); 174 | } 175 | else 176 | { 177 | int meterSamples = sample - StartSamples; 178 | int bar = (int)(meterSamples / SamplesPerBar); 179 | int beat = (int)((meterSamples - bar * SamplesPerBar) / SamplesPerBeat); 180 | int unit = (int)(((meterSamples - bar * SamplesPerBar) - beat * SamplesPerBeat) / SamplesPerUnit); 181 | var res = new Timing(bar + StartBar, beat, unit); 182 | res.Fix(this); 183 | return res; 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /Classes/MusicMode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.UI; 6 | 7 | [Serializable] 8 | public class MusicMode 9 | { 10 | public string Name = "Mode"; 11 | [Range(0,1)] 12 | public float TotalVolume = 1.0f; 13 | public List LayerVolumes; 14 | 15 | [Serializable] 16 | public class TransitionParams 17 | { 18 | public float FadeTime = 1.0f; 19 | public float FadeOffset = 0.0f; 20 | public Music.TimeUnitType TimeUnitType = Music.TimeUnitType.Sec; 21 | public Music.SyncType SyncType = Music.SyncType.Immediate; 22 | public int SyncFactor = 1; 23 | 24 | public float FadeTimeSec { get { return Music.TimeUtility.ConvertTime(FadeTime, TimeUnitType, Music.TimeUnitType.Sec); } } 25 | public float FadeOffsetSec { get { return Music.TimeUtility.ConvertTime(FadeOffset, TimeUnitType, Music.TimeUnitType.Sec); } } 26 | } 27 | } -------------------------------------------------------------------------------- /Classes/MusicSection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.UI; 6 | 7 | [Serializable] 8 | public class MusicSection 9 | { 10 | public string Name = "Section"; 11 | public AudioClip[] Clips = new AudioClip[1]; 12 | public MusicMeterBySample[] Meters = new MusicMeterBySample[1] { new MusicMeterBySample(0) }; 13 | 14 | public Music.SyncType SyncType = Music.SyncType.Bar; 15 | public int SyncFactor = 1; 16 | public MusicMarker[] Markers; 17 | 18 | [Serializable] 19 | public class MusicMarker 20 | { 21 | public Timing[] Timings; 22 | } 23 | 24 | public Timing EntryPointTiming = new Timing(); 25 | public Timing ExitPointTiming = new Timing(); 26 | public Timing LoopStartTiming = new Timing(); 27 | public Timing LoopEndTiming = new Timing(); 28 | 29 | public TransitionParams Transition; 30 | 31 | [Serializable] 32 | public class TransitionParams 33 | { 34 | public TransitionParams() { } 35 | public TransitionParams(TransitionParams other) 36 | { 37 | UseFadeOut = other.UseFadeOut; 38 | FadeOutTime = other.FadeOutTime; 39 | FadeOutOffset = other.FadeOutOffset; 40 | UseFadeIn = other.UseFadeIn; 41 | FadeInTime = other.FadeInTime; 42 | FadeInOffset = other.FadeInOffset; 43 | } 44 | 45 | public bool UseFadeOut = false; 46 | public float FadeOutTime; 47 | public float FadeOutOffset; 48 | 49 | public bool UseFadeIn = false; 50 | public float FadeInTime; 51 | public float FadeInOffset; 52 | } 53 | 54 | public enum AutoTransitionType 55 | { 56 | Loop, 57 | Transition, 58 | End, 59 | } 60 | public AutoTransitionType TransitionType; 61 | public int TransitionDestinationIndex; 62 | 63 | public bool IsValid { get; private set; } 64 | public int LoopStartSample { get; private set; } 65 | public int LoopEndSample { get; private set; } 66 | public int EntryPointSample { get; private set; } 67 | public int ExitPointSample { get; private set; } 68 | public Timing ClipEndTiming { get; private set; } 69 | 70 | public void Validate(int sampleRate) 71 | { 72 | IsValid = false; 73 | if( Clips.Length == 0 || Clips[0] == null || Meters.Length == 0 ) 74 | { 75 | return; 76 | } 77 | 78 | // 最初のメーターをValidateして、EntryPointのサンプル数を計算 79 | Meters[0].OnValidate(sampleRate, 0); 80 | EntryPointSample = Meters[0].GetSampleFromTiming(EntryPointTiming); 81 | 82 | // 後続のメーターすべてをValidate 83 | int startSample = EntryPointSample; 84 | MusicMeterBySample lastMeter = null; 85 | foreach( MusicMeterBySample meter in Meters ) 86 | { 87 | if( lastMeter != null ) 88 | { 89 | startSample += lastMeter.SamplesPerBar * meter.StartBar; 90 | } 91 | meter.OnValidate(sampleRate, startSample); 92 | lastMeter = meter; 93 | } 94 | 95 | ClipEndTiming = lastMeter.GetTimingFromSample(Clips[0].samples); 96 | // 波形終わりのタイミングを参考にExitPointを設定する 97 | if( ExitPointTiming <= EntryPointTiming || ClipEndTiming < ExitPointTiming ) 98 | { 99 | ExitPointTiming = new Timing(ClipEndTiming); 100 | ExitPointTiming.FixToFloor(); 101 | } 102 | if( ClipEndTiming < LoopEndTiming ) 103 | { 104 | LoopEndTiming = new Timing(ClipEndTiming); 105 | LoopEndTiming.FixToFloor(); 106 | } 107 | 108 | if( TransitionType == AutoTransitionType.Loop ) 109 | { 110 | // LoopEndはLoopStartより後じゃないとダメ 111 | if( LoopStartTiming >= LoopEndTiming ) 112 | { 113 | LoopEndTiming.Set(ExitPointTiming); 114 | } 115 | // ExitPointはLoopEndと同一 116 | ExitPointTiming = LoopEndTiming; 117 | 118 | LoopStartSample = GetSampleFromTiming(LoopStartTiming); 119 | LoopEndSample = GetSampleFromTiming(LoopEndTiming); 120 | } 121 | 122 | ExitPointSample = GetSampleFromTiming(ExitPointTiming); 123 | 124 | IsValid = true; 125 | } 126 | 127 | public int GetSampleFromTiming(Timing timing) 128 | { 129 | Timing nextMeterTiming = new Timing(); 130 | if( timing < nextMeterTiming ) 131 | { 132 | return 0; 133 | } 134 | 135 | MusicMeterBySample meter = null; 136 | for( int i = 0; i < Meters.Length; ++i ) 137 | { 138 | if( i + 1 < Meters.Length ) 139 | { 140 | nextMeterTiming.Set(Meters[i + 1].StartBar); 141 | if( timing < nextMeterTiming ) 142 | { 143 | meter = Meters[i]; 144 | break; 145 | } 146 | } 147 | else // 最後のメーター 148 | { 149 | meter = Meters[i]; 150 | break; 151 | } 152 | } 153 | 154 | return meter.GetSampleFromTiming(timing); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Classes/Timing.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | [Serializable] 7 | public class Timing : IComparable, IEquatable 8 | { 9 | public int Bar, Beat, Unit; 10 | 11 | public Timing(int bar = 0, int beat = 0, int unit = 0) 12 | { 13 | Bar = bar; 14 | Beat = beat; 15 | Unit = unit; 16 | } 17 | public Timing() { this.Reset(); } 18 | public Timing(Timing t) { Set(t); } 19 | 20 | public bool IsZero() 21 | { 22 | return Bar == 0 && Beat == 0 && Unit == 0; 23 | } 24 | public int GetTotalUnits(MusicMeter meter) 25 | { 26 | return Bar * meter.UnitPerBar + Beat * meter.UnitPerBeat + Unit; 27 | } 28 | 29 | public void SetBar(int bar) { Bar = bar; } 30 | public void SetBeat(int beat) { Beat = beat; } 31 | public void SetUnit(int unit) { Unit = unit; } 32 | public void Set(int bar, int beat = 0, int unit = 0) { Bar = bar; Beat = beat; Unit = unit; } 33 | public void Set(Timing t) { Bar = t.Bar; Beat = t.Beat; Unit = t.Unit; } 34 | public void Reset() { Bar = 0; Beat = 0; Unit = 0; } 35 | 36 | public void Fix(MusicMeter meter) 37 | { 38 | int totalUnit = Bar * meter.UnitPerBar + Beat * meter.UnitPerBeat + Unit; 39 | Bar = totalUnit / meter.UnitPerBar; 40 | Beat = (totalUnit - Bar * meter.UnitPerBar) / meter.UnitPerBeat; 41 | Unit = (totalUnit - Bar * meter.UnitPerBar - Beat * meter.UnitPerBeat); 42 | } 43 | public void FixToCeil() 44 | { 45 | if( Beat > 0 || Unit > 0 ) 46 | { 47 | ++Bar; 48 | Beat = Unit = 0; 49 | } 50 | } 51 | public void FixToFloor() 52 | { 53 | Beat = 0; 54 | Unit = 0; 55 | } 56 | public void LoopBack(int loopBar, MusicMeter meter) 57 | { 58 | if( loopBar > 0 ) 59 | { 60 | Bar += loopBar; 61 | Fix(meter); 62 | Bar %= loopBar; 63 | } 64 | } 65 | 66 | public void Increment(MusicMeter meter) 67 | { 68 | ++Unit; 69 | Fix(meter); 70 | } 71 | public void Decrement(MusicMeter meter) 72 | { 73 | --Unit; 74 | Fix(meter); 75 | } 76 | public void Add(int bar, int beat = 0, int unit = 0, MusicMeter meter = null) 77 | { 78 | Bar += bar; Beat += beat; Unit += unit; 79 | if( meter != null ) 80 | { 81 | Fix(meter); 82 | } 83 | } 84 | public void Add(Timing t, MusicMeter meter = null) 85 | { 86 | Add(t.Bar, t.Beat, t.Unit, meter); 87 | } 88 | public void Subtract(int bar, int beat = 0, int unit = 0, MusicMeter meter = null) 89 | { 90 | Bar -= bar; Beat -= beat; Unit -= unit; 91 | if( meter != null ) 92 | { 93 | Fix(meter); 94 | } 95 | } 96 | public void Subtract(Timing t, MusicMeter meter = null) 97 | { 98 | Subtract(t.Bar, t.Beat, t.Unit, meter); 99 | } 100 | 101 | public static bool operator ==(Timing t, Timing t2) 102 | { 103 | return (object.ReferenceEquals(t, null) == true && object.ReferenceEquals(t2, null) == true ) 104 | || (object.ReferenceEquals(t, null) == false && object.ReferenceEquals(t2, null) == false && (t.Bar == t2.Bar && t.Beat == t2.Beat && t.Unit == t2.Unit)); 105 | } 106 | public static bool operator !=(Timing t, Timing t2) { return !(t == t2); } 107 | public static bool operator >(Timing t, Timing t2) { return t.Bar > t2.Bar || (t.Bar == t2.Bar && t.Beat > t2.Beat) || (t.Bar == t2.Bar && t.Beat == t2.Beat && t.Unit > t2.Unit); } 108 | public static bool operator <(Timing t, Timing t2) { return !(t > t2) && !(t == t2); } 109 | public static bool operator <=(Timing t, Timing t2) { return !(t > t2); } 110 | public static bool operator >=(Timing t, Timing t2) { return !(t < t2); } 111 | public static Timing Zero = new Timing(0); 112 | 113 | public override bool Equals(object obj) 114 | { 115 | if( object.ReferenceEquals(obj, null) ) 116 | { 117 | return false; 118 | } 119 | if( object.ReferenceEquals(obj, this) ) 120 | { 121 | return true; 122 | } 123 | Timing other = (obj as Timing); 124 | if( other == null ) 125 | { 126 | return false; 127 | } 128 | return (this.Bar == other.Bar && this.Beat == other.Beat && this.Unit == other.Unit); 129 | } 130 | public override int GetHashCode() 131 | { 132 | return base.GetHashCode(); 133 | } 134 | public bool Equals(Timing other) 135 | { 136 | return (this.Bar == other.Bar && this.Beat == other.Beat && this.Unit == other.Unit); 137 | } 138 | public int CompareTo(Timing tother) 139 | { 140 | if( this.Equals(tother) ) return 0; 141 | else if( this > tother ) return 1; 142 | else return -1; 143 | } 144 | 145 | public override string ToString() 146 | { 147 | return Bar + " " + Beat + " " + Unit; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Editor/MusicMeterPropertyDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | 8 | [CustomPropertyDrawer(typeof(MusicMeter))] 9 | public class MusicMeterPropertyDrawer : PropertyDrawer 10 | { 11 | private class PropertyData 12 | { 13 | // 現状MusicADX2とMusicWwiseでは使わないプロパティなので非表示 14 | //public SerializedProperty StartBar; 15 | public SerializedProperty Tempo; 16 | public SerializedProperty UnitPerBeat; 17 | public SerializedProperty UnitPerBar; 18 | public SerializedProperty Numerator; 19 | public SerializedProperty Denominator; 20 | } 21 | 22 | private Dictionary _propertyDataPerPropertyPath = new Dictionary(); 23 | private PropertyData _property; 24 | private bool showUnitPerBarBeat_ = false; 25 | 26 | private void Init(SerializedProperty property) 27 | { 28 | if( _propertyDataPerPropertyPath.TryGetValue(property.propertyPath, out _property) ) 29 | { 30 | return; 31 | } 32 | 33 | _property = new PropertyData(); 34 | //_property.StartBar = property.FindPropertyRelative("StartBar"); 35 | _property.Tempo = property.FindPropertyRelative("Tempo"); 36 | _property.UnitPerBeat = property.FindPropertyRelative("UnitPerBeat"); 37 | _property.UnitPerBar = property.FindPropertyRelative("UnitPerBar"); 38 | _property.Numerator = property.FindPropertyRelative("Numerator"); 39 | _property.Denominator = property.FindPropertyRelative("Denominator"); 40 | _propertyDataPerPropertyPath.Add(property.propertyPath, _property); 41 | } 42 | 43 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 44 | { 45 | Init(property); 46 | 47 | var fieldRect = position; 48 | fieldRect.height = GetPropertyHeight(property, label); 49 | 50 | using( new EditorGUI.PropertyScope(fieldRect, label, property) ) 51 | { 52 | //int startBar = _property.StartBar.intValue; 53 | double tempo = _property.Tempo.doubleValue; 54 | int unitPerBar = _property.UnitPerBar.intValue; 55 | int unitPerBeat = _property.UnitPerBeat.intValue; 56 | int numerator = _property.Numerator.intValue; 57 | int denominator = _property.Denominator.intValue; 58 | 59 | fieldRect.height = EditorGUIUtility.singleLineHeight; 60 | 61 | property.isExpanded = EditorGUI.Foldout(fieldRect, property.isExpanded, string.Format("{0} ({1}/{2}, {3:F2})", label.text, numerator, denominator, tempo)); 62 | if( property.isExpanded ) 63 | { 64 | EditorGUI.indentLevel++; 65 | 66 | //fieldRect.y += EditorGUIUtility.singleLineHeight; 67 | //EditorGUI.PropertyField(fieldRect, _property.StartBar); 68 | fieldRect.y += EditorGUIUtility.singleLineHeight; 69 | EditorGUI.PropertyField(fieldRect, _property.Tempo); 70 | 71 | fieldRect.y += EditorGUIUtility.singleLineHeight; 72 | showUnitPerBarBeat_ = EditorGUI.Toggle(fieldRect, "Show Meter In UnitPerBar/Beat", showUnitPerBarBeat_); 73 | 74 | if( showUnitPerBarBeat_ ) 75 | { 76 | fieldRect.y += EditorGUIUtility.singleLineHeight; 77 | EditorGUI.PropertyField(fieldRect, _property.UnitPerBeat); 78 | fieldRect.y += EditorGUIUtility.singleLineHeight; 79 | EditorGUI.PropertyField(fieldRect, _property.UnitPerBar); 80 | 81 | if( unitPerBar != _property.UnitPerBar.intValue || unitPerBeat != _property.UnitPerBeat.intValue ) 82 | { 83 | MusicMeter.CalcMeterByUnits(_property.UnitPerBeat.intValue, _property.UnitPerBar.intValue, out numerator, out denominator); 84 | _property.Numerator.intValue = numerator; 85 | _property.Denominator.intValue = denominator; 86 | } 87 | } 88 | else 89 | { 90 | fieldRect.y += EditorGUIUtility.singleLineHeight; 91 | fieldRect = EditorGUI.PrefixLabel(fieldRect, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Meter")); 92 | fieldRect.x -= 16; 93 | fieldRect.width = 55; 94 | EditorGUI.PropertyField(fieldRect, _property.Numerator, GUIContent.none); 95 | fieldRect.x += 60; 96 | fieldRect.width = 60; 97 | _property.Denominator.intValue = EditorGUI.IntPopup(fieldRect, _property.Denominator.intValue, MeterDenominatorCandidatesString, MeterDenominatorCandidates); 98 | 99 | if( numerator != _property.Numerator.intValue || denominator != _property.Denominator.intValue ) 100 | { 101 | MusicMeter.CalcMeterByFraction(_property.Numerator.intValue, _property.Denominator.intValue, out unitPerBeat, out unitPerBar); 102 | _property.UnitPerBeat.intValue = unitPerBeat; 103 | _property.UnitPerBar.intValue = unitPerBar; 104 | } 105 | } 106 | 107 | EditorGUI.indentLevel--; 108 | } 109 | } 110 | } 111 | static string[] MeterDenominatorCandidatesString = new string[3] { "/4", "/8", "/16" }; 112 | static int[] MeterDenominatorCandidates = new int[] { 4, 8, 16 }; 113 | 114 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 115 | { 116 | if( property.isExpanded ) 117 | { 118 | if( showUnitPerBarBeat_ ) 119 | { 120 | return EditorGUIUtility.singleLineHeight * 5; 121 | } 122 | else 123 | { 124 | return EditorGUIUtility.singleLineHeight * 4; 125 | } 126 | } 127 | return EditorGUIUtility.singleLineHeight; 128 | } 129 | } -------------------------------------------------------------------------------- /Editor/MusicUnityEditor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.UI; 5 | using UnityEditor; 6 | 7 | [CustomEditor(typeof(MusicUnity))] 8 | [CanEditMultipleObjects] 9 | public class MusicUnityEditor : Editor 10 | { 11 | SerializedProperty sectionListProperty_; 12 | SerializedProperty modeListProperty_; 13 | SerializedProperty modeTransitionParamProperty_; 14 | SerializedProperty sectionTransitionOverridesListProperty_; 15 | SerializedProperty modeTransitionOverridesListProperty_; 16 | 17 | SerializedProperty volumeProperty_; 18 | SerializedProperty playOnStartProperty_; 19 | SerializedProperty mixerGroupProperty_; 20 | SerializedProperty seekBarProperty_; 21 | SerializedProperty previewSectionProperty_; 22 | SerializedProperty previewModeProperty_; 23 | SerializedProperty numTracksProperty_; 24 | 25 | bool showOverrides_; 26 | bool showOptions_; 27 | bool showHelpTexts_; 28 | bool showUnitPerBarBeat_; 29 | string[] sectionListDisplayOptions_; 30 | string[] sectionListDisplayOptionsWithAny_; 31 | int[] sectionListValueOptions_; 32 | string[] modeListDisplayOptions_; 33 | string[] modeListDisplayOptionsWithAny_; 34 | int[] modeListValueOptions_; 35 | 36 | void InitializeProperties() 37 | { 38 | sectionListProperty_ = serializedObject.FindProperty("Sections"); 39 | modeListProperty_ = serializedObject.FindProperty("Modes"); 40 | modeTransitionParamProperty_ = serializedObject.FindProperty("ModeTransitionParam"); 41 | 42 | sectionTransitionOverridesListProperty_ = serializedObject.FindProperty("SectionTransitionOverrides"); 43 | modeTransitionOverridesListProperty_ = serializedObject.FindProperty("ModeTransitionOverrides"); 44 | 45 | volumeProperty_ = serializedObject.FindProperty("Volume"); 46 | playOnStartProperty_ = serializedObject.FindProperty("PlayOnStart"); 47 | mixerGroupProperty_ = serializedObject.FindProperty("OutputMixerGroup"); 48 | seekBarProperty_ = serializedObject.FindProperty("SeekBar"); 49 | previewSectionProperty_ = serializedObject.FindProperty("PreviewSectionIndex"); 50 | previewModeProperty_ = serializedObject.FindProperty("PreviewModeIndex"); 51 | numTracksProperty_ = serializedObject.FindProperty("NumTracks"); 52 | } 53 | 54 | public override void OnInspectorGUI() 55 | { 56 | if( sectionListProperty_ == null ) 57 | { 58 | InitializeProperties(); 59 | } 60 | 61 | serializedObject.Update(); 62 | 63 | // params 64 | EditorGUILayout.PropertyField(volumeProperty_); 65 | EditorGUILayout.PropertyField(playOnStartProperty_); 66 | EditorGUILayout.PropertyField(mixerGroupProperty_); 67 | 68 | // 複数選択では以降無視 69 | if( serializedObject.isEditingMultipleObjects ) 70 | { 71 | EditorGUILayout.LabelField("Cannot draw section list while multi editing", EditorStyles.boldLabel); 72 | serializedObject.ApplyModifiedProperties(); 73 | return; 74 | } 75 | 76 | // musicを取得 77 | MusicUnity music = serializedObject.targetObject as MusicUnity; 78 | sectionListDisplayOptions_ = new string[music.Sections.Length]; 79 | for( int i = 0; i < sectionListProperty_.arraySize; ++i ) 80 | { 81 | sectionListDisplayOptions_[i] = music.Sections[i].Name; 82 | } 83 | modeListDisplayOptions_ = new string[music.Modes.Length]; 84 | for( int i = 0; i < modeListProperty_.arraySize; ++i ) 85 | { 86 | modeListDisplayOptions_[i] = music.Modes[i].Name; 87 | } 88 | 89 | // num tracks 90 | EditorGUILayout.PropertyField(numTracksProperty_); 91 | 92 | // sections 93 | sectionListProperty_.isExpanded = EditorGUILayout.Foldout(sectionListProperty_.isExpanded, "Sections"); 94 | if( sectionListProperty_.isExpanded ) 95 | { 96 | EditorGUI.indentLevel++; 97 | for( int i = 0; i < sectionListProperty_.arraySize; ++i ) 98 | { 99 | DrawSection(sectionListProperty_.GetArrayElementAtIndex(i), (i < music.Sections.Length ? music.Sections[i] : null)); 100 | } 101 | DrawContainerButtons(sectionListProperty_); 102 | EditorGUI.indentLevel--; 103 | } 104 | 105 | // modes 106 | modeListProperty_.isExpanded = EditorGUILayout.Foldout(modeListProperty_.isExpanded, "Modes"); 107 | if( modeListProperty_.isExpanded ) 108 | { 109 | EditorGUI.indentLevel++; 110 | for( int i = 0; i < modeListProperty_.arraySize; ++i ) 111 | { 112 | DrawMode(modeListProperty_.GetArrayElementAtIndex(i)); 113 | } 114 | DrawContainerButtons(modeListProperty_); 115 | DrawModeTransitionParam(modeTransitionParamProperty_); 116 | EditorGUI.indentLevel--; 117 | } 118 | 119 | showOverrides_ = EditorGUILayout.Foldout(showOverrides_, "Overrides"); 120 | if( showOverrides_ ) 121 | { 122 | EditorGUI.indentLevel++; 123 | 124 | // section transition overrides 125 | sectionTransitionOverridesListProperty_.isExpanded = EditorGUILayout.Foldout(sectionTransitionOverridesListProperty_.isExpanded, "Section Transition Overrides"); 126 | if( sectionTransitionOverridesListProperty_.isExpanded ) 127 | { 128 | EditorGUI.indentLevel++; 129 | sectionTransitionOverridesListProperty_.arraySize = EditorGUILayout.IntField("Size", sectionTransitionOverridesListProperty_.arraySize); 130 | if( sectionTransitionOverridesListProperty_.arraySize > 0 ) 131 | { 132 | sectionListDisplayOptionsWithAny_ = new string[music.Sections.Length + 1]; 133 | sectionListValueOptions_ = new int[sectionListDisplayOptionsWithAny_.Length]; 134 | sectionListDisplayOptionsWithAny_[0] = "Any"; 135 | sectionListValueOptions_[0] = -1; 136 | for( int i = 0; i < music.Sections.Length; ++i ) 137 | { 138 | sectionListDisplayOptionsWithAny_[i + 1] = music.Sections[i].Name; 139 | sectionListValueOptions_[i + 1] = i; 140 | } 141 | } 142 | 143 | for( int i = 0; i < sectionTransitionOverridesListProperty_.arraySize; ++i ) 144 | { 145 | DrawSectionTransitionOverrideParam(sectionTransitionOverridesListProperty_.GetArrayElementAtIndex(i)); 146 | } 147 | EditorGUI.indentLevel--; 148 | } 149 | 150 | // mode transition overrides 151 | modeTransitionOverridesListProperty_.isExpanded = EditorGUILayout.Foldout(modeTransitionOverridesListProperty_.isExpanded, "Mode Transition Overrides"); 152 | if( modeTransitionOverridesListProperty_.isExpanded ) 153 | { 154 | if( modeTransitionOverridesListProperty_.arraySize > 0 ) 155 | { 156 | modeListDisplayOptionsWithAny_ = new string[music.Modes.Length + 1]; 157 | modeListValueOptions_ = new int[modeListDisplayOptionsWithAny_.Length]; 158 | modeListDisplayOptionsWithAny_[0] = "Any"; 159 | modeListValueOptions_[0] = -1; 160 | for( int i = 0; i < music.Modes.Length; ++i ) 161 | { 162 | modeListDisplayOptionsWithAny_[i + 1] = music.Modes[i].Name; 163 | modeListValueOptions_[i + 1] = i; 164 | } 165 | } 166 | 167 | EditorGUI.indentLevel++; 168 | modeTransitionOverridesListProperty_.arraySize = EditorGUILayout.IntField("Size", modeTransitionOverridesListProperty_.arraySize); 169 | for( int i = 0; i < modeTransitionOverridesListProperty_.arraySize; ++i ) 170 | { 171 | DrawModeTransitionOverrideParam(modeTransitionOverridesListProperty_.GetArrayElementAtIndex(i)); 172 | } 173 | EditorGUI.indentLevel--; 174 | } 175 | 176 | EditorGUI.indentLevel--; 177 | } 178 | 179 | // options 180 | showOptions_ = EditorGUILayout.Foldout(showOptions_, "Options"); 181 | if( showOptions_ ) 182 | { 183 | EditorGUI.indentLevel++; 184 | //showHelpTexts_ = EditorGUILayout.Toggle("Show Help", showHelpTexts_); 185 | showUnitPerBarBeat_ = EditorGUILayout.Toggle("Show Meters in UnitPerBeat/Bar", showUnitPerBarBeat_); 186 | EditorGUI.indentLevel--; 187 | } 188 | 189 | // player 190 | if( UnityEditor.EditorApplication.isPlaying ) 191 | { 192 | GUILayout.BeginHorizontal(); 193 | { 194 | if( GUILayout.Button("Play") ) 195 | { 196 | if( music.IsPlaying == false ) 197 | { 198 | music.Play(); 199 | } 200 | } 201 | if( GUILayout.Button(music.State == Music.PlayState.Suspended ? "Resume" : "Suspend") ) 202 | { 203 | if( music.State == Music.PlayState.Suspended ) 204 | { 205 | music.Resume(); 206 | } 207 | else 208 | { 209 | music.Suspend(); 210 | } 211 | } 212 | if( GUILayout.Button("Stop") ) 213 | { 214 | if( music.IsPlaying || music.State == Music.PlayState.Suspended ) 215 | { 216 | music.Stop(); 217 | } 218 | } 219 | } 220 | GUILayout.EndHorizontal(); 221 | 222 | EditorGUI.BeginDisabledGroup(true); 223 | { 224 | EditorGUILayout.TextField("State", music.State.ToString()); 225 | EditorGUILayout.TextField("TransitionState", music.TransitionState.ToString()); 226 | if( music.NextSectionIndex >= 0 && music.NextSectionIndex != music.SectionIndex ) 227 | { 228 | EditorGUILayout.LabelField("to " + sectionListDisplayOptions_[music.NextSectionIndex], GUILayout.Width(120)); 229 | } 230 | EditorGUILayout.TextField("ModeTransitionState", music.ModeTransitionState.ToString()); 231 | if( music.NextModeIndex >= 0 && music.NextModeIndex != music.ModeIndex ) 232 | { 233 | EditorGUILayout.LabelField("to " + modeListDisplayOptions_[music.NextModeIndex], GUILayout.Width(120)); 234 | } 235 | } 236 | EditorGUI.EndDisabledGroup(); 237 | 238 | if( music.State == Music.PlayState.Playing || music.State == Music.PlayState.Suspended ) 239 | { 240 | for( int i = 0; i < sectionListDisplayOptions_.Length; ++i ) 241 | { 242 | if( i != music.SequenceIndex ) 243 | { 244 | var buttonText = sectionListDisplayOptions_[i]; 245 | if( i == music.NextSectionIndex ) 246 | { 247 | buttonText = "transition to " + buttonText; 248 | } 249 | if( GUILayout.Button(buttonText) ) 250 | { 251 | music.SetHorizontalSequenceByIndex(i); 252 | } 253 | } 254 | else 255 | { 256 | EditorGUI.BeginDisabledGroup(true); 257 | GUILayout.Button(sectionListDisplayOptions_[i]); 258 | EditorGUI.EndDisabledGroup(); 259 | } 260 | } 261 | 262 | 263 | for( int i = 0; i < modeListDisplayOptions_.Length; ++i ) 264 | { 265 | if( i != music.ModeIndex ) 266 | { 267 | var buttonText = modeListDisplayOptions_[i]; 268 | if( i == music.NextModeIndex ) 269 | { 270 | buttonText = "transition to " + buttonText; 271 | } 272 | if( GUILayout.Button(buttonText) ) 273 | { 274 | music.SetVerticalMixByIndex(i); 275 | } 276 | } 277 | else 278 | { 279 | EditorGUI.BeginDisabledGroup(true); 280 | GUILayout.Button(modeListDisplayOptions_[i]); 281 | EditorGUI.EndDisabledGroup(); 282 | } 283 | } 284 | 285 | EditorGUILayout.Slider("Seek Bar", music.MusicalTime, music.CurrentSection.EntryPointTiming.IsZero() ? 0 : -1, music.CurrentSection.ExitPointTiming.Bar); 286 | EditorUtility.SetDirty(serializedObject.targetObject); 287 | } 288 | else 289 | { 290 | previewSectionProperty_.intValue = EditorGUILayout.Popup("Section", previewSectionProperty_.intValue, sectionListDisplayOptions_); 291 | previewModeProperty_.intValue = EditorGUILayout.Popup("Mode", previewModeProperty_.intValue, modeListDisplayOptions_); 292 | seekBarProperty_.intValue = EditorGUILayout.IntSlider("Seek Bar", seekBarProperty_.intValue, music.Sections[previewSectionProperty_.intValue].EntryPointTiming.IsZero() ? 0 : -1, music.Sections[previewSectionProperty_.intValue].ExitPointTiming.Bar); 293 | } 294 | } 295 | 296 | serializedObject.ApplyModifiedProperties(); 297 | } 298 | 299 | void DrawContainerButtons(SerializedProperty containerProp) 300 | { 301 | EditorGUILayout.BeginHorizontal(); 302 | if( GUILayout.Button("Add") ) 303 | { 304 | containerProp.InsertArrayElementAtIndex(containerProp.arraySize); 305 | } 306 | if( GUILayout.Button("Remove") && containerProp.arraySize > 0 ) 307 | { 308 | containerProp.DeleteArrayElementAtIndex(containerProp.arraySize - 1); 309 | } 310 | if( GUILayout.Button("Clear") && containerProp.arraySize > 0 ) 311 | { 312 | containerProp.ClearArray(); 313 | } 314 | EditorGUILayout.EndHorizontal(); 315 | } 316 | 317 | void DrawSection(SerializedProperty sectionProp, MusicSection section) 318 | { 319 | SerializedProperty nameProp = sectionProp.FindPropertyRelative("Name"); 320 | sectionProp.isExpanded = EditorGUILayout.Foldout(sectionProp.isExpanded, nameProp.stringValue); 321 | if( sectionProp.isExpanded ) 322 | { 323 | EditorGUI.indentLevel++; 324 | 325 | // name 326 | EditorGUILayout.PropertyField(nameProp); 327 | 328 | // type 329 | SerializedProperty transitionTypeProp = sectionProp.FindPropertyRelative("TransitionType"); 330 | MusicSection.AutoTransitionType transitionType = (MusicSection.AutoTransitionType)transitionTypeProp.enumValueIndex; 331 | if( transitionType == MusicSection.AutoTransitionType.Transition ) 332 | { 333 | EditorGUILayout.BeginHorizontal(); 334 | EditorGUILayout.PropertyField(transitionTypeProp, new GUIContent("Transition Type"), GUILayout.Width(EditorGUIUtility.labelWidth + 80)); 335 | SerializedProperty destinationProp = sectionProp.FindPropertyRelative("TransitionDestinationIndex"); 336 | destinationProp.intValue = EditorGUILayout.Popup(destinationProp.intValue, sectionListDisplayOptions_); 337 | EditorGUILayout.EndHorizontal(); 338 | } 339 | else 340 | { 341 | EditorGUILayout.PropertyField(transitionTypeProp); 342 | } 343 | 344 | // clips 345 | SerializedProperty clipListProp = sectionProp.FindPropertyRelative("Clips"); 346 | clipListProp.arraySize = Mathf.Max(1, numTracksProperty_.intValue); 347 | for( int i = 0; i < clipListProp.arraySize; ++i ) 348 | { 349 | EditorGUILayout.PropertyField(clipListProp.GetArrayElementAtIndex(i), new GUIContent("Track " + i.ToString())); 350 | } 351 | 352 | // meters 353 | SerializedProperty meterListProp = sectionProp.FindPropertyRelative("Meters"); 354 | meterListProp.isExpanded = EditorGUILayout.Foldout(meterListProp.isExpanded, "Meters " + (section != null && section.IsValid ? section.Meters[0].ToString() : "")); 355 | if( meterListProp.isExpanded ) 356 | { 357 | EditorGUI.indentLevel++; 358 | meterListProp.arraySize = Mathf.Max(1, EditorGUILayout.IntField("Size", meterListProp.arraySize)); 359 | for( int i = 0; i < meterListProp.arraySize; ++i ) 360 | { 361 | DrawMeter(meterListProp.GetArrayElementAtIndex(i)); 362 | } 363 | EditorGUI.indentLevel--; 364 | } 365 | 366 | // markers 367 | SerializedProperty markerListProp = sectionProp.FindPropertyRelative("Markers"); 368 | EditorGUILayout.PropertyField(markerListProp, includeChildren: true); 369 | 370 | // transition 371 | DrawSectionTransitionParam(sectionProp); 372 | 373 | // entry / exit / loop 374 | if( section != null && section.IsValid ) 375 | { 376 | EditorGUILayout.PropertyField(sectionProp.FindPropertyRelative("EntryPointTiming")); 377 | EditorGUILayout.PropertyField(sectionProp.FindPropertyRelative("ExitPointTiming")); 378 | float minValue = section.EntryPointTiming.Bar; 379 | float maxValue = section.EntryPointTiming.Bar + section.ExitPointTiming.Bar; 380 | EditorGUILayout.MinMaxSlider(ref minValue, ref maxValue, 0, section.ClipEndTiming.Bar); 381 | if( transitionType == MusicSection.AutoTransitionType.Loop ) 382 | { 383 | EditorGUILayout.PropertyField(sectionProp.FindPropertyRelative("LoopStartTiming")); 384 | EditorGUILayout.PropertyField(sectionProp.FindPropertyRelative("LoopEndTiming")); 385 | minValue = section.EntryPointTiming.Bar + section.LoopStartTiming.Bar; 386 | maxValue = section.EntryPointTiming.Bar + section.LoopEndTiming.Bar; 387 | EditorGUILayout.MinMaxSlider(ref minValue, ref maxValue, 0, section.ClipEndTiming.Bar); 388 | } 389 | } 390 | 391 | EditorGUI.indentLevel--; 392 | } 393 | } 394 | 395 | void DrawMeter(SerializedProperty meterProp) 396 | { 397 | SerializedProperty startBarProp = meterProp.FindPropertyRelative("StartBar"); 398 | SerializedProperty tempoProp = meterProp.FindPropertyRelative("Tempo"); 399 | SerializedProperty unitPerBarProp = meterProp.FindPropertyRelative("UnitPerBar"); 400 | SerializedProperty unitPerBeatProp = meterProp.FindPropertyRelative("UnitPerBeat"); 401 | SerializedProperty numeratorProp = meterProp.FindPropertyRelative("Numerator"); 402 | SerializedProperty denominatorProp = meterProp.FindPropertyRelative("Denominator"); 403 | 404 | int startBar = startBarProp.intValue; 405 | double tempo = tempoProp.doubleValue; 406 | int unitPerBar = unitPerBarProp.intValue; 407 | int unitPerBeat = unitPerBeatProp.intValue; 408 | int numerator = numeratorProp.intValue; 409 | int denominator = denominatorProp.intValue; 410 | 411 | meterProp.isExpanded = EditorGUILayout.Foldout(meterProp.isExpanded, string.Format("Meter {0}~ ({1}/{2}, {3:F2})", startBar, numerator, denominator, tempo)); 412 | if( meterProp.isExpanded ) 413 | { 414 | EditorGUI.indentLevel++; 415 | EditorGUILayout.PropertyField(startBarProp); 416 | EditorGUILayout.PropertyField(tempoProp); 417 | 418 | if( showUnitPerBarBeat_ ) 419 | { 420 | EditorGUILayout.PropertyField(unitPerBeatProp); 421 | EditorGUILayout.PropertyField(unitPerBarProp); 422 | 423 | if( unitPerBar != unitPerBarProp.intValue || unitPerBeat != unitPerBeatProp.intValue ) 424 | { 425 | MusicMeter.CalcMeterByUnits(unitPerBeatProp.intValue, unitPerBarProp.intValue, out numerator, out denominator); 426 | numeratorProp.intValue = numerator; 427 | denominatorProp.intValue = denominator; 428 | } 429 | } 430 | else 431 | { 432 | EditorGUILayout.BeginHorizontal(); 433 | { 434 | EditorGUILayout.LabelField("Meter", GUILayout.Width(EditorGUIUtility.labelWidth - 4)); 435 | EditorGUIUtility.labelWidth = 1; 436 | EditorGUILayout.PropertyField(numeratorProp, GUILayout.Width(55)); 437 | denominatorProp.intValue = EditorGUILayout.IntPopup("/", denominatorProp.intValue, MeterDenominatorCandidatesString, MeterDenominatorCandidates); 438 | EditorGUIUtility.labelWidth = 0; 439 | 440 | if( numerator != numeratorProp.intValue || denominator != denominatorProp.intValue ) 441 | { 442 | MusicMeter.CalcMeterByFraction(numeratorProp.intValue, denominatorProp.intValue, out unitPerBeat, out unitPerBar); 443 | unitPerBeatProp.intValue = unitPerBeat; 444 | unitPerBarProp.intValue = unitPerBar; 445 | } 446 | } 447 | EditorGUILayout.EndHorizontal(); 448 | } 449 | 450 | EditorGUI.indentLevel--; 451 | } 452 | } 453 | static string[] MeterDenominatorCandidatesString = new string[3] { "/4", "/8", "/16" }; 454 | static int[] MeterDenominatorCandidates = new int[] { 4, 8, 16 }; 455 | 456 | void DrawSyncType(SerializedProperty syncTypeProp, SerializedProperty syncFactorProp) 457 | { 458 | Music.SyncType syncType = (Music.SyncType)syncTypeProp.enumValueIndex; 459 | if( Music.SyncType.Unit <= syncType && syncType <= Music.SyncType.Marker ) 460 | { 461 | EditorGUILayout.BeginHorizontal(); 462 | EditorGUILayout.PropertyField(syncFactorProp, new GUIContent("Sync Type")); 463 | EditorGUILayout.PropertyField(syncTypeProp, GUIContent.none); 464 | EditorGUILayout.EndHorizontal(); 465 | } 466 | else 467 | { 468 | EditorGUILayout.PropertyField(syncTypeProp); 469 | } 470 | } 471 | 472 | void DrawSectionTransitionParam(SerializedProperty ownerProp) 473 | { 474 | SerializedProperty transitionProp = ownerProp.FindPropertyRelative("Transition"); 475 | 476 | transitionProp.isExpanded = EditorGUILayout.Foldout(transitionProp.isExpanded, "Transition"); 477 | if( transitionProp.isExpanded ) 478 | { 479 | EditorGUI.indentLevel++; 480 | 481 | // sync 482 | DrawSyncType(ownerProp.FindPropertyRelative("SyncType"), ownerProp.FindPropertyRelative("SyncFactor")); 483 | 484 | // fade 485 | SerializedProperty useFadeOutProp = transitionProp.FindPropertyRelative("UseFadeOut"); 486 | SerializedProperty useFadeInProp = transitionProp.FindPropertyRelative("UseFadeIn"); 487 | EditorGUILayout.PropertyField(useFadeOutProp); 488 | if( useFadeOutProp.boolValue ) 489 | { 490 | EditorGUILayout.PropertyField(transitionProp.FindPropertyRelative("FadeOutTime")); 491 | EditorGUILayout.PropertyField(transitionProp.FindPropertyRelative("FadeOutOffset")); 492 | } 493 | EditorGUILayout.PropertyField(useFadeInProp); 494 | if( useFadeInProp.boolValue ) 495 | { 496 | EditorGUILayout.PropertyField(transitionProp.FindPropertyRelative("FadeInTime")); 497 | EditorGUILayout.PropertyField(transitionProp.FindPropertyRelative("FadeInOffset")); 498 | } 499 | 500 | EditorGUI.indentLevel--; 501 | } 502 | } 503 | 504 | void DrawSectionTransitionOverrideParam(SerializedProperty transitionOverrideProp) 505 | { 506 | SerializedProperty fromProp = transitionOverrideProp.FindPropertyRelative("FromSectionIndex"); 507 | SerializedProperty toProp = transitionOverrideProp.FindPropertyRelative("ToSectionIndex"); 508 | 509 | string fromStr = sectionListDisplayOptionsWithAny_[fromProp.intValue + 1]; 510 | string toStr = sectionListDisplayOptionsWithAny_[toProp.intValue + 1]; 511 | 512 | transitionOverrideProp.isExpanded = EditorGUILayout.Foldout(transitionOverrideProp.isExpanded, string.Format("from {0} to {1}", fromStr, toStr)); 513 | if( transitionOverrideProp.isExpanded ) 514 | { 515 | EditorGUI.indentLevel++; 516 | 517 | fromProp.intValue = EditorGUILayout.IntPopup("From", fromProp.intValue, sectionListDisplayOptionsWithAny_, sectionListValueOptions_); 518 | toProp.intValue = EditorGUILayout.IntPopup("To", toProp.intValue, sectionListDisplayOptionsWithAny_, sectionListValueOptions_); 519 | 520 | // transition 521 | DrawSectionTransitionParam(transitionOverrideProp); 522 | 523 | EditorGUI.indentLevel--; 524 | } 525 | } 526 | 527 | void DrawMode(SerializedProperty modeProp) 528 | { 529 | SerializedProperty nameProp = modeProp.FindPropertyRelative("Name"); 530 | modeProp.isExpanded = EditorGUILayout.Foldout(modeProp.isExpanded, nameProp.stringValue); 531 | if( modeProp.isExpanded ) 532 | { 533 | EditorGUI.indentLevel++; 534 | 535 | EditorGUILayout.PropertyField(nameProp); 536 | EditorGUILayout.PropertyField(modeProp.FindPropertyRelative("TotalVolume")); 537 | 538 | SerializedProperty layerVolumeProp = modeProp.FindPropertyRelative("LayerVolumes"); 539 | { 540 | EditorGUI.indentLevel++; 541 | layerVolumeProp.arraySize = Mathf.Max(1, numTracksProperty_.intValue); 542 | for( int i = 0; i < layerVolumeProp.arraySize; ++i ) 543 | { 544 | SerializedProperty layerProp = layerVolumeProp.GetArrayElementAtIndex(i); 545 | layerProp.floatValue = EditorGUILayout.Slider("Layer " + i.ToString(), layerProp.floatValue, 0.0f, 1.0f); 546 | } 547 | EditorGUI.indentLevel--; 548 | } 549 | 550 | EditorGUI.indentLevel--; 551 | } 552 | } 553 | 554 | void DrawModeTransitionParam(SerializedProperty modeTransitionProp) 555 | { 556 | modeTransitionProp.isExpanded = EditorGUILayout.Foldout(modeTransitionProp.isExpanded, "Transition"); 557 | if( modeTransitionProp.isExpanded ) 558 | { 559 | EditorGUI.indentLevel++; 560 | 561 | // sync 562 | SerializedProperty syncTypeProp = modeTransitionProp.FindPropertyRelative("SyncType"); 563 | DrawSyncType(syncTypeProp, modeTransitionProp.FindPropertyRelative("SyncFactor")); 564 | 565 | EditorGUILayout.PropertyField(modeTransitionProp.FindPropertyRelative("TimeUnitType")); 566 | EditorGUILayout.PropertyField(modeTransitionProp.FindPropertyRelative("FadeTime")); 567 | if( (Music.SyncType)syncTypeProp.enumValueIndex != Music.SyncType.Immediate ) 568 | { 569 | EditorGUILayout.PropertyField(modeTransitionProp.FindPropertyRelative("FadeOffset")); 570 | } 571 | 572 | EditorGUI.indentLevel--; 573 | } 574 | } 575 | 576 | void DrawModeTransitionOverrideParam(SerializedProperty modeTransitionOverrideProp) 577 | { 578 | SerializedProperty fromProp = modeTransitionOverrideProp.FindPropertyRelative("FromModeIndex"); 579 | SerializedProperty toProp = modeTransitionOverrideProp.FindPropertyRelative("ToModeIndex"); 580 | 581 | string fromStr = modeListDisplayOptionsWithAny_[fromProp.intValue + 1]; 582 | string toStr = modeListDisplayOptionsWithAny_[toProp.intValue + 1]; 583 | 584 | modeTransitionOverrideProp.isExpanded = EditorGUILayout.Foldout(modeTransitionOverrideProp.isExpanded, string.Format("from {0} to {1}", fromStr, toStr)); 585 | if( modeTransitionOverrideProp.isExpanded ) 586 | { 587 | EditorGUI.indentLevel++; 588 | 589 | fromProp.intValue = EditorGUILayout.IntPopup("From", fromProp.intValue, modeListDisplayOptionsWithAny_, modeListValueOptions_); 590 | toProp.intValue = EditorGUILayout.IntPopup("To", toProp.intValue, modeListDisplayOptionsWithAny_, modeListValueOptions_); 591 | 592 | DrawModeTransitionParam(modeTransitionOverrideProp.FindPropertyRelative("Transition")); 593 | 594 | EditorGUI.indentLevel--; 595 | } 596 | } 597 | } -------------------------------------------------------------------------------- /Editor/TimingPropertyDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | // http://light11.hatenadiary.com/entry/2018/08/04/195629 8 | [CustomPropertyDrawer(typeof(Timing))] 9 | public class TimingPropertyDrawer : PropertyDrawer 10 | { 11 | private class PropertyData 12 | { 13 | public SerializedProperty Bar; 14 | public SerializedProperty Beat; 15 | public SerializedProperty Unit; 16 | } 17 | 18 | private Dictionary _propertyDataPerPropertyPath = new Dictionary(); 19 | private PropertyData _property; 20 | 21 | private void Init(SerializedProperty property) 22 | { 23 | if( _propertyDataPerPropertyPath.TryGetValue(property.propertyPath, out _property) ) 24 | { 25 | return; 26 | } 27 | 28 | _property = new PropertyData(); 29 | _property.Bar = property.FindPropertyRelative("Bar"); 30 | _property.Beat = property.FindPropertyRelative("Beat"); 31 | _property.Unit = property.FindPropertyRelative("Unit"); 32 | _propertyDataPerPropertyPath.Add(property.propertyPath, _property); 33 | } 34 | 35 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 36 | { 37 | Init(property); 38 | var fieldRect = position; 39 | fieldRect.height = EditorGUIUtility.singleLineHeight; 40 | 41 | // Prefab化した後プロパティに変更を加えた際に太字にしたりする機能を加えるためPropertyScopeを使う 42 | using( new EditorGUI.PropertyScope(fieldRect, label, property) ) 43 | { 44 | // ラベルを表示し、ラベルの右側のプロパティを描画すべき領域のpositionを得る 45 | fieldRect = EditorGUI.PrefixLabel(fieldRect, GUIUtility.GetControlID(FocusType.Passive), label); 46 | 47 | // ここでIndentを0に 48 | var preIndent = EditorGUI.indentLevel; 49 | EditorGUI.indentLevel = 0; 50 | 51 | // プロパティを描画 52 | var rect = fieldRect; 53 | rect.width /= 3; 54 | EditorGUI.PropertyField(rect, _property.Bar, GUIContent.none); 55 | 56 | rect.x += rect.width; 57 | rect.width /= 2; 58 | EditorGUI.PropertyField(rect, _property.Beat, GUIContent.none); 59 | 60 | rect.x += rect.width; 61 | EditorGUI.PropertyField(rect, _property.Unit, GUIContent.none); 62 | 63 | EditorGUI.indentLevel = preIndent; 64 | } 65 | } 66 | 67 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 68 | { 69 | Init(property); 70 | 71 | return EditorGUIUtility.singleLineHeight; 72 | } 73 | } -------------------------------------------------------------------------------- /Music.cs: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2022 geekdrums 2 | //Released under the MIT license 3 | //http://opensource.org/licenses/mit-license.php 4 | //Feel free to use this for your lovely musical games :) 5 | 6 | using UnityEngine; 7 | using UnityEngine.UI; 8 | using System; 9 | using System.Collections; 10 | using System.Collections.Generic; 11 | 12 | /// 13 | /// Static access to currently playing music. 14 | /// Music.Play will set and change current instance. 15 | /// 16 | public static class Music 17 | { 18 | #region [public] current music 19 | 20 | public static MusicBase Current { get { return current_; } } 21 | public static string CurrentMusicName { get { return current_ != null ? current_.name : ""; } } 22 | public static bool IsPlaying { get { return current_ != null ? current_.IsPlaying : false; } } 23 | public static bool IsPlayingOrSuspended { get { return current_ != null ? current_.IsPlayingOrSuspended : false; } } 24 | public static PlayState State { get { return current_ != null ? current_.State : PlayState.Invalid; } } 25 | 26 | #endregion 27 | 28 | 29 | #region [public] just / near timing 30 | 31 | /// 32 | /// 経過した最後のタイミングを指し示します 33 | /// Last timing. 34 | /// 35 | public static Timing Just { get { return current_ != null ? current_.Just : null; } } 36 | 37 | /// 38 | /// is Just changed in this frame or not. 39 | /// 40 | public static bool IsJustChanged { get { return current_ != null ? current_.IsJustChanged : false; } } 41 | 42 | /// 43 | /// current musical time in units 44 | /// 45 | public static int JustTotalUnits { get { return current_ != null ? current_.JustTotalUnits : 0; } } 46 | 47 | public static bool IsJustChangedWhen(Predicate pred) 48 | { 49 | return current_ != null ? current_.IsJustChangedWhen(pred) : false; 50 | } 51 | public static bool IsJustChangedBar() 52 | { 53 | return current_ != null ? current_.IsJustChangedBar() : false; 54 | } 55 | public static bool IsJustChangedBeat() 56 | { 57 | return current_ != null ? current_.IsJustChangedBeat() : false; 58 | } 59 | public static bool IsJustChangedAt(int bar = 0, int beat = 0, int unit = 0) 60 | { 61 | return current_ != null ? current_.IsJustChangedAt(bar, beat, unit) : false; 62 | } 63 | public static bool IsJustChangedAt(Timing t) 64 | { 65 | return current_ != null ? current_.IsJustChangedAt(t.Bar, t.Beat, t.Unit) : false; 66 | } 67 | public static bool IsJustLooped() 68 | { 69 | return current_ != null ? current_.IsJustLooped() : false; 70 | } 71 | 72 | /// 73 | /// 最も近いタイミングを指し示します。 74 | /// Nearest timing. 75 | /// 76 | public static Timing Near { get { return current_ != null ? current_.Near : null; } } 77 | 78 | /// 79 | /// is Near changed in this frame or not. 80 | /// 81 | public static bool IsNearChanged { get { return current_ != null ? current_.IsNearChanged : false; } } 82 | 83 | /// 84 | /// current musical time in units 85 | /// 86 | public static int NearTotalUnits { get { return current_ != null ? current_.NearTotalUnits : 0; } } 87 | 88 | public static bool IsNearChangedWhen(Predicate pred) 89 | { 90 | return current_ != null ? current_.IsNearChangedWhen(pred) : false; 91 | } 92 | public static bool IsNearChangedBar() 93 | { 94 | return current_ != null ? current_.IsNearChangedBar() : false; 95 | } 96 | public static bool IsNearChangedBeat() 97 | { 98 | return current_ != null ? current_.IsNearChangedBeat() : false; 99 | } 100 | public static bool IsNearChangedAt(int bar, int beat = 0, int unit = 0) 101 | { 102 | return current_ != null ? current_.IsNearChangedAt(bar, beat, unit) : false; 103 | } 104 | public static bool IsNearChangedAt(Timing t) 105 | { 106 | return current_ != null ? current_.IsNearChangedAt(t.Bar, t.Beat, t.Unit) : false; 107 | } 108 | 109 | /// 110 | /// is currently former half in a MusicTimeUnit, or last half. 111 | /// 112 | public static bool IsFormerHalf { get { return current_ != null ? current_.IsFormerHalf : false; } } 113 | /// 114 | /// returns sec from last Just timing. 115 | /// 116 | public static double SecFromJust { get { return current_ != null ? current_.SecFromJust : 0; } } 117 | /// 118 | /// returns normalized time (0 to 1) from last Just timing. 119 | /// 120 | public static double UnitFromJust { get { return current_ != null ? current_.UnitFromJust : 0; } } 121 | 122 | #endregion 123 | 124 | 125 | #region [public] play / stop / suspend / resume 126 | 127 | /// 128 | /// Change current music and play. 129 | /// 130 | /// name of the GameObject that include Music 131 | public static void Play(string musicName) 132 | { 133 | MusicBase music = musicList_.Find((MusicBase m) => m != null && m.name == musicName); 134 | if( music != null ) 135 | { 136 | music.Play(); 137 | } 138 | else 139 | { 140 | Debug.Log("Can't find music: " + musicName); 141 | } 142 | } 143 | public static void PlayFrom(string musicName, Timing seekTiming, int sequenceIndex = 0) 144 | { 145 | MusicBase music = musicList_.Find((MusicBase m) => m != null && m.name == musicName); 146 | if( music != null ) 147 | { 148 | music.Seek(seekTiming, sequenceIndex); 149 | music.Play(); 150 | } 151 | else 152 | { 153 | Debug.Log("Can't find music: " + musicName); 154 | } 155 | } 156 | public static void Suspend() { current_?.Suspend(); } 157 | public static void Resume() { current_?.Resume(); } 158 | public static void Stop() { current_?.Stop(); } 159 | 160 | #endregion 161 | 162 | 163 | #region [public] interactive music 164 | 165 | /// 166 | /// 横の遷移を実行します 167 | /// execute Horizontal Resequencing 168 | /// in MusicUnity, SetNextSection 169 | /// in MusicADX2, SetNextBlock 170 | /// in MusicWwise, SetState 171 | /// 172 | /// Section/Block/State name 173 | public static void SetHorizontalSequence(string name) { current_?.SetHorizontalSequence(name); } 174 | 175 | /// 176 | /// 横の遷移を実行します 177 | /// execute Horizontal Resequencing 178 | /// in MusicUnity, SetNextSection 179 | /// in MusicADX2, SetNextBlock 180 | /// in MusicWwise, not implemented 181 | /// 182 | /// Section/Block index 183 | public static void SetHorizontalSequenceByIndex(int index) { current_?.SetHorizontalSequenceByIndex(index); } 184 | 185 | /// 186 | /// 縦の遷移を実行します 187 | /// execute Vertical Remixing 188 | /// in MusicUnity, SetMode 189 | /// in MusicADX2, not implemented 190 | /// in MusicWwise, SetState 191 | /// 192 | /// Mode/State name 193 | public static void SetVerticalMix(string name) { current_?.SetVerticalMix(name); } 194 | 195 | /// 196 | /// 縦の遷移を実行します 197 | /// execute Vertical Remixing 198 | /// in MusicUnity, SetMode 199 | /// in MusicADX2, SetAisacControl 200 | /// in MusicWwise, not implemented 201 | /// 202 | /// Mode/Aisac index 203 | public static void SetVerticalMixByIndex(int index) { current_?.SetVerticalMixByIndex(index); } 204 | 205 | #endregion 206 | 207 | 208 | #region [public] musical time / meter / sequence 209 | 210 | // musical time 211 | 212 | /// 213 | /// current musical time in bars 214 | /// 215 | public static float MusicalTime { get { return current_ != null ? current_.MusicalTime : 0; } } 216 | 217 | /// 218 | /// returns musically synced cos wave. 219 | /// if default( MusicalCos(16,0,0,1), 220 | /// starts from max=1, 221 | /// reaches min=0 on MusicalTime = cycle/2 = 8, 222 | /// back to max=1 on MusicalTIme = cycle = 16. 223 | /// 224 | /// wave cycle in musical unit 225 | /// wave offset in musical unit 226 | /// 227 | /// 228 | /// 229 | public static float MusicalCos(float cycle = 16, float offset = 0, float min = 0, float max = 1) 230 | { 231 | return Mathf.Lerp(min, max, ((float)Math.Cos(Math.PI * 2 * (CurrentUnitPerBar * MusicalTime + offset) / cycle) + 1.0f) / 2.0f); 232 | } 233 | 234 | // current meter 235 | 236 | public static bool HasValidMeter { get { return current_ != null && current_.CurrentMeter != null; } } 237 | public static MusicMeter CurrentMeter { get { return current_ != null ? current_.CurrentMeter : null; } } 238 | public static double CurrentTempo { get { return current_ != null ? current_.CurrentMeter.Tempo : 0; } } 239 | public static int CurrentUnitPerBar { get { return current_ != null ? current_.CurrentMeter.UnitPerBar : 0; } } 240 | public static int CurrentUnitPerBeat { get { return current_ != null ? current_.CurrentMeter.UnitPerBeat : 0; } } 241 | 242 | // current sequence 243 | 244 | public static string CurrentSequenceName { get { return current_ != null ? current_.SequenceName : ""; } } 245 | public static int CurrentSequenceIndex { get { return current_ != null ? current_.SequenceIndex : 0; } } 246 | public static int NumRepeat { get { return current_ != null ? current_.NumRepeat : 0; } } 247 | 248 | #endregion 249 | 250 | 251 | #region params 252 | 253 | static MusicBase current_; 254 | static List musicList_ = new List(); 255 | 256 | public static void RegisterMusic(MusicBase music) 257 | { 258 | if( musicList_.Contains(music) == false ) 259 | { 260 | musicList_.Add(music); 261 | } 262 | } 263 | 264 | public static void OnPlay(MusicBase music) 265 | { 266 | if( current_ != null && current_ != music && current_.IsPlaying ) 267 | { 268 | current_.Stop(); 269 | } 270 | 271 | current_ = music; 272 | } 273 | 274 | public static void OnFinish(MusicBase music) 275 | { 276 | if( current_ == music ) 277 | { 278 | current_ = null; 279 | } 280 | } 281 | 282 | #endregion 283 | 284 | 285 | #region enum / delegate 286 | 287 | public enum PlayState 288 | { 289 | Invalid, 290 | Ready, 291 | Playing, 292 | Suspended, 293 | Finished 294 | }; 295 | 296 | public enum SyncType 297 | { 298 | Immediate, 299 | Unit, 300 | Beat, 301 | Bar, 302 | Marker, 303 | ExitPoint, 304 | }; 305 | 306 | public enum TimeUnitType 307 | { 308 | Sec, 309 | MSec, 310 | Bar, 311 | Beat, 312 | Unit, 313 | }; 314 | 315 | public static class TimeUtility 316 | { 317 | public static float DefaultBPM = 120; 318 | 319 | public static float ConvertTime(float time, TimeUnitType from, TimeUnitType to = TimeUnitType.Sec) 320 | { 321 | if( from == to ) return time; 322 | 323 | float sec = time; 324 | if( from == TimeUnitType.Sec ) 325 | { 326 | sec = time; 327 | } 328 | else if( from == TimeUnitType.MSec ) 329 | { 330 | sec = time / 1000.0f; 331 | } 332 | else 333 | { 334 | if( HasValidMeter ) 335 | { 336 | switch( from ) 337 | { 338 | case TimeUnitType.Bar: 339 | sec = time * (float)current_.CurrentMeter.SecPerBar; 340 | break; 341 | case TimeUnitType.Beat: 342 | sec = time * (float)current_.CurrentMeter.SecPerBeat; 343 | break; 344 | case TimeUnitType.Unit: 345 | sec = time * (float)current_.CurrentMeter.SecPerUnit; 346 | break; 347 | } 348 | } 349 | else 350 | { 351 | switch( from ) 352 | { 353 | case TimeUnitType.Bar: 354 | sec = time * (60.0f * 4.0f / DefaultBPM); 355 | break; 356 | case TimeUnitType.Beat: 357 | sec = time * (60.0f / DefaultBPM); 358 | break; 359 | case TimeUnitType.Unit: 360 | sec = time * (60.0f / 4.0f / DefaultBPM); 361 | break; 362 | } 363 | } 364 | } 365 | 366 | if( to == TimeUnitType.Sec ) 367 | { 368 | return sec; 369 | } 370 | else if( to == TimeUnitType.MSec ) 371 | { 372 | return sec * 1000.0f; 373 | } 374 | else 375 | { 376 | if( HasValidMeter ) 377 | { 378 | switch( to ) 379 | { 380 | case TimeUnitType.Bar: 381 | return sec / (float)current_.CurrentMeter.SecPerBar; 382 | case TimeUnitType.Beat: 383 | return sec / (float)current_.CurrentMeter.SecPerBeat; 384 | case TimeUnitType.Unit: 385 | return sec / (float)current_.CurrentMeter.SecPerUnit; 386 | } 387 | } 388 | else 389 | { 390 | switch( to ) 391 | { 392 | case TimeUnitType.Bar: 393 | return sec / (60.0f * 4.0f / DefaultBPM); 394 | case TimeUnitType.Beat: 395 | return sec / (60.0f / DefaultBPM); 396 | case TimeUnitType.Unit: 397 | return sec / (60.0f / 4.0f / DefaultBPM); 398 | } 399 | } 400 | } 401 | 402 | return sec; 403 | } 404 | } 405 | static readonly float PITCH_UNIT = Mathf.Pow(2.0f, 1.0f / 12.0f); 406 | 407 | #endregion 408 | } -------------------------------------------------------------------------------- /MusicADX2.cs: -------------------------------------------------------------------------------- 1 | //#define ADX2 2 | #if ADX2 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using UnityEngine; 8 | using System.Xml; 9 | using CriWare; 10 | 11 | 12 | [RequireComponent(typeof(CriAtomSource))] 13 | public class MusicADX2 : MusicBase 14 | { 15 | #region editor params 16 | 17 | public MusicMeter Meter = new MusicMeter(0); 18 | 19 | [Tooltip("ブロック遷移時にタイミングを(0,0,0)から開始したい場合はtrue, AtomCraft上のタイムラインと同じように進行させたい場合はfalse")] 20 | public bool ResetTimingOnEachBlock = false; 21 | 22 | public List BlockInfos; 23 | 24 | [Tooltip("SetVerticalMixByIndex関数で使われるAISACコントロールのID")] 25 | public uint AisacControlID = 0; 26 | 27 | [Tooltip("AisacControlIDで変化させる状態の数。0.0f~1.0fのAisac値をこの値によって均等分割してIndexで設定できるようにする。")] 28 | public uint AisacStateCount = 2; 29 | 30 | public float AisacInitialValue = 0.0f; 31 | 32 | public float AisacFadeTime = 1.0f; 33 | 34 | #endregion 35 | 36 | 37 | #region ADX2 resources 38 | 39 | private CriAtomSource atomSource_; 40 | private CriAtomExPlayback playback_; 41 | private CriAtomExAcb acbData_; 42 | private CriAtomEx.CueInfo cueInfo_; 43 | 44 | #endregion 45 | 46 | 47 | #region block / aisac params 48 | 49 | [System.Serializable] 50 | public class BlockInfo 51 | { 52 | public BlockInfo(string blockName, int numBar, int startBar = 0) 53 | { 54 | this.BlockName = blockName; 55 | this.NumBar = numBar; 56 | this.StartBar = startBar; 57 | } 58 | public string BlockName = "Block"; 59 | public int NumBar = 4; 60 | public int StartBar = 0; 61 | } 62 | 63 | private int currentMSec_; 64 | private int currentBlockIndex_; 65 | private int nextBlockIndex_; 66 | private int prevBlockIndex_; 67 | 68 | private float lastAisacValue_; 69 | private float currentAisacValue_; 70 | private Coroutine currentAisacCoroutine_; 71 | 72 | public BlockInfo CurrentBlock { get { return BlockInfos[currentBlockIndex_]; } } 73 | public BlockInfo NextBlock { get { return BlockInfos[nextBlockIndex_]; } } 74 | public override int SequenceIndex { get { return currentBlockIndex_; } } 75 | public override string SequenceName { get { return BlockInfos[currentBlockIndex_].BlockName; } } 76 | 77 | public void SetAisacControl(string controlName, float value) 78 | { 79 | atomSource_.SetAisacControl(controlName, value); 80 | } 81 | 82 | public CriAtomSource Source { get { return atomSource_; } } 83 | 84 | #endregion 85 | 86 | 87 | #region override functions 88 | 89 | // internal 90 | 91 | protected override bool ReadyInternal() 92 | { 93 | atomSource_ = GetComponent(); 94 | if( atomSource_.playOnStart ) 95 | { 96 | atomSource_.playOnStart = false; 97 | PlayOnStart = true; 98 | } 99 | acbData_ = CriAtom.GetAcb(atomSource_.cueSheet); 100 | acbData_.GetCueInfo(atomSource_.cueName, out cueInfo_); 101 | Meter.Validate(0); 102 | return true; 103 | } 104 | 105 | protected override void SeekInternal(Timing seekTiming, int sequenceIndex = 0) 106 | { 107 | // ResetTimingOnEachBlockがfalseの時は、タイミングがブロックごとではなく 108 | // 全体で一つのものと判断されるので、sequenceIndex引数は無視されます。 109 | if( ResetTimingOnEachBlock == false ) 110 | { 111 | sequenceIndex = BlockInfos.Count - 1; 112 | for( int i = 1; i < BlockInfos.Count; ++i ) 113 | { 114 | if( seekTiming.Bar < BlockInfos[i].StartBar ) 115 | { 116 | sequenceIndex = i - 1; 117 | break; 118 | } 119 | } 120 | 121 | seekTiming = new Timing(seekTiming.Bar - BlockInfos[sequenceIndex].StartBar, seekTiming.Beat, seekTiming.Unit); 122 | } 123 | 124 | nextBlockIndex_ = sequenceIndex; 125 | currentBlockIndex_ = sequenceIndex; 126 | atomSource_.player.SetFirstBlockIndex(sequenceIndex); 127 | atomSource_.startTime = (int)Meter.GetMilliSecondsFromTiming(seekTiming); 128 | } 129 | 130 | protected override bool PlayInternal() 131 | { 132 | playback_ = atomSource_.Play(); 133 | return true; 134 | } 135 | 136 | protected override bool SuspendInternal() 137 | { 138 | atomSource_.Pause(true); 139 | return true; 140 | } 141 | 142 | protected override bool ResumeInternal() 143 | { 144 | atomSource_.Pause(false); 145 | return true; 146 | } 147 | 148 | protected override bool StopInternal() 149 | { 150 | atomSource_.Stop(); 151 | return true; 152 | } 153 | 154 | protected override void ResetParamsInternal() 155 | { 156 | currentAisacValue_ = lastAisacValue_ = AisacInitialValue; 157 | currentBlockIndex_ = 0; 158 | prevBlockIndex_ = 0; 159 | nextBlockIndex_ = 0; 160 | currentMeter_ = Meter; 161 | } 162 | 163 | // timing 164 | 165 | protected override void UpdateTimingInternal() 166 | { 167 | double startMSec = 0.0; 168 | if( ResetTimingOnEachBlock ) 169 | { 170 | prevBlockIndex_ = currentBlockIndex_; 171 | currentBlockIndex_ = playback_.GetCurrentBlockIndex(); 172 | startMSec = Meter.MSecPerBar * CurrentBlock.StartBar; 173 | } 174 | 175 | // playback_.GetNumPlayedSamplesは、見つかった最初の波形のサンプル数を返してしまい、 176 | // 遷移時に残っていた前のブロックの波形を取ってきてしまうことがあるので断念。 177 | currentMSec_ = Math.Max(0, (int)playback_.GetSequencePosition() - (int)startMSec); 178 | } 179 | 180 | protected override void CalcTimingAndFraction(ref Timing just, out float fraction) 181 | { 182 | if( currentMeter_ == null ) 183 | { 184 | just.Set(-1, 0, 0); 185 | fraction = 0; 186 | } 187 | else 188 | { 189 | just.Set(currentMeter_.GetTimingFromMilliSeconds(currentMSec_)); 190 | fraction = (float)((currentMSec_ - currentMeter_.GetMilliSecondsFromTiming(just)) / currentMeter_.MSecPerUnit); 191 | } 192 | } 193 | 194 | protected override Timing GetSequenceEndTiming() 195 | { 196 | return ResetTimingOnEachBlock ? new Timing(CurrentBlock.NumBar) : null; 197 | } 198 | 199 | // update 200 | 201 | protected override bool CheckFinishPlaying() 202 | { 203 | if( atomSource_.status == CriAtomSource.Status.PlayEnd ) 204 | { 205 | return true; 206 | } 207 | return false; 208 | } 209 | 210 | protected override void UpdateInternal() 211 | { 212 | 213 | } 214 | 215 | // event 216 | 217 | protected override void OnRepeated() 218 | { 219 | base.OnRepeated(); 220 | nextBlockIndex_ = -1; 221 | } 222 | 223 | protected override void OnHorizontalSequenceChanged() 224 | { 225 | base.OnHorizontalSequenceChanged(); 226 | nextBlockIndex_ = -1; 227 | } 228 | 229 | // interactive music 230 | 231 | public override void SetHorizontalSequence(string name) 232 | { 233 | if( name == CurrentBlock.BlockName ) return; 234 | int index = BlockInfos.FindIndex((BlockInfo info) => info.BlockName == name); 235 | if( index >= 0 ) 236 | { 237 | nextBlockIndex_ = index; 238 | playback_.SetNextBlockIndex(index); 239 | } 240 | else 241 | { 242 | Debug.LogError("Error!! MusicSourceADX2.SetHorizontalSequence Can't find block name: " + name); 243 | } 244 | } 245 | 246 | public override void SetHorizontalSequenceByIndex(int index) 247 | { 248 | if( index == currentBlockIndex_ ) return; 249 | if( index < cueInfo_.numBlocks ) 250 | { 251 | nextBlockIndex_ = index; 252 | playback_.SetNextBlockIndex(index); 253 | } 254 | else 255 | { 256 | Debug.LogError("Error!! MusicSourceADX2.SetHorizontalSequenceByIndex index is out of range: " + index); 257 | } 258 | } 259 | 260 | public override void SetVerticalMix(string name) 261 | { 262 | Debug.LogWarning("MusicADX2.SetVerticalMix is not implemented."); 263 | } 264 | 265 | public override void SetVerticalMixByIndex(int index) 266 | { 267 | if( AisacControlID >= 0 && AisacStateCount > 1 && playback_.status == CriAtomExPlayback.Status.Playing ) 268 | { 269 | // 前のコルーチンが終わってなければ殺す 270 | if( currentAisacCoroutine_ != null ) 271 | { 272 | StopCoroutine(currentAisacCoroutine_); 273 | } 274 | currentAisacCoroutine_ = StartCoroutine(FadeAisacCoroutine((float)index / (AisacStateCount - 1))); 275 | } 276 | } 277 | 278 | IEnumerator FadeAisacCoroutine(float targetAisacValue) 279 | { 280 | // 現在値を反映。これがないと、連続で切り替えた時にAISAC値が飛んでしまう 281 | lastAisacValue_ = currentAisacValue_; 282 | // FadeTimeプロパティを使ってフェード 283 | for( float t = 0; t < AisacFadeTime; t += Time.deltaTime ) 284 | { 285 | currentAisacValue_ = Mathf.Lerp(lastAisacValue_, targetAisacValue, Mathf.Clamp01(t / AisacFadeTime)); 286 | atomSource_.SetAisacControl(AisacControlID, currentAisacValue_); 287 | yield return null; 288 | } 289 | 290 | // フェード完了 291 | currentAisacValue_ = lastAisacValue_ = targetAisacValue; 292 | atomSource_.SetAisacControl(AisacControlID, targetAisacValue); 293 | 294 | currentAisacCoroutine_ = null; 295 | } 296 | 297 | #endregion 298 | 299 | 300 | #region utils 301 | 302 | void OnValidate() 303 | { 304 | Meter.Validate(0); 305 | } 306 | 307 | public static void UpdateBlockInfo(string outputAssetsRoot) 308 | { 309 | string[] acbInfoFileList = System.IO.Directory.GetFiles(outputAssetsRoot.Replace("/Assets", ""), "*_acb_info.xml", System.IO.SearchOption.AllDirectories); 310 | List musicList = new List(GameObject.FindObjectsOfType()); 311 | 312 | foreach( string acbInfoFile in acbInfoFileList ) 313 | { 314 | string cueSheetName = System.IO.Path.GetFileName(acbInfoFile).Replace("_acb_info.xml", ""); 315 | 316 | XmlReaderSettings settings = new XmlReaderSettings(); 317 | settings.IgnoreWhitespace = true; 318 | settings.IgnoreComments = true; 319 | using( XmlReader reader = XmlReader.Create(System.IO.File.OpenText(acbInfoFile), settings) ) 320 | { 321 | while( reader.Read() ) 322 | { 323 | // if this is a Cue and it has Bpm setting 324 | if( reader.GetAttribute("CueID") != null && reader.GetAttribute("Bpm") != null ) 325 | { 326 | string cueName = reader.GetAttribute("OrcaName"); 327 | MusicADX2 musicADX2 = musicList.Find((m) => m.GetComponent().cueSheet == cueSheetName 328 | && m.GetComponent().cueName == cueName); 329 | if( musicADX2 != null ) 330 | { 331 | musicADX2.LoadAcbInfoData(reader.ReadSubtree(), double.Parse(reader.GetAttribute("Bpm"))); 332 | musicList.Remove(musicADX2); 333 | } 334 | } 335 | } 336 | reader.Close(); 337 | } 338 | } 339 | } 340 | 341 | void LoadAcbInfoData(XmlReader reader, double Bpm) 342 | { 343 | Meter.Tempo = Bpm; 344 | Meter.Validate(0); 345 | BlockInfos = new List(); 346 | int startBar = 0; 347 | while( reader.Read() ) 348 | { 349 | if( Meter.Tempo > 0 && reader.GetAttribute("BlockEndPositionMs") != null ) 350 | { 351 | string blockName = reader.GetAttribute("OrcaName"); 352 | double sec = double.Parse(reader.GetAttribute("BlockEndPositionMs")) / 1000.0; 353 | int bar = Mathf.RoundToInt((float)(sec / Meter.SecPerBar)); 354 | MusicADX2.BlockInfo blockInfo = new MusicADX2.BlockInfo(blockName, bar, startBar); 355 | BlockInfos.Add(blockInfo); 356 | startBar += bar; 357 | } 358 | } 359 | } 360 | 361 | #endregion 362 | } 363 | 364 | #endif -------------------------------------------------------------------------------- /MusicBase.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.UI; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | /// 8 | /// base class for MusicUnity / MusicADX2 / MusicWwise 9 | /// 10 | public abstract class MusicBase : MonoBehaviour 11 | { 12 | #region properties 13 | 14 | // editor params 15 | public bool PlayOnStart; 16 | 17 | // state 18 | public Music.PlayState State { get; private set; } = Music.PlayState.Invalid; 19 | public bool IsPlaying { get { return State == Music.PlayState.Playing; } } 20 | public bool IsPlayingOrSuspended { get { return State == Music.PlayState.Playing || State == Music.PlayState.Suspended; } } 21 | 22 | // timing 23 | public Timing Just { get { return just_; } } 24 | public Timing Near { get { return near_; } } 25 | public bool IsJustChanged { get { return isJustChanged_; } } 26 | public bool IsNearChanged { get { return isNearChanged_; } } 27 | public bool IsFormerHalf { get { return isFormerHalf_; } } 28 | public double SecFromJust { get { return fractionFromJust_ * currentMeter_.SecPerUnit; } } 29 | public double UnitFromJust { get { return SecFromJust / currentMeter_.SecPerUnit; } } 30 | 31 | // meter 32 | public bool HasValidMeter { get { return currentMeter_ != null; } } 33 | public MusicMeter CurrentMeter { get { return currentMeter_; } } 34 | public double Tempo { get { return currentMeter_.Tempo; } } 35 | public int UnitPerBar { get { return currentMeter_.UnitPerBar; } } 36 | public int UnitPerBeat { get { return currentMeter_.UnitPerBeat; } } 37 | 38 | // musical time 39 | public float MusicalTime { get { return currentMeter_ != null ? currentMeter_.GetMusicalTime(just_, fractionFromJust_) : 0.0f; } } 40 | public int JustTotalUnits { get { return just_.GetTotalUnits(currentMeter_); } } 41 | public int NearTotalUnits { get { return near_.GetTotalUnits(currentMeter_); } } 42 | 43 | // sequence 44 | public abstract string SequenceName { get; } 45 | public abstract int SequenceIndex { get; } 46 | public int NumRepeat { get { return numRepeat_; } } 47 | 48 | #endregion 49 | 50 | 51 | #region predicates 52 | 53 | public bool IsNearChangedWhen(Predicate pred) 54 | { 55 | if( isNearChanged_ ) 56 | { 57 | if( pred(near_) ) return true; 58 | } 59 | return false; 60 | } 61 | public bool IsNearChangedBar() 62 | { 63 | return isNearChanged_ && (oldNear_.Bar != near_.Bar); 64 | } 65 | public bool IsNearChangedBeat() 66 | { 67 | return isNearChanged_ && (oldNear_.Beat != near_.Beat); 68 | } 69 | public bool IsNearChangedAt(int bar = 0, int beat = 0, int unit = 0) 70 | { 71 | return IsNearChangedAt(new Timing(bar, beat, unit)); 72 | } 73 | public bool IsNearChangedAt(Timing t) 74 | { 75 | return (isNearChanged_ && (oldNear_ < t && t <= near_)) || (isNearLooped_ && (oldNear_ < t || t <= near_)); 76 | } 77 | public bool IsJustChangedWhen(Predicate pred) 78 | { 79 | if( isJustChanged_ ) 80 | { 81 | if( pred(just_) ) return true; 82 | } 83 | return false; 84 | } 85 | public bool IsJustChangedBar() 86 | { 87 | return isJustChanged_ && (oldJust_.Bar != just_.Bar); 88 | } 89 | public bool IsJustChangedBeat() 90 | { 91 | return isJustChanged_ && (oldJust_.Beat != just_.Beat); 92 | } 93 | public bool IsJustChangedAt(int bar = 0, int beat = 0, int unit = 0) 94 | { 95 | return IsJustChangedAt(new Timing(bar, beat, unit)); 96 | } 97 | public bool IsJustChangedAt(Timing t) 98 | { 99 | return (isJustChanged_ && (oldJust_ < t && t <= just_)) || (isJustLooped_ && (oldJust_ < t || t <= just_)); 100 | } 101 | public bool IsJustLooped() 102 | { 103 | return isJustLooped_; 104 | } 105 | 106 | #endregion 107 | 108 | 109 | #region public functions 110 | 111 | public void Play() 112 | { 113 | if( State == Music.PlayState.Playing || State == Music.PlayState.Suspended || State == Music.PlayState.Invalid ) 114 | { 115 | return; 116 | } 117 | 118 | if( PlayInternal() ) 119 | { 120 | Music.OnPlay(this); 121 | State = Music.PlayState.Playing; 122 | OnHorizontalSequenceChanged(); 123 | } 124 | } 125 | 126 | public void Seek(Timing seekTiming, int sequenceIndex = 0) 127 | { 128 | SeekInternal(seekTiming, sequenceIndex); 129 | } 130 | 131 | public void Stop() 132 | { 133 | if( State == Music.PlayState.Invalid ) 134 | { 135 | return; 136 | } 137 | 138 | if( StopInternal() ) 139 | { 140 | State = Music.PlayState.Finished; 141 | ResetParams(); 142 | Music.OnFinish(this); 143 | } 144 | } 145 | 146 | public void Suspend() 147 | { 148 | if( State != Music.PlayState.Playing ) 149 | { 150 | return; 151 | } 152 | 153 | if( SuspendInternal() ) 154 | { 155 | State = Music.PlayState.Suspended; 156 | } 157 | } 158 | 159 | public void Resume() 160 | { 161 | if( State != Music.PlayState.Suspended ) 162 | { 163 | return; 164 | } 165 | 166 | if( ResumeInternal() ) 167 | { 168 | State = Music.PlayState.Playing; 169 | } 170 | } 171 | 172 | public abstract void SetHorizontalSequence(string name); 173 | 174 | public abstract void SetHorizontalSequenceByIndex(int index); 175 | 176 | public abstract void SetVerticalMix(string name); 177 | 178 | public abstract void SetVerticalMixByIndex(int index); 179 | 180 | #endregion 181 | 182 | 183 | #region protected functions 184 | 185 | // internal 186 | 187 | protected abstract bool ReadyInternal(); 188 | 189 | protected abstract void SeekInternal(Timing seekTiming, int sequenceIndex = 0); 190 | 191 | protected abstract bool PlayInternal(); 192 | 193 | protected abstract bool SuspendInternal(); 194 | 195 | protected abstract bool ResumeInternal(); 196 | 197 | protected abstract bool StopInternal(); 198 | 199 | protected abstract void ResetParamsInternal(); 200 | 201 | // update 202 | 203 | protected abstract void UpdateInternal(); 204 | 205 | protected abstract bool CheckFinishPlaying(); 206 | 207 | //timing 208 | 209 | protected abstract void UpdateTimingInternal(); 210 | 211 | protected abstract void CalcTimingAndFraction(ref Timing just, out float fraction); 212 | 213 | protected abstract Timing GetSequenceEndTiming(); 214 | 215 | #endregion 216 | 217 | 218 | #region params 219 | 220 | // 現在再生中の箇所のメーター情報。 221 | protected MusicMeter currentMeter_; 222 | // 現在のシーケンス(横の遷移の単位)の小節数。 223 | protected Timing sequenceEndTiming_ = null; 224 | 225 | // 最新のJustタイミング。(タイミングちょうどになってから切り替わる) 226 | private Timing just_ = new Timing(-1, 0, 0); 227 | // 最新のNearタイミング。(最も近いタイミングが変わった地点、つまり2つのタイミングの中間で切り替わる) 228 | private Timing near_ = new Timing(-1, 0, 0); 229 | // 1フレーム前のJustタイミング。 230 | private Timing oldJust_ = new Timing(-1, 0, 0); 231 | // 1フレーム前のNearタイミング。 232 | private Timing oldNear_ = new Timing(-1, 0, 0); 233 | // 以前のシーケンスインデックス 234 | private int oldSequenceIndex_ = 0; 235 | // 今のフレームでjust_が変化したフラグ。 236 | private bool isJustChanged_ = false; 237 | // 今のフレームでnear_が変化したフラグ。 238 | private bool isNearChanged_ = false; 239 | // 今のフレームでjust_がループして戻ったフラグ。 240 | private bool isJustLooped_ = false; 241 | // 今のフレームでnear_がループして戻ったフラグ。 242 | private bool isNearLooped_ = false; 243 | // 今がunit内の前半かどうか。 true なら just_ == near_, false なら ++just == near。 244 | private bool isFormerHalf_; 245 | // Justのタイミングから次のタイミングまでを0~1で表した小数。 246 | private float fractionFromJust_; 247 | // 現在のループカウント。 248 | private int numRepeat_; 249 | 250 | #endregion 251 | 252 | 253 | #region functions 254 | 255 | void Awake() 256 | { 257 | Music.RegisterMusic(this); 258 | Ready(); 259 | } 260 | 261 | void Start() 262 | { 263 | #if UNITY_EDITOR 264 | UnityEditor.EditorApplication.pauseStateChanged += OnPlaymodeStateChanged; 265 | #endif 266 | if( PlayOnStart ) 267 | { 268 | Play(); 269 | } 270 | } 271 | 272 | #if UNITY_EDITOR 273 | void OnPlaymodeStateChanged(UnityEditor.PauseState state) 274 | { 275 | if( State == Music.PlayState.Playing || State == Music.PlayState.Suspended ) 276 | { 277 | if( state == UnityEditor.PauseState.Paused ) 278 | { 279 | Suspend(); 280 | } 281 | else 282 | { 283 | Resume(); 284 | } 285 | } 286 | } 287 | #endif 288 | 289 | void Update() 290 | { 291 | if( IsPlaying ) 292 | { 293 | if( CheckFinishPlaying() ) 294 | { 295 | Stop(); 296 | } 297 | else 298 | { 299 | UpdateTiming(); 300 | UpdateInternal(); 301 | } 302 | } 303 | } 304 | 305 | void Ready() 306 | { 307 | if( State != Music.PlayState.Invalid ) 308 | { 309 | return; 310 | } 311 | 312 | if( ReadyInternal() ) 313 | { 314 | State = Music.PlayState.Ready; 315 | ResetParams(); 316 | } 317 | } 318 | 319 | void ResetParams() 320 | { 321 | isJustChanged_ = false; 322 | isNearChanged_ = false; 323 | isJustLooped_ = false; 324 | isNearLooped_ = false; 325 | near_.Set(-1, 0, 0); 326 | just_.Set(-1, 0, 0); 327 | oldNear_.Set(near_); 328 | oldJust_.Set(just_); 329 | fractionFromJust_ = 0.0f; 330 | isFormerHalf_ = true; 331 | numRepeat_ = 0; 332 | sequenceEndTiming_ = null; 333 | currentMeter_ = null; 334 | 335 | ResetParamsInternal(); 336 | } 337 | 338 | void UpdateTiming() 339 | { 340 | oldNear_.Set(near_); 341 | oldJust_.Set(just_); 342 | isNearChanged_ = false; 343 | isJustChanged_ = false; 344 | isJustLooped_ = false; 345 | isNearLooped_ = false; 346 | 347 | UpdateTimingInternal(); 348 | CalcTimingAndFraction(ref just_, out fractionFromJust_); 349 | 350 | if( currentMeter_ != null ) 351 | { 352 | while( sequenceEndTiming_ != null && just_ >= sequenceEndTiming_ ) 353 | { 354 | just_.Decrement(currentMeter_); 355 | fractionFromJust_ = 1.0f; 356 | } 357 | 358 | isFormerHalf_ = fractionFromJust_ < 0.5f; 359 | 360 | near_.Set(just_); 361 | if( !isFormerHalf_ ) 362 | { 363 | near_.Increment(currentMeter_); 364 | } 365 | if( sequenceEndTiming_ != null && near_ >= sequenceEndTiming_ ) 366 | { 367 | near_.Reset(); 368 | } 369 | 370 | isJustChanged_ = (just_.Equals(oldJust_) == false); 371 | isNearChanged_ = (near_.Equals(oldNear_) == false); 372 | isJustLooped_ = isJustChanged_ && (just_ < oldJust_ || (oldJust_.Bar < 0 && just_.Bar >= 0)); 373 | isNearLooped_ = isNearChanged_ && (near_ < oldNear_ || (oldNear_.Bar < 0 && near_.Bar >= 0)); 374 | 375 | if( isJustLooped_ ) 376 | { 377 | if( oldSequenceIndex_ != SequenceIndex ) 378 | { 379 | OnHorizontalSequenceChanged(); 380 | } 381 | else if( SequenceIndex != -1 ) 382 | { 383 | OnRepeated(); 384 | } 385 | } 386 | } 387 | } 388 | 389 | public override string ToString() 390 | { 391 | return String.Format("{0}", just_.ToString()); 392 | } 393 | 394 | #endregion 395 | 396 | 397 | #region events 398 | 399 | protected virtual void OnRepeated() 400 | { 401 | ++numRepeat_; 402 | } 403 | 404 | protected virtual void OnHorizontalSequenceChanged() 405 | { 406 | oldSequenceIndex_ = SequenceIndex; 407 | numRepeat_ = 0; 408 | sequenceEndTiming_ = GetSequenceEndTiming(); 409 | } 410 | 411 | #endregion 412 | 413 | } 414 | -------------------------------------------------------------------------------- /MusicUnity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.Audio; 6 | 7 | public class MusicUnity : MusicBase 8 | { 9 | #region editor params 10 | 11 | [Range(0,1)] 12 | public float Volume = 1.0f; 13 | public AudioMixerGroup OutputMixerGroup; 14 | 15 | public MusicSection[] Sections = new MusicSection[1] { new MusicSection() }; 16 | public MusicMode[] Modes = new MusicMode[1] { new MusicMode() }; 17 | public MusicMode.TransitionParams ModeTransitionParam; 18 | 19 | [Serializable] 20 | public class SectionTransitionOverride 21 | { 22 | public int FromSectionIndex = -1; 23 | public int ToSectionIndex = -1; 24 | public Music.SyncType SyncType = Music.SyncType.Bar; 25 | public int SyncFactor = 1; 26 | public MusicSection.TransitionParams Transition; 27 | } 28 | public List SectionTransitionOverrides; 29 | 30 | [Serializable] 31 | public class ModeTransitionOverride 32 | { 33 | public int FromModeIndex = -1; 34 | public int ToModeIndex = -1; 35 | public MusicMode.TransitionParams Transition; 36 | } 37 | public List ModeTransitionOverrides; 38 | public int NumTracks = 1; 39 | 40 | #if UNITY_EDITOR 41 | public int SeekBar = -1; 42 | public int PreviewSectionIndex = 0; 43 | public int PreviewModeIndex = 0; 44 | #endif 45 | 46 | #endregion 47 | 48 | 49 | #region properties 50 | 51 | // states 52 | public enum ETransitionState 53 | { 54 | Invalid = 0, 55 | Intro, 56 | Ready, 57 | Synced, //再生予約完了後~再生開始前 58 | PreEntry, //再生開始〜EntryPointまで 59 | PostEntry, //EntryPoint〜フェード完了まで 60 | Outro, 61 | }; 62 | public ETransitionState TransitionState { get; private set; } = ETransitionState.Invalid; 63 | public enum EModeTransitionState 64 | { 65 | Invalid, 66 | Ready, 67 | Sync, //次のモードへのFading開始前 68 | Fading, //次のモードへフェードイン中 69 | }; 70 | public EModeTransitionState ModeTransitionState { get; private set; } = EModeTransitionState.Invalid; 71 | 72 | // section property 73 | public int SectionIndex { get { return sectionIndex_; } } 74 | public int NextSectionIndex { get { return nextSectionIndex_; } } 75 | public override int SequenceIndex { get { return sectionIndex_; } } 76 | public override string SequenceName { get { return Sections[sectionIndex_].Name; } } 77 | public MusicSection CurrentSection { get { return Sections[sectionIndex_]; } } 78 | public MusicSection NextSection { get { return Sections[nextSectionIndex_]; } } 79 | public MusicSection PrevSection { get { return Sections[prevSectionIndex_]; } } 80 | public MusicSection this[int index] 81 | { 82 | get 83 | { 84 | if( 0 <= index && index < Sections.Length ) 85 | { 86 | return Sections[index]; 87 | } 88 | else 89 | { 90 | Debug.LogWarning("Section index out of range! index = " + index + ", SectionCount = " + Sections.Length); 91 | return null; 92 | } 93 | } 94 | } 95 | 96 | // mode property 97 | public int ModeIndex { get { return modeIndex_; } } 98 | public int NextModeIndex { get { return nextModeIndex_; } } 99 | public MusicMode CurrentMode { get { return Modes[modeIndex_]; } } 100 | public MusicMode NextMode { get { return Modes[nextModeIndex_]; } } 101 | public MusicMode GetModeAt(int index) 102 | { 103 | if( 0 <= index && index < Modes.Length ) 104 | { 105 | return Modes[index]; 106 | } 107 | else 108 | { 109 | Debug.LogWarning("Mode index out of range! index = " + index + ", ModeCount = " + Modes.Length); 110 | return null; 111 | } 112 | } 113 | 114 | #endregion 115 | 116 | 117 | #region params 118 | 119 | // samples 120 | protected int sampleRate_ = 44100; 121 | protected int currentSample_; 122 | 123 | // sources 124 | private AudioSource[] musicSources_; 125 | private AudioSource[] transitionMusicSources_; 126 | 127 | // dspTime 128 | private double playedDSPTime_; 129 | private double syncScheduledDSPTime_ = 0.0; 130 | private double endScheduledDSPTime_ = 0.0; 131 | private double suspendedDSPTime_ = 0.0; 132 | 133 | // section 134 | private int sectionIndex_ = 0; 135 | private int nextSectionIndex_ = -1; 136 | private int prevSectionIndex_ = -1; 137 | 138 | // section transition fade 139 | public class Fade 140 | { 141 | public Fade(bool isFadeOut) 142 | { 143 | isFadeOut_ = isFadeOut; 144 | } 145 | 146 | public int FadeStartSample { get { return fadeStartSample_; } } 147 | public int FadeEndSample { get { return fadeStartSample_ + fadeLengthSample_; } } 148 | 149 | public float GetVolume(int currentSample) 150 | { 151 | if( isFadeOut_ ) 152 | { 153 | return 1.0f - Mathf.Clamp01((float)(currentSample - fadeStartSample_) / fadeLengthSample_); 154 | } 155 | else 156 | { 157 | return Mathf.Clamp01((float)(currentSample - fadeStartSample_) / fadeLengthSample_); 158 | } 159 | } 160 | 161 | public void SetFade(int start, int length) 162 | { 163 | fadeStartSample_ = start; 164 | fadeLengthSample_ = length; 165 | } 166 | public void SetFadeIn(int start, int length) 167 | { 168 | fadeStartSample_ = start; 169 | fadeLengthSample_ = length; 170 | isFadeOut_ = false; 171 | } 172 | public void SetFadeOut(int start, int length) 173 | { 174 | fadeStartSample_ = start; 175 | fadeLengthSample_ = length; 176 | isFadeOut_ = true; 177 | } 178 | public void OnEntryPoint(int newStart) 179 | { 180 | fadeStartSample_ = newStart; 181 | } 182 | 183 | bool isFadeOut_; 184 | int fadeStartSample_; 185 | int fadeLengthSample_; 186 | } 187 | private Fade transitionFadeIn_ = new Fade(false); 188 | private Fade transitionFadeOut_ = new Fade(true); 189 | private float transitionFadeInVolume_ = 1.0f; 190 | private float transitionFadeOutVolume_ = 1.0f; 191 | 192 | // section transition param 193 | [Serializable] 194 | public class SectionTransitionParam 195 | { 196 | public SectionTransitionParam(int targetIndex, int currentIndex, MusicUnity music) 197 | { 198 | SectionIndex = targetIndex; 199 | 200 | SectionTransitionOverride overrideParam = music.SectionTransitionOverrides.Find( 201 | (SectionTransitionOverride ov) => 202 | { 203 | return (ov.FromSectionIndex == currentIndex || ov.FromSectionIndex == -1) 204 | && (ov.ToSectionIndex == targetIndex || ov.ToSectionIndex == -1); 205 | }); 206 | 207 | if( overrideParam !=null ) 208 | { 209 | SyncType = overrideParam.SyncType; 210 | SyncFactor = overrideParam.SyncFactor; 211 | Transition = new MusicSection.TransitionParams(overrideParam.Transition); 212 | } 213 | else 214 | { 215 | SyncType = music.Sections[currentIndex].SyncType; 216 | SyncFactor = music.Sections[currentIndex].SyncFactor; 217 | Transition = new MusicSection.TransitionParams(music.Sections[targetIndex].Transition); 218 | } 219 | 220 | if( SyncType == Music.SyncType.ExitPoint ) 221 | { 222 | Transition.UseFadeOut = false; 223 | } 224 | } 225 | public SectionTransitionParam(int index, MusicUnity music, Music.SyncType syncType, int syncFactor = 1) 226 | { 227 | SectionIndex = index; 228 | SyncType = syncType; 229 | SyncFactor = syncFactor; 230 | Transition = new MusicSection.TransitionParams(music.Sections[index].Transition); 231 | if( syncType == Music.SyncType.ExitPoint ) 232 | { 233 | Transition.UseFadeOut = false; 234 | } 235 | } 236 | 237 | public int SectionIndex; 238 | public Music.SyncType SyncType = Music.SyncType.Bar; 239 | public int SyncFactor = 1; 240 | public MusicSection.TransitionParams Transition; 241 | } 242 | private SectionTransitionParam requenstedTransition_; 243 | private SectionTransitionParam executedTransition_; 244 | 245 | // mode 246 | private int modeIndex_ = 0; 247 | private int nextModeIndex_ = 0; 248 | private int requestedModeIndex_ = -1; 249 | 250 | // mode transition fade 251 | private Fade modeFade_ = new Fade(false); 252 | private List modeLayerVolumes_ = new List(); 253 | private List modeLayerBaseVolumes_ = new List(); 254 | private float modeVolume_ = 1.0f; 255 | private float modeBaseVolume_ = 1.0f; 256 | 257 | // 僅かに遅らせることでPlayとPlayScheduleのズレを回避 https://qiita.com/tatmos/items/4c78c127291a0c3b74ed 258 | private static double SCHEDULE_DELAY = 0.1; 259 | private static double ScheduleDSPTime { get { return AudioSettings.dspTime + SCHEDULE_DELAY; } } 260 | 261 | #endregion 262 | 263 | 264 | #region override functions 265 | 266 | // internal 267 | 268 | protected override bool ReadyInternal() 269 | { 270 | if( Validate() ) 271 | { 272 | InstantiateAudioSourceObjects(); 273 | ResetSectionClips(); 274 | ResetModeLayerVolumes(); 275 | UpdateVolumes(); 276 | return true; 277 | } 278 | return false; 279 | } 280 | 281 | protected override void SeekInternal(Timing seekTiming, int sequenceIndex = 0) 282 | { 283 | sectionIndex_ = sequenceIndex; 284 | int seekSample = CurrentSection.GetSampleFromTiming(seekTiming); 285 | for( int i = 0; i < NumTracks; ++i ) 286 | { 287 | if( i < CurrentSection.Clips.Length ) 288 | { 289 | musicSources_[i].clip = CurrentSection.Clips[i]; 290 | musicSources_[i].timeSamples = seekSample; 291 | } 292 | else 293 | { 294 | musicSources_[i].clip = null; 295 | } 296 | } 297 | } 298 | 299 | protected override bool PlayInternal() 300 | { 301 | #if UNITY_EDITOR 302 | if( PreviewSectionIndex != sectionIndex_ || SeekBar >= 0 ) 303 | { 304 | SeekInternal(new Timing(SeekBar), PreviewSectionIndex); 305 | } 306 | if( PreviewModeIndex != modeIndex_ ) 307 | { 308 | modeIndex_ = nextModeIndex_ = PreviewModeIndex; 309 | ResetModeLayerVolumes(); 310 | } 311 | #endif 312 | 313 | TransitionState = ETransitionState.Intro; 314 | ModeTransitionState = EModeTransitionState.Ready; 315 | UpdateVolumes(); 316 | 317 | playedDSPTime_ = MusicUnity.ScheduleDSPTime; 318 | for( int i = 0; i < NumTracks; ++i ) 319 | { 320 | if( musicSources_[i].clip != null ) 321 | { 322 | musicSources_[i].PlayScheduled(playedDSPTime_); 323 | } 324 | else 325 | { 326 | break; 327 | } 328 | } 329 | 330 | return true; 331 | } 332 | 333 | protected override bool SuspendInternal() 334 | { 335 | for( int i = 0; i < NumTracks; ++i ) 336 | { 337 | if( musicSources_[i].clip != null ) 338 | { 339 | musicSources_[i].Pause(); 340 | } 341 | if( transitionMusicSources_[i].clip != null ) 342 | { 343 | transitionMusicSources_[i].Pause(); 344 | } 345 | } 346 | 347 | suspendedDSPTime_ = AudioSettings.dspTime; 348 | return true; 349 | } 350 | 351 | protected override bool ResumeInternal() 352 | { 353 | double suspendedDuration = AudioSettings.dspTime - suspendedDSPTime_; 354 | if( TransitionState == ETransitionState.Synced && syncScheduledDSPTime_ > 0.0 ) 355 | { 356 | syncScheduledDSPTime_ += suspendedDuration; 357 | for( int i = 0; i < NumTracks; ++i ) 358 | { 359 | if( transitionMusicSources_[i].clip != null ) 360 | { 361 | transitionMusicSources_[i].SetScheduledStartTime(syncScheduledDSPTime_); 362 | } 363 | } 364 | } 365 | if( endScheduledDSPTime_ > 0.0 ) 366 | { 367 | endScheduledDSPTime_ += suspendedDuration; 368 | for( int i = 0; i < NumTracks; ++i ) 369 | { 370 | if( musicSources_[i].clip != null ) 371 | { 372 | musicSources_[i].SetScheduledEndTime(endScheduledDSPTime_); 373 | } 374 | } 375 | } 376 | 377 | for( int i = 0; i < NumTracks; ++i ) 378 | { 379 | if( musicSources_[i].clip != null ) 380 | { 381 | musicSources_[i].UnPause(); 382 | } 383 | if( transitionMusicSources_[i].clip != null ) 384 | { 385 | transitionMusicSources_[i].UnPause(); 386 | } 387 | } 388 | 389 | if( requenstedTransition_ != null ) 390 | { 391 | SetNextSection(requenstedTransition_); 392 | } 393 | if( requestedModeIndex_ >= 0 ) 394 | { 395 | SetMode(requestedModeIndex_); 396 | } 397 | return true; 398 | } 399 | 400 | protected override bool StopInternal() 401 | { 402 | for( int i = 0; i < NumTracks; ++i ) 403 | { 404 | if( musicSources_[i].clip != null ) 405 | { 406 | musicSources_[i].Stop(); 407 | musicSources_[i].clip = null; 408 | } 409 | if( transitionMusicSources_[i].clip != null ) 410 | { 411 | transitionMusicSources_[i].Stop(); 412 | transitionMusicSources_[i].clip = null; 413 | } 414 | } 415 | 416 | ResetSectionClips(); 417 | ResetModeLayerVolumes(); 418 | 419 | return true; 420 | } 421 | 422 | protected override void ResetParamsInternal() 423 | { 424 | currentSample_ = 0; 425 | sectionIndex_ = 0; 426 | nextSectionIndex_ = -1; 427 | prevSectionIndex_ = -1; 428 | requenstedTransition_ = null; 429 | modeIndex_ = 0; 430 | nextModeIndex_ = 0; 431 | requestedModeIndex_ = -1; 432 | endScheduledDSPTime_ = 0.0; 433 | syncScheduledDSPTime_ = 0.0; 434 | ModeTransitionState = EModeTransitionState.Invalid; 435 | TransitionState = ETransitionState.Invalid; 436 | 437 | ResetSectionClips(); 438 | ResetModeLayerVolumes(); 439 | } 440 | 441 | // timing 442 | 443 | protected override void UpdateTimingInternal() 444 | { 445 | if( musicSources_[0].isPlaying ) 446 | { 447 | currentSample_ = musicSources_[0].timeSamples; 448 | currentMeter_ = GetMeterFromSample(currentSample_); 449 | } 450 | 451 | UpdateHorizontalState(); 452 | UpdateVerticalState(); 453 | } 454 | 455 | protected override void CalcTimingAndFraction(ref Timing just, out float fraction) 456 | { 457 | if( currentMeter_ == null ) 458 | { 459 | just.Set(-1, 0, 0); 460 | fraction = 0; 461 | } 462 | else 463 | { 464 | MusicMeterBySample meter = currentMeter_ as MusicMeterBySample; 465 | just.Set(meter.GetTimingFromSample(currentSample_)); 466 | fraction = (float)(currentSample_ - meter.GetSampleFromTiming(just)) / meter.SamplesPerUnit; 467 | } 468 | } 469 | 470 | protected override Timing GetSequenceEndTiming() 471 | { 472 | return CurrentSection.ExitPointTiming; 473 | } 474 | 475 | // update 476 | 477 | protected override bool CheckFinishPlaying() 478 | { 479 | if( nextSectionIndex_ == -1 ) // will end 480 | { 481 | bool isSomeSourcePlaying = false; 482 | foreach( AudioSource source in musicSources_ ) 483 | { 484 | if( source.isPlaying ) 485 | { 486 | isSomeSourcePlaying = true; 487 | break; 488 | } 489 | } 490 | 491 | if( isSomeSourcePlaying == false ) 492 | { 493 | return true; 494 | } 495 | } 496 | return false; 497 | } 498 | 499 | protected override void UpdateInternal() 500 | { 501 | #if UNITY_EDITOR 502 | UpdateGameObjectNames(); 503 | #endif 504 | 505 | UpdateQuantizedAudio(); 506 | } 507 | 508 | protected void UpdateHorizontalState() 509 | { 510 | int transitionCurrentSample; 511 | 512 | switch( TransitionState ) 513 | { 514 | case ETransitionState.Intro: 515 | if( AudioSettings.dspTime >= playedDSPTime_ && currentSample_ > CurrentSection.EntryPointSample ) 516 | { 517 | TransitionState = ETransitionState.Ready; 518 | OnTransitionReady(); 519 | } 520 | break; 521 | case ETransitionState.Ready: 522 | if( nextSectionIndex_ == -1 && currentSample_ > CurrentSection.ExitPointSample ) 523 | { 524 | TransitionState = ETransitionState.Outro; 525 | } 526 | break; 527 | case ETransitionState.Outro: 528 | break; 529 | case ETransitionState.Synced: 530 | transitionCurrentSample = transitionMusicSources_[0].timeSamples; 531 | if( nextSectionIndex_ == sectionIndex_ ) 532 | { 533 | // ループ処理 534 | if( transitionCurrentSample > CurrentSection.LoopStartSample ) 535 | { 536 | OnEntryPoint(); 537 | TransitionState = ETransitionState.Ready; 538 | OnTransitionReady(); 539 | } 540 | } 541 | else 542 | { 543 | // 遷移処理 544 | if( transitionCurrentSample > 0 ) 545 | { 546 | TransitionState = ETransitionState.PreEntry; 547 | if( NextSection.EntryPointSample == 0 ) 548 | { 549 | OnEntryPoint(); 550 | TransitionState = ETransitionState.PostEntry; 551 | } 552 | } 553 | } 554 | break; 555 | case ETransitionState.PreEntry: 556 | transitionCurrentSample = transitionMusicSources_[0].timeSamples; 557 | if( executedTransition_.Transition.UseFadeIn ) 558 | { 559 | transitionFadeInVolume_ = transitionFadeIn_.GetVolume(transitionCurrentSample); 560 | } 561 | if( executedTransition_.Transition.UseFadeOut ) 562 | { 563 | transitionFadeOutVolume_ = transitionFadeOut_.GetVolume(transitionCurrentSample); 564 | } 565 | UpdateVolumes(); 566 | if( transitionCurrentSample > NextSection.EntryPointSample ) 567 | { 568 | OnEntryPoint(); 569 | TransitionState = ETransitionState.PostEntry; 570 | } 571 | break; 572 | case ETransitionState.PostEntry: 573 | transitionCurrentSample = musicSources_[0].timeSamples; 574 | bool isFadeInFinished = false; 575 | if( executedTransition_.Transition.UseFadeIn ) 576 | { 577 | transitionFadeInVolume_ = transitionFadeIn_.GetVolume(transitionCurrentSample); 578 | isFadeInFinished = transitionFadeInVolume_ >= 1.0f; 579 | } 580 | else 581 | { 582 | isFadeInFinished = true; 583 | } 584 | bool isFadeOutFinished = false; 585 | if( executedTransition_.Transition.UseFadeOut ) 586 | { 587 | transitionFadeOutVolume_ = transitionFadeOut_.GetVolume(transitionCurrentSample); 588 | if( transitionFadeOutVolume_ <= 0 ) 589 | { 590 | foreach( AudioSource prevSectionSource in transitionMusicSources_ ) 591 | { 592 | if( prevSectionSource.clip != null ) 593 | { 594 | prevSectionSource.Stop(); 595 | } 596 | else 597 | { 598 | break; 599 | } 600 | } 601 | isFadeOutFinished = true; 602 | } 603 | } 604 | else 605 | { 606 | if( transitionMusicSources_[0].isPlaying == false ) 607 | { 608 | isFadeOutFinished = true; 609 | } 610 | } 611 | UpdateVolumes(); 612 | 613 | if( isFadeOutFinished && isFadeInFinished ) 614 | { 615 | TransitionState = ETransitionState.Ready; 616 | OnTransitionReady(); 617 | } 618 | break; 619 | } 620 | } 621 | 622 | protected void UpdateVerticalState() 623 | { 624 | int currentSample = currentSample_; 625 | 626 | if( ModeTransitionState == EModeTransitionState.Sync ) 627 | { 628 | if( modeFade_.FadeStartSample <= currentSample ) 629 | { 630 | ModeTransitionState = EModeTransitionState.Fading; 631 | } 632 | } 633 | 634 | if( ModeTransitionState == EModeTransitionState.Fading ) 635 | { 636 | float fade = modeFade_.GetVolume(currentSample); 637 | modeVolume_ = modeBaseVolume_ + (NextMode.TotalVolume - modeBaseVolume_) * fade; 638 | for( int i = 0; i < NumTracks; ++i ) 639 | { 640 | modeLayerVolumes_[i] = modeLayerBaseVolumes_[i] + (NextMode.LayerVolumes[i] - modeLayerBaseVolumes_[i]) * fade; 641 | } 642 | UpdateVolumes(); 643 | if( fade >= 1.0f ) 644 | { 645 | ModeTransitionState = EModeTransitionState.Ready; 646 | modeIndex_ = nextModeIndex_; 647 | } 648 | } 649 | } 650 | 651 | // interactive music 652 | 653 | public override void SetHorizontalSequence(string name) 654 | { 655 | for( int i = 0; i < Sections.Length; ++i ) 656 | { 657 | if( Sections[i].Name == name ) 658 | { 659 | SetHorizontalSequenceByIndex(i); 660 | return; 661 | } 662 | } 663 | print("Couldn't find section name = " + name); 664 | } 665 | 666 | public override void SetHorizontalSequenceByIndex(int index) 667 | { 668 | if( index < 0 || Sections.Length <= index 669 | // インデックス範囲外 670 | || index == sectionIndex_ 671 | // 今のセクションと同じ 672 | || index == nextSectionIndex_ 673 | // 既に遷移確定済み 674 | || (requenstedTransition_ != null && requenstedTransition_.SectionIndex == index) ) 675 | // 既にリクエスト済み 676 | { 677 | return; 678 | } 679 | 680 | // 再生中以外は設定だけ 681 | switch( State ) 682 | { 683 | case Music.PlayState.Invalid: 684 | return; 685 | case Music.PlayState.Ready: 686 | case Music.PlayState.Finished: 687 | sectionIndex_ = index; 688 | ResetSectionClips(); 689 | return; 690 | case Music.PlayState.Suspended: 691 | requenstedTransition_ = new SectionTransitionParam(index, sectionIndex_, this); 692 | return; 693 | case Music.PlayState.Playing: 694 | break; 695 | } 696 | 697 | // 今と同じセクションが指定されたら 698 | if( index == sectionIndex_ ) 699 | { 700 | // 予約中のはキャンセル 701 | requenstedTransition_ = null; 702 | // 遷移中のはキャンセルできるならキャンセル 703 | if( TransitionState == ETransitionState.Synced ) 704 | { 705 | CancelSyncedTransition(); 706 | OnTransitionReady(); 707 | } 708 | else if( TransitionState == ETransitionState.PreEntry ) 709 | { 710 | requenstedTransition_ = new SectionTransitionParam(index, sectionIndex_, this); 711 | } 712 | return; 713 | } 714 | 715 | switch( TransitionState ) 716 | { 717 | case ETransitionState.Invalid: 718 | case ETransitionState.Outro: 719 | return; 720 | case ETransitionState.Intro: 721 | case ETransitionState.PreEntry: 722 | case ETransitionState.PostEntry: 723 | requenstedTransition_ = new SectionTransitionParam(index, sectionIndex_, this); 724 | return; 725 | default: 726 | //case ETransitionState.Ready: 727 | //case ETransitionState.Synced: 728 | break; 729 | } 730 | 731 | SetNextSection(new SectionTransitionParam(index, sectionIndex_, this)); 732 | } 733 | 734 | public override void SetVerticalMix(string name) 735 | { 736 | for( int i = 0; i < Modes.Length; ++i ) 737 | { 738 | if( Modes[i].Name == name ) 739 | { 740 | SetVerticalMixByIndex(i); 741 | return; 742 | } 743 | } 744 | print("Couldn't find mode name = " + name); 745 | } 746 | 747 | public override void SetVerticalMixByIndex(int index) 748 | { 749 | if( index < 0 || Modes.Length <= index 750 | // インデックス範囲外 751 | || index == nextModeIndex_ 752 | // 既に遷移確定済み 753 | || (requestedModeIndex_ >= 0 && requestedModeIndex_ == index) ) 754 | // 既にリクエスト済み 755 | { 756 | return; 757 | } 758 | 759 | // 再生中以外は設定だけ 760 | switch( State ) 761 | { 762 | case Music.PlayState.Invalid: 763 | return; 764 | case Music.PlayState.Ready: 765 | case Music.PlayState.Finished: 766 | modeIndex_ = index; 767 | nextModeIndex_ = index; 768 | ResetModeLayerVolumes(); 769 | return; 770 | case Music.PlayState.Suspended: 771 | requestedModeIndex_ = index; 772 | return; 773 | case Music.PlayState.Playing: 774 | break; 775 | } 776 | 777 | // 今と同じモードが指定されたら 778 | if( index == modeIndex_ && ModeTransitionState != EModeTransitionState.Fading ) 779 | { 780 | // 予約中のはキャンセル 781 | requestedModeIndex_ = -1; 782 | // 遷移前のもキャンセル 783 | if( ModeTransitionState == EModeTransitionState.Sync ) 784 | { 785 | ModeTransitionState = EModeTransitionState.Ready; 786 | nextModeIndex_ = modeIndex_; 787 | } 788 | return; 789 | } 790 | 791 | SetMode(index); 792 | } 793 | 794 | #endregion 795 | 796 | class QuantizedAudio 797 | { 798 | public AudioSource source_; 799 | public double syncedDspTime_; 800 | public Action action_; 801 | } 802 | List quantizedAudioList_ = new List(); 803 | 804 | public bool QuantizePlay(AudioSource audio, Action action = null, Music.SyncType syncType = Music.SyncType.Unit, int syncFactor = 1) 805 | { 806 | int syncPointSample = 0; 807 | var currentSample = musicSources_[0].timeSamples; 808 | if( FindSyncPoint(syncType, syncFactor, currentSample, (int)(currentMeter_.SecPerUnit * sampleRate_) / 4, out syncPointSample) ) 809 | { 810 | var quantizedAudio = new QuantizedAudio(); 811 | quantizedAudio.action_ = action; 812 | quantizedAudio.source_ = audio; 813 | quantizedAudio.syncedDspTime_ = AudioSettings.dspTime + (double)(syncPointSample - currentSample) / sampleRate_; 814 | quantizedAudio.source_.PlayScheduled(quantizedAudio.syncedDspTime_); 815 | quantizedAudioList_.Add(quantizedAudio); 816 | return true; 817 | } 818 | else 819 | { 820 | return false; 821 | } 822 | } 823 | 824 | public bool IsPlayScheduled(AudioSource audio) 825 | { 826 | if( quantizedAudioList_.Find(q => q.source_ == audio) != null ) 827 | { 828 | return true; 829 | } 830 | return false; 831 | } 832 | 833 | private void UpdateQuantizedAudio() 834 | { 835 | foreach( var quantizedAudio in quantizedAudioList_.FindAll(a => a.syncedDspTime_ <= AudioSettings.dspTime) ) 836 | { 837 | if( quantizedAudio.syncedDspTime_ <= AudioSettings.dspTime ) 838 | { 839 | if( quantizedAudio.action_ != null ) 840 | { 841 | quantizedAudio.action_.Invoke(); 842 | } 843 | } 844 | } 845 | 846 | quantizedAudioList_.RemoveAll(a => a.syncedDspTime_ <= AudioSettings.dspTime); 847 | } 848 | 849 | public void SetVolume(float volume) 850 | { 851 | Volume = volume; 852 | UpdateVolumes(); 853 | } 854 | 855 | #region private functions 856 | 857 | // initialize 858 | 859 | void OnValidate() 860 | { 861 | Validate(); 862 | } 863 | 864 | bool Validate() 865 | { 866 | if( Sections.Length == 0 ) 867 | { 868 | return false; 869 | } 870 | if( NumTracks < 1 ) 871 | { 872 | return false; 873 | } 874 | if( Sections[0].Clips.Length == 0 ) 875 | { 876 | return false; 877 | } 878 | 879 | sampleRate_ = Sections[0].Clips[0].frequency; 880 | 881 | foreach( MusicSection section in Sections ) 882 | { 883 | section.Validate(sampleRate_); 884 | if( section.IsValid == false ) 885 | { 886 | return false; 887 | } 888 | } 889 | 890 | if( Modes.Length == 0 ) 891 | { 892 | Modes = new MusicMode[1] { new MusicMode() }; 893 | } 894 | foreach( MusicMode mode in Modes ) 895 | { 896 | for( int i = mode.LayerVolumes.Count; i < NumTracks; ++i ) 897 | { 898 | mode.LayerVolumes.Add(1.0f); 899 | } 900 | for( int i = NumTracks; i < mode.LayerVolumes.Count; ++i ) 901 | { 902 | mode.LayerVolumes.RemoveAt(i); 903 | } 904 | } 905 | 906 | return true; 907 | } 908 | 909 | void InstantiateAudioSourceObjects() 910 | { 911 | // AudioSourceを最大トラック数*2(遷移時に重なる分)まで生成。 912 | musicSources_ = new AudioSource[NumTracks]; 913 | transitionMusicSources_ = new AudioSource[NumTracks]; 914 | for( int i = 0; i < NumTracks; ++i ) 915 | { 916 | musicSources_[i] = new GameObject("audioSource_" + i.ToString(), typeof(AudioSource)).GetComponent(); 917 | musicSources_[i].transform.parent = this.transform; 918 | musicSources_[i].outputAudioMixerGroup = OutputMixerGroup; 919 | musicSources_[i].playOnAwake = false; 920 | musicSources_[i].loop = false; 921 | } 922 | for( int i = 0; i < NumTracks; ++i ) 923 | { 924 | transitionMusicSources_[i] = new GameObject("audioSource_t_" + i.ToString(), typeof(AudioSource)).GetComponent(); 925 | transitionMusicSources_[i].transform.parent = this.transform; 926 | transitionMusicSources_[i].outputAudioMixerGroup = OutputMixerGroup; 927 | transitionMusicSources_[i].playOnAwake = false; 928 | musicSources_[i].loop = false; 929 | } 930 | } 931 | 932 | void ResetSectionClips() 933 | { 934 | for( int i = 0; i < NumTracks; ++i ) 935 | { 936 | if( i < CurrentSection.Clips.Length ) 937 | { 938 | musicSources_[i].clip = CurrentSection.Clips[i]; 939 | musicSources_[i].timeSamples = 0; 940 | } 941 | else 942 | { 943 | musicSources_[i].clip = null; 944 | } 945 | transitionMusicSources_[i].clip = null; 946 | } 947 | } 948 | 949 | void ResetModeLayerVolumes() 950 | { 951 | modeLayerVolumes_.Clear(); 952 | modeLayerBaseVolumes_.Clear(); 953 | for( int i = 0; i < NumTracks; ++i ) 954 | { 955 | modeLayerVolumes_.Add(CurrentMode.LayerVolumes[i]); 956 | modeLayerBaseVolumes_.Add(CurrentMode.LayerVolumes[i]); 957 | } 958 | modeVolume_ = CurrentMode.TotalVolume; 959 | } 960 | 961 | // update 962 | 963 | void UpdateVolumes() 964 | { 965 | float mainVolume = Volume * modeVolume_; 966 | float transitionVolume = Volume * modeVolume_; 967 | switch( TransitionState ) 968 | { 969 | case ETransitionState.Synced: 970 | transitionVolume *= transitionFadeInVolume_; 971 | break; 972 | case ETransitionState.PreEntry: 973 | transitionVolume *= transitionFadeInVolume_; 974 | mainVolume *= transitionFadeOutVolume_; 975 | break; 976 | case ETransitionState.PostEntry: 977 | mainVolume *= transitionFadeInVolume_; 978 | transitionVolume *= transitionFadeOutVolume_; 979 | break; 980 | default: 981 | break; 982 | } 983 | 984 | for( int i = 0; i < musicSources_.Length; ++i ) 985 | { 986 | AudioSource source = musicSources_[i]; 987 | if( source.clip != null ) 988 | { 989 | source.volume = mainVolume * modeLayerVolumes_[i]; 990 | } 991 | else 992 | { 993 | break; 994 | } 995 | } 996 | for( int i = 0; i < transitionMusicSources_.Length; ++i ) 997 | { 998 | AudioSource transitionSource = transitionMusicSources_[i]; 999 | if( transitionSource.clip != null ) 1000 | { 1001 | transitionSource.volume = transitionVolume * modeLayerVolumes_[i]; 1002 | } 1003 | else 1004 | { 1005 | break; 1006 | } 1007 | } 1008 | } 1009 | 1010 | void UpdateGameObjectNames() 1011 | { 1012 | for( int i = 0; i < musicSources_.Length; ++i ) 1013 | { 1014 | AudioSource source = musicSources_[i]; 1015 | if( source.clip != null ) 1016 | { 1017 | source.gameObject.name = String.Format("{0}[{1}] Playing ({2:F2})", CurrentSection.Name, i, modeLayerVolumes_[i]); 1018 | } 1019 | else 1020 | { 1021 | source.gameObject.name = "_no track_"; 1022 | } 1023 | } 1024 | 1025 | MusicSection transitionSection = null; 1026 | switch(TransitionState ) 1027 | { 1028 | case ETransitionState.Synced: 1029 | case ETransitionState.PreEntry: 1030 | transitionSection = NextSection; 1031 | break; 1032 | case ETransitionState.PostEntry: 1033 | transitionSection = PrevSection; 1034 | break; 1035 | } 1036 | 1037 | for( int i = 0; i < transitionMusicSources_.Length; ++i ) 1038 | { 1039 | AudioSource transitionSource = transitionMusicSources_[i]; 1040 | if( transitionSection != null && transitionSource.clip != null ) 1041 | { 1042 | transitionSource.gameObject.name = String.Format("{0}[{1}] {2}", transitionSection.Name, i, TransitionState); 1043 | } 1044 | else 1045 | { 1046 | transitionSource.gameObject.name = "_no track_"; 1047 | } 1048 | } 1049 | } 1050 | 1051 | // transition event 1052 | 1053 | void OnEntryPoint() 1054 | { 1055 | // ソースの切り替え 1056 | AudioSource[] oldTracks = musicSources_; 1057 | musicSources_ = transitionMusicSources_; 1058 | transitionMusicSources_ = oldTracks; 1059 | currentSample_ = musicSources_[0].timeSamples; 1060 | // インデックス切り替え 1061 | prevSectionIndex_ = sectionIndex_; 1062 | sectionIndex_ = nextSectionIndex_; 1063 | switch( CurrentSection.TransitionType ) 1064 | { 1065 | case MusicSection.AutoTransitionType.Loop: 1066 | nextSectionIndex_ = sectionIndex_; 1067 | break; 1068 | case MusicSection.AutoTransitionType.Transition: 1069 | nextSectionIndex_ = CurrentSection.TransitionDestinationIndex; 1070 | break; 1071 | case MusicSection.AutoTransitionType.End: 1072 | nextSectionIndex_ = -1; 1073 | break; 1074 | } 1075 | 1076 | if( requestedModeIndex_ >= 0 ) 1077 | { 1078 | SetMode(requestedModeIndex_); 1079 | } 1080 | 1081 | if( ModeTransitionState == EModeTransitionState.Fading ) 1082 | { 1083 | modeFade_.OnEntryPoint(musicSources_[0].timeSamples + modeFade_.FadeStartSample - transitionMusicSources_[0].timeSamples); 1084 | } 1085 | } 1086 | 1087 | void OnTransitionReady() 1088 | { 1089 | executedTransition_ = null; 1090 | if( requenstedTransition_ != null ) 1091 | { 1092 | SetNextSection(requenstedTransition_); 1093 | } 1094 | else 1095 | { 1096 | switch( CurrentSection.TransitionType ) 1097 | { 1098 | case MusicSection.AutoTransitionType.Loop: 1099 | SetNextLoop(); 1100 | break; 1101 | case MusicSection.AutoTransitionType.Transition: 1102 | SetNextSection(new SectionTransitionParam(CurrentSection.TransitionDestinationIndex, this, Music.SyncType.ExitPoint)); 1103 | break; 1104 | case MusicSection.AutoTransitionType.End: 1105 | nextSectionIndex_ = -1; 1106 | endScheduledDSPTime_ = 0.0; 1107 | break; 1108 | } 1109 | } 1110 | } 1111 | 1112 | // sync 1113 | 1114 | MusicMeterBySample GetMeterFromSample(int sample) 1115 | { 1116 | MusicMeterBySample res = null; 1117 | foreach( MusicMeterBySample meter in CurrentSection.Meters ) 1118 | { 1119 | if( sample < meter.StartSamples ) 1120 | { 1121 | return res; 1122 | } 1123 | res = meter; 1124 | } 1125 | return res; 1126 | } 1127 | 1128 | MusicMeterBySample GetMeterFromTiming(Timing timing) 1129 | { 1130 | MusicMeterBySample res = null; 1131 | foreach( MusicMeterBySample meter in CurrentSection.Meters ) 1132 | { 1133 | if( timing.Bar < meter.StartBar ) 1134 | { 1135 | return res; 1136 | } 1137 | res = meter; 1138 | } 1139 | return res; 1140 | } 1141 | 1142 | bool FindSyncPoint(Music.SyncType syncType, int syncFactor, int currentSample, int entryPointSample, out int syncPointSample) 1143 | { 1144 | syncPointSample = currentSample; 1145 | 1146 | MusicMeterBySample currentMeter = GetMeterFromSample(currentSample); 1147 | Timing currentTiming = currentMeter != null ? currentMeter.GetTimingFromSample(currentSample) : new Timing(); 1148 | Timing syncPointCandidateTiming = new Timing(currentTiming); 1149 | 1150 | switch( syncType ) 1151 | { 1152 | case Music.SyncType.Immediate: 1153 | syncPointSample = currentSample + entryPointSample; 1154 | return true; 1155 | case Music.SyncType.ExitPoint: 1156 | syncPointSample = CurrentSection.ExitPointSample; 1157 | if( syncPointSample <= currentSample + entryPointSample ) 1158 | { 1159 | return false; 1160 | } 1161 | return true; 1162 | case Music.SyncType.Bar: 1163 | syncPointCandidateTiming.Set(currentTiming.Bar - (currentTiming.Bar - currentMeter.StartBar) % syncFactor + syncFactor, 0, 0); 1164 | syncPointSample = CurrentSection.GetSampleFromTiming(syncPointCandidateTiming); 1165 | while( syncPointSample <= currentSample + entryPointSample ) 1166 | { 1167 | syncPointCandidateTiming.Add(syncFactor); 1168 | syncPointSample = CurrentSection.GetSampleFromTiming(syncPointCandidateTiming); 1169 | } 1170 | break; 1171 | case Music.SyncType.Beat: 1172 | syncPointCandidateTiming.Set(currentTiming.Bar, currentTiming.Beat - (currentTiming.Beat % syncFactor) + syncFactor, 0); 1173 | syncPointCandidateTiming.Fix(currentMeter); 1174 | syncPointSample = CurrentSection.GetSampleFromTiming(syncPointCandidateTiming); 1175 | while( syncPointSample <= currentSample + entryPointSample ) 1176 | { 1177 | syncPointCandidateTiming.Add(0, syncFactor, 0, currentMeter); 1178 | syncPointCandidateTiming.Fix(currentMeter); 1179 | currentMeter = GetMeterFromTiming(syncPointCandidateTiming); 1180 | syncPointSample = CurrentSection.GetSampleFromTiming(syncPointCandidateTiming); 1181 | } 1182 | break; 1183 | case Music.SyncType.Unit: 1184 | syncPointCandidateTiming.Set(currentTiming.Bar, currentTiming.Beat, currentTiming.Unit - (currentTiming.Unit % syncFactor) + syncFactor); 1185 | syncPointCandidateTiming.Fix(currentMeter); 1186 | syncPointSample = CurrentSection.GetSampleFromTiming(syncPointCandidateTiming); 1187 | while( syncPointSample <= currentSample + entryPointSample ) 1188 | { 1189 | syncPointCandidateTiming.Add(0, 0, syncFactor, currentMeter); 1190 | syncPointCandidateTiming.Fix(currentMeter); 1191 | currentMeter = GetMeterFromTiming(syncPointCandidateTiming); 1192 | syncPointSample = CurrentSection.GetSampleFromTiming(syncPointCandidateTiming); 1193 | } 1194 | break; 1195 | case Music.SyncType.Marker: 1196 | if( 0 <= syncFactor && syncFactor < CurrentSection.Markers.Length && CurrentSection.Markers[syncFactor].Timings.Length > 0 ) 1197 | { 1198 | MusicSection.MusicMarker marker = CurrentSection.Markers[syncFactor]; 1199 | int markerIndex = 0; 1200 | syncPointCandidateTiming.Set(marker.Timings[markerIndex]); 1201 | 1202 | syncPointSample = CurrentSection.GetSampleFromTiming(syncPointCandidateTiming); 1203 | while( syncPointSample <= currentSample + entryPointSample ) 1204 | { 1205 | ++markerIndex; 1206 | if( marker.Timings.Length <= markerIndex ) 1207 | { 1208 | return false; 1209 | } 1210 | syncPointCandidateTiming.Set(marker.Timings[markerIndex]); 1211 | syncPointSample = CurrentSection.GetSampleFromTiming(syncPointCandidateTiming); 1212 | } 1213 | } 1214 | else 1215 | { 1216 | print(String.Format("Failed to SetNextSection. {0} section doesn't have Marker[{1}].", CurrentSection.Name, syncFactor)); 1217 | return false; 1218 | } 1219 | break; 1220 | } 1221 | if( syncPointCandidateTiming > CurrentSection.ExitPointTiming ) 1222 | { 1223 | return false; 1224 | } 1225 | return true; 1226 | } 1227 | 1228 | // transition 1229 | 1230 | void SetNextLoop() 1231 | { 1232 | syncScheduledDSPTime_ = AudioSettings.dspTime + (double)(CurrentSection.LoopEndSample - currentSample_) / sampleRate_; 1233 | endScheduledDSPTime_ = syncScheduledDSPTime_; 1234 | for( int i = 0; i < NumTracks; ++i ) 1235 | { 1236 | if( i < CurrentSection.Clips.Length ) 1237 | { 1238 | // ループ波形予約 1239 | transitionMusicSources_[i].clip = CurrentSection.Clips[i]; 1240 | transitionMusicSources_[i].timeSamples = CurrentSection.LoopStartSample; 1241 | transitionMusicSources_[i].PlayScheduled(syncScheduledDSPTime_); 1242 | musicSources_[i].SetScheduledEndTime(endScheduledDSPTime_); 1243 | } 1244 | else 1245 | { 1246 | musicSources_[i].clip = null; 1247 | transitionMusicSources_[i].clip = null; 1248 | } 1249 | } 1250 | 1251 | UpdateVolumes(); 1252 | 1253 | TransitionState = ETransitionState.Synced; 1254 | nextSectionIndex_ = sectionIndex_; 1255 | } 1256 | 1257 | void SetNextSection(SectionTransitionParam param) 1258 | { 1259 | // 遷移タイミング計算 1260 | MusicSection requestedSection = Sections[param.SectionIndex]; 1261 | int entryPointSample = requestedSection.EntryPointSample; 1262 | int currentSample = currentSample_; 1263 | int syncPointSample = 0; 1264 | 1265 | requenstedTransition_ = null; 1266 | if( FindSyncPoint(param.SyncType, param.SyncFactor, currentSample, entryPointSample, out syncPointSample) == false ) 1267 | { 1268 | requenstedTransition_ = param; 1269 | return; 1270 | } 1271 | 1272 | // 遷移状態によって前のをキャンセルしたり 1273 | switch( TransitionState ) 1274 | { 1275 | case ETransitionState.Ready: 1276 | // 準備OK 1277 | break; 1278 | case ETransitionState.Synced: 1279 | // 前のをキャンセル 1280 | CancelSyncedTransition(); 1281 | break; 1282 | case ETransitionState.Intro: 1283 | case ETransitionState.PreEntry: 1284 | case ETransitionState.PostEntry: 1285 | case ETransitionState.Outro: 1286 | // 遷移できないはずなんですが…… 1287 | print("invalid transition state " + TransitionState); 1288 | return; 1289 | } 1290 | 1291 | // 波形終了予約 1292 | endScheduledDSPTime_ = 0.0; 1293 | if( param.Transition.UseFadeOut == false ) 1294 | { 1295 | if( param.SyncType == Music.SyncType.ExitPoint ) 1296 | { 1297 | endScheduledDSPTime_ = AudioSettings.dspTime + (double)(musicSources_[0].clip.samples - currentSample) / sampleRate_; ; 1298 | } 1299 | else 1300 | { 1301 | endScheduledDSPTime_ = AudioSettings.dspTime + (double)(syncPointSample - currentSample) / sampleRate_; 1302 | } 1303 | } 1304 | 1305 | // 遷移波形予約 1306 | syncScheduledDSPTime_ = AudioSettings.dspTime + (double)(syncPointSample - currentSample - entryPointSample) / sampleRate_; 1307 | for( int i = 0; i < NumTracks; ++i ) 1308 | { 1309 | if( i < requestedSection.Clips.Length ) 1310 | { 1311 | transitionMusicSources_[i].clip = requestedSection.Clips[i]; 1312 | transitionMusicSources_[i].timeSamples = 0; 1313 | transitionMusicSources_[i].PlayScheduled(syncScheduledDSPTime_); 1314 | } 1315 | else 1316 | { 1317 | transitionMusicSources_[i].clip = null; 1318 | } 1319 | 1320 | if( i < CurrentSection.Clips.Length && endScheduledDSPTime_ > 0 ) 1321 | { 1322 | musicSources_[i].SetScheduledEndTime(endScheduledDSPTime_); 1323 | } 1324 | } 1325 | 1326 | // 状態遷移 1327 | TransitionState = ETransitionState.Synced; 1328 | nextSectionIndex_ = param.SectionIndex; 1329 | requenstedTransition_ = null; 1330 | executedTransition_ = param; 1331 | 1332 | // フェード時間計算 1333 | transitionFadeOutVolume_ = 1.0f; 1334 | transitionFadeInVolume_ = 1.0f; 1335 | if( param.Transition.UseFadeOut ) 1336 | { 1337 | transitionFadeOut_.SetFade( 1338 | (int)(param.Transition.FadeOutOffset * sampleRate_) + NextSection.EntryPointSample, 1339 | (int)(param.Transition.FadeOutTime * sampleRate_)); 1340 | } 1341 | if( param.Transition.UseFadeIn ) 1342 | { 1343 | transitionFadeInVolume_ = 0.0f; 1344 | transitionFadeIn_.SetFade( 1345 | (int)(param.Transition.FadeInOffset * sampleRate_), 1346 | (int)(param.Transition.FadeInTime * sampleRate_)); 1347 | } 1348 | 1349 | UpdateVolumes(); 1350 | } 1351 | 1352 | void CancelSyncedTransition() 1353 | { 1354 | if( endScheduledDSPTime_ > 0.0 ) 1355 | { 1356 | endScheduledDSPTime_ = AudioSettings.dspTime + (double)(musicSources_[0].clip.samples - currentSample_) / sampleRate_; 1357 | for( int i = 0; i < NumTracks; ++i ) 1358 | { 1359 | if( musicSources_[i].clip != null ) 1360 | { 1361 | musicSources_[i].SetScheduledEndTime(endScheduledDSPTime_); 1362 | } 1363 | } 1364 | } 1365 | 1366 | for( int i = 0; i < NumTracks; ++i ) 1367 | { 1368 | if( transitionMusicSources_[i].clip != null ) 1369 | { 1370 | transitionMusicSources_[i].Stop(); 1371 | } 1372 | } 1373 | } 1374 | 1375 | void SetMode(int index) 1376 | { 1377 | MusicMode.TransitionParams param; 1378 | 1379 | int fromModeIndex = modeIndex_; 1380 | if( index == modeIndex_ && ModeTransitionState == EModeTransitionState.Fading ) 1381 | { 1382 | fromModeIndex = nextModeIndex_; 1383 | } 1384 | 1385 | ModeTransitionOverride overrideParam = ModeTransitionOverrides.Find( 1386 | (ModeTransitionOverride ov) => 1387 | { 1388 | return (ov.FromModeIndex == fromModeIndex || ov.FromModeIndex == -1) 1389 | && (ov.ToModeIndex == index || ov.ToModeIndex == -1); 1390 | }); 1391 | if( overrideParam != null ) 1392 | { 1393 | param = overrideParam.Transition; 1394 | } 1395 | else 1396 | { 1397 | param = ModeTransitionParam; 1398 | } 1399 | 1400 | // 遷移タイミング計算 1401 | int currentSample = currentSample_; 1402 | int fadeOffsetSample = param.SyncType == Music.SyncType.Immediate ? 0 : (int)(param.FadeOffsetSec * sampleRate_); 1403 | int entryPointSample = Math.Max(-fadeOffsetSample, 0); 1404 | int syncPointSample = 0; 1405 | if( FindSyncPoint(param.SyncType, param.SyncFactor, currentSample, entryPointSample, out syncPointSample) == false ) 1406 | { 1407 | requestedModeIndex_ = index; 1408 | return; 1409 | } 1410 | 1411 | // BaseVolume計算 1412 | modeBaseVolume_ = modeVolume_; 1413 | for( int i = 0; i < NumTracks; ++i ) 1414 | { 1415 | modeLayerBaseVolumes_[i] = modeLayerVolumes_[i]; 1416 | } 1417 | 1418 | // フェード時間計算 1419 | modeFade_.SetFade( 1420 | Math.Max(currentSample, syncPointSample + fadeOffsetSample), 1421 | (int)(param.FadeTimeSec * sampleRate_)); 1422 | 1423 | // 状態遷移 1424 | nextModeIndex_ = index; 1425 | requestedModeIndex_ = -1; 1426 | if( modeFade_.FadeStartSample <= currentSample ) 1427 | { 1428 | ModeTransitionState = EModeTransitionState.Fading; 1429 | } 1430 | else 1431 | { 1432 | ModeTransitionState = EModeTransitionState.Sync; 1433 | } 1434 | 1435 | UpdateVolumes(); 1436 | } 1437 | 1438 | #endregion 1439 | 1440 | } 1441 | -------------------------------------------------------------------------------- /MusicWwise.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geekdrums/MusicEngine/b202a37bbee460d59b88b3b3e6b08b4988f6da9b/MusicWwise.cs -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MusicEngine 2 | =========== 3 | 4 | ## 概要: 5 | 6 | 音楽を再生するオブジェクトにテンポや拍子を指定する事で 7 | 音楽のタイミングを取得したりクオンタイズさせたり 8 | 音楽に合わせた演出を簡単に作れるようになります。 9 | 10 | また、複数のセクションや複数のトラックを追加して 11 | 小節に合わせた横の遷移や 12 | クロスフェードによる縦の遷移を 13 | 関数一つで簡単に実行できます。 14 | 15 | Unity単体での再生と、 16 | オーディオミドルウェアのCRI ADX2/Audiokinetic Wwiseを使った再生に対応しています。 17 | 18 | Released under the MIT license 19 | http://opensource.org/licenses/mit-license.php 20 | 21 | ## 利用方法: 22 | 23 | MusicEngineフォルダごとプロジェクトに追加してください。 24 | 25 | Unity単体で利用する場合はMusicUnityコンポーネントを追加し、Sectionsの中にAudioClipを追加してください。 26 | (AudioSourceコンポーネントは再生時に内部で自動的に生成されます) 27 | 28 | ADX2を利用する場合は、MusicADX2.csの #define ADX2 を有効にした上で、 29 | MusicADX2コンポーネントをCriAtomSourceコンポーネントと同じオブジェクトに追加してください。 30 | 31 | Wwiseを利用する場合は、MusicWwise.csの #define Wwise を有効にした上で、 32 | MusicWwiseコンポーネントを追加し、Eventを設定してください。 33 | 34 | 何か使い方や変数の意味がわからなかったり、使いにくかったりする場合は 35 | @geekdrums までご連絡いただけると、今後の改善の参考にさせていただきます。 36 | 37 | 38 | ## Abstract: 39 | 40 | You can get musical timing information with Unity or CRI ADX2 / Audiokinetic Wwise. 41 | 42 | And you can also use interactive music functions such as Horizontal Resequencing and Vertical Remixing. 43 | 44 | Released under the MIT license 45 | http://opensource.org/licenses/mit-license.php 46 | 47 | ## How To Use: 48 | 49 | Add MusicEngine folder to your Unity project. 50 | 51 | Use MusicUnity component if you don't use any other audio middleware. 52 | Add MusicUnity component and set AudioClips into Sections property. 53 | (AudioSource component will be automatically instantiated inside this MusicEngine.) 54 | 55 | If you use CRI ADX2, enable #ADX2 in MusicADX2.cs. 56 | Add MusicADX2 component to the same gameObject with CriAtomSource component. 57 | 58 | If you use Audiokinetic Wwise, enable #Wwise in MusicWwise.cs. 59 | Add MusicWwise component and set Event property. 60 | 61 | If you have any questions or suggestions, please let me (@geekdrums) know. 62 | 63 | 64 | --------------------------------------------------------------------------------