Drawing Rect using Slider Button & Midi

hi

I'm hoping someone might help I feel if its a simple fix but keep banging my head against the wall


I'm trying to get a rectangle to move following a midi input using note values. I have broken down the program to it's bear essentials to hilight the problem.

I'm using xcode 8 swift 3 and this is a mac os app


my program contains

one slider : named slider001

one button: named needsDisplayButton

one custom view named "GBV" using custom class called "gateBoxView"


here is the mainWindowController


    import Cocoa
    import CoreMIDI

     var globalVariable = 40.0

    class MainWindowController: NSWindowController {

        @IBOutlet weak var GBV: gateBoxView!

        override func windowDidLoad() {
            super.windowDidLoad()
            GBV.needsDisplay = true
            //MIDI SETUP
            var midiClient: MIDIClientRef = 0;
            var inPort:MIDIPortRef = 0;
            var src:MIDIEndpointRef = MIDIGetSource(0);
            MIDIClientCreate("MidiTestClient", nil, nil, &midiClient);
            MIDIInputPortCreate(midiClient, "MidiTest_InPort", MyMIDIReadProc, nil, &inPort);
            MIDIPortConnectSource(inPort, src, &src);
        }

        @IBAction func slider001(_ sender: AnyObject) {
            let data = sender.doubleValue!
            globalVariable = data
            print("data", data)
            GBV.needsDisplay = true
        }


        @IBAction func needsDisplayButton(_ sender: AnyObject) {
            globalVariable = 250.0
            test(data: globalVariable, dataBool: true)
        }


        func test(data: Double, dataBool: Bool){
            print("data", data)
            globalVariable = data

            //condition test
            if GBV != nil {
                print("Contains a value!",GBV)
            } else {
                print("Doesn’t contain a value.")
            }

            GBV.needsDisplay = dataBool
        }




        // MIDI INPUT USING OBJ C
        let MyMIDIReadProc: MIDIReadProc = {pktList, readProcRefCon, srcConnRefCon in
            let packetList:MIDIPacketList = pktList.pointee;
            let srcRef: MIDIEndpointRef = UnsafeMutablePointer(srcConnRefCon!).pointee
            var packet:MIDIPacket = packetList.packet;
            for _ in 1...packetList.numPackets
            {
                let bytes = Mirror(reflecting: packet.data).children;
                var dumpStr = "";

                var i = packet.length;
                for (_, attr) in bytes.enumerated()
                {
                    dumpStr += String(format:"$%02X", attr.value as! UInt8);
                    i -= 1;
                    if (i <= 0)
                    {
                        break;
                    }
                }
                let mySendString = dumpStr
                midiInput(midiInString: mySendString)
                packet = MIDIPacketNext(&packet).pointee;
            }
        }   /


    }//end of the world



When I move the slider this updates the globalVariable and uses GBV.needsDisplay = true to refresh the view in my custom class gateBoxView: NSView

as shown here


//  GateBox.swift

import Cocoa

    class gateBoxView: NSView {
        override func draw(_ dirtyRect: NSRect) {



            let mybackgroundColor = NSColor.init(colorLiteralRed: 0.9, green: 0.4, blue: 0.4, alpha: 0.2)
            mybackgroundColor.set()
            NSBezierPath.fill(bounds)


            let myY = globalVariable * 0.85
            Swift.print("myY",myY)


            let myRectangleColor = NSColor(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0)
            let myRectangle = NSMakeRect(75.0, CGFloat(myY),40.0 , 20.0)
            let cPath: NSBezierPath = NSBezierPath(rect: myRectangle)
            myRectangleColor.set()
            cPath.fill()

    }
    }


the result is my rectangle moves up and down which is great so i know that IBOutlet connections are working

When I press the button It sets the globalVariable and then passes that information to the test function on line 38 of the mainWindowController

which then draws the rectangle at point 250 and using GBV.needsDisplay = true refreshes then view. This works and the rectangle is drawn. So i know now that the test func is working.

Using the midi code i send the program notes valued 1 to 10 and the program receives everything fine here is the MidiIn

I then send that data to the test function on line 38 of the mainWindowController expecting it to draw the rectangle according to the midi note values

Firstly if i delete line 49 on the mainWindowController i can see the midi data getting to the function so i know thats working and the numbers look good

BUT if GBV.needsDisplay = dataBool is included which it needs to refresh the custom view I get the usual error of fatal error: unexpectedly found nil while unwrapping an Optional value

which usually means the IBOulet is not setup correctly but the slider and button code prove that it is

I have tried working round the error using if Let.

using the condition test on line 43 of the mainWindowController i can see that when using the button or the slider GBV has a value

and if i use midi it says GBV has no value which really confuses me

I pretty sure it has nothing to do with midi

I have been stuck on this for a number of days. And would really appreciate any help

thanks for reading if you got this far


// MidiIn.swift
import Foundation
// This grabs the Midi in from the C code and Converts it to numbers
func midiInput(midiInString:String){
    let parts = midiInString.components(separatedBy: "$")
    let pitch = parts[2]
    let pitchWrapped = Int(pitch, radix: 16)
    / //unwrap to Int
    let pitchInt = pitchWrapped!
   /


    ///Send Midi note Number to function test()
    let myData = Double(pitchInt)*20.0
    let InstanceOfMain = MainWindowController()
    InstanceOfMain.test(data: myData, dataBool: true)

}//eo midiInput


Here is the complete error


fatal error: unexpectedly found nil while unwrapping an Optional value
Current stack trace:
0    libswiftCore.dylib                 0x00000001002d97c0 swift_reportError + 132
1    libswiftCore.dylib                 0x00000001002f6300 _swift_stdlib_reportFatalError + 61
2    libswiftCore.dylib                 0x000000010024f9e0 partial apply for thunk + 63
3    libswiftCore.dylib                 0x00000001002a5b40 partial apply for thunk + 14
4    libswiftCore.dylib                 0x00000001000e6a00 specialized StaticString.withUTF8Buffer<A> (invoke : (UnsafeBufferPointer<UInt8>) -> A) -> A + 351
5    libswiftCore.dylib                 0x0000000100268fc0 partial apply for (_fatalErrorMessage(StaticString, StaticString, StaticString, UInt, flags : UInt32) -> ()).(closure #2) + 158
6    libswiftCore.dylib                 0x000000010024f9e0 partial apply for thunk + 63
7    libswiftCore.dylib                 0x00000001002a5b20 partial apply for thunk + 14
8    libswiftCore.dylib                 0x00000001000e6a00 specialized StaticString.withUTF8Buffer<A> (invoke : (UnsafeBufferPointer<UInt8>) -> A) -> A + 351
9    libswiftCore.dylib                 0x0000000100228cb0 specialized _fatalErrorMessage(StaticString, StaticString, StaticString, UInt, flags : UInt32) -> () + 143
10   Imaginator_001                     0x0000000100002000 MainWindowController.test(data : Double, dataBool : Bool) -> () + 1012
11   Imaginator_001                     0x0000000100009b40 midiInput(midiInString : String) -> () + 414
12   Imaginator_001                     0x0000000100002710 MainWindowController.(MyMIDIReadProc).(variable initialization expression).(closure #1) + 16256
13   Imaginator_001                     0x00000001000082d0 @objc MainWindowController.(MyMIDIReadProc).(variable initialization expression).(closure #1) + 9
14   CoreMIDI                           0x0000000100066c70 LocalMIDIReceiverList::HandleMIDIIn(unsigned int, unsigned int, void*, MIDIPacketList const*) + 120
15   CoreMIDI                           0x000000010006400e MIDIProcess::RunMIDIInThread() + 260
16   CoreMIDI                           0x0000000100049cf8 XThread::RunHelper(void*) + 10
17   CoreMIDI                           0x00000001000498de CAPThread::Entry(CAPThread*) + 80
18   libsystem_pthread.dylib            0x00000001009c3782 _pthread_body + 131
19   libsystem_pthread.dylib            0x00000001009c3782 _pthread_body + 0
20   libsystem_pthread.dylib            0x00000001009c0f94 thread_start + 13
(lldb)
Answered by QuinceyMorris in 159251022

My only interest in "correcting" your naming conventions is to make it easy to people to understand — and therefore answer — any forum questions you ask. The "power" of an initial miscapital is amazingly strong, and confusing.


My mistake about the code. 'oneAndOnlyWindowController' is a type ("class", "static") variable, but 'windowDidLoad' is an instance method, so class variable names must be fully qualified:


MainWindowController.oneAndOnlyWindowController = self


That's what the compiler was trying to tell you. 🙂

I'm not sure it's your main issue, but I notice that you have :


            if GBV != nil {
                print("Contains a value!",GBV)
            } else {
                print("Doesn’t contain a value.")
            }

            GBV.needsDisplay = dataBool


So, if GBV is nil, you should get "Doesn’t contain a value"


But then you try to set a value on a nil:

GBV.needsDisplay = data


So you should probably change as follows :


            if GBV != nil {
                print("Contains a value!",GBV)
            } else {
                print("Doesn’t contain a value.")

                return

            }

thanks for the reply


That code was only used to try and work out why

GBV.needsDisplay = dataBool

cannot be called from another function but can be called from a Action

So even if i remove that test it still throws up the error

Your problem is here:


///Send Midi note Number to function test()
    let myData = Double(pitchInt)*20.0
    let InstanceOfMain = MainWindowController()
    InstanceOfMain.test(data: myData, dataBool: true)


The expression 'MainWindowController()' creates a new instance of your window controller class. That's the wrong thing to do because only the existing instance "knows" where the window is (as a result of having loaded it earlier). On top of that, because you used the default initializer, its GBV outlet isn't set up properly, which leads quickly to the crash.


Your MIDI input function must send its data back to the correct (existing) window controller. There are many ways to approach that, but the absolute simplest change to existing code (and poorest in terms of design, but that's a different matter) — assuming there's only ever going to be one window controller — is to make a static variable in the window controller class that records a reference to the instance:


class MainWindowController: NSWindowController {
     static var oneAndOnlyWindowController: MainWindowController!
     @IBOutlet weak var GBV: gateBoxView! 
     override func windowDidLoad() {
          super.windowDidLoad()
          oneAndOnlyWindowController = self
          …


Then your MIDI function can do:


///Send Midi note Number to function test()
let myData = Double(pitchInt)*20.0
MainWindowController.oneAndOnlyWindowController.test(data: myData, dataBool: true)


In order not to drive other people crazy (and in order to get better answers in the forums), I'm begging you to follow proper Swift naming conventions, in particular:


1. Capitalize the first letter of a class name (GateBoxView, not gateBoxView).


2. Use lower case for the first letter of variable names (instanceOfMain, not InstanceOfMain).


3. Don't abbreviate words in variable names, and don't use acronyms unless they're "terms of art" (gateBoxView, not GBV or even gbv).


4. Don't put syntactical details in variable names (mainWindowController, not instanceOfMain).


5. Start a method name with a verb (adjustSlider001, not slider001).


6. Name methods according to the function they perform, not the UI element they're hooked to (adjustPitchData, not adjustSlider001).


I strongly recommend you read and absorb the recommendations here:


swift.org/documentation/api-design-guidelines/


Taking trouble over this really will help you to write better (that is, more bug-free) code.

hi

Thanks QM for the reply

sorry for the naming conventions I promise to do better after this. My excuse is that i'm self taught and very old... so old i remember using punch cards at school


I tried your approach

    class MainWindowController: NSWindowController {
        static var oneAndOnlyWindowController: MainWindowController!
        @IBOutlet weak var GBV: gateBoxView!
  
        override func windowDidLoad() {
            super.windowDidLoad()
         oneAndOnlyWindowController = self

but when it gets to adding oneAndOnlyWindowController = self this throws up

Static member 'oneAndOnlyWindowController' cannot be used on instance of type 'MainWindowController'


Im wondering if this has anything to do with my AppDelegate.swift


//  AppDelegate.swift
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
    var mainWindowController: MainWindowController?
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        let mainWindowController = MainWindowController(windowNibName: "MainWindowController")
        mainWindowController.showWindow(self)
        self.mainWindowController = mainWindowController
    }
    func applicationWillTerminate(_ aNotification: Notification) {
    }
}


I think I understand your way of thinking that my old code just made an instance of mainWindowController

Accepted Answer

My only interest in "correcting" your naming conventions is to make it easy to people to understand — and therefore answer — any forum questions you ask. The "power" of an initial miscapital is amazingly strong, and confusing.


My mistake about the code. 'oneAndOnlyWindowController' is a type ("class", "static") variable, but 'windowDidLoad' is an instance method, so class variable names must be fully qualified:


MainWindowController.oneAndOnlyWindowController = self


That's what the compiler was trying to tell you. 🙂

Thank you for you time and patience with my problem

Have a beer on me 🙂



Thanking you

OldMan

Drawing Rect using Slider Button & Midi
 
 
Q