From 7b273e2b8a258f0e47279742d6962d41c5ef44f5 Mon Sep 17 00:00:00 2001 From: Chikashi Miyama Date: Sun, 27 Oct 2019 11:21:33 +0100 Subject: [PATCH] add midi clock --- UnityProject/Assembly-CSharp.csproj | 1 + UnityProject/Assets/Editor/PdBackendEditor.cs | 4 + .../Assets/Scenes/Examples/MIDI/MIDI.unity | 270 ++++++++++++++++++ .../Scripts/PdConnection/MidiClockCounter.cs | 21 ++ .../PdConnection/MidiClockCounter.cs.meta | 11 + .../Assets/Scripts/PdConnection/MidiLogger.cs | 1 + .../Assets/Scripts/PdConnection/MidiParser.cs | 17 +- .../Assets/Scripts/PdConnection/PdBackend.cs | 17 +- .../StreamingAssets/pd/patch/analyzer.pd | 54 ++-- 9 files changed, 356 insertions(+), 40 deletions(-) create mode 100644 UnityProject/Assets/Scripts/PdConnection/MidiClockCounter.cs create mode 100644 UnityProject/Assets/Scripts/PdConnection/MidiClockCounter.cs.meta diff --git a/UnityProject/Assembly-CSharp.csproj b/UnityProject/Assembly-CSharp.csproj index dfd7428..ced6be8 100644 --- a/UnityProject/Assembly-CSharp.csproj +++ b/UnityProject/Assembly-CSharp.csproj @@ -71,6 +71,7 @@ + diff --git a/UnityProject/Assets/Editor/PdBackendEditor.cs b/UnityProject/Assets/Editor/PdBackendEditor.cs index 8b90ff9..c7cd88f 100644 --- a/UnityProject/Assets/Editor/PdBackendEditor.cs +++ b/UnityProject/Assets/Editor/PdBackendEditor.cs @@ -8,6 +8,7 @@ namespace cylvester { private PdBackend pdBackend_; private SerializedProperty midiMessageReceivedProperty_; + private SerializedProperty midiClockReceivedProperty_; private readonly string[] samples_ = { @@ -36,6 +37,9 @@ namespace cylvester midiMessageReceivedProperty_ = serializedObject.FindProperty("midiMessageReceived"); EditorGUILayout.PropertyField(midiMessageReceivedProperty_); + midiClockReceivedProperty_ = serializedObject.FindProperty("midiClockReceived"); + EditorGUILayout.PropertyField(midiClockReceivedProperty_); + if (Application.isPlaying) { RenderSamplePlayback(); diff --git a/UnityProject/Assets/Scenes/Examples/MIDI/MIDI.unity b/UnityProject/Assets/Scenes/Examples/MIDI/MIDI.unity index 4f0745f..74b3e5b 100644 --- a/UnityProject/Assets/Scenes/Examples/MIDI/MIDI.unity +++ b/UnityProject/Assets/Scenes/Examples/MIDI/MIDI.unity @@ -212,6 +212,171 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &560516510 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 560516514} + - component: {fileID: 560516513} + - component: {fileID: 560516512} + - component: {fileID: 560516511} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &560516511 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 560516510} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &560516512 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 560516510} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 +--- !u!223 &560516513 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 560516510} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &560516514 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 560516510} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_Children: + - {fileID: 1790437804} + m_Father: {fileID: 0} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &700239840 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 700239843} + - component: {fileID: 700239842} + - component: {fileID: 700239841} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &700239841 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 700239840} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &700239842 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 700239840} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &700239843 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 700239840} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1261823479 GameObject: m_ObjectHideFlags: 0 @@ -223,6 +388,7 @@ GameObject: - component: {fileID: 1261823481} - component: {fileID: 1261823480} - component: {fileID: 1261823482} + - component: {fileID: 1261823483} m_Layer: 0 m_Name: PdBackend m_TagString: Untagged @@ -267,6 +433,20 @@ MonoBehaviour: m_StringArgument: m_BoolArgument: 0 m_CallState: 2 + midiClockReceived: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1261823483} + m_MethodName: onClockReceived + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 samplePlayback: 0 --- !u!4 &1261823481 Transform: @@ -297,6 +477,19 @@ MonoBehaviour: logAll: 0 logFiltered: 0 filterStatusByte: 176 +--- !u!114 &1261823483 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1261823479} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 10a68b6f417f08e428a5e9c5e21c485a, type: 3} + m_Name: + m_EditorClassIdentifier: + counter: {fileID: 1790437805} --- !u!1 &1461024797 GameObject: m_ObjectHideFlags: 0 @@ -480,6 +673,83 @@ MonoBehaviour: enableFptlForForwardOpaque: 0 enableBigTilePrepass: 0 isFptlEnabled: 0 +--- !u!1 &1790437803 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1790437804} + - component: {fileID: 1790437806} + - component: {fileID: 1790437805} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1790437804 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1790437803} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 560516514} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: -31.3} + m_SizeDelta: {x: 300, y: 90} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1790437805 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1790437803} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 32 + m_FontStyle: 1 + m_BestFit: 0 + m_MinSize: 3 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 1:1:0 +--- !u!222 &1790437806 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1790437803} + m_CullTransparentMesh: 0 --- !u!1 &2049244779 GameObject: m_ObjectHideFlags: 0 diff --git a/UnityProject/Assets/Scripts/PdConnection/MidiClockCounter.cs b/UnityProject/Assets/Scripts/PdConnection/MidiClockCounter.cs new file mode 100644 index 0000000..0a1f37d --- /dev/null +++ b/UnityProject/Assets/Scripts/PdConnection/MidiClockCounter.cs @@ -0,0 +1,21 @@ + +using UnityEngine; +using UnityEngine.UI; + +public class MidiClockCounter : MonoBehaviour +{ + + [SerializeField] private Text counter; + + private int count_ = 0; + + public void onClockReceived() + { + count_++; + var tick = count_ % 24; + var beat = count_ / 24; + var measure = beat / 4; + + counter.text = (measure+1) + ":" + (beat%4+1) + ":" + tick; + } +} diff --git a/UnityProject/Assets/Scripts/PdConnection/MidiClockCounter.cs.meta b/UnityProject/Assets/Scripts/PdConnection/MidiClockCounter.cs.meta new file mode 100644 index 0000000..455d81c --- /dev/null +++ b/UnityProject/Assets/Scripts/PdConnection/MidiClockCounter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 10a68b6f417f08e428a5e9c5e21c485a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Scripts/PdConnection/MidiLogger.cs b/UnityProject/Assets/Scripts/PdConnection/MidiLogger.cs index 942b23f..a5ec4ab 100644 --- a/UnityProject/Assets/Scripts/PdConnection/MidiLogger.cs +++ b/UnityProject/Assets/Scripts/PdConnection/MidiLogger.cs @@ -10,6 +10,7 @@ namespace cylvester public void OnMidiMessageReceived(MidiMessage mes) { + if(logAll) Debug.Log("MIDI Received: " + mes); diff --git a/UnityProject/Assets/Scripts/PdConnection/MidiParser.cs b/UnityProject/Assets/Scripts/PdConnection/MidiParser.cs index eba3f2d..395c315 100644 --- a/UnityProject/Assets/Scripts/PdConnection/MidiParser.cs +++ b/UnityProject/Assets/Scripts/PdConnection/MidiParser.cs @@ -4,7 +4,7 @@ using UnityEngine.Events; namespace cylvester { [Serializable] - public class UnityControlEvent : UnityEvent + public class UnityMidiEvent : UnityEvent {} public struct MidiMessage @@ -24,6 +24,8 @@ namespace cylvester public interface IMidiParser : IDisposable { event Action MidiMessageReceived; + event Action MidiClockReceived; + } public class MidiParser : IMidiParser @@ -39,6 +41,7 @@ namespace cylvester private Accept accept_ = Accept.StatusByte; private readonly IPdReceiver pdReceiver_; private readonly Action onDataReceived_; + private static byte MIDI_CLOCK = 248; public MidiParser(IPdReceiver pdReceiver) { @@ -48,6 +51,9 @@ namespace cylvester { foreach (var element in bytes) { + if (element == MIDI_CLOCK) + MidiClockReceived?.Invoke(); + if (element >= 128) { message_ = new MidiMessage {Status = element}; @@ -63,7 +69,7 @@ namespace cylvester else if (accept_ == Accept.DataByte2 && element <= 128) { message_.Data2 = element; - Invoke(); + MidiMessageReceived?.Invoke(message_); accept_ = Accept.StatusByte; } } @@ -72,17 +78,12 @@ namespace cylvester pdReceiver_.DataReceived += onDataReceived_; } - private void Invoke() - { - MidiMessageReceived?.Invoke(message_); - } - public void Dispose() { pdReceiver_.DataReceived -= onDataReceived_; } public event Action MidiMessageReceived; - + public event Action MidiClockReceived; } } \ No newline at end of file diff --git a/UnityProject/Assets/Scripts/PdConnection/PdBackend.cs b/UnityProject/Assets/Scripts/PdConnection/PdBackend.cs index 030f9ca..d83886a 100644 --- a/UnityProject/Assets/Scripts/PdConnection/PdBackend.cs +++ b/UnityProject/Assets/Scripts/PdConnection/PdBackend.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using UnityEngine; +using UnityEngine.Events; namespace cylvester { @@ -14,8 +15,10 @@ namespace cylvester public class PdBackend : MonoBehaviour, IPdBackend { - [SerializeField] UnityControlEvent midiMessageReceived = null; - + [SerializeField] UnityMidiEvent midiMessageReceived = null; + [SerializeField] UnityEvent midiClockReceived = null; + + public int samplePlayback; private IChangeObserver samplePlaybackObserver_; @@ -33,6 +36,7 @@ namespace cylvester private Action onSamplePlaybackChanged_; private Action onMidiMessageReceived_; + private Action onMidiClockReceived_; private void Awake() { @@ -53,13 +57,12 @@ namespace cylvester onSamplePlaybackChanged_ = () => { pdSender_.Send(new[]{(byte)PdMessage.SampleSound, (byte)samplePlayback}); }; - onMidiMessageReceived_ = (message) => { - midiMessageReceived.Invoke(message); - }; + onMidiMessageReceived_ = (message) => { midiMessageReceived.Invoke(message); }; + onMidiClockReceived_ = () => { midiClockReceived.Invoke(); }; samplePlaybackObserver_.ValueChanged += onSamplePlaybackChanged_; midiParser_.MidiMessageReceived += onMidiMessageReceived_; - + midiParser_.MidiClockReceived += onMidiClockReceived_; dspController_.State = true; } @@ -69,6 +72,8 @@ namespace cylvester pdSender_?.Dispose(); samplePlaybackObserver_.ValueChanged -= onSamplePlaybackChanged_; midiParser_.MidiMessageReceived -= onMidiMessageReceived_; + midiParser_.MidiClockReceived -= onMidiClockReceived_; + } public void Update() diff --git a/UnityProject/Assets/StreamingAssets/pd/patch/analyzer.pd b/UnityProject/Assets/StreamingAssets/pd/patch/analyzer.pd index c492cd9..a36eeb6 100644 --- a/UnityProject/Assets/StreamingAssets/pd/patch/analyzer.pd +++ b/UnityProject/Assets/StreamingAssets/pd/patch/analyzer.pd @@ -1,6 +1,6 @@ -#N canvas 216 302 670 468 10; -#X obj 125 345 adc~ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; -#X obj 55 386 dac~, f 6; +#N canvas 758 125 670 468 10; +#X obj 142 343 adc~ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; +#X obj 72 384 dac~, f 6; #N canvas 215 619 730 390 analyzers_______________________________ 0; #X obj 95 44 inlet~; @@ -51,11 +51,11 @@ #X connect 13 0 29 0; #X connect 14 0 30 0; #X connect 15 0 31 0; -#X restore 126 385 pd analyzers_______________________________; -#X obj 46 26 bang~; -#X obj 46 49 count 16; -#X obj 46 76 sel 0; -#X obj 46 102 s shmemupdate; +#X restore 143 383 pd analyzers_______________________________; +#X obj 63 24 bang~; +#X obj 63 47 count 16; +#X obj 63 74 sel 0; +#X obj 63 100 s shmemupdate; #N canvas 763 290 408 235 window 0; #N canvas 0 0 450 300 (subpatch) 0; #X array hann 1024 float 1; @@ -194,7 +194,7 @@ 0.000244498 0.000150442 9.39965e-005 3.75807e-005 1.87755e-005; #X coords 0 1 1023 0 300 100 1 0 0; #X restore 39 32 graph; -#X restore 533 23 pd window; +#X restore 550 21 pd window; #N canvas 2182 728 332 292 commands_from_unity 0; #X obj 66 31 inlet; #X obj 130 130 s sample_playback; @@ -209,7 +209,7 @@ #X connect 4 0 6 0; #X connect 4 1 1 0; #X connect 6 0 7 0; -#X restore 231 60 pd commands_from_unity; +#X restore 248 58 pd commands_from_unity; #N canvas 667 603 480 371 sample_playback 0; #X obj 86 166 readsf~; #X obj 95 6 r sample_playback; @@ -235,29 +235,30 @@ #X connect 9 0 7 0; #X connect 10 0 0 0; #X connect 11 0 0 0; -#X restore 55 306 pd sample_playback; -#X obj 231 22 netreceive -u -b 54345; -#X obj 45 140 text define -k samplefiles; +#X restore 72 304 pd sample_playback; +#X obj 248 20 netreceive -u -b 54345; +#X obj 62 138 text define -k samplefiles; #A set Back_Back.wav \; Brutal_Synth.wav \; Dialog.wav \; Drums.wav \; Fox_Melo.wav \; Kick.wav \; Pads+Strings.wav \; Rose_Sax.wav \; Roses_Front.wav; -#X obj 231 245 netsend -u -b; -#X msg 231 144 connect localhost 56765; -#X obj 253 114 loadbang; -#X obj 86 199 list prepend send; -#X obj 86 223 list trim; -#X obj 86 175 midiin; -#X msg 410 115 23 31; -#X msg 457 115 80 84, f 10; -#X text 422 89 sample CC; -#X msg 424 161 send 176 \$1 \$2; -#X text 423 183 176... control channel 1; -#N canvas 695 332 450 300 level 1; +#X obj 248 243 netsend -u -b; +#X msg 248 142 connect localhost 56765; +#X obj 270 112 loadbang; +#X obj 103 197 list prepend send; +#X obj 103 221 list trim; +#X obj 103 173 midiin; +#X msg 427 113 23 31; +#X msg 474 113 80 84, f 10; +#X text 439 87 sample CC; +#X msg 441 159 send 176 \$1 \$2; +#X text 440 181 176... control channel 1; +#N canvas 695 332 450 300 level 0; #X obj 90 182 table level 16; #X obj 89 152 shmem level 16; #X obj 89 127 r shmemupdate; #X connect 2 0 1 0; -#X restore 532 55 pd level; +#X restore 549 53 pd level; +#X obj 12 173 midirealtimein; #X connect 0 0 2 0; #X connect 0 1 2 1; #X connect 0 2 2 2; @@ -290,3 +291,4 @@ Roses_Front.wav; #X connect 18 0 21 0; #X connect 19 0 21 0; #X connect 21 0 12 0; +#X connect 24 0 15 0;