github.com/256dpi/max-go@v0.7.0/max.go (about)

     1  package max
     2  
     3  // #cgo CFLAGS: -I${SRCDIR}/lib/max -I${SRCDIR}/lib/msp
     4  // #cgo windows CFLAGS: -DWIN_VERSION=1 -Wno-macro-redefined
     5  // #cgo darwin CFLAGS: -DMAC_VERSION=1
     6  // #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup
     7  // #cgo windows LDFLAGS: -L${SRCDIR}/lib/max/x64 -L${SRCDIR}/lib/msp/x64 -lMaxAPI -lMaxAudio
     8  // #include "max.h"
     9  import "C"
    10  
    11  import (
    12  	"fmt"
    13  	"sync"
    14  	"sync/atomic"
    15  	"time"
    16  	"unsafe"
    17  
    18  	"github.com/kr/pretty"
    19  )
    20  
    21  /* Types */
    22  
    23  // Type describes an inlet or outlet type.
    24  type Type string
    25  
    26  // The available inlet and outlet types.
    27  const (
    28  	Bang   Type = "bang"
    29  	Int    Type = "int"
    30  	Float  Type = "float"
    31  	List   Type = "list"
    32  	Any    Type = "any"
    33  	Signal Type = "signal"
    34  )
    35  
    36  func (t Type) enum() C.maxgo_type_e {
    37  	switch t {
    38  	case Bang:
    39  		return C.MAXGO_BANG
    40  	case Int:
    41  		return C.MAXGO_INT
    42  	case Float:
    43  		return C.MAXGO_FLOAT
    44  	case List:
    45  		return C.MAXGO_LIST
    46  	case Any:
    47  		return C.MAXGO_ANY
    48  	case Signal:
    49  		return C.MAXGO_SIGNAL
    50  	default:
    51  		panic("invalid type")
    52  	}
    53  }
    54  
    55  // Atom is a Max atom of type int64, float64 or string.
    56  type Atom = interface{}
    57  
    58  // Event describes an emitted event.
    59  type Event struct {
    60  	Outlet *Outlet
    61  	Type   Type
    62  	Msg    string
    63  	Data   []Atom
    64  }
    65  
    66  /* Basic */
    67  
    68  // Log will print a message to the max console.
    69  func Log(format string, args ...interface{}) {
    70  	C.maxgo_log(C.CString(fmt.Sprintf(format, args...))) // string freed by receiver
    71  }
    72  
    73  // Error will print an error to the max console.
    74  func Error(format string, args ...interface{}) {
    75  	C.maxgo_error(C.CString(fmt.Sprintf(format, args...))) // string freed by receiver
    76  }
    77  
    78  // Alert will show an alert dialog.
    79  func Alert(format string, args ...interface{}) {
    80  	C.maxgo_alert(C.CString(fmt.Sprintf(format, args...))) // string freed by receiver
    81  }
    82  
    83  // Pretty will pretty print and log the provided values.
    84  func Pretty(a ...interface{}) {
    85  	Log(pretty.Sprint(a...))
    86  }
    87  
    88  var symbols sync.Map
    89  
    90  func gensym(str string) *C.t_symbol {
    91  	// check cache
    92  	val, ok := symbols.Load(str)
    93  	if ok {
    94  		return val.(*C.t_symbol)
    95  	}
    96  
    97  	// get and cache symbol
    98  	sym := C.maxgo_gensym(C.CString(str)) // string freed by receiver
    99  	symbols.Store(str, sym)
   100  
   101  	return sym
   102  }
   103  
   104  /* Initialization */
   105  
   106  // InitCallback is called to initialize objects.
   107  type InitCallback func(obj *Object, atoms []Atom) bool
   108  
   109  // HandleCallback is called to handle messages.
   110  type HandleCallback func(obj *Object, inlet int, name string, atoms []Atom)
   111  
   112  // ProcessCallback is called to process audio.
   113  type ProcessCallback func(obj *Object, ins, outs [][]float64)
   114  
   115  // FreeCallback is called to free objects.
   116  type FreeCallback func(obj *Object)
   117  
   118  var initCallback InitCallback
   119  var handleCallback HandleCallback
   120  var processCallback ProcessCallback
   121  var freeCallback FreeCallback
   122  
   123  var initOnce bool
   124  var initDone bool
   125  var initMutex sync.Mutex
   126  
   127  //go:linkname mainMain main.main
   128  func mainMain()
   129  
   130  //export maxgoMain
   131  func maxgoMain() {
   132  	// check init
   133  	initMutex.Lock()
   134  	once := initOnce
   135  	initOnce = true
   136  	initMutex.Unlock()
   137  	if once {
   138  		Error("main called again")
   139  		return
   140  	}
   141  
   142  	// call main
   143  	mainMain()
   144  
   145  	// acquire mutex
   146  	initMutex.Lock()
   147  	defer initMutex.Unlock()
   148  
   149  	// check flag
   150  	if !initDone {
   151  		Error("external not initialized")
   152  	}
   153  }
   154  
   155  // Init will initialize the Max class with the specified name using the provided
   156  // callbacks to initialize and free objects. This function must be called from
   157  // the main packages main() function.
   158  //
   159  // The provided callbacks are called to initialize and object, handle messages,
   160  // process audio and free the object when it is not used anymore. The callbacks
   161  // are usually called on the Max main thread. However, the handler may be called
   162  // from an unknown thread in parallel to the other callbacks.
   163  func Init(name string, init InitCallback, handle HandleCallback, process ProcessCallback, free FreeCallback) {
   164  	// ensure mutex
   165  	initMutex.Lock()
   166  	defer initMutex.Unlock()
   167  
   168  	// check flag
   169  	if initDone {
   170  		panic("already initialized")
   171  	}
   172  
   173  	// set callbacks
   174  	initCallback = init
   175  	handleCallback = handle
   176  	processCallback = process
   177  	freeCallback = free
   178  
   179  	// initialize
   180  	C.maxgo_init(C.CString(name)) // string freed by receiver
   181  
   182  	// set flag
   183  	initDone = true
   184  }
   185  
   186  /* Classes */
   187  
   188  var counter uint64
   189  
   190  var objects = map[uint64]*Object{}
   191  var objectsMutex sync.Mutex
   192  
   193  //export maxgoInit
   194  func maxgoInit(ptr unsafe.Pointer, argc int64, argv *C.t_atom) (uint64, int, int) {
   195  	// decode atoms
   196  	atoms := decodeAtoms(argc, argv)
   197  
   198  	// get ref
   199  	ref := atomic.AddUint64(&counter, 1)
   200  
   201  	// prepare object
   202  	obj := &Object{
   203  		ref:   ref,
   204  		ptr:   ptr,
   205  		queue: make(chan Event, 256),
   206  	}
   207  
   208  	// store object
   209  	objectsMutex.Lock()
   210  	objects[ref] = obj
   211  	objectsMutex.Unlock()
   212  
   213  	// call init callback
   214  	ok := initCallback(obj, atoms)
   215  	if !ok {
   216  		return 0, 0, 0
   217  	}
   218  
   219  	// determine required proxies and signals
   220  	var proxies int
   221  	var signals int
   222  	for _, inlet := range obj.in {
   223  		if inlet.Type() == Signal {
   224  			signals++
   225  		} else {
   226  			proxies++
   227  		}
   228  	}
   229  	if signals == 0 && proxies > 0 {
   230  		proxies--
   231  	}
   232  
   233  	// create outlets in reverse order
   234  	for i := len(obj.out) - 1; i >= 0; i-- {
   235  		outlet := obj.out[i]
   236  		switch outlet.typ {
   237  		case Bang:
   238  			outlet.ptr = C.bangout(obj.ptr)
   239  		case Int:
   240  			outlet.ptr = C.intout(obj.ptr)
   241  		case Float:
   242  			outlet.ptr = C.floatout(obj.ptr)
   243  		case List:
   244  			outlet.ptr = C.listout(obj.ptr)
   245  		case Any:
   246  			outlet.ptr = C.outlet_new(obj.ptr, nil)
   247  		case Signal:
   248  			str := C.CString("signal")
   249  			outlet.ptr = C.outlet_new(obj.ptr, str)
   250  			C.free(unsafe.Pointer(str))
   251  		default:
   252  			panic("invalid outlet type")
   253  		}
   254  	}
   255  
   256  	return ref, proxies, signals
   257  }
   258  
   259  //export maxgoHandle
   260  func maxgoHandle(ref uint64, msg *C.char, inlet int64, argc int64, argv *C.t_atom) {
   261  	// get object
   262  	objectsMutex.Lock()
   263  	obj, ok := objects[ref]
   264  	objectsMutex.Unlock()
   265  	if !ok {
   266  		return
   267  	}
   268  
   269  	// decode atoms
   270  	atoms := decodeAtoms(argc, argv)
   271  
   272  	// get name
   273  	name := C.GoString(msg)
   274  
   275  	// check inlet
   276  	if inlet >= 0 {
   277  		// get inlet
   278  		in := obj.in[inlet]
   279  		if in == nil {
   280  			return
   281  		}
   282  
   283  		// check signal
   284  		if in.typ == Signal {
   285  			Error("message received on signal inlet %d", inlet)
   286  			return
   287  		}
   288  
   289  		// check name
   290  		if in.typ != Any && Type(name) != in.typ {
   291  			Error("invalid message received on inlet %d", inlet)
   292  			return
   293  		}
   294  
   295  		// check atoms
   296  		if in.typ == Bang && len(atoms) != 0 || (in.typ == Int || in.typ == Float) && len(atoms) != 1 {
   297  			Error("unexpected input received on inlet %d", inlet)
   298  			return
   299  		}
   300  
   301  		// check types
   302  		switch in.typ {
   303  		case Int:
   304  			if _, ok := atoms[0].(int64); !ok {
   305  				Error("invalid input received on inlet %d", inlet)
   306  				return
   307  			}
   308  		case Float:
   309  			if _, ok := atoms[0].(float64); !ok {
   310  				Error("invalid input received on inlet %d", inlet)
   311  				return
   312  			}
   313  		}
   314  	}
   315  
   316  	// run callback if available
   317  	if handleCallback != nil {
   318  		handleCallback(obj, int(inlet), name, atoms)
   319  	}
   320  }
   321  
   322  //export maxgoProcess
   323  func maxgoProcess(ref uint64, ins, outs **float64, numIns, numOuts uint8, samples int32) {
   324  	// get object
   325  	objectsMutex.Lock()
   326  	obj, ok := objects[ref]
   327  	objectsMutex.Unlock()
   328  	if !ok {
   329  		return
   330  	}
   331  
   332  	// prepare inputs and outputs
   333  	var inputs [][]float64
   334  	var outputs [][]float64
   335  
   336  	// convert inputs
   337  	insSlice := unsafe.Slice(ins, int(numIns))
   338  	for i := uint8(0); i < numIns; i++ {
   339  		inputs = append(inputs, unsafe.Slice(insSlice[i], int(samples)))
   340  	}
   341  
   342  	// convert outputs
   343  	outsSlice := unsafe.Slice(outs, int(numOuts))
   344  	for i := uint8(0); i < numOuts; i++ {
   345  		outputs = append(outputs, unsafe.Slice(outsSlice[i], int(samples)))
   346  	}
   347  
   348  	// max may use the same array for inputs and outputs, as the outlet order
   349  	// is internally reversed, we need to use a temporary array for capturing
   350  	// the outputs and then copy them back to the original array
   351  
   352  	// prepare temp outputs
   353  	var tempOuts [][]float64
   354  	for i := uint8(0); i < numOuts; i++ {
   355  		tempOuts = append(tempOuts, make([]float64, samples))
   356  	}
   357  
   358  	// run callback if available
   359  	if processCallback != nil {
   360  		processCallback(obj, inputs, tempOuts)
   361  	}
   362  
   363  	// copy outputs reversed
   364  	for i := uint8(0); i < numOuts; i++ {
   365  		for j := int32(0); j < samples; j++ {
   366  			outputs[i][j] = tempOuts[i][j]
   367  		}
   368  	}
   369  }
   370  
   371  //export maxgoPop
   372  func maxgoPop(ref uint64) (unsafe.Pointer, C.maxgo_type_e, *C.t_symbol, int64, *C.t_atom, bool) {
   373  	// get object
   374  	objectsMutex.Lock()
   375  	obj, ok := objects[ref]
   376  	objectsMutex.Unlock()
   377  	if !ok {
   378  		return nil, 0, nil, 0, nil, false
   379  	}
   380  
   381  	// get event
   382  	var evt Event
   383  	select {
   384  	case evt = <-obj.queue:
   385  	default:
   386  		return nil, 0, nil, 0, nil, false
   387  	}
   388  
   389  	// encode atoms
   390  	argc, argv := encodeAtoms(evt.Data)
   391  
   392  	// get symbol if available
   393  	var sym *C.t_symbol
   394  	if evt.Type == Any {
   395  		sym = gensym(evt.Msg)
   396  	}
   397  
   398  	// determine if there are more events
   399  	more := len(obj.queue) > 0
   400  
   401  	return evt.Outlet.ptr, evt.Type.enum(), sym, argc, argv, more
   402  }
   403  
   404  //export maxgoDescribe
   405  func maxgoDescribe(ref uint64, io, i int64) (*C.char, bool) {
   406  	// get object
   407  	objectsMutex.Lock()
   408  	obj, ok := objects[ref]
   409  	objectsMutex.Unlock()
   410  	if !ok {
   411  		return nil, false
   412  	}
   413  
   414  	// return label
   415  	if io == 1 {
   416  		if int(i) < len(obj.in) {
   417  			label := fmt.Sprintf("%s (%s)", obj.in[i].label, obj.in[i].typ)
   418  			return C.CString(label), obj.in[i].hot // string freed by receiver
   419  		}
   420  	} else {
   421  		if int(i) < len(obj.out) {
   422  			label := fmt.Sprintf("%s (%s)", obj.out[i].label, obj.out[i].typ)
   423  			return C.CString(label), false // string freed by receiver
   424  		}
   425  	}
   426  
   427  	return nil, false
   428  }
   429  
   430  //export maxgoFree
   431  func maxgoFree(ref uint64) {
   432  	// get and delete object
   433  	objectsMutex.Lock()
   434  	obj, ok := objects[ref]
   435  	delete(objects, ref)
   436  	objectsMutex.Unlock()
   437  	if !ok {
   438  		return
   439  	}
   440  
   441  	// run callback if available
   442  	if freeCallback != nil {
   443  		freeCallback(obj)
   444  	}
   445  }
   446  
   447  /* Objects */
   448  
   449  // Object is single Max object.
   450  type Object struct {
   451  	ref   uint64
   452  	ptr   unsafe.Pointer
   453  	in    []*Inlet
   454  	out   []*Outlet
   455  	queue chan Event
   456  }
   457  
   458  // Push will add the provided events to the objects queue.
   459  func (o *Object) Push(events ...Event) {
   460  	// queue events
   461  	for _, evt := range events {
   462  		select {
   463  		case o.queue <- evt:
   464  		case <-time.After(5 * time.Second):
   465  			Error("dropped event after 5s due to full queue")
   466  		}
   467  	}
   468  
   469  	// notify
   470  	C.maxgo_notify(o.ptr)
   471  }
   472  
   473  // Inlet is a single Max inlet.
   474  type Inlet struct {
   475  	typ   Type
   476  	label string
   477  	hot   bool
   478  }
   479  
   480  // Inlet will declare an inlet. If no inlets are added to an object it will have
   481  // a default inlet to receive messages.
   482  func (o *Object) Inlet(typ Type, label string, hot bool) *Inlet {
   483  	// check signal
   484  	if typ == Signal {
   485  		var nonSignals int
   486  		for _, in := range o.in {
   487  			if in.typ != Signal {
   488  				nonSignals++
   489  			}
   490  		}
   491  		if nonSignals > 0 {
   492  			panic("signal only supported as first inlets")
   493  		}
   494  	}
   495  
   496  	// create inlet
   497  	inlet := &Inlet{typ: typ, label: label, hot: hot}
   498  
   499  	// store inlet
   500  	o.in = append(o.in, inlet)
   501  
   502  	return inlet
   503  }
   504  
   505  // Type will return the inlets type.
   506  func (i *Inlet) Type() Type {
   507  	return i.typ
   508  }
   509  
   510  // Label will return the inlets label.
   511  func (i *Inlet) Label() string {
   512  	return i.label
   513  }
   514  
   515  // Outlet is a single MAx outlet.
   516  type Outlet struct {
   517  	obj   *Object
   518  	typ   Type
   519  	label string
   520  	ptr   unsafe.Pointer
   521  }
   522  
   523  // Outlet will declare an outlet.
   524  func (o *Object) Outlet(typ Type, label string) *Outlet {
   525  	// check signal
   526  	if typ == Signal {
   527  		var nonSignals int
   528  		for _, out := range o.out {
   529  			if out.typ != Signal {
   530  				nonSignals++
   531  			}
   532  		}
   533  		if nonSignals > 0 {
   534  			panic("signal only supported as first outlets")
   535  		}
   536  	}
   537  
   538  	// create outlet
   539  	outlet := &Outlet{obj: o, typ: typ, label: label}
   540  
   541  	// store outlet
   542  	o.out = append(o.out, outlet)
   543  
   544  	return outlet
   545  }
   546  
   547  // Type will return the outlets type.
   548  func (o *Outlet) Type() Type {
   549  	return o.typ
   550  }
   551  
   552  // Label will return the outlets label.
   553  func (o *Outlet) Label() string {
   554  	return o.label
   555  }
   556  
   557  // Bang will send a bang.
   558  func (o *Outlet) Bang() {
   559  	if o.typ == Bang || o.typ == Any {
   560  		o.obj.Push(Event{Outlet: o, Type: Bang})
   561  	} else {
   562  		Error("bang sent to outlet of type %s", o.typ)
   563  	}
   564  }
   565  
   566  // Int will send and int.
   567  func (o *Outlet) Int(n int64) {
   568  	if o.typ == Int || o.typ == Any {
   569  		o.obj.Push(Event{Outlet: o, Type: Int, Data: []Atom{n}})
   570  	} else {
   571  		Error("int sent to outlet of type %s", o.typ)
   572  	}
   573  }
   574  
   575  // Float will send a float.
   576  func (o *Outlet) Float(n float64) {
   577  	if o.typ == Float || o.typ == Any {
   578  		o.obj.Push(Event{Outlet: o, Type: Float, Data: []Atom{n}})
   579  	} else {
   580  		Error("float sent to outlet of type %s", o.typ)
   581  	}
   582  }
   583  
   584  // List will send a list.
   585  func (o *Outlet) List(atoms []Atom) {
   586  	if o.typ == List || o.typ == Any {
   587  		o.obj.Push(Event{Outlet: o, Type: List, Data: atoms})
   588  	} else {
   589  		Error("list sent to outlet of type %s", o.typ)
   590  	}
   591  }
   592  
   593  // Any will send any message.
   594  func (o *Outlet) Any(msg string, atoms []Atom) {
   595  	if o.typ == Any {
   596  		o.obj.Push(Event{Outlet: o, Type: Any, Msg: msg, Data: atoms})
   597  	} else {
   598  		Error("any sent to outlet of type %s", o.typ)
   599  	}
   600  }
   601  
   602  /* Threads */
   603  
   604  var queue = map[uint64]func(){}
   605  var queueMutex sync.Mutex
   606  
   607  // IsMainThread will return if the Max main thead is executing.
   608  func IsMainThread() bool {
   609  	return C.systhread_ismainthread() == 1
   610  }
   611  
   612  //export maxgoYield
   613  func maxgoYield(ref uint64) {
   614  	// get function
   615  	queueMutex.Lock()
   616  	fn := queue[ref]
   617  	delete(queue, ref)
   618  	queueMutex.Unlock()
   619  
   620  	// execute function
   621  	fn()
   622  }
   623  
   624  // Defer will run the provided function on the Max main thread.
   625  func Defer(fn func()) {
   626  	// get reference
   627  	ref := atomic.AddUint64(&counter, 1)
   628  
   629  	// store function
   630  	queueMutex.Lock()
   631  	queue[ref] = fn
   632  	queueMutex.Unlock()
   633  
   634  	// defer call
   635  	C.maxgo_defer(C.ulonglong(ref))
   636  }
   637  
   638  /* Atoms */
   639  
   640  func decodeAtoms(argc int64, argv *C.t_atom) []Atom {
   641  	// check empty
   642  	if argc == 0 {
   643  		return nil
   644  	}
   645  
   646  	// cast to slice
   647  	list := unsafe.Slice(argv, int(argc))
   648  
   649  	// allocate result
   650  	atoms := make([]interface{}, len(list))
   651  
   652  	// add atoms
   653  	for i, item := range list {
   654  		switch item.a_type {
   655  		case C.A_LONG:
   656  			atoms[i] = int64(C.atom_getlong(&item))
   657  		case C.A_FLOAT:
   658  			atoms[i] = float64(C.atom_getfloat(&item))
   659  		case C.A_SYM:
   660  			atoms[i] = C.GoString(C.atom_getsym(&item).s_name)
   661  		default:
   662  			atoms[i] = nil
   663  		}
   664  	}
   665  
   666  	return atoms
   667  }
   668  
   669  // the receiver must arrange for the returned non-nil array to be freed
   670  func encodeAtoms(atoms []Atom) (int64, *C.t_atom) {
   671  	// check length
   672  	if len(atoms) == 0 {
   673  		return 0, nil
   674  	}
   675  
   676  	// allocate atom array
   677  	array := (*C.t_atom)(unsafe.Pointer(C.getbytes(C.t_getbytes_size(len(atoms) * C.sizeof_t_atom))))
   678  
   679  	// cast to slice
   680  	slice := unsafe.Slice(array, len(atoms))
   681  
   682  	// set atoms
   683  	for i, atom := range atoms {
   684  		switch atom := atom.(type) {
   685  		case int64:
   686  			C.atom_setlong(&slice[i], C.t_atom_long(atom))
   687  		case float64:
   688  			C.atom_setfloat(&slice[i], C.double(atom))
   689  		case string:
   690  			C.atom_setsym(&slice[i], gensym(atom))
   691  		}
   692  	}
   693  
   694  	return int64(len(atoms)), array
   695  }