class library: SynthDef - replaceUGen fixes
[supercollider.git] / server / scsynth / SC_World.cpp
blob4d422350130ef778cae511bc26efeb895baa3a33
1 /*
2 SuperCollider real time audio synthesis system
3 Copyright (c) 2002 James McCartney. All rights reserved.
4 http://www.audiosynth.com
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 #include "SC_World.h"
23 #include "SC_WorldOptions.h"
24 #include "SC_HiddenWorld.h"
25 #include "SC_InterfaceTable.h"
26 #include "SC_AllocPool.h"
27 #include "SC_GraphDef.h"
28 #include "SC_UnitDef.h"
29 #include "SC_BufGen.h"
30 #include "SC_Node.h"
31 #include "SC_CoreAudio.h"
32 #include "SC_Group.h"
33 #include "SC_Errors.h"
34 #include <stdio.h>
35 #include "SC_Prototypes.h"
36 #include "SC_Samp.h"
37 #include "SC_DirUtils.h"
38 #ifdef _WIN32
39 # include "../../include/server/SC_ComPort.h"
40 # include "SC_Win32Utils.h"
41 #else
42 # include "SC_ComPort.h"
43 #endif
44 #include "SC_StringParser.h"
45 #ifdef _WIN32
46 # include <direct.h>
47 #else
48 # include <sys/param.h>
49 #endif
51 // undefine the shadowed scfft functions
52 #undef scfft_create
53 #undef scfft_dofft
54 #undef scfft_doifft
55 #undef scfft_destroy
57 #if (_POSIX_MEMLOCK - 0) >= 200112L
58 # include <sys/resource.h>
59 # include <sys/mman.h>
60 #endif
62 #include "server_shm.hpp"
65 InterfaceTable gInterfaceTable;
66 PrintFunc gPrint = 0;
68 extern HashTable<struct UnitDef, Malloc> *gUnitDefLib;
69 extern HashTable<struct BufGen, Malloc> *gBufGenLib;
70 extern HashTable<PlugInCmd, Malloc> *gPlugInCmds;
72 extern "C" {
74 #ifdef NO_LIBSNDFILE
75 struct SF_INFO {};
76 #endif
78 int sndfileFormatInfoFromStrings(struct SF_INFO *info,
79 const char *headerFormatString, const char *sampleFormatString);
81 bool SendMsgToEngine(World *inWorld, FifoMsg& inMsg);
82 bool SendMsgFromEngine(World *inWorld, FifoMsg& inMsg);
85 bool sc_UseVectorUnit();
86 void sc_SetDenormalFlags();
88 ////////////////////////////////////////////////////////////////////////////////
90 #ifdef __linux__
92 #ifndef _XOPEN_SOURCE
93 #define _XOPEN_SOURCE 600
94 #endif
95 #include <stdlib.h>
96 #include <errno.h>
98 #ifndef SC_MEMORY_ALIGNMENT
99 # error SC_MEMORY_ALIGNMENT undefined
100 #endif
101 #define SC_DBUG_MEMORY 0
103 inline void* sc_malloc(size_t size)
105 #if SC_MEMORY_ALIGNMENT > 1
106 void* ptr;
107 int err = posix_memalign(&ptr, SC_MEMORY_ALIGNMENT, size);
108 if (err) {
109 if (err != ENOMEM) {
110 perror("sc_malloc");
111 abort();
113 return 0;
115 return ptr;
116 #else
117 return malloc(size);
118 #endif
121 void* sc_dbg_malloc(size_t size, const char* tag, int line)
123 void* ptr = sc_malloc(size);
124 fprintf(stderr, "sc_dbg_malloc [%s:%d] %p %zu\n", tag, line, ptr, size);
125 #if SC_MEMORY_ALIGNMENT > 1
126 if (((intptr_t)ptr % SC_MEMORY_ALIGNMENT) != 0) {
127 fprintf(stderr, "sc_dbg_malloc [%s:%d] %p %zu: memory alignment error\n",
128 tag, line, ptr, size);
129 abort();
131 #endif
132 return ptr;
135 inline void sc_free(void* ptr)
137 free(ptr);
140 void sc_dbg_free(void* ptr, const char* tag, int line)
142 fprintf(stderr, "sc_dbg_free [%s:%d]: %p\n", tag, line, ptr);
143 free(ptr);
146 inline void* sc_zalloc(size_t n, size_t size)
148 size *= n;
149 if (size) {
150 void* ptr = sc_malloc(size);
151 if (ptr) {
152 memset(ptr, 0, size);
153 return ptr;
156 return 0;
159 void* sc_dbg_zalloc(size_t n, size_t size, const char* tag, int line)
161 void* ptr = sc_zalloc(n, size);
162 fprintf(stderr, "sc_dbg_zalloc [%s:%d]: %p %zu %zu\n", tag, line, ptr, n, size);
163 return ptr;
166 # if SC_DEBUG_MEMORY
167 # define malloc(size) sc_dbg_malloc((size), __FUNCTION__, __LINE__)
168 # define free(ptr) sc_dbg_free((ptr), __FUNCTION__, __LINE__)
169 # define zalloc(n, size) sc_dbg_zalloc((n), (size), __FUNCTION__, __LINE__)
170 # else
171 # define malloc(size) sc_malloc((size))
172 # define free(ptr) sc_free((ptr))
173 # define zalloc(n, size) sc_zalloc((n), (size))
174 # endif // SC_DEBUG_MEMORY
176 #else // !__linux__
178 // replacement for calloc.
179 // calloc lazily zeroes memory on first touch. This is good for most purposes, but bad for realtime audio.
180 void *zalloc(size_t n, size_t size)
182 size *= n;
183 if (size) {
184 void* ptr = malloc(size);
185 if (ptr) {
186 memset(ptr, 0, size);
187 return ptr;
190 return 0;
192 #endif // __linux__
194 ////////////////////////////////////////////////////////////////////////////////
196 static bool getScopeBuffer(World *inWorld, int index, int channels, int maxFrames, ScopeBufferHnd &hnd);
197 static void pushScopeBuffer(World *inWorld, ScopeBufferHnd &hnd, int frames);
198 static void releaseScopeBuffer(World *inWorld, ScopeBufferHnd &hnd);
200 void InterfaceTable_Init()
202 InterfaceTable *ft = &gInterfaceTable;
204 ft->mSine = gSine;
205 ft->mCosecant = gInvSine;
206 ft->mSineSize = kSineSize;
207 ft->mSineWavetable = gSineWavetable;
209 ft->fPrint = &scprintf;
211 ft->fRanSeed = &server_timeseed;
213 ft->fNodeEnd = &Node_End;
215 ft->fDefineUnit = &UnitDef_Create;
216 ft->fDefineBufGen = &BufGen_Create;
217 ft->fClearUnitOutputs = &Unit_ZeroOutputs;
219 ft->fNRTAlloc = &malloc;
220 ft->fNRTRealloc = &realloc;
221 ft->fNRTFree = &free;
223 ft->fRTAlloc = &World_Alloc;
224 ft->fRTRealloc = &World_Realloc;
225 ft->fRTFree = &World_Free;
227 ft->fNodeRun = &Node_SetRun;
229 ft->fSendTrigger = &Node_SendTrigger;
230 ft->fSendNodeReply = &Node_SendReply;
233 ft->fDefineUnitCmd = &UnitDef_AddCmd;
234 ft->fDefinePlugInCmd = &PlugIn_DefineCmd;
236 ft->fSendMsgFromRT = &SendMsgFromEngine;
237 ft->fSendMsgToRT = &SendMsgToEngine;
238 #ifdef NO_LIBSNDFILE
239 ft->fSndFileFormatInfoFromStrings = NULL;
240 #else
241 ft->fSndFileFormatInfoFromStrings = &sndfileFormatInfoFromStrings;
242 #endif
243 ft->fGetNode = &World_GetNode;
244 ft->fGetGraph = &World_GetGraph;
246 ft->fNRTLock = &World_NRTLock;
247 ft->fNRTUnlock = &World_NRTUnlock;
249 ft->mAltivecAvailable = sc_UseVectorUnit();
251 ft->fGroup_DeleteAll = &Group_DeleteAll;
252 ft->fDoneAction = &Unit_DoneAction;
253 ft->fDoAsynchronousCommand = &PerformAsynchronousCommand;
254 ft->fBufAlloc = &bufAlloc;
256 ft->fSCfftCreate = &scfft_create;
257 ft->fSCfftDestroy = &scfft_destroy;
258 ft->fSCfftDoFFT = &scfft_dofft;
259 ft->fSCfftDoIFFT = &scfft_doifft;
261 ft->fGetScopeBuffer = &getScopeBuffer;
262 ft->fPushScopeBuffer = &pushScopeBuffer;
263 ft->fReleaseScopeBuffer = &releaseScopeBuffer;
266 void initialize_library(const char *mUGensPluginPath);
267 void initializeScheduler();
269 static void World_LoadGraphDefs(World* world);
270 void World_LoadGraphDefs(World* world)
272 GraphDef *list = 0;
274 if(getenv("SC_SYNTHDEF_PATH")){
275 if(world->mVerbosity > 0)
276 scprintf("Loading synthdefs from path: %s\n", getenv("SC_SYNTHDEF_PATH"));
277 SC_StringParser sp(getenv("SC_SYNTHDEF_PATH"), SC_STRPARSE_PATHDELIMITER);
278 while (!sp.AtEnd()) {
279 GraphDef *list = 0;
280 char *path = const_cast<char *>(sp.NextToken());
281 list = GraphDef_LoadDir(world, path, list);
282 GraphDef_Define(world, list);
284 }else{
285 char resourceDir[MAXPATHLEN];
286 if(sc_IsStandAlone())
287 sc_GetResourceDirectory(resourceDir, MAXPATHLEN);
288 else
289 sc_GetUserAppSupportDirectory(resourceDir, MAXPATHLEN);
290 sc_AppendToPath(resourceDir, MAXPATHLEN, "synthdefs");
291 if(world->mVerbosity > 0)
292 scprintf("Loading synthdefs from default path: %s\n", resourceDir);
293 list = GraphDef_LoadDir(world, resourceDir, list);
294 GraphDef_Define(world, list);
299 SC_DLLEXPORT_C World* World_New(WorldOptions *inOptions)
301 #if (_POSIX_MEMLOCK - 0) >= 200112L
302 if (inOptions->mMemoryLocking && inOptions->mRealTime)
304 bool lock_memory = false;
306 rlimit limit;
308 int failure = getrlimit(RLIMIT_MEMLOCK, &limit);
309 if (failure)
310 scprintf("getrlimit failure\n");
311 else
313 if (limit.rlim_cur == RLIM_INFINITY and
314 limit.rlim_max == RLIM_INFINITY)
315 lock_memory = true;
316 else
317 scprintf("memory locking disabled due to resource limiting\n");
319 if (lock_memory)
321 if (mlockall(MCL_FUTURE) != -1)
322 scprintf("memory locking enabled.\n");
326 #endif
328 World *world = 0;
330 try {
331 static bool gLibInitted = false;
332 if (!gLibInitted) {
333 InterfaceTable_Init();
334 initialize_library(inOptions->mUGensPluginPath);
335 initializeScheduler();
336 gLibInitted = true;
339 world = (World*)zalloc(1, sizeof(World));
341 world->hw = (HiddenWorld*)zalloc(1, sizeof(HiddenWorld));
343 world->hw->mAllocPool = new AllocPool(malloc, free, inOptions->mRealTimeMemorySize * 1024, 0);
344 world->hw->mQuitProgram = new SC_Semaphore(0);
345 world->hw->mTerminating = false;
347 extern Malloc gMalloc;
349 HiddenWorld *hw = world->hw;
350 hw->mGraphDefLib = new HashTable<struct GraphDef, Malloc>(&gMalloc, inOptions->mMaxGraphDefs, false);
351 hw->mNodeLib = new IntHashTable<Node, AllocPool>(hw->mAllocPool, inOptions->mMaxNodes, false);
352 hw->mUsers = (ReplyAddress*)zalloc(inOptions->mMaxLogins, sizeof(ReplyAddress));
353 hw->mNumUsers = 0;
354 hw->mMaxUsers = inOptions->mMaxLogins;
355 hw->mHiddenID = -8;
356 hw->mRecentID = -8;
359 world->mNumUnits = 0;
360 world->mNumGraphs = 0;
361 world->mNumGroups = 0;
363 world->mBufCounter = 0;
364 world->mBufLength = inOptions->mBufLength;
365 world->mSampleOffset = 0;
366 world->mSubsampleOffset = 0.f;
367 world->mNumAudioBusChannels = inOptions->mNumAudioBusChannels;
368 world->mNumControlBusChannels = inOptions->mNumControlBusChannels;
369 world->mNumInputs = inOptions->mNumInputBusChannels;
370 world->mNumOutputs = inOptions->mNumOutputBusChannels;
372 world->mVerbosity = inOptions->mVerbosity;
373 world->mErrorNotification = 1; // i.e., 0x01 | 0x02
374 world->mLocalErrorNotification = 0;
376 if (inOptions->mSharedMemoryID) {
377 server_shared_memory_creator::cleanup(inOptions->mSharedMemoryID);
378 hw->mShmem = new server_shared_memory_creator(inOptions->mSharedMemoryID, inOptions->mNumControlBusChannels);
379 world->mControlBus = hw->mShmem->get_control_busses();
380 } else
381 world->mControlBus = (float*)zalloc(world->mNumControlBusChannels, sizeof(float));
383 world->mNumSharedControls = 0;
384 world->mSharedControls = inOptions->mSharedControls;
386 int numsamples = world->mBufLength * world->mNumAudioBusChannels;
387 world->mAudioBus = (float*)zalloc(numsamples, sizeof(float));
389 world->mAudioBusTouched = (int32*)zalloc(inOptions->mNumAudioBusChannels, sizeof(int32));
390 world->mControlBusTouched = (int32*)zalloc(inOptions->mNumControlBusChannels, sizeof(int32));
392 world->mNumSndBufs = inOptions->mNumBuffers;
393 world->mSndBufs = (SndBuf*)zalloc(world->mNumSndBufs, sizeof(SndBuf));
394 world->mSndBufsNonRealTimeMirror = (SndBuf*)zalloc(world->mNumSndBufs, sizeof(SndBuf));
395 world->mSndBufUpdates = (SndBufUpdates*)zalloc(world->mNumSndBufs, sizeof(SndBufUpdates));
397 GroupNodeDef_Init();
399 int err = Group_New(world, 0, &world->mTopGroup);
400 if (err) throw err;
402 world->mRealTime = inOptions->mRealTime;
404 world->ft = &gInterfaceTable;
406 world->mNumRGens = inOptions->mNumRGens;
407 world->mRGen = new RGen[world->mNumRGens];
408 for (uint32 i=0; i<world->mNumRGens; ++i) {
409 world->mRGen[i].init(server_timeseed());
412 world->mNRTLock = new SC_Lock();
413 world->mDriverLock = new SC_Lock();
415 if (inOptions->mPassword) {
416 strncpy(world->hw->mPassword, inOptions->mPassword, 31);
417 world->hw->mPassword[31] = 0;
418 } else {
419 world->hw->mPassword[0] = 0;
422 #ifdef __APPLE__
423 world->hw->mInputStreamsEnabled = inOptions->mInputStreamsEnabled;
424 world->hw->mOutputStreamsEnabled = inOptions->mOutputStreamsEnabled;
425 #endif
426 world->hw->mInDeviceName = inOptions->mInDeviceName;
427 world->hw->mOutDeviceName = inOptions->mOutDeviceName;
428 hw->mMaxWireBufs = inOptions->mMaxWireBufs;
429 hw->mWireBufSpace = 0;
431 world->mRendezvous = inOptions->mRendezvous;
433 world->mRestrictedPath = inOptions->mRestrictedPath;
435 if(inOptions->mVerbosity >= 1) {
436 scprintf("Using vector unit: %s\n", sc_UseVectorUnit() ? "yes" : "no");
438 sc_SetDenormalFlags();
440 if (world->mRealTime) {
441 hw->mAudioDriver = SC_NewAudioDriver(world);
442 hw->mAudioDriver->SetPreferredHardwareBufferFrameSize(
443 inOptions->mPreferredHardwareBufferFrameSize
445 hw->mAudioDriver->SetPreferredSampleRate(
446 inOptions->mPreferredSampleRate
449 if (inOptions->mLoadGraphDefs) {
450 World_LoadGraphDefs(world);
453 if (!hw->mAudioDriver->Setup()) {
454 scprintf("could not initialize audio.\n");
455 return 0;
457 if (!hw->mAudioDriver->Start()) {
458 scprintf("start audio failed.\n");
459 return 0;
461 } else {
462 hw->mAudioDriver = 0;
464 } catch (std::exception& exc) {
465 scprintf("Exception in World_New: %s\n", exc.what());
466 World_Cleanup(world);
467 return 0;
468 } catch (...) {
470 return world;
473 SC_DLLEXPORT_C int World_CopySndBuf(World *world, uint32 index, SndBuf *outBuf, bool onlyIfChanged, bool *outDidChange)
475 if (index > world->mNumSndBufs) return kSCErr_IndexOutOfRange;
477 SndBufUpdates *updates = world->mSndBufUpdates + index;
478 bool didChange = updates->reads != updates->writes;
480 if (!onlyIfChanged || didChange)
483 world->mNRTLock->Lock();
485 SndBuf *buf = world->mSndBufsNonRealTimeMirror + index;
487 if (buf->data && buf->samples)
489 uint32 bufSize = buf->samples * sizeof(float);
490 if (buf->samples != outBuf->samples)
492 free(outBuf->data);
493 outBuf->data = (float*)malloc(bufSize);
495 memcpy(outBuf->data, buf->data, bufSize);
496 outBuf->channels = buf->channels;
497 outBuf->samples = buf->samples;
498 outBuf->frames = buf->frames;
499 outBuf->mask = buf->mask;
500 outBuf->mask1 = buf->mask1;
502 else
504 free(outBuf->data);
505 outBuf->data = 0;
506 outBuf->channels = 0;
507 outBuf->samples = 0;
508 outBuf->frames = 0;
509 outBuf->mask = 0;
510 outBuf->mask1 = 0;
513 outBuf->samplerate = buf->samplerate;
514 outBuf->sampledur = buf->sampledur;
515 outBuf->coord = buf->coord;
516 outBuf->sndfile = 0;
518 updates->reads = updates->writes;
520 world->mNRTLock->Unlock();
523 if (outDidChange) *outDidChange = didChange;
525 return kSCErr_None;
528 bool nextOSCPacket(FILE *file, OSC_Packet *packet, int64& outTime)
530 int32 msglen;
531 if (fread(&msglen, 1, sizeof(int32), file) != sizeof(int32))
532 return true;
533 // msglen is in network byte order
534 msglen = OSCint((char*)&msglen);
535 if (msglen > 8192)
536 throw std::runtime_error("OSC packet too long. > 8192 bytes\n");
538 size_t read = fread(packet->mData, 1, msglen, file);
539 if (read != msglen)
540 throw std::runtime_error("nextOSCPacket: invalid read of OSC packaet\n");
542 if (strcmp(packet->mData, "#bundle")!=0)
543 throw std::runtime_error("OSC packet not a bundle\n");
545 packet->mSize = msglen;
547 outTime = OSCtime(packet->mData+8);
548 return false;
551 void PerformOSCBundle(World *inWorld, OSC_Packet *inPacket);
553 #ifndef NO_LIBSNDFILE
554 SC_DLLEXPORT_C void World_NonRealTimeSynthesis(struct World *world, WorldOptions *inOptions)
556 if (inOptions->mLoadGraphDefs) {
557 World_LoadGraphDefs(world);
559 int bufLength = world->mBufLength;
560 int fileBufFrames = inOptions->mPreferredHardwareBufferFrameSize;
561 if (fileBufFrames <= 0) fileBufFrames = 8192;
562 int bufMultiple = (fileBufFrames + bufLength - 1) / bufLength;
563 fileBufFrames = bufMultiple * bufLength;
565 // batch process non real time audio
566 if (!inOptions->mNonRealTimeOutputFilename)
567 throw std::runtime_error("Non real time output filename is NULL.\n");
569 SF_INFO inputFileInfo, outputFileInfo;
570 float *inputFileBuf = 0;
571 float *outputFileBuf = 0;
572 int numInputChannels = 0;
573 int numOutputChannels;
575 outputFileInfo.samplerate = inOptions->mPreferredSampleRate;
576 numOutputChannels = outputFileInfo.channels = world->mNumOutputs;
577 sndfileFormatInfoFromStrings(&outputFileInfo,
578 inOptions->mNonRealTimeOutputHeaderFormat, inOptions->mNonRealTimeOutputSampleFormat);
580 world->hw->mNRTOutputFile = sf_open(inOptions->mNonRealTimeOutputFilename, SFM_WRITE, &outputFileInfo);
581 if (!world->hw->mNRTOutputFile)
582 throw std::runtime_error("Couldn't open non real time output file.\n");
584 outputFileBuf = (float*)calloc(1, world->mNumOutputs * fileBufFrames * sizeof(float));
586 if (inOptions->mNonRealTimeInputFilename) {
587 world->hw->mNRTInputFile = sf_open(inOptions->mNonRealTimeInputFilename, SFM_READ, &inputFileInfo);
588 if (!world->hw->mNRTInputFile)
589 throw std::runtime_error("Couldn't open non real time input file.\n");
591 inputFileBuf = (float*)calloc(1, inputFileInfo.channels * fileBufFrames * sizeof(float));
593 if (world->mNumInputs != (uint32)inputFileInfo.channels)
594 scprintf("WARNING: input file channels didn't match number of inputs specified in options.\n");
596 numInputChannels = world->mNumInputs = inputFileInfo.channels; // force it.
598 if (inputFileInfo.samplerate != (int)inOptions->mPreferredSampleRate)
599 scprintf("WARNING: input file sample rate does not equal output sample rate.\n");
601 } else {
602 world->hw->mNRTInputFile = 0;
605 FILE *cmdFile;
606 if (inOptions->mNonRealTimeCmdFilename) {
607 #ifdef _WIN32
608 cmdFile = fopen(inOptions->mNonRealTimeCmdFilename, "rb");
609 #else
610 cmdFile = fopen(inOptions->mNonRealTimeCmdFilename, "r");
611 #endif
612 } else cmdFile = stdin;
613 if (!cmdFile)
614 throw std::runtime_error("Couldn't open non real time command file.\n");
616 char msgbuf[8192];
617 OSC_Packet packet;
618 memset(&packet, 0, sizeof(packet));
619 packet.mData = msgbuf;
620 packet.mIsBundle = true;
621 packet.mReplyAddr.mReplyFunc = null_reply_func;
623 int64 schedTime;
624 if (nextOSCPacket(cmdFile, &packet, schedTime))
625 throw std::runtime_error("command file empty.\n");
626 int64 prevTime = schedTime;
628 World_SetSampleRate(world, inOptions->mPreferredSampleRate);
629 World_Start(world);
631 int64 oscTime = 0;
632 double oscToSeconds = 1. / pow(2.,32.);
633 double oscToSamples = inOptions->mPreferredSampleRate * oscToSeconds;
634 int64 oscInc = (int64)((double)bufLength / oscToSamples);
636 if(inOptions->mVerbosity >= 0) {
637 printf("start time %g\n", schedTime * oscToSeconds);
640 bool run = true;
641 int inBufStep = numInputChannels * bufLength;
642 int outBufStep = numOutputChannels * bufLength;
643 float* inputBuses = world->mAudioBus + world->mNumOutputs * bufLength;
644 float* outputBuses = world->mAudioBus;
645 int32* inputTouched = world->mAudioBusTouched + world->mNumOutputs;
646 int32* outputTouched = world->mAudioBusTouched;
647 for (; run;) {
648 int bufFramesCalculated = 0;
649 float* inBufPos = inputFileBuf;
650 float* outBufPos = outputFileBuf;
652 if (world->hw->mNRTInputFile) {
653 int framesRead = sf_readf_float(world->hw->mNRTInputFile, inputFileBuf, fileBufFrames);
654 if (framesRead < fileBufFrames) {
655 memset(inputFileBuf + framesRead * numInputChannels, 0,
656 (fileBufFrames - framesRead) * numInputChannels * sizeof(float));
660 for (int i=0; i<bufMultiple && run; ++i) {
661 int bufCounter = world->mBufCounter;
663 // deinterleave input to input buses
664 if (inputFileBuf) {
665 float *inBus = inputBuses;
666 for (int j=0; j<numInputChannels; ++j, inBus += bufLength) {
667 float *inFileBufPtr = inBufPos + j;
668 for (int k=0; k<bufLength; ++k) {
669 inBus[k] = *inFileBufPtr;
670 inFileBufPtr += numInputChannels;
672 inputTouched[j] = bufCounter;
676 // execute ready commands
677 int64 nextTime = oscTime + oscInc;
679 while (schedTime <= nextTime) {
680 float diffTime = (float)(schedTime - oscTime) * oscToSamples + 0.5;
681 float diffTimeFloor = floor(diffTime);
682 world->mSampleOffset = (int)diffTimeFloor;
683 world->mSubsampleOffset = diffTime - diffTimeFloor;
685 if (world->mSampleOffset < 0) world->mSampleOffset = 0;
686 else if (world->mSampleOffset >= bufLength) world->mSampleOffset = bufLength-1;
689 PerformOSCBundle(world, &packet);
690 if (nextOSCPacket(cmdFile, &packet, schedTime)) { run = false; break; }
691 if(inOptions->mVerbosity >= 0) {
692 printf("nextOSCPacket %g\n", schedTime * oscToSeconds);
694 if (schedTime < prevTime) {
695 scprintf("ERROR: Packet time stamps out-of-order.\n");
696 run = false;
697 goto Bail;
699 prevTime = schedTime;
702 World_Run(world);
704 // interleave output to output buffer
705 float *outBus = outputBuses;
706 for (int j=0; j<numOutputChannels; ++j, outBus += bufLength) {
707 float *outFileBufPtr = outBufPos + j;
708 if (outputTouched[j] == bufCounter) {
709 for (int k=0; k<bufLength; ++k) {
710 *outFileBufPtr = outBus[k];
711 outFileBufPtr += numOutputChannels;
713 } else {
714 for (int k=0; k<bufLength; ++k) {
715 *outFileBufPtr = 0.f;
716 outFileBufPtr += numOutputChannels;
720 bufFramesCalculated += bufLength;
721 inBufPos += inBufStep;
722 outBufPos += outBufStep;
723 world->mBufCounter++;
724 oscTime = nextTime;
727 Bail:
728 // write output
729 sf_writef_float(world->hw->mNRTOutputFile, outputFileBuf, bufFramesCalculated);
732 if (cmdFile != stdin) fclose(cmdFile);
733 sf_close(world->hw->mNRTOutputFile);
734 world->hw->mNRTOutputFile = 0;
736 if (world->hw->mNRTInputFile) {
737 sf_close(world->hw->mNRTInputFile);
738 world->hw->mNRTInputFile = 0;
741 World_Cleanup(world);
743 #endif // !NO_LIBSNDFILE
745 SC_DLLEXPORT_C int World_OpenUDP(struct World *inWorld, int inPort)
747 try {
748 new SC_UdpInPort(inWorld, inPort);
749 return true;
750 } catch (std::exception& exc) {
751 scprintf("Exception in World_OpenUDP: %s\n", exc.what());
752 } catch (...) {
754 return false;
757 SC_DLLEXPORT_C int World_OpenTCP(struct World *inWorld, int inPort, int inMaxConnections, int inBacklog)
759 try {
760 new SC_TcpInPort(inWorld, inPort, inMaxConnections, inBacklog);
761 return true;
762 } catch (std::exception& exc) {
763 scprintf("Exception in World_OpenTCP: %s\n", exc.what());
764 } catch (...) {
766 return false;
769 SC_DLLEXPORT_C void World_WaitForQuit(struct World *inWorld)
771 try {
772 inWorld->hw->mQuitProgram->Acquire();
773 World_Cleanup(inWorld);
774 } catch (std::exception& exc) {
775 scprintf("Exception in World_WaitForQuit: %s\n", exc.what());
776 } catch (...) {
780 void World_SetSampleRate(World *inWorld, double inSampleRate)
782 inWorld->mSampleRate = inSampleRate;
783 Rate_Init(&inWorld->mFullRate, inSampleRate, inWorld->mBufLength);
784 Rate_Init(&inWorld->mBufRate, inSampleRate / inWorld->mBufLength, 1);
787 ////////////////////////////////////////////////////////////////////////////////
789 void* World_Alloc(World *inWorld, size_t inByteSize)
791 return inWorld->hw->mAllocPool->Alloc(inByteSize);
794 void* World_Realloc(World *inWorld, void *inPtr, size_t inByteSize)
796 return inWorld->hw->mAllocPool->Realloc(inPtr, inByteSize);
799 size_t World_TotalFree(World *inWorld)
801 return inWorld->hw->mAllocPool->TotalFree();
804 size_t World_LargestFreeChunk(World *inWorld)
806 return inWorld->hw->mAllocPool->LargestFreeChunk();
809 void World_Free(World *inWorld, void *inPtr)
811 inWorld->hw->mAllocPool->Free(inPtr);
814 ////////////////////////////////////////////////////////////////////////////////
816 int32 *GetKey(GraphDef *inGraphDef)
818 return inGraphDef->mNodeDef.mName;
821 int32 GetHash(GraphDef *inGraphDef)
823 return inGraphDef->mNodeDef.mHash;
826 void World_AddGraphDef(World *inWorld, GraphDef* inGraphDef)
828 bool added = inWorld->hw->mGraphDefLib->Add(inGraphDef);
829 if(!added) scprintf("ERROR: Could not add SynthDef %s.\nTry adjusting ServerOptions:maxSynthDefs or the -d cmdline flag.\n", (char*)inGraphDef->mNodeDef.mName);
830 for (uint32 i=0; i<inGraphDef->mNumVariants; ++i) {
831 GraphDef* var = inGraphDef->mVariants + i;
832 added = inWorld->hw->mGraphDefLib->Add(var);
833 if(!added) scprintf("ERROR: Could not add SynthDef %s.\nTry adjusting ServerOptions:maxSynthDefs or the -d cmdline flag.\n", (char*)var->mNodeDef.mName);
837 void World_RemoveGraphDef(World *inWorld, GraphDef* inGraphDef)
839 for (uint32 i=0; i<inGraphDef->mNumVariants; ++i) {
840 GraphDef* var = inGraphDef->mVariants + i;
841 inWorld->hw->mGraphDefLib->Remove(var);
843 inWorld->hw->mGraphDefLib->Remove(inGraphDef);
846 void World_FreeAllGraphDefs(World *inWorld)
848 GrafDefTable* lib = inWorld->hw->mGraphDefLib;
849 int size = lib->TableSize();
850 for (int i=0; i<size; ++i) {
851 GraphDef *def = lib->AtIndex(i);
852 if (def) GraphDef_Free(def);
854 lib->MakeEmpty();
857 GraphDef* World_GetGraphDef(World *inWorld, int32* inKey)
859 return inWorld->hw->mGraphDefLib->Get(inKey);
862 ////////////////////////////////////////////////////////////////////////////////
864 int32 *GetKey(UnitDef *inUnitDef)
866 return inUnitDef->mUnitDefName;
869 int32 GetHash(UnitDef *inUnitDef)
871 return inUnitDef->mHash;
874 bool AddUnitDef(UnitDef* inUnitDef)
876 return gUnitDefLib->Add(inUnitDef);
879 bool RemoveUnitDef(UnitDef* inUnitDef)
881 return gUnitDefLib->Remove(inUnitDef);
884 UnitDef* GetUnitDef(int32* inKey)
886 return gUnitDefLib->Get(inKey);
889 ////////////////////////////////////////////////////////////////////////////////
891 int32 *GetKey(BufGen *inBufGen)
893 return inBufGen->mBufGenName;
896 int32 GetHash(BufGen *inBufGen)
898 return inBufGen->mHash;
901 bool AddBufGen(BufGen* inBufGen)
903 return gBufGenLib->Add(inBufGen);
906 bool RemoveBufGen(BufGen* inBufGen)
908 return gBufGenLib->Remove(inBufGen);
911 BufGen* GetBufGen(int32* inKey)
913 return gBufGenLib->Get(inKey);
916 ////////////////////////////////////////////////////////////////////////////////
918 int32 *GetKey(PlugInCmd *inPlugInCmd)
920 return inPlugInCmd->mCmdName;
923 int32 GetHash(PlugInCmd *inPlugInCmd)
925 return inPlugInCmd->mHash;
928 bool AddPlugInCmd(PlugInCmd* inPlugInCmd)
930 return gPlugInCmds->Add(inPlugInCmd);
933 bool RemovePlugInCmd(PlugInCmd* inPlugInCmd)
935 return gPlugInCmds->Remove(inPlugInCmd);
938 PlugInCmd* GetPlugInCmd(int32* inKey)
940 return gPlugInCmds->Get(inKey);
943 ////////////////////////////////////////////////////////////////////////////////
945 int32 GetKey(Node *inNode)
947 return inNode->mID;
950 int32 GetHash(Node *inNode)
952 return inNode->mHash;
955 bool World_AddNode(World *inWorld, Node* inNode)
957 return inWorld->hw->mNodeLib->Add(inNode);
960 bool World_RemoveNode(World *inWorld, Node* inNode)
962 return inWorld->hw->mNodeLib->Remove(inNode);
965 Node* World_GetNode(World *inWorld, int32 inID)
967 if (inID == -1) inID = inWorld->hw->mRecentID;
968 return inWorld->hw->mNodeLib->Get(inID);
971 Graph* World_GetGraph(World *inWorld, int32 inID)
973 if (inID == -1) inID = inWorld->hw->mRecentID;
974 Node *node = World_GetNode(inWorld, inID);
975 if (!node) return 0;
976 return node->mIsGroup ? 0 : (Graph*)node;
979 Group* World_GetGroup(World *inWorld, int32 inID)
981 Node *node = World_GetNode(inWorld, inID);
982 if (!node) return 0;
983 return node->mIsGroup ? (Group*)node : 0;
986 ////////////////////////////////////////////////////////////////////////////////
988 void World_Run(World *inWorld)
990 // run top group
991 Node *node = (Node*)inWorld->mTopGroup;
992 (*node->mCalcFunc)(node);
995 void World_Start(World *inWorld)
997 inWorld->mBufCounter = 0;
998 for (uint32 i=0; i<inWorld->mNumAudioBusChannels; ++i) inWorld->mAudioBusTouched[i] = -1;
999 for (uint32 i=0; i<inWorld->mNumControlBusChannels; ++i) inWorld->mControlBusTouched[i] = -1;
1001 inWorld->hw->mWireBufSpace = (float*)malloc(inWorld->hw->mMaxWireBufs * inWorld->mBufLength * sizeof(float));
1003 inWorld->hw->mTriggers.MakeEmpty();
1004 inWorld->hw->mNodeMsgs.MakeEmpty();
1005 inWorld->hw->mNodeEnds.MakeEmpty();
1006 inWorld->mRunning = true;
1009 SC_DLLEXPORT_C void World_Cleanup(World *world)
1011 if (!world) return;
1013 HiddenWorld *hw = world->hw;
1015 if (hw && world->mRealTime) hw->mAudioDriver->Stop();
1017 world->mRunning = false;
1019 if (world->mTopGroup) Group_DeleteAll(world->mTopGroup);
1021 world->mDriverLock->Lock(); // never unlock..
1022 if (hw) {
1023 free(hw->mWireBufSpace);
1024 delete hw->mAudioDriver;
1025 hw->mAudioDriver = 0;
1027 delete world->mNRTLock;
1028 delete world->mDriverLock;
1029 World_Free(world, world->mTopGroup);
1031 for (uint32 i=0; i<world->mNumSndBufs; ++i) {
1032 SndBuf *nrtbuf = world->mSndBufsNonRealTimeMirror + i;
1033 SndBuf * rtbuf = world->mSndBufs + i;
1035 if (nrtbuf->data) free(nrtbuf->data);
1036 if (rtbuf->data && rtbuf->data != nrtbuf->data) free(rtbuf->data);
1038 #ifndef NO_LIBSNDFILE
1039 if (nrtbuf->sndfile) sf_close(nrtbuf->sndfile);
1040 if (rtbuf->sndfile && rtbuf->sndfile != nrtbuf->sndfile) sf_close(rtbuf->sndfile);
1041 #endif
1044 free(world->mSndBufsNonRealTimeMirror);
1045 free(world->mSndBufs);
1047 free(world->mControlBusTouched);
1048 free(world->mAudioBusTouched);
1049 if (hw->mShmem) {
1050 delete hw->mShmem;
1051 } else
1052 free(world->mControlBus);
1053 free(world->mAudioBus);
1054 delete [] world->mRGen;
1055 if (hw) {
1057 #ifndef NO_LIBSNDFILE
1058 if (hw->mNRTInputFile) sf_close(hw->mNRTInputFile);
1059 if (hw->mNRTOutputFile) sf_close(hw->mNRTOutputFile);
1060 if (hw->mNRTCmdFile) fclose(hw->mNRTCmdFile);
1061 #endif
1062 free(hw->mUsers);
1063 delete hw->mNodeLib;
1064 delete hw->mGraphDefLib;
1065 delete hw->mQuitProgram;
1066 delete hw->mAllocPool;
1067 free(hw);
1069 free(world);
1073 void World_NRTLock(World *world)
1075 world->mNRTLock->Lock();
1078 void World_NRTUnlock(World *world)
1080 world->mNRTLock->Unlock();
1083 ////////////////////////////////////////////////////////////////////////////////
1085 bool getScopeBuffer(World *inWorld, int index, int channels, int maxFrames, ScopeBufferHnd &hnd)
1087 server_shared_memory_creator * shm = inWorld->hw->mShmem;
1089 scope_buffer_writer writer = shm->get_scope_buffer_writer( index, channels, maxFrames );
1091 if( writer.valid() ) {
1092 hnd.internalData = writer.buffer;
1093 hnd.data = writer.data();
1094 hnd.channels = channels;
1095 hnd.maxFrames = maxFrames;
1096 return true;
1098 else {
1099 hnd.internalData = 0;
1100 return false;
1104 void pushScopeBuffer(World *inWorld, ScopeBufferHnd &hnd, int frames)
1106 scope_buffer_writer writer(reinterpret_cast<scope_buffer*>(hnd.internalData));
1107 writer.push(frames);
1108 hnd.data = writer.data();
1111 void releaseScopeBuffer(World *inWorld, ScopeBufferHnd &hnd)
1113 scope_buffer_writer writer(reinterpret_cast<scope_buffer*>(hnd.internalData));
1114 server_shared_memory_creator * shm = inWorld->hw->mShmem;
1115 shm->release_scope_buffer_writer( writer );
1118 ////////////////////////////////////////////////////////////////////////////////
1121 inline int32 BUFMASK(int32 x)
1123 return (1 << (31 - CLZ(x))) - 1;
1126 SCErr bufAlloc(SndBuf* buf, int numChannels, int numFrames, double sampleRate)
1128 long numSamples = numFrames * numChannels;
1129 if(numSamples < 1) return kSCErr_Failed;
1130 buf->data = (float*)zalloc(numSamples, sizeof(float));
1131 if (!buf->data) return kSCErr_Failed;
1133 buf->channels = numChannels;
1134 buf->frames = numFrames;
1135 buf->samples = numSamples;
1136 buf->mask = BUFMASK(numSamples); // for delay lines
1137 buf->mask1 = buf->mask - 1; // for oscillators
1138 buf->samplerate = sampleRate;
1139 buf->sampledur = 1. / sampleRate;
1141 return kSCErr_None;
1144 #ifndef NO_LIBSNDFILE
1145 int sampleFormatFromString(const char* name);
1146 int sampleFormatFromString(const char* name)
1148 if (!name) return SF_FORMAT_PCM_16;
1150 size_t len = strlen(name);
1151 if (len < 1) return 0;
1153 if (name[0] == 'u') {
1154 if (len < 5) return 0;
1155 if (name[4] == '8') return SF_FORMAT_PCM_U8; // uint8
1156 return 0;
1157 } else if (name[0] == 'i') {
1158 if (len < 4) return 0;
1159 if (name[3] == '8') return SF_FORMAT_PCM_S8; // int8
1160 else if (name[3] == '1') return SF_FORMAT_PCM_16; // int16
1161 else if (name[3] == '2') return SF_FORMAT_PCM_24; // int24
1162 else if (name[3] == '3') return SF_FORMAT_PCM_32; // int32
1163 } else if (name[0] == 'f') {
1164 return SF_FORMAT_FLOAT; // float
1165 } else if (name[0] == 'd') {
1166 return SF_FORMAT_DOUBLE; // double
1167 } else if (name[0] == 'm' || name[0] == 'u') {
1168 return SF_FORMAT_ULAW; // mulaw ulaw
1169 } else if (name[0] == 'a') {
1170 return SF_FORMAT_ALAW; // alaw
1172 return 0;
1175 int headerFormatFromString(const char *name);
1176 int headerFormatFromString(const char *name)
1178 if (!name) return SF_FORMAT_AIFF;
1179 if (strcasecmp(name, "AIFF")==0) return SF_FORMAT_AIFF;
1180 if (strcasecmp(name, "AIFC")==0) return SF_FORMAT_AIFF;
1181 if (strcasecmp(name, "RIFF")==0) return SF_FORMAT_WAV;
1182 if (strcasecmp(name, "WAVEX")==0) return SF_FORMAT_WAVEX;
1183 if (strcasecmp(name, "WAVE")==0) return SF_FORMAT_WAV;
1184 if (strcasecmp(name, "WAV" )==0) return SF_FORMAT_WAV;
1185 if (strcasecmp(name, "Sun" )==0) return SF_FORMAT_AU;
1186 if (strcasecmp(name, "IRCAM")==0) return SF_FORMAT_IRCAM;
1187 if (strcasecmp(name, "NeXT")==0) return SF_FORMAT_AU;
1188 if (strcasecmp(name, "raw")==0) return SF_FORMAT_RAW;
1189 if (strcasecmp(name, "MAT4")==0) return SF_FORMAT_MAT4;
1190 if (strcasecmp(name, "MAT5")==0) return SF_FORMAT_MAT5;
1191 if (strcasecmp(name, "PAF")==0) return SF_FORMAT_PAF;
1192 if (strcasecmp(name, "SVX")==0) return SF_FORMAT_SVX;
1193 if (strcasecmp(name, "NIST")==0) return SF_FORMAT_NIST;
1194 if (strcasecmp(name, "VOC")==0) return SF_FORMAT_VOC;
1195 if (strcasecmp(name, "W64")==0) return SF_FORMAT_W64;
1196 if (strcasecmp(name, "PVF")==0) return SF_FORMAT_PVF;
1197 if (strcasecmp(name, "XI")==0) return SF_FORMAT_XI;
1198 if (strcasecmp(name, "HTK")==0) return SF_FORMAT_HTK;
1199 if (strcasecmp(name, "SDS")==0) return SF_FORMAT_SDS;
1200 if (strcasecmp(name, "AVR")==0) return SF_FORMAT_AVR;
1201 if (strcasecmp(name, "SD2")==0) return SF_FORMAT_SD2;
1202 if (strcasecmp(name, "FLAC")==0) return SF_FORMAT_FLAC;
1203 // TODO allow other platforms to know vorbis once libsndfile 1.0.18 is established
1204 #if defined(__APPLE__) || defined(_WIN32) || LIBSNDFILE_1018
1205 if (strcasecmp(name, "vorbis")==0) return SF_FORMAT_VORBIS;
1206 #endif
1207 if (strcasecmp(name, "CAF")==0) return SF_FORMAT_CAF;
1208 return 0;
1211 int sndfileFormatInfoFromStrings(struct SF_INFO *info, const char *headerFormatString, const char *sampleFormatString)
1213 int headerFormat = headerFormatFromString(headerFormatString);
1214 if (!headerFormat) return kSCErr_Failed;
1216 int sampleFormat = sampleFormatFromString(sampleFormatString);
1217 if (!sampleFormat) return kSCErr_Failed;
1219 info->format = (unsigned int)(headerFormat | sampleFormat);
1220 return kSCErr_None;
1223 #else // NO_LIBSNDFILE
1225 int sndfileFormatInfoFromStrings(struct SF_INFO *info, const char *headerFormatString, const char *sampleFormatString) {
1226 return kSCErr_Failed;
1229 #endif // NO_LIBSNDFILE
1231 #include "scsynthsend.h"
1233 void TriggerMsg::Perform()
1235 small_scpacket packet;
1236 packet.adds("/tr");
1237 packet.maketags(4);
1238 packet.addtag(',');
1239 packet.addtag('i');
1240 packet.addtag('i');
1241 packet.addtag('f');
1242 packet.addi(mNodeID);
1243 packet.addi(mTriggerID);
1244 packet.addf(mValue);
1246 ReplyAddress *users = mWorld->hw->mUsers;
1247 int numUsers = mWorld->hw->mNumUsers;
1248 for (int i=0; i<numUsers; ++i) {
1249 SendReply(users+i, packet.data(), packet.size());
1253 static void NodeReplyMsg_RTFree(FifoMsg* msg)
1255 //scprintf("NodeReplyMsg_RTFree()\n");
1256 World_Free(msg->mWorld, msg->mData);
1259 void NodeReplyMsg::Perform()
1261 small_scpacket packet;
1262 packet.adds(mCmdName, mCmdNameSize);
1263 packet.maketags(3 + mNumArgs);
1264 packet.addtag(',');
1265 packet.addtag('i');
1266 packet.addi(mNodeID);
1267 packet.addtag('i');
1268 packet.addi(mID);
1269 for(int i=0; i<mNumArgs; ++i) {
1270 packet.addtag('f');
1271 packet.addf(mValues[i]);
1274 ReplyAddress *users = mWorld->hw->mUsers;
1275 int numUsers = mWorld->hw->mNumUsers;
1276 for (int i=0; i<numUsers; ++i) {
1277 SendReply(users+i, packet.data(), packet.size());
1280 // Free memory in realtime thread
1281 FifoMsg msg;
1282 msg.Set(mWorld, NodeReplyMsg_RTFree, 0, mRTMemory);
1283 AudioDriver(mWorld)->SendMsgToEngine(msg);
1287 void NodeEndMsg::Perform()
1289 small_scpacket packet;
1290 switch (mState) {
1291 case kNode_Go :
1292 packet.adds("/n_go");
1293 break;
1294 case kNode_End :
1295 packet.adds("/n_end");
1296 break;
1297 case kNode_On :
1298 packet.adds("/n_on");
1299 break;
1300 case kNode_Off :
1301 packet.adds("/n_off");
1302 break;
1303 case kNode_Move :
1304 packet.adds("/n_move");
1305 break;
1306 case kNode_Info :
1307 packet.adds("/n_info");
1308 break;
1310 if (mIsGroup) {
1311 packet.maketags(8);
1312 packet.addtag(',');
1313 packet.addtag('i');
1314 packet.addtag('i');
1315 packet.addtag('i');
1316 packet.addtag('i');
1317 packet.addtag('i');
1318 packet.addtag('i');
1319 packet.addtag('i');
1320 packet.addi(mNodeID);
1321 packet.addi(mGroupID);
1322 packet.addi(mPrevNodeID);
1323 packet.addi(mNextNodeID);
1324 packet.addi(mIsGroup);
1325 packet.addi(mHeadID);
1326 packet.addi(mTailID);
1327 } else {
1328 packet.maketags(6);
1329 packet.addtag(',');
1330 packet.addtag('i');
1331 packet.addtag('i');
1332 packet.addtag('i');
1333 packet.addtag('i');
1334 packet.addtag('i');
1335 packet.addi(mNodeID);
1336 packet.addi(mGroupID);
1337 packet.addi(mPrevNodeID);
1338 packet.addi(mNextNodeID);
1339 packet.addi(mIsGroup);
1342 ReplyAddress *users = mWorld->hw->mUsers;
1343 int numUsers = mWorld->hw->mNumUsers;
1344 for (int i=0; i<numUsers; ++i) {
1345 SendReply(users+i, packet.data(), packet.size());
1349 void DeleteGraphDefMsg::Perform()
1351 GraphDef_Free(mDef);
1354 void NotifyNoArgs(World *inWorld, char *inString);
1355 void NotifyNoArgs(World *inWorld, char *inString)
1357 small_scpacket packet;
1358 packet.adds(inString);
1360 ReplyAddress *users = inWorld->hw->mUsers;
1361 int numUsers = inWorld->hw->mNumUsers;
1362 for (int i=0; i<numUsers; ++i) {
1363 SendReply(users+i, packet.data(), packet.size());
1368 bool SendMsgToEngine(World *inWorld, FifoMsg& inMsg)
1370 return inWorld->hw->mAudioDriver->SendMsgToEngine(inMsg);
1373 bool SendMsgFromEngine(World *inWorld, FifoMsg& inMsg)
1375 return inWorld->hw->mAudioDriver->SendMsgFromEngine(inMsg);
1378 SC_DLLEXPORT_C void SetPrintFunc(PrintFunc func)
1380 gPrint = func;
1384 SC_DLLEXPORT_C int scprintf(const char *fmt, ...)
1386 va_list vargs;
1387 va_start(vargs, fmt);
1389 if (gPrint) return (*gPrint)(fmt, vargs);
1390 else return vprintf(fmt, vargs);