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 }