Browse Source

cmXScore.h/c : Added kOnsetXsFl. Added octave-shift processing. Added initial MIDI file processing.

master
kevin 8 years ago
parent
commit
655b2e6f3b
2 changed files with 277 additions and 39 deletions
  1. 270
    35
      app/cmXScore.c
  2. 7
    4
      app/cmXScore.h

+ 270
- 35
app/cmXScore.c View File

@@ -12,6 +12,7 @@
12 12
 #include "cmXScore.h"
13 13
 #include "cmTime.h"
14 14
 #include "cmMidi.h"
15
+#include "cmMidiFile.h"
15 16
 #include "cmLex.h"
16 17
 #include "cmCsv.h"
17 18
 
@@ -35,7 +36,8 @@ enum
35 36
   kPedalDnXsFl   = 0x02000,
36 37
   kPedalUpXsFl   = 0x04000,
37 38
   kPedalUpDnXsFl = 0x08000,
38
-  kMetronomeXsFl = 0x10000  // duration holds BPM
39
+  kMetronomeXsFl = 0x10000,  // duration holds BPM
40
+  kOnsetXsFl     = 0x20000   // this is a sounding note
39 41
 };
40 42
 
41 43
 struct cmXsMeas_str;
@@ -45,16 +47,18 @@ typedef struct cmXsNote_str
45 47
 {
46 48
   unsigned                    flags;    // See k???XsFl 
47 49
   unsigned                    pitch;    // midi pitch
50
+  unsigned                    velocity; // midi velocity
48 51
   cmChar_t                    step;     // A-G
49 52
   unsigned                    octave;   // sci pitch octave
50 53
   int                         alter;    // +n=sharps,-n=flats
54
+  unsigned                    staff;
51 55
   unsigned                    tick;     // 
52 56
   unsigned                    duration; // duration in ticks
53 57
   double                      rvalue;   // 1/rvalue = rythmic value (1/0.5 double whole 1/1 whole 1/2 half 1/4=quarter note, 1/8=eighth note, ...)
54 58
   const cmChar_t*             tvalue;   // text value
55 59
 
56
-  const struct cmXsVoice_str* voice;    // voice this note belongs to 
57
-  const struct cmXsMeas_str*  meas;     // measure this note belongs to
60
+  struct cmXsVoice_str*       voice;    // voice this note belongs to 
61
+  struct cmXsMeas_str*        meas;     // measure this note belongs to
58 62
 
59 63
   const cmXmlNode_t*          xmlNode;  // note xml ptr
60 64
   
@@ -63,15 +67,6 @@ typedef struct cmXsNote_str
63 67
   
64 68
 } cmXsNote_t;
65 69
 
66
-typedef struct cmXsConnect_str
67
-{
68
-  bool                    doneFl; // this tie has been completed (slurs only occur in pairs)
69
-  bool                    closeFl;// this tie was properly closed
70
-  const cmXsNote_t*       note;   // associated
71
-  struct cmXsConnect_str* nlink;  // next connected note
72
-  struct cmXsConnect_str* link;   // p->tieL,p->slurL links
73
-} cmXsConnect_t;
74
-
75 70
 typedef struct cmXsVoice_str
76 71
 {
77 72
   unsigned              id;    // Voice id
@@ -79,6 +74,17 @@ typedef struct cmXsVoice_str
79 74
   struct cmXsVoice_str* link;  // Link to other voices in this measure
80 75
 } cmXsVoice_t;
81 76
 
77
+typedef struct cmXsSpan_str
78
+{
79
+  unsigned             staff;
80
+  unsigned             number;
81
+  struct cmXsMeas_str* meas;
82
+  unsigned             tick0;
83
+  unsigned             tick1;
84
+  int                  pitch_offset;
85
+  struct cmXsSpan_str* link;
86
+} cmXsSpan_t;
87
+
82 88
 typedef struct cmXsMeas_str
83 89
 {
84 90
   unsigned number;      // Measure number
@@ -107,8 +113,7 @@ typedef struct
107 113
   cmXsPart_t* partL;
108 114
   cmCsvH_t    csvH;
109 115
   
110
-  cmXsConnect_t*  slurL;
111
-  cmXsConnect_t*  tieL;
116
+  cmXsSpan_t* spanL;
112 117
 } cmXScore_t;
113 118
 
114 119
 cmXScore_t* _cmXScoreHandleToPtr( cmXsH_t h )
@@ -262,6 +267,7 @@ cmXsRC_t  _cmXScoreParsePitch( cmXScore_t* p, const cmXmlNode_t* nnp, cmXsNote_t
262 267
   np->step  = *step;
263 268
   np->octave = octave;
264 269
   np->alter  = alter;
270
+  np->flags |= kOnsetXsFl;
265 271
 
266 272
   return rc;  
267 273
 }
@@ -400,8 +406,9 @@ cmXsRC_t _cmXScoreParseNote(cmXScore_t* p, cmXsMeas_t* meas, const cmXmlNode_t*
400 406
     if((rc = _cmXScoreParsePitch(p,nnp,note)) != kOkXsRC )
401 407
       return rc;
402 408
 
403
-  // get the note duration
404
-  cmXmlNodeUInt(nnp,&note->duration,"duration",NULL);
409
+  
410
+  cmXmlNodeUInt(nnp,&note->duration,"duration",NULL);  // get the note duration
411
+  cmXmlNodeUInt(nnp,&note->staff,"staff",NULL);        // get th staff number
405 412
   
406 413
   // is 'rest'
407 414
   if( cmXmlNodeHasChild(nnp,"rest",NULL) )
@@ -472,6 +479,45 @@ cmXsRC_t _cmXScorePushNonNote( cmXScore_t* p, cmXsMeas_t* meas, const cmXmlNode_
472 479
   return _cmXScorePushNote(p, meas, voiceId, note );
473 480
 }
474 481
 
482
+cmXsSpan_t* _cmXScoreFindOpenOctaveShift( cmXScore_t* p, unsigned staff, unsigned number )
483
+{
484
+  cmXsSpan_t* s = p->spanL;
485
+  for(; s!=NULL; s=s->link)
486
+    if( s->tick1 == -1 && s->staff == staff && s->number == number )
487
+      return s;
488
+
489
+  return NULL;
490
+}
491
+
492
+cmXsRC_t _cmXScorePushOctaveShift(cmXScore_t* p, cmXsMeas_t* meas, unsigned staff, unsigned span_number, const cmChar_t* type_str, unsigned tick)
493
+{
494
+  assert( meas != NULL);
495
+  
496
+  cmXsSpan_t* s;
497
+  if( cmTextCmp(type_str,"stop") == 0 )
498
+  {
499
+    if((s = _cmXScoreFindOpenOctaveShift(p,staff,span_number)) == NULL )
500
+      return cmErrWarnMsg(&p->err,kUnterminatedOctaveShiftXsrRC,"An illegal octave shift was encounted in meas %i.\n",meas->number);
501
+      
502
+    s->tick1 = tick;
503
+  }
504
+  else
505
+  {
506
+    s = cmLhAllocZ(p->lhH,cmXsSpan_t,1);
507
+    s->staff  = staff;
508
+    s->meas   = meas;
509
+    s->number = span_number;
510
+    s->tick0  = tick;
511
+    s->tick1  = -1;
512
+    s->pitch_offset = cmTextCmp(type_str,"up")==0 ? -12 : 12;
513
+    s->link   = p->spanL;
514
+    p->spanL  = s;    
515
+  }
516
+
517
+  return kOkXsRC;
518
+}
519
+
520
+
475 521
 cmXsRC_t  _cmXScoreParseDirection(cmXScore_t* p, cmXsMeas_t* meas, const cmXmlNode_t* dnp, unsigned tick)
476 522
 {
477 523
   cmXsRC_t           rc       = kOkXsRC;
@@ -483,9 +529,11 @@ cmXsRC_t  _cmXScoreParseDirection(cmXScore_t* p, cmXsMeas_t* meas, const cmXmlNo
483 529
   const cmChar_t*    tvalue   = NULL;
484 530
   unsigned           duration = 0;
485 531
   bool               pushFl   = true;
486
-
487
-  cmXmlNodeInt(dnp, &offset, "offset", NULL );
488
-   
532
+  unsigned           staff    = 0;
533
+  
534
+  cmXmlNodeInt( dnp, &offset, "offset", NULL );
535
+  cmXmlNodeUInt(dnp, &staff,  "staff",  NULL );
536
+  
489 537
   // if this is a metronome direction
490 538
   if((np = cmXmlSearch( dnp, "metronome", NULL, 0)) != NULL )
491 539
   {
@@ -532,6 +580,24 @@ cmXsRC_t  _cmXScoreParseDirection(cmXScore_t* p, cmXsMeas_t* meas, const cmXmlNo
532 580
     }
533 581
   }
534 582
   else
583
+
584
+  // if this is an 'octave-shift' direction  
585
+  if((np = cmXmlSearch( dnp, "octave-shift", NULL, 0)) != NULL )
586
+  {
587
+    unsigned span_number = -1;
588
+    if( cmXmlAttrUInt(np,"number",&span_number) != kOkXmlRC )
589
+      return cmErrMsg(&p->err,kSyntaxErrorXsRC,"Octave-shift is missing a 'number' attribute.");
590
+    
591
+    
592
+    if((a = cmXmlFindAttrib(np,"type")) == NULL)
593
+      return cmErrMsg(&p->err,kSyntaxErrorXsRC,"Octave-shift is missing a 'type' attribute.");
594
+    
595
+
596
+    rc = _cmXScorePushOctaveShift(p,meas,staff,span_number,a->value,tick+offset);
597
+
598
+    pushFl = false;
599
+  }
600
+  else
535 601
   {
536 602
     pushFl = false;
537 603
   }
@@ -549,6 +615,7 @@ cmXsRC_t _cmXScoreParseMeasure(cmXScore_t* p, cmXsPart_t* pp, const cmXmlNode_t*
549 615
   const cmXmlNode_t* np   = NULL;  
550 616
   unsigned           tick = 0;
551 617
   unsigned           tick0= 0;
618
+  cmXsMeas_t*        m    = NULL;
552 619
 
553 620
   // allocate the 'measure' record
554 621
   cmXsMeas_t* meas = cmLhAllocZ(p->lhH,cmXsMeas_t,1);
@@ -561,11 +628,14 @@ cmXsRC_t _cmXScoreParseMeasure(cmXScore_t* p, cmXsPart_t* pp, const cmXmlNode_t*
561 628
     pp->measL = meas;
562 629
   else
563 630
   {
564
-    cmXsMeas_t* m = pp->measL;
631
+    m = pp->measL;
565 632
     while( m->link != NULL )
566 633
       m = m->link;
567 634
     
568
-    m->link       = meas;
635
+    m->link         = meas;
636
+    meas->divisions = m->divisions;
637
+    meas->beats     = m->beats;
638
+    meas->beat_type = m->beat_type;
569 639
   }
570 640
   
571 641
   // get measure attributes node
@@ -597,7 +667,11 @@ cmXsRC_t _cmXScoreParseMeasure(cmXScore_t* p, cmXsPart_t* pp, const cmXmlNode_t*
597 667
       {
598 668
         unsigned backup;
599 669
         cmXmlNodeUInt(np,&backup,"duration",NULL);
600
-        tick -= backup;
670
+        if( backup > tick )
671
+          tick = 0;
672
+        else
673
+          tick -= backup;
674
+        
601 675
         tick0 = tick;
602 676
       }
603 677
       else
@@ -701,6 +775,7 @@ bool  _cmXScoreFindTiedNote( cmXScore_t* p, cmXsMeas_t* mp, cmXsNote_t* np )
701 775
       if( /*nnp->voice->id == np->voice->id &&*/ nnp->step == np->step && nnp->octave == np->octave )
702 776
       {
703 777
         nnp->flags |= kTieProcXsFl;
778
+        nnp->flags  = cmClrFlag(nnp->flags,kOnsetXsFl); 
704 779
 
705 780
         if( cmIsNotFlag(nnp->flags,kTieBegXsFl) )
706 781
         {
@@ -749,7 +824,148 @@ void  _cmXScoreProcessTies( cmXScore_t* p )
749 824
   printf("Found:%i Not Found:%i\n",m,n-m);
750 825
 }
751 826
 
752
-cmXsRC_t cmXScoreInitialize( cmCtx_t* ctx, cmXsH_t* hp, const cmChar_t* xmlFn )
827
+cmXsRC_t  _cmXScoreResolveOctaveShift( cmXScore_t* p )
828
+{
829
+ 
830
+  const cmXsSpan_t* s;
831
+  for(s=p->spanL; s!=NULL; s=s->link)
832
+  {
833
+    if( s->tick1 == -1)
834
+    {
835
+      cmErrWarnMsg(&p->err,kSyntaxErrorXsRC,"An unterminated octave shift span was encountered in measure %i staff=%i.",s->meas->number,s->staff);
836
+    }
837
+    else
838
+    {
839
+      cmXsMeas_t* m = p->partL->measL;
840
+      for(; m!=NULL; m=m->link)
841
+        if( m->number == s->meas->number )
842
+          break;
843
+
844
+      assert( m != NULL );
845
+
846
+      cmXsNote_t* note = m->noteL;
847
+      for(; note!=NULL; note=note->slink)
848
+        if( note->staff==s->staff && s->tick0 <= note->tick && note->tick < s->tick1 )
849
+          note->pitch += s->pitch_offset;
850
+      
851
+      
852
+    }
853
+  }
854
+
855
+  return kOkXsRC;
856
+}
857
+
858
+
859
+cmXsMeas_t* _cmXScoreNextNonEmptyMeas( cmXsPart_t* pp, cmXsMeas_t* meas )
860
+{
861
+  if( meas == NULL )
862
+  {
863
+    if( pp==NULL || pp->measL==NULL )
864
+      return NULL;
865
+    
866
+    meas = pp->measL;
867
+  }
868
+  else
869
+  {
870
+    meas = meas->link;
871
+  }
872
+
873
+  while( meas != NULL && meas->noteL == NULL )
874
+    meas=meas->link;
875
+
876
+  return meas;
877
+}
878
+
879
+cmXsNote_t* _cmXScoreNextNote( cmXsPart_t* pp, cmXsNote_t* note )
880
+{
881
+  // meas should always be valid (unless this is the first note in the score)
882
+  cmXsMeas_t* meas = note==NULL ? NULL : note->meas;
883
+  
884
+  do
885
+  { 
886
+    if( note == NULL || note->slink==NULL )
887
+    {
888
+      if((meas = _cmXScoreNextNonEmptyMeas(pp,meas)) == NULL)
889
+        return NULL;
890
+      
891
+      assert( meas->noteL != NULL );
892
+      
893
+      note = meas->noteL;
894
+    }
895
+    else
896
+    {
897
+      note = note->slink;        
898
+    }
899
+
900
+    assert( note != NULL );
901
+    
902
+    meas = note->meas;
903
+
904
+    // note is now set to a non-NULL candidate note - advance to a sounding note
905
+    while( note!=NULL && cmIsNotFlag(note->flags,kOnsetXsFl) )
906
+      note = note->slink;
907
+
908
+    // if no note was found in this measure
909
+  }while( note == NULL );
910
+     
911
+  return note;
912
+}
913
+
914
+cmXsRC_t    _cmXScoreProcessMidi(cmXScore_t* p, cmCtx_t* ctx, const cmChar_t* midiFn)
915
+{
916
+  cmXsRC_t                 rc   = kOkXsRC;
917
+  cmMidiFileH_t            mfH  = cmMidiFileNullHandle;
918
+  const cmMidiTrackMsg_t** m    = NULL;
919
+  unsigned                 mN   = 0;
920
+  unsigned                 i    = 0;
921
+  unsigned                 j    = 0;
922
+  cmXsNote_t*              note = NULL;
923
+  
924
+  if( cmMidiFileOpen(ctx, &mfH, midiFn ) != kOkMfRC )
925
+    return cmErrMsg(&p->err,kMidiFailXsRC,"The MIDI file object could not be opened from '%s'.",cmStringNullGuard(midiFn));
926
+
927
+  if( (m = cmMidiFileMsgArray(mfH)) == NULL || (mN = cmMidiFileMsgCount(mfH)) == 0 )
928
+  {
929
+    rc = cmErrMsg(&p->err,kMidiFailXsRC,"The MIDI file object appears to be empty.");
930
+    goto errLabel;
931
+  }
932
+
933
+  if((note = _cmXScoreNextNote(p->partL,NULL)) == NULL)
934
+  {
935
+    rc = cmErrWarnMsg(&p->err,kSyntaxErrorXsRC,"No MIDI processing to be done. The score appears to be empty.");
936
+    goto errLabel;
937
+  }
938
+
939
+  printf(" i     j    score    midi\n");
940
+  printf("---- ---- --- ---- --- ----\n");
941
+  
942
+  for(j=0; note!=NULL; note=_cmXScoreNextNote(p->partL,note),++j)
943
+  {
944
+    unsigned midiPitch = 0;
945
+    for(; i<mN; ++i)
946
+      if( m[i]!=NULL && cmMidiIsChStatus(m[i]->status) && cmMidiIsNoteOn(m[i]->status) && m[i]->u.chMsgPtr->d1>0 )
947
+      {
948
+        midiPitch = m[i]->u.chMsgPtr->d0;
949
+        ++i;
950
+        break;
951
+      }
952
+
953
+    char buf[6];
954
+    printf("%4i %4i %3i %4s %3i %4s\n",j,i,
955
+      note->pitch,
956
+      cmMidiToSciPitch(note->pitch,NULL,0),
957
+      midiPitch,
958
+      cmMidiToSciPitch(midiPitch,buf,5));
959
+    
960
+  }
961
+
962
+ errLabel:
963
+  cmMidiFileClose(&mfH);
964
+  return rc;
965
+}
966
+
967
+
968
+cmXsRC_t cmXScoreInitialize( cmCtx_t* ctx, cmXsH_t* hp, const cmChar_t* xmlFn, const cmChar_t* midiFn )
753 969
 {
754 970
   cmXsRC_t rc = kOkXsRC;
755 971
 
@@ -788,13 +1004,14 @@ cmXsRC_t cmXScoreInitialize( cmCtx_t* ctx, cmXsH_t* hp, const cmChar_t* xmlFn )
788 1004
 
789 1005
   _cmXScoreProcessTies(p);
790 1006
 
1007
+  //_cmXScoreResolveOctaveShift(p);
1008
+
1009
+  if( midiFn != NULL )
1010
+    _cmXScoreProcessMidi(p,ctx,midiFn);
1011
+
791 1012
   // CSV output initialize failed.
792 1013
   if( cmCsvInitialize(&p->csvH,ctx) != kOkCsvRC )
793 1014
     rc = cmErrMsg(&p->err,kCsvFailXsRC,"CSV output object create failed.");
794
-
795
-  cmXsConnect_t* c = p->tieL;
796
-  for(; c!=NULL; c=c->link)
797
-    cmErrWarnMsg(&p->err,kUnterminatedTieXsRC,"The tie begun from note on line %i was not terminated. (pitch=%i)",c->note->xmlNode->line,c->note->pitch);
798 1015
   
799 1016
  errLabel:
800 1017
   if( rc != kOkXsRC )
@@ -858,8 +1075,6 @@ void _cmXScoreReportNote( cmRpt_t* rpt, const cmXsNote_t* note )
858 1075
   if( cmIsFlag(note->flags,kMetronomeXsFl) )
859 1076
     cmRptPrintf(rpt," %i bpm",note->duration);
860 1077
 
861
-  printf("\n");
862
-  
863 1078
 }
864 1079
 
865 1080
 /*
@@ -1183,13 +1398,21 @@ void  cmXScoreReport( cmXsH_t h, cmRpt_t* rpt, bool sortFl )
1183 1398
     const cmXsMeas_t* meas = pp->measL;
1184 1399
     for(; meas!=NULL; meas=meas->link)
1185 1400
     {
1186
-      cmRptPrintf(rpt,"  %i : div:%i beat:%i beat-type:%i\n",meas->number,meas->divisions,meas->beats,meas->beat_type);
1401
+      cmRptPrintf(rpt,"  %i : div:%i beat:%i beat-type:%i (%i)\n",meas->number,meas->divisions,meas->beats,meas->beat_type,meas->divisions*meas->beats);
1187 1402
 
1188 1403
       if( sortFl )
1189 1404
       {
1190 1405
         const cmXsNote_t* note = meas->noteL;
1191 1406
         for(; note!=NULL; note=note->slink)
1192
-          _cmXScoreReportNote(rpt,note);        
1407
+        {
1408
+          _cmXScoreReportNote(rpt,note);
1409
+        
1410
+          if( note->slink!=NULL  || note->voice->id==0)
1411
+            cmRptPrintf(rpt,"\n");
1412
+          else
1413
+            cmRptPrintf(rpt," %i\n", note->tick + note->duration);  
1414
+        }
1415
+        
1193 1416
       }
1194 1417
       else
1195 1418
       {
@@ -1202,9 +1425,21 @@ void  cmXScoreReport( cmXsH_t h, cmRpt_t* rpt, bool sortFl )
1202 1425
           cmRptPrintf(rpt,"    voice:%i\n",v->id);
1203 1426
           
1204 1427
           for(; note!=NULL; note=note->mlink)
1428
+          {
1205 1429
             _cmXScoreReportNote(rpt,note);
1430
+
1431
+            if( note->mlink!=NULL || note->voice->id==0)
1432
+              cmRptPrintf(rpt,"\n");
1433
+            else
1434
+              cmRptPrintf(rpt," %i\n", note->tick + note->duration);
1435
+          }
1436
+
1206 1437
         }
1207
-      }      
1438
+      }
1439
+
1440
+      
1441
+  
1442
+
1208 1443
     }
1209 1444
   }  
1210 1445
 }
@@ -1230,16 +1465,16 @@ cmXsRC_t cmXScoreWriteMidi( cmXsH_t h, const cmChar_t* fn )
1230 1465
   }  
1231 1466
 }
1232 1467
 
1233
-cmXsRC_t cmXScoreTest( cmCtx_t* ctx, const cmChar_t* fn )
1468
+cmXsRC_t cmXScoreTest( cmCtx_t* ctx, const cmChar_t* xmlFn, const cmChar_t* midiFn )
1234 1469
 {
1235 1470
   cmXsRC_t rc;
1236 1471
   cmXsH_t h = cmXsNullHandle;
1237 1472
   
1238
-  if((rc = cmXScoreInitialize( ctx, &h, fn)) != kOkXsRC )
1473
+  if((rc = cmXScoreInitialize( ctx, &h, xmlFn, midiFn)) != kOkXsRC )
1239 1474
     return cmErrMsg(&ctx->err,rc,"XScore alloc failed.");
1240 1475
 
1241 1476
   cmXScoreWriteCsv(h,"/Users/kevin/temp/a0.csv");
1242
-  cmXScoreReport(h,&ctx->rpt,true);
1477
+  cmXScoreReport(h,&ctx->rpt,false);
1243 1478
   
1244 1479
   return cmXScoreFinalize(&h);
1245 1480
 

+ 7
- 4
app/cmXScore.h View File

@@ -13,7 +13,9 @@ extern "C" {
13 13
     kSyntaxErrorXsRC,
14 14
     kCsvFailXsRC,
15 15
     kUnterminatedTieXsRC,
16
-    kUnterminatedSlurXsRC
16
+    kUnterminatedSlurXsRC,
17
+    kUnterminatedOctaveShiftXsrRC,
18
+    kMidiFailXsRC
17 19
   };
18 20
 
19 21
   typedef cmRC_t     cmXsRC_t;
@@ -41,9 +43,10 @@ extern "C" {
41 43
   //11) Mark tied notes for skip. (done)
42 44
   //12) Determine note off locations based on ties and slurs - defer 'pedal' to player
43 45
   //13) Check that the measures are given in sorted order.
44
-  
46
+  //14) Current implementation assumes meter changes only occur at measure boundaries.
47
+  //15) Score Fixes: Add meter to bar 1, fix time errors (shown in voice report)
45 48
  
46
-  cmXsRC_t cmXScoreInitialize( cmCtx_t* ctx, cmXsH_t* hp, const cmChar_t* xmlFn );
49
+  cmXsRC_t cmXScoreInitialize( cmCtx_t* ctx, cmXsH_t* hp, const cmChar_t* xmlFn, const cmChar_t* midiFn );
47 50
   cmXsRC_t cmXScoreFinalize( cmXsH_t* hp );
48 51
 
49 52
   bool     cmXScoreIsValid( cmXsH_t h );
@@ -54,7 +57,7 @@ extern "C" {
54 57
 
55 58
   void     cmXScoreReport( cmXsH_t h, cmRpt_t* rpt, bool sortFl );
56 59
 
57
-  cmXsRC_t cmXScoreTest( cmCtx_t* ctx, const cmChar_t* fn );
60
+  cmXsRC_t cmXScoreTest( cmCtx_t* ctx, const cmChar_t* xmlFn, const cmChar_t* midiFn );
58 61
   
59 62
 #ifdef __cplusplus
60 63
 }

Loading…
Cancel
Save