aboutsummaryrefslogtreecommitdiff
path: root/desiredata/src/s_audio_asio.cpp
blob: fde2891d10323ad96dab76a7c53713b4ed2008d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
/* Copyright (c) 2004, Tim Blechmann and others
 * supported by vibrez.net
 * For information on usage and redistribution, and for a DISCLAIMER OF ALL
 * WARRANTIES, see the file, "LICENSE.txt" in this distribution.  */

/* native ASIO interface for windows
 * adapted from hostsample.cpp (ASIO SDK)
 */

#ifdef MSW
#include "windows.h" /* for application window handle */
#define IEEE754_64FLOAT 1
#else
#error This is for MS Windows (Intel CPU architecture) only!!
#endif

#ifdef _MSC_VER
#pragma warning( disable : 4091 )
#endif

#include "m_pd.h"
extern "C" {
#include "s_stuff.h"
#include "m_simd.h"
}

#include "asio.h"     /* steinberg's header file */
#include "asiodrivers.h" /* ASIODrivers class */
#include "asiosys.h"
#include "pthread.h"
#include "stdio.h" /* for sprintf */

#include <time.h>
#include <sys/timeb.h>

#include "assert.h"
#define ASSERT assert


/* fast float to integer conversion adapted from Erik de Castro Lopo */
#define	_ISOC9X_SOURCE	1
#define _ISOC99_SOURCE	1
#define	__USE_ISOC9X	1
#define	__USE_ISOC99	1
#include "math.h"

// seconds to wait for driver to respond
#define DRIVERWAIT 1

#define ASIODEBUG

/* public function prototypes */
// extern "C" void asio_open_audio(int naudioindev, int *audioindev, int nchindev,
// int *chindev, int naudiooutdev, int *audiooutdev, int nchoutdev, int *choutdev, int srate, int scheduler);
extern "C" void asio_close_audio();
extern "C" void asio_getdevs(char *indevlist, int *nindevs, char *outdevlist, int *noutdevs, int *canmulti, int maxndev, int devdescsize);
extern "C" int asio_send_dacs();

/* asio callback prototypes for traditional scheduling*/
static void asio_bufferSwitch(long db_idx, ASIOBool directprocess);
static void asio_sampleRateDidChange(ASIOSampleRate srate);
static long asio_messages(long selector, long value, void* message, double* opt);
static ASIOTime *asio_bufferSwitchTimeInfo(ASIOTime *params, long db_idx, ASIOBool directprocess);

/* asio callback prototypes for callback-based scheduling*/
static void asio_bufferSwitch_cb(long db_idx, ASIOBool directprocess);
static void asio_sampleRateDidChange_cb(ASIOSampleRate srate);
static long asio_messages_cb(long selector, long value, void* message, double* opt);
static ASIOTime *asio_bufferSwitchTimeInfo_cb(ASIOTime *params, long db_idx, ASIOBool directprocess);

static void float32tofloat32(void* inbuffer, void* outbuffer, long frames);
static void float32tofloat32_S(void* inbuffer, void* outbuffer, long frames);
static void float32tofloat64(void* inbuffer, void* outbuffer, long frames);
static void float64tofloat32(void* inbuffer, void* outbuffer, long frames);
static void float32toInt16(void* inbuffer, void* outbuffer, long frames);
static void Int16tofloat32(void* inbuffer, void* outbuffer, long frames);
static void float32toInt24(void* inbuffer, void* outbuffer, long frames);
static void Int24tofloat32(void* inbuffer, void* outbuffer, long frames);
static void float32toInt32(void* inbuffer, void* outbuffer, long frames);
static void Int32tofloat32(void* inbuffer, void* outbuffer, long frames);
static void float32toInt16_S(void* inbuffer, void* outbuffer, long frames);
static void Int16tofloat32_S(void* inbuffer, void* outbuffer, long frames);
static void float32toInt24_S(void* inbuffer, void* outbuffer, long frames);
static void Int24tofloat32_S(void* inbuffer, void* outbuffer, long frames);
static void float32toInt32_S(void* inbuffer, void* outbuffer, long frames);
static void Int32tofloat32_S(void* inbuffer, void* outbuffer, long frames);
void asio_close_audio(void);

typedef void converter_t(void* inbuffer, void* outbuffer, long frames);

/* sample converting helper functions:
 * - global send / receive functions
 * - sample conversion functions (adapted from ASIOConvertSamples.cpp */
static converter_t *asio_converter_send (ASIOSampleType format);
static converter_t *asio_converter_receive (ASIOSampleType format);

/* pointers to the converter functions of each channel are stored here */
static converter_t **asio_converter = NULL;

/* function to get sample width of data according to ASIOSampleType */
static int asio_get_samplewidth(ASIOSampleType format);

/* that's the sample width in bytes (per output channel) -
 * it's only for silence when stopping the driver.... (please find a better solution) */
static int *asio_samplewidth = NULL;


/* some local helper functions */
static void prepare_asio_drivernames();

/* system dependent helper functions */
static unsigned long get_sys_reference_time();

/* global storage */
static ASIODriverInfo * asio_driver = NULL;
static ASIOBufferInfo * asio_bufferinfo = NULL;
static ASIOChannelInfo* asio_channelinfo = NULL;
static AsioTimeInfo   * asio_timerinfo = NULL;
static ASIOCallbacks    asio_callbacks;
extern AsioDrivers *    asioDrivers; /* declared in asiodrivers.cpp */

static char ** asio_drivernames = NULL;

static ASIOSampleRate asio_srate;
static long asio_inchannels;
static long asio_outchannels;

static long asio_minbufsize;
static long asio_maxbufsize;
static long asio_prefbufsize;
static long asio_granularity;
static unsigned char asio_useoutputready;
static long asio_inputlatency;
static long asio_outputlatency;

static long asio_bufsize;                /* hardware buffer size */
static long asio_ticks_per_callback;

unsigned long sys_reftime;

/* ringbuffer stuff */
static t_sample ** asio_ringbuffer = NULL;                   /* ringbuffers */
static int asio_ringbuffer_inoffset;     /* ringbuffer(in) pointer offset for dac */
static int asio_ringbuffer_outoffset;    /* ringbuffer(out) pointer offset */
static int asio_ringbuffer_length;       /* latency - hardware latency in samples*/

/* i hope we can remove this to use callback based dsp scheduling */
static pthread_mutex_t asio_ringbuf_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t asio_ringbuf_cond = PTHREAD_COND_INITIALIZER;

/* some global definitions: */
#define ASIOVERSION 2          /* i hope we are compatible with asio 2 */

/* definitions from s_audio.c ... it should be save to use them */
#define DEVDESCSIZE   80
#define MAXNDEV   20

/* from m_sched.c: */
extern "C" double sys_time_per_dsp_tick;
extern "C" double sys_time;


/**************************************************************************/
/* some function pointers for eventual fast copying when SIMD is possible */

static void (*copyblock)(t_sample *dst,t_sample *src,int n);
static void (*zeroblock)(t_sample *dst,int n);
static t_int *(*clipblock)(t_int *w);

static void copyvec_nrm(t_sample *dst,t_sample *src,int n) { memcpy(dst,src,n*sizeof(t_sample)); }
static void zerovec_nrm(t_sample *dst,int n) { memset(dst,0,n*sizeof(t_sample)); }

/*************************************************************************/


/* open asio interface */
/* todo: some more error messages */
void asio_open_audio(int naudioindev, int *audioindev, int nchindev,
int *chindev, int naudiooutdev, int *audiooutdev, int nchoutdev, int *choutdev, int srate, int scheduler) {
	ASIOError status = ASE_OK;
	ASIOBufferInfo * buffers = NULL;
	int i;
	int channels;

#ifdef IEEE754_64FLOAT
	asio_srate=(ASIOSampleRate)srate;
#else
	sprintf(asio_srate,"%8d",srate);
#endif
	/* check, if the driver is still running */
	if(asio_driver) asio_close_audio();
	/* check, if we use the first asio device */
	prepare_asio_drivernames();
	asioDrivers->getDriverNames(asio_drivernames,MAXNDEV);

    try {
	    asioDrivers->loadDriver(asio_drivernames[*audioindev]);
    }
    catch(...) {
        error("ASIO: Error loading driver");
        goto bailout;
    }
	/* initialize ASIO */
	asio_driver = (ASIODriverInfo*) getbytes (sizeof(ASIODriverInfo));
	asio_driver->asioVersion = ASIOVERSION;
	asio_driver->sysRef = GetDesktopWindow();
	status = ASIOInit(asio_driver);
#ifdef ASIODEBUG
	post("sysRef: %x", asio_driver->sysRef);
	post("asioversion: %d", asio_driver->asioVersion);
	post("driverversion: %d", asio_driver->driverVersion);
	post("name: %s", asio_driver->name);
#endif

	switch (status) {
		if(status) post("error: %s", asio_driver->errorMessage);
	case ASE_NotPresent:    error("ASIO: ASE_NotPresent");    goto bailout;
 	case ASE_NoMemory:      error("ASIO: ASE_NoMemory");      goto bailout;
 	case ASE_HWMalfunction: error("ASIO: ASE_HWMalfunction"); goto bailout;
	}
#ifdef ASIODEBUG
	post("ASIO initialized successfully");
#endif


	/* query driver */
	status = ASIOGetChannels(&asio_inchannels, &asio_outchannels);
    if(status != ASE_OK) {
        error("ASIO: Couldn't get channel count");
        goto bailout;
    }

#ifdef ASIODEBUG
	post ("ASIOGetChannels\tinputs: %d, outputs: %d", asio_inchannels,
		  asio_outchannels);
#endif

	sys_inchannels = *chindev <= asio_inchannels ? *chindev : asio_inchannels;
	sys_outchannels = *choutdev <= asio_outchannels ? *choutdev : asio_outchannels;
	channels = sys_inchannels + sys_outchannels;
	status = ASIOGetBufferSize(&asio_minbufsize, &asio_maxbufsize, &asio_prefbufsize, &asio_granularity);
    if(status != ASE_OK) {
        error("ASIO: Couldn't get buffer size");
        goto bailout;
    }
#ifdef ASIODEBUG
	post ("ASIOGetBufferSize\tmin: %d, max: %d, preferred: %d, granularity: "
		  "%d", asio_minbufsize, asio_maxbufsize, asio_prefbufsize,
		  asio_granularity);
#endif

	/* todo: buffer size hardcoded to asio hardware */
	asio_bufsize = asio_prefbufsize;
	if (scheduler) {
		if ( asio_bufsize % sys_dacblocksize == 0 ) {
			/* use callback scheduler */
			sys_setscheduler(1);
			asio_ticks_per_callback = asio_bufsize / sys_dacblocksize;
			post("ASIO: using callback-based scheduler");
		}
	} else post("ASIO: using traditional scheduler");
	/* try to set sample rate */
	if(ASIOCanSampleRate( asio_srate ) != ASE_OK) {
		error ("Samplerate not supported, using default");
#ifdef IEEE754_64FLOAT
		asio_srate = (ASIOSampleRate)44100.0;
#else
		sprintf(&asio_srate,"%8d",44100);
#endif
		srate=44100;
	}

    status = ASIOSetSampleRate( asio_srate );
    if(status != ASE_OK)
#ifdef IEEE754_64FLOAT
	    post("Setting ASIO sample rate to %lg failed... is the device in slave sync mode?", (double)asio_srate);
#else
	    post("Setting ASIO sample rate to %s failed... is the device in slave sync mode?", asio_srate);
#endif

	if(ASIOOutputReady() == ASE_OK)
		asio_useoutputready = 1;
	else
		asio_useoutputready = 0;


	/* set callbacks */
	if(sys_callbackscheduler) {
		asio_callbacks.bufferSwitch = &asio_bufferSwitch_cb;
		asio_callbacks.sampleRateDidChange = &asio_sampleRateDidChange_cb;
		asio_callbacks.asioMessage = &asio_messages_cb;
		asio_callbacks.bufferSwitchTimeInfo = &asio_bufferSwitchTimeInfo_cb;
	} else {
		asio_callbacks.bufferSwitch = &asio_bufferSwitch;
		asio_callbacks.sampleRateDidChange = &asio_sampleRateDidChange;
		asio_callbacks.asioMessage = &asio_messages;
		asio_callbacks.bufferSwitchTimeInfo = &asio_bufferSwitchTimeInfo;
	}
	/* prepare, create and set up buffers */
	asio_bufferinfo  = (ASIOBufferInfo*) getbytes (channels*sizeof(ASIOBufferInfo));
	asio_channelinfo = (ASIOChannelInfo*) getbytes(channels*sizeof(ASIOChannelInfo));
	if (!(asio_bufferinfo && asio_channelinfo)) {
		error("ASIO: couldn't allocate buffer or channel info");
        goto bailout;
	}

	for (i = 0; i != sys_inchannels + sys_outchannels; ++i) {
		if (i < sys_outchannels) {
			asio_bufferinfo[i].isInput = ASIOFalse;
			asio_bufferinfo[i].channelNum = i;
			asio_bufferinfo[i].buffers[0] = asio_bufferinfo[i].buffers[1] = 0;
		} else {
			asio_bufferinfo[i].isInput = ASIOTrue;
			asio_bufferinfo[i].channelNum = i - sys_outchannels;
			asio_bufferinfo[i].buffers[0] = asio_bufferinfo[i].buffers[1] = 0;
		}
	}
	status = ASIOCreateBuffers(asio_bufferinfo, sys_inchannels + sys_outchannels, asio_bufsize, &asio_callbacks);
	if(status != ASE_OK) {
		error("ASIO: couldn't allocate buffers");
		goto bailout;
	}
#ifdef ASIODEBUG
	post("ASIO: buffers allocated");
#endif

	asio_converter = (converter_t **)getbytes(channels * sizeof (converter_t *));
	asio_samplewidth = (int *)getbytes((sys_outchannels + sys_inchannels) * sizeof (int));
	for (i = 0; i != sys_outchannels + sys_inchannels; ++i) {
		asio_channelinfo[i].channel = asio_bufferinfo[i].channelNum;
		asio_channelinfo[i].isInput = asio_bufferinfo[i].isInput;
		ASIOGetChannelInfo(&asio_channelinfo[i]);

#ifdef ASIODEBUG
		post("ASIO: channel %d type %d", i, asio_channelinfo[i].type);
#endif
		asio_samplewidth[i] = asio_get_samplewidth(asio_channelinfo[i].type);
        if (i < sys_outchannels) asio_converter[i] = asio_converter_send(   asio_channelinfo[i].type);
        else                     asio_converter[i] = asio_converter_receive(asio_channelinfo[i].type);
	}

	/* get latencies */
	ASIOGetLatencies(&asio_inputlatency, &asio_outputlatency);
#ifdef ASIODEBUG
	post("ASIO: input latency: %d, output latency: %d",asio_inputlatency, asio_outputlatency);
#endif


	/* we need a ringbuffer if we use the traditional scheduler */
	if (!sys_callbackscheduler) {
		/* a strange way to find the least common multiple, but works, since sys_dacblocksize (expt 2 x) */
		asio_ringbuffer_length = asio_bufsize * sys_dacblocksize;
		while ( !(asio_ringbuffer_length % sys_dacblocksize) && !(asio_ringbuffer_length % asio_bufsize)) {
			asio_ringbuffer_length /= 2;
		}
		asio_ringbuffer_length *= 2;
#ifdef ASIODEBUG
		post("ASIO: ringbuffer size: %d",asio_ringbuffer_length);
#endif
		/* allocate ringbuffer */
		asio_ringbuffer = (t_sample**) getbytes (channels * sizeof (t_sample*));
		for (i = 0; i != channels; ++i) {
			asio_ringbuffer[i] = (t_sample*)getalignedbytes(asio_ringbuffer_length * sizeof (t_sample));
			if (!asio_ringbuffer[i])
				error("ASIO: couldn't allocate ASIO ringbuffer");
			memset(asio_ringbuffer[i], 0, asio_ringbuffer_length * sizeof (t_sample));
		}
		/* initialize ringbuffer pointers */
		asio_ringbuffer_inoffset = asio_ringbuffer_outoffset = 0;
	}
    if(ASIOStart() != ASE_OK) goto bailout;
    /* set block copy/zero/clip functions */
    if(SIMD_CHKCNT(sys_dacblocksize) && simd_runtime_check()) {
        // urgh... ugly cast
        copyblock = (void (*)(t_sample *,t_sample *,int))&copyvec_simd;
        zeroblock = &zerovec_simd;
        clipblock = &clip_perf_simd;
    } else {
        copyblock = &copyvec_nrm;
        zeroblock = &zerovec_nrm;
        clipblock = &clip_perform;
    }

	post("ASIO: started");
    return;

bailout:
	if(status) post("error: %s", asio_driver->errorMessage);
    post("ASIO: couldn't start");
    asio_close_audio();
    return;
}



/* stop asio, free buffers and close asio interface */
void asio_close_audio() {
	if (asio_driver) {
    	pthread_cond_broadcast(&asio_ringbuf_cond);

	    ASIOError status;
	    int channels = sys_inchannels + sys_outchannels;
	    int i;

        if(asio_useoutputready) {
            // the DMA buffers would be played past ASIOStop
            // -> clear output buffers and notify driver
#if 0
            if(asio_ringbuffer) {
                // slow, blocking method
	            for(i = 0; i != sys_outchannels; ++i)
		            zerovec_simd(asio_ringbuffer[i], asio_ringbuffer_length);
                // wait for bufferswitch to process silence (twice)
	            pthread_cond_wait(&asio_ringbuf_cond, &asio_ringbuf_mutex);
	            for(i = 0; i != sys_outchannels; ++i) memset(asio_ringbuffer[i], 0, asio_ringbuffer_length * sizeof (t_sample));
	            pthread_cond_wait(&asio_ringbuf_cond, &asio_ringbuf_mutex);
            }
#else
            // direct method - clear both hardware buffers
            if(asio_bufferinfo && asio_samplewidth) {
                for(i = 0; i < sys_outchannels; ++i) {
                    long bytes = asio_bufsize*asio_samplewidth[i];
	                memset(asio_bufferinfo[i].buffers[0],0,bytes);
	                memset(asio_bufferinfo[i].buffers[1],0,bytes);
                }
            }
            // notify driver
		    status = ASIOOutputReady();
#endif
        }

        status = ASIOStop();
        if(status == ASE_OK) post("ASIO: stopped");
        status = ASIODisposeBuffers();
        try {
            // ASIOExit can crash if driver not really running
            status = ASIOExit();
        } catch(...) {}
        // deallocate all memory
	if(asio_ringbuffer) {
    		for(i = 0; i < channels; i++)
	    		if(asio_ringbuffer[i]) freealignedbytes(asio_ringbuffer[i],asio_ringbuffer_length * sizeof (t_sample));
            freebytes(asio_ringbuffer, channels * sizeof (t_sample *));
    		asio_ringbuffer = NULL;
        }

	if(asio_bufferinfo) {
            freebytes(asio_bufferinfo, channels * sizeof (ASIOBufferInfo));
            asio_bufferinfo = NULL;
        }
	if(asio_channelinfo) {
            freebytes(asio_channelinfo, channels * sizeof (ASIOChannelInfo));
            asio_channelinfo = NULL;
        }
	if(asio_converter) {
	    freebytes(asio_converter, channels * sizeof (converter_t *));
            asio_converter = NULL;
        }
	if(asio_samplewidth) {
		freebytes(asio_samplewidth, (sys_outchannels + sys_inchannels) * sizeof (int));
		asio_samplewidth = NULL;
        }
	freebytes(asio_driver, sizeof (ASIODriverInfo));
	asio_driver = NULL;
	/* leave the scheduler and return to traditional mode */
	if (sys_callbackscheduler) sys_setscheduler(0);
    }
}

void asio_getdevs(char *indevlist, int *nindevs, char *outdevlist, int *noutdevs, int *canmulti, int maxndev, int devdescsize) {
	prepare_asio_drivernames();
	*canmulti = 0; /* we will only support one asio device */
	*nindevs = *noutdevs = (int)asioDrivers->getDriverNames(asio_drivernames, maxndev);
	for(int i = 0; i!= *nindevs; ++i) {
		sprintf(indevlist  + i * devdescsize, "%s", asio_drivernames[i]);
		sprintf(outdevlist + i * devdescsize, "%s", asio_drivernames[i]);
	}
}

/* called on every dac~ send */
int asio_send_dacs() {
	t_sample * sp; /* sample pointer */
	int i, j;
	double timenow;
	double timeref = sys_getrealtime();
#ifdef ASIODEBUG
	if (!asio_driver) {
        static int written = 0;
		if(written%100 == 0) error("ASIO not running");
        written++;
		return SENDDACS_NO;
	}

#endif

    /* send sound to ringbuffer */
    sp = sys_soundout;
    for (i = 0; i < sys_outchannels; i++) {
	t_float lo = -1.f;
	t_float hi = 1.f;
	t_int clipargs[6];
	clipargs[1] = (t_int)sp;
	clipargs[2] = (t_int)(asio_ringbuffer[i] + asio_ringbuffer_inoffset);
	clipargs[3] = (t_int)&lo;
	clipargs[4] = (t_int)&hi;
	clipargs[5] = (t_int)sys_dacblocksize;
	clipblock(clipargs);
	zeroblock(sp,sys_dacblocksize);
	sp+=sys_dacblocksize;
    }
    /* get sound from ringbuffer */
    sp = sys_soundin;
    for (j = 0; j < sys_inchannels; j++) {
#if 0
	/* we should be able to read from the ringbuffer on a different position
	 * to reduce latency for asio buffer sizes that aren't multiples of 64... */
	int offset = asio_bufsize + sys_dacblocksize;
	offset += sys_dacblocksize - offset % sys_dacblocksize;
	if (asio_ringbuffer_inoffset < offset) {
 		memcpy(sp, asio_ringbuffer[i+j] + asio_ringbuffer_length +
 		   asio_ringbuffer_inoffset - offset, 64 *sizeof(t_sample));
	} else memcpy(sp, asio_ringbuffer[i+j] + asio_ringbuffer_inoffset - offset, 64*sizeof(t_sample));
#else
        /* working but higher latency */
	copyblock(sp, asio_ringbuffer[i+j] + asio_ringbuffer_inoffset,sys_dacblocksize);
#endif
	sp+=sys_dacblocksize;
    }
    asio_ringbuffer_inoffset += sys_dacblocksize;
#if 1
    // blocking method
    if (asio_ringbuffer_inoffset >= asio_ringbuffer_outoffset + asio_bufsize) {
        struct timespec tm;
	    _timeb tmb;
	    _ftime(&tmb);
	    tm.tv_nsec = tmb.millitm*1000000;
	    tm.tv_sec = tmb.time+DRIVERWAIT; // delay
        if(pthread_cond_timedwait(&asio_ringbuf_cond, &asio_ringbuf_mutex, &tm) == ETIMEDOUT) {
            error("ASIO: ASIO driver non-responsive! - closing");
            asio_close_audio();
	    return SENDDACS_SLEPT;
        }
	if (asio_ringbuffer_inoffset == asio_ringbuffer_length) {
		asio_ringbuffer_outoffset = 0;
		asio_ringbuffer_inoffset = 0;
	} else asio_ringbuffer_outoffset += asio_bufsize;
    }
    if ((timenow = sys_getrealtime()) - timeref > 0.002) {
	return SENDDACS_SLEPT;
    }
#else
    // non-blocking... let PD wait -> doesn't work!
    if (asio_ringbuffer_inoffset >= asio_ringbuffer_outoffset + asio_bufsize) return SENDDACS_NO;
    if (asio_ringbuffer_inoffset == asio_ringbuffer_length) {
	asio_ringbuffer_outoffset = 0;
	asio_ringbuffer_inoffset = 0;
    } else asio_ringbuffer_outoffset += asio_bufsize;
#endif
    return SENDDACS_YES;
}

/* buffer switch callback */
static void asio_bufferSwitch(long db_idx, ASIOBool directprocess) {
	ASIOTime time;
#ifdef ASIODEBUG
	static int written = 0;
	if(written  == 0) {
		post("ASIO: asio_bufferSwitch_cb");
		written = 1;
	}
#endif
	memset (&time, 0, sizeof (time));
	/* todo: do we need to syncronize with other media ??? */
	asio_bufferSwitchTimeInfo(&time, db_idx, directprocess);
}

/* sample rate change callback */
static void asio_sampleRateDidChange(ASIOSampleRate srate) {
	asio_srate = srate;
#ifdef ASIODEBUG
	post("sample rate changed");
#endif
}

/* asio messaging callback */
static long asio_messages(long selector, long value, void* message, double* opt) {
	switch (selector) {
	case kAsioSelectorSupported:
                if (value == kAsioResetRequest || value == kAsioSupportsTimeInfo) return 1L;
		return 0L;
	case kAsioEngineVersion:
		return ASIOVERSION;
	case kAsioResetRequest:
		/* how to handle this without changing the dsp scheduler? */
        post("ASIO: Reset request");
		return 1L;
	case kAsioBufferSizeChange:
		/* todo */
        post("ASIO: Buffer size changed");
		sys_reopen_audio();
		return 1L;
	case kAsioResyncRequest:
        post("ASIO: Resync request");
		return 0L;
	case kAsioLatenciesChanged:
		/* we are not handling the latencies atm */
		return 0L;
	case kAsioSupportsTimeInfo:
		return 1L;
	case kAsioSupportsTimeCode:
		/* we don't support that atm */
		return 0L;
	}
	return 0L;
}

static ASIOTime *asio_bufferSwitchTimeInfo(ASIOTime *params, long db_idx, ASIOBool directprocess) {
	/* todo: i'm not sure if we'll have to synchronize with other media ... probably yes ... */
	/* sys_reftime = get_sys_reference_time(); */
	/* perform the processing */
	int timeout = sys_dacblocksize * (float)asio_ticks_per_callback / (float) sys_dacsr * 1e6;
	if (sys_timedlock(timeout) == ETIMEDOUT) /* we're late */ {
		post("timeout %d", timeout);
		sys_log_error(ERR_SYSLOCK);
		return 0;
	}
	for (long i = 0; i < sys_outchannels + sys_inchannels; i++) {
        if(asio_converter[i])
			if (asio_bufferinfo[i].isInput != ASIOTrue) {
				asio_converter[i](asio_ringbuffer[i]+asio_ringbuffer_outoffset,
				  asio_bufferinfo[i].buffers[db_idx], asio_bufsize);
			}
			else /* these are the input channels */ {
				asio_converter[i](asio_bufferinfo[i].buffers[db_idx],
				  asio_ringbuffer[i]+asio_ringbuffer_outoffset, asio_bufsize);
			}
	}
	pthread_cond_broadcast(&asio_ringbuf_cond);
	sys_unlock();
	if(asio_useoutputready) ASIOOutputReady();
        return 0L; /* time info!!! */
}

/* get system reference time on both platforms */
static unsigned long get_sys_reference_time() {
#if WINDOWS
	return timeGetTime();
#elif MAC
	static const double twoRaisedTo32 = 4294967296.;
	UnsignedWide ys;
	Microseconds(&ys);
	double r = ((double)ys.hi * twoRaisedTo32 + (double)ys.lo);
	return (unsigned long)(r / 1000.);
#endif
}

/* sample converting helper functions */
static converter_t *asio_converter_send(ASIOSampleType format) {
#ifdef ASIODEBUG
 	/* post("ASIO: Sample Type %d", format); */
#endif
	switch (format) {
	case ASIOSTInt16LSB:  return float32toInt16;
	case ASIOSTInt24LSB:  return float32toInt24; // used for 20 bits as well
	case ASIOSTInt32LSB:  return float32toInt32;
	case ASIOSTInt16MSB:  return float32toInt16_S;
	case ASIOSTInt24MSB:  return float32toInt24_S; // used for 20 bits as well
	case ASIOSTInt32MSB:  return float32toInt32_S;
	case ASIOSTFloat32LSB:return float32tofloat32; // IEEE 754 32 bit float, as found on Intel x86 architecture
	case ASIOSTFloat32MSB:return float32tofloat32_S;
	case ASIOSTFloat64LSB:return float32tofloat64; // IEEE 754 64 bit double float, as found on Intel x86 architecture
	case ASIOSTFloat64MSB:
	// these are used for 32 bit data buffer, with different alignment of the data inside
	// 32 bit PCI bus systems can more easily used with these
	case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment
	case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment
	case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment
	case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment
	// these are used for 32 bit data buffer, with different alignment of the data inside
	// 32 bit PCI bus systems can more easily used with these
	case ASIOSTInt32MSB16: // 32 bit data with 18 bit alignment
	case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment
	case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment
	case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment
    default:
	post("Output sample Type %d not supported, yet!!!",format);
        return NULL;
    }
}

static converter_t *asio_converter_receive (ASIOSampleType format) {
#ifdef ASIODEBUG
 	/* post("ASIO: Sample Type %d", format); */
#endif
    switch (format) {
	case ASIOSTInt16LSB:  return Int16tofloat32;
	case ASIOSTInt24LSB:  return Int24tofloat32; // used for 20 bits as well
	case ASIOSTInt32LSB:  return Int32tofloat32;
	case ASIOSTInt16MSB:  return Int16tofloat32_S;
	case ASIOSTInt24MSB:  return Int24tofloat32_S; // used for 20 bits as well
	case ASIOSTInt32MSB:  return Int32tofloat32_S;
	case ASIOSTFloat32MSB:return float32tofloat32_S; // IEEE 754 32 bit float, as found on Intel x86 architecture
	case ASIOSTFloat32LSB:return float32tofloat32;   // IEEE 754 32 bit float, as found on Intel x86 architecture
	case ASIOSTFloat64LSB:return float64tofloat32;   // IEEE 754 64 bit double float, as found on Intel x86 architecture
    case ASIOSTFloat64MSB:
	// these are used for 32 bit data buffer, with different alignment of the data inside
	// 32 bit PCI bus systems can more easily used with these
	case ASIOSTInt32LSB16: // 32 bit data with 18 bit alignment
	case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment
	case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment
	case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment
	// these are used for 32 bit data buffer, with different alignment of the data inside
	// 32 bit PCI bus systems can more easily used with these
	case ASIOSTInt32MSB16: // 32 bit data with 18 bit alignment
	case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment
	case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment
	case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment
    default:
	post("Input sample Type %d not supported, yet!!!",format);
        return NULL;
    }
}

static int asio_get_samplewidth(ASIOSampleType format) {
	switch (format) {
	case ASIOSTInt16LSB:  case ASIOSTInt16MSB: return 2;
	case ASIOSTInt24LSB:  case ASIOSTInt24MSB: return 3;
	case ASIOSTFloat32LSB:case ASIOSTFloat32MSB:
	case ASIOSTInt32LSB:  case ASIOSTInt32MSB:
	// these are used for 32 bit data buffer, with different alignment of the data inside
	// 32 bit PCI bus systems can more easily used with these
	case ASIOSTInt32LSB16:
	case ASIOSTInt32LSB18:
	case ASIOSTInt32LSB20:
	case ASIOSTInt32LSB24:
	// these are used for 32 bit data buffer, with different alignment of the data inside
	// 32 bit PCI bus systems can more easily used with these
	case ASIOSTInt32MSB16:
	case ASIOSTInt32MSB18:
	case ASIOSTInt32MSB20:
	case ASIOSTInt32MSB24:
        return 4;
	case ASIOSTFloat64MSB:
	case ASIOSTFloat64LSB:
        return 8;
    default:
        post("Input sample Type %d not supported, yet!!!",format);
        return 0;
	}
}

/* dithering algo taken from Portaudio ASIO implementation */
/*************************************************************
** Calculate 2 LSB dither signal with a triangular distribution.
** Ranged properly for adding to a 32 bit integer prior to >>15.
*/
#define DITHER_BITS   (15)
#define DITHER_SCALE  (1.0f / ((1<<DITHER_BITS)-1))
inline static long triangulardither() {
        static unsigned long previous = 0;
        static unsigned long randSeed1 = 22222;
        static unsigned long randSeed2 = 5555555;
        long current, highPass;
/* Generate two random numbers. */
        randSeed1 = (randSeed1 * 196314165) + 907633515;
        randSeed2 = (randSeed2 * 196314165) + 907633515;
/* Generate triangular distribution about 0. */
        current = (((long)randSeed1)>>(32-DITHER_BITS)) + (((long)randSeed2)>>(32-DITHER_BITS));
 /* High pass filter to reduce audibility. */
        highPass = current - previous;
        previous = current;
        return highPass;
}

/* sample conversion functions */

#define SCALE_INT16 32767.f       /* (- (expt 2 15) 1) */
#define SCALE_INT24 8388607.f     /* (- (expt 2 23) 1) */
#define SCALE_INT32 2147483647.f  /* (- (expt 2 31) 1) */

/* Swap LSB to MSB and vice versa */
inline __int32 swaplong(__int32 v) {
    return ((v>>24)&0xFF)|((v>>8)&0xFF00)|((v&0xFF00)<<8)|((v&0xFF)<<24);
}

inline __int16 swapshort(__int16 v) {
    return ((v>>8)&0xFF)|((v&0xFF)<<8);
}

/* todo: check dithering */

static void float32tofloat32(void* inbuffer, void* outbuffer, long frames) {
    if(SIMD_CHECK2(frames,inbuffer,outbuffer)) copyvec_simd((float *)outbuffer,(float *)inbuffer,frames);
    else memcpy (outbuffer, inbuffer, frames* sizeof (float));
}

static void float32tofloat32_S(void* inbuffer, void* outbuffer, long frames) {
    __int32 *in = (__int32 *)inbuffer;
    __int32* out = (__int32*)outbuffer;
    while (frames--) *out++ = swaplong(*(in++));
}

static void float32tofloat64(void* inbuffer, void* outbuffer, long frames) {
    const float *in = (const float *)inbuffer;
    double* out = (double*)outbuffer;
    while (frames--) *(out++) = *(in++);
}

static void float64tofloat32(void* inbuffer, void* outbuffer, long frames) {
    const double *in = (const double *)inbuffer;
    float *out = (float *)outbuffer;
    while (frames--) *(out++) = *(in++);
}

static void float32toInt16(void* inbuffer, void* outbuffer, long frames) {
    const float *in = (const float *)inbuffer;
    __int16* out = (__int16*)outbuffer;
    while (frames--) {
	float o = *(in++) * SCALE_INT16 + triangulardither() * DITHER_SCALE;
	__int16 lng = lrintf(o);
	*out++ = lng ;
    }
}

static void Int16tofloat32(void* inbuffer, void* outbuffer, long frames) {
    const __int16* in = (const __int16*)inbuffer;
    float *out = (float *)outbuffer;
    while (frames--) *(out++) = (float)*(in++) * (1.f / SCALE_INT16);
}

static void float32toInt24(void* inbuffer, void* outbuffer, long frames) {
    const float *in = (const float *)inbuffer;
    __int32* out = (__int32*)outbuffer;
    while (frames--) {
        float o = *(in++) * SCALE_INT24;
	__int32 intg = (__int32)lrintf(o);
	*(out++) = intg;
    }
}

static void Int24tofloat32(void* inbuffer, void* outbuffer, long frames) {
    const __int32* in = (const __int32*)inbuffer;
    float *out = (float *)outbuffer;
    while (frames--) *(out++) = (float)*(in++) * (1.f / SCALE_INT24);
}

static void float32toInt32(void* inbuffer, void* outbuffer, long frames) {
    const float *in = (const float *)inbuffer;
    __int32* out = (__int32*)outbuffer;
    while (frames--) {
        float o = (float)*(in++) * SCALE_INT32 + triangulardither() * DITHER_SCALE;
	*out++ = lrintf(o);
    }
}

static void Int32tofloat32(void* inbuffer, void* outbuffer, long frames) {
    const __int32* in = (const __int32*)inbuffer;
    float *out = (float *)outbuffer;
    while (frames--) *(out++) = (float)*(in++) * (1.f / SCALE_INT32);
}

static void float32toInt16_S(void* inbuffer, void* outbuffer, long frames) {
    const float *in = (const float *)inbuffer;
    __int16* out = (__int16*)outbuffer;
    while (frames--) {
	float o = (float)*(in++) * SCALE_INT16 + triangulardither() * DITHER_SCALE;
	__int16 reverse = (__int16)lrintf(o);
        *out++ = swapshort(reverse);
    }
}

static void Int16tofloat32_S(void* inbuffer, void* outbuffer, long frames) {
    const __int16* in = (const __int16*)inbuffer;
    float *out = (float *)outbuffer;
    while (frames--) *(out++) = (float)swapshort(*(in++)) * (1.f / SCALE_INT16);
}

static void float32toInt24_S(void* inbuffer, void* outbuffer, long frames) {
    const float *in = (const float *)inbuffer;
    char* out = (char*)outbuffer;
    while (frames--) {
        float o = (float)*(in++) * SCALE_INT24;
	__int32 reverse = (__int32)lrintf(o);
        out[2] = ((char *)&reverse)[0];
        out[1] = ((char *)&reverse)[1];
        out[0] = ((char *)&reverse)[2];
        out += 3;
    }
}

static void Int24tofloat32_S(void* inbuffer, void* outbuffer, long frames) {
    const char* in = (const char*)inbuffer;
    float *out = (float *)outbuffer;
    __int32 d = 0;
    while (frames--) {
        ((char *)&d)[1] = in[2];
        ((char *)&d)[2] = in[1];
        ((char *)&d)[3] = in[0];
	*(out++) = (float)d * (1.f / SCALE_INT24);
        in += 3;
    }
}

static void float32toInt32_S(void* inbuffer, void* outbuffer, long frames) {
    const float *in = (const float *)inbuffer;
    __int32* out = (__int32*)outbuffer;
    while (frames--) {
    float o = (float)*(in++) * SCALE_INT32 + triangulardither() * DITHER_SCALE;
	__int32 reverse = (__int32)lrintf(o);
        *out++ = swaplong(reverse);
    }
}

static void Int32tofloat32_S(void* inbuffer, void* outbuffer, long frames) {
    const __int32* in = (const __int32*)inbuffer;
    float *out = (float *)outbuffer;
    while (frames--) *(out++) = (float)swaplong(*(in++)) * (1.f / SCALE_INT32);
}


/* some local helper functions */
static void prepare_asio_drivernames() {
	if (!asio_drivernames) {
		asio_drivernames = (char**)getbytes(MAXNDEV * sizeof(char*));
		for (int i = 0; i!= MAXNDEV; ++i) {
			asio_drivernames[i] = (char*)getbytes (32 * sizeof(char));
		}
	}
	/* load the driver  */
	if (!asioDrivers) asioDrivers = new AsioDrivers();
	return;
}

/* callback-based scheduling callbacks: */

/* buffer switch callback */
static void asio_bufferSwitch_cb(long db_idx, ASIOBool directprocess) {
	ASIOTime time;
#ifdef ASIODEBUG
	static int written = 0;
	if(written  == 0) {
		post("ASIO: asio_bufferSwitch_cb");
		written = 1;
	}
#endif
	memset (&time, 0, sizeof (time));
	/* todo: do we need to syncronize with other media ??? */
	asio_bufferSwitchTimeInfo_cb(&time, db_idx, directprocess);
}

/* sample rate change callback */
static void asio_sampleRateDidChange_cb(ASIOSampleRate srate) {
	asio_sampleRateDidChange(srate);
}

/* asio messaging callback */
static long asio_messages_cb(long selector, long value, void* message, double* opt) {
	return asio_messages(selector, value, message, opt);
}

static ASIOTime *asio_bufferSwitchTimeInfo_cb(ASIOTime *params, long db_idx, ASIOBool directprocess) {
	/* todo: i'm not sure if we'll have to synchronize with other media ... probably yes ... */
	/* perform the processing */
	int timeout = sys_dacblocksize * (float)asio_ticks_per_callback / (float) sys_dacsr * 1e6;
	if (sys_timedlock(timeout) == ETIMEDOUT)
		/* we're late ... lets hope that jack doesn't kick us out */
		return 0;

	for (int j = 0; j != asio_ticks_per_callback; j++) {
		t_sample * sp = sys_soundin;
		/* get sounds from input channels */
		for (long i = 0; i < sys_outchannels + sys_inchannels; i++) {
			if(asio_converter[i])
				if (asio_bufferinfo[i].isInput == ASIOTrue) {
					asio_converter[i]((char*)asio_bufferinfo[i].buffers[db_idx] +
						asio_samplewidth[i] * j *sys_dacblocksize, sp, sys_dacblocksize);
					sp += sys_dacblocksize;
				}
		}
		/* run dsp */
		sched_tick(sys_time + sys_time_per_dsp_tick);
		sp = sys_soundout;
		/* send sound to hardware */
		for (long i = 0; i < sys_outchannels + sys_inchannels; i++) {
			if (asio_bufferinfo[i].isInput != ASIOTrue) {
				/* clip */
				t_float lo = -1.f;
				t_float hi = 1.f;
				t_int clipargs[6];
				clipargs[1] = clipargs[2] = (t_int)sp;
				clipargs[3] = (t_int)&lo;
				clipargs[4] = (t_int)&hi;
				clipargs[5] = (t_int)sys_dacblocksize;
				clipblock(clipargs);
				/* send */
				if(asio_converter[i])
					asio_converter[i](sp, (char*)asio_bufferinfo[i].buffers[db_idx]
						+ asio_samplewidth[i] * j *sys_dacblocksize, sys_dacblocksize);
				zeroblock(sp,sys_dacblocksize);
				sp += sys_dacblocksize;
			}
		}
	}
	if(asio_useoutputready) ASIOOutputReady();
	sys_unlock();
    return 0L; /* time info!!! */
}

t_audioapi asio_api = {
    asio_open_audio,
    asio_close_audio,
    asio_send_dacs,
    asio_getdevs,
};