Quick hack to make NSX-39 (Poke-Miku) USB MIDI device to speak "mbed" from mbed which acts as an USB host.

Dependencies:   FatFileSystem mbed

Fork of MIDI_BlueUSB by Radio Junk Box

Description of the project

This is quick hack to control Poke-miku (NSX-39) from mbed. The mbed acts as an USB host and controls USB MIDI device NSX-39. It speaks "mbed" if you send "s¥n" from virtual USB serial (connected to PC or Mac) or push SW connected to p21. It plays MIDI file "test.mid" on local file-system if you push SW connected to p22. You can find files that I have tested at the bottom. The standard MIDI file support is still preliminary. See TestShell.cpp for the hack. This program is derived from MIDI_BlueUSB (http://mbed.org/users/radiojunkbox/code/MIDI_BlueUSB/) by Radio Junk Box.

ポケミク(NSX-39)を無改造のままmbedから鳴らせるようにしてみました。mbedがUSB hostになって、USB MIDIデバイスのポケミクを鳴らします。mbedのバーチャルシリアル(USBシリアル)にPCからs\nを送るか、p21につないだスイッチを押すとmbedとしゃべります。p22につないだスイッチを押すと、ローカルファイルシステム(.binと同じ場所)に保存した test.mid を再生します。試したファイルは下にある test1.mid と test2.mid です。MIDIファイルのサポートはまだまだ完全とはいえません。

tested MIDI files

Video: Poke-miku speaks `mbed'

Revision:
3:31fbce33c25b
Parent:
2:7576d1327cf1
Child:
4:cd0d8ce967d8
--- a/TestShell.cpp	Sun Apr 27 01:36:30 2014 +0000
+++ b/TestShell.cpp	Sun Apr 27 07:40:40 2014 +0000
@@ -1,6 +1,7 @@
 
 /*
 Copyright (c) 2010 Peter Barrett
+Copyright (c) 2014 Noriaki Mitsunaga (SMF part)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -35,7 +36,9 @@
 #include "TestShell.h"
 
 static DigitalOut myLED1(LED1);
-static DigitalIn mySW(SWpin);
+static DigitalIn mySW1(SW1pin);
+static DigitalIn mySW2(SW2pin);
+int PlaySMF(char *fname);
 
 void printf(const BD_ADDR* addr)
 {
@@ -52,9 +55,8 @@
     u8* _hciBuffer;
     u8* _aclBuffer;
 
-    public:
-    void Open(int device, u8* hciBuffer, u8* aclBuffer)
-    {
+public:
+    void Open(int device, u8* hciBuffer, u8* aclBuffer) {
         _device = device;
         _hciBuffer = hciBuffer;
         _aclBuffer = aclBuffer;
@@ -62,29 +64,25 @@
         USBBulkTransfer(_device,0x82,_aclBuffer,MAX_ACL_SIZE,AclCallback,this);
     }
 
-    static void HciCallback(int device, int endpoint, int status, u8* data, int len, void* userData)
-    {
+    static void HciCallback(int device, int endpoint, int status, u8* data, int len, void* userData) {
         HCI* t = ((HCITransportUSB*)userData)->_target;
         if (t)
             t->HCIRecv(data,len);
         USBInterruptTransfer(device,0x81,data,MAX_HCL_SIZE,HciCallback,userData);
     }
 
-    static void AclCallback(int device, int endpoint, int status, u8* data, int len, void* userData)
-    {
+    static void AclCallback(int device, int endpoint, int status, u8* data, int len, void* userData) {
         HCI* t = ((HCITransportUSB*)userData)->_target;
         if (t)
             t->ACLRecv(data,len);
         USBBulkTransfer(device,0x82,data,MAX_ACL_SIZE,AclCallback,userData);
     }
 
-    virtual void HCISend(const u8* data, int len)
-    {
+    virtual void HCISend(const u8* data, int len) {
         USBControlTransfer(_device,REQUEST_TYPE_CLASS, 0, 0, 0,(u8*)data,len);
     }
 
-    virtual void ACLSend(const u8* data, int len)
-    {
+    virtual void ACLSend(const u8* data, int len) {
         USBBulkTransfer(_device,0x02,(u8*)data,len);
     }
 };
@@ -116,8 +114,9 @@
 void Miku(u8 chr)
 {
     static u8 d[64] = {0x04, 0xf0, 0x43, 0x79,
-                       0x04, 0x09, 0x11, 0x0a, 
-                       0x07, 0x00, 0x00, 0xf7};
+                       0x04, 0x09, 0x11, 0x0a,
+                       0x07, 0x00, 0x00, 0xf7
+                      };
     d[10] = chr;
     USBBulkTransfer(NSX39_device, NSX39_endpoint, d, 12, NULL, NULL);
 }
@@ -130,22 +129,18 @@
     int _devClass;
     BD_ADDR _addr;
     u8  _pad[2];    // Struct align
-    
+
 public:
     HIDBluetooth() : _control(0),_interrupt(0),_devClass(0) {};
 
-    bool InUse()
-    {
+    bool InUse() {
         return _control != 0;
     }
 
-    static void OnHidInterrupt(int socket, SocketState state, const u8* data, int len, void* userData)
-    {
+    static void OnHidInterrupt(int socket, SocketState state, const u8* data, int len, void* userData) {
         HIDBluetooth* t = (HIDBluetooth*)userData;
-        if (data)
-        {
-            if (t->_devClass == WII_REMOTE && data[1] == 0x30)
-            {
+        if (data) {
+            if (t->_devClass == WII_REMOTE && data[1] == 0x30) {
                 printf("================wii====================\n");
                 t->Led();
                 t->Hid();   // ask for accelerometer
@@ -153,18 +148,15 @@
             }
 
             const u8* d = data;
-            switch (d[1])
-            {
-                case 0x02:
-                {
+            switch (d[1]) {
+                case 0x02: {
                     int x = (signed char)d[3];
                     int y = (signed char)d[4];
                     printf("Mouse %2X dx:%d dy:%d\n",d[2],x,y);
                 }
                 break;
 
-                case 0x37: // Accelerometer http://wiki.wiimoteproject.com/Reports
-                {
+                case 0x37: { // Accelerometer http://wiki.wiimoteproject.com/Reports
                     int pad = (d[2] & 0x9F) | ((d[3] & 0x9F) << 8);
                     int x = (d[2] & 0x60) >> 5 | d[4] << 2;
                     int y = (d[3] & 0x20) >> 4 | d[5] << 2;
@@ -178,21 +170,19 @@
         }
     }
 
-    static void OnHidControl(int socket, SocketState state, const u8* data, int len, void* userData)
-    {
+    static void OnHidControl(int socket, SocketState state, const u8* data, int len, void* userData) {
         printf("OnHidControl\n");
         if (data)
             printHex(data,len);
     }
 
-    void Open(BD_ADDR* bdAddr, inquiry_info* info)
-    {
+    void Open(BD_ADDR* bdAddr, inquiry_info* info) {
         printf("L2CAPAddr size %d\n",sizeof(L2CAPAddr));
         _addr = *bdAddr;
         L2CAPAddr sockAddr;
         sockAddr.bdaddr = _addr;
         sockAddr.psm = L2CAP_PSM_HID_INTR;
-                printf("Socket_Open size %d\n",sizeof(L2CAPAddr));
+        printf("Socket_Open size %d\n",sizeof(L2CAPAddr));
         _interrupt = Socket_Open(SOCKET_L2CAP,&sockAddr.hdr,OnHidInterrupt,this);
         sockAddr.psm = L2CAP_PSM_HID_CNTL;
         _control = Socket_Open(SOCKET_L2CAP,&sockAddr.hdr,OnHidControl,this);
@@ -201,24 +191,21 @@
         _devClass = (info->dev_class[0] << 16) | (info->dev_class[1] << 8) | info->dev_class[2];
     }
 
-    void Close()
-    {
+    void Close() {
         if (_control)
             Socket_Close(_control);
         if (_interrupt)
             Socket_Close(_interrupt);
-       _control = _interrupt = 0;
+        _control = _interrupt = 0;
     }
 
-    void Led(int id = 0x10)
-    {
+    void Led(int id = 0x10) {
         u8 led[3] = {0x52, 0x11, id};
         if (_control)
             Socket_Send(_control,led,3);
     }
 
-    void Hid(int report = 0x37)
-    {
+    void Hid(int report = 0x37) {
         u8 hid[4] = { 0x52, 0x12, 0x00, report };
         if (_control != -1)
             Socket_Send(_control,hid,4);
@@ -236,18 +223,16 @@
     HIDBluetooth    _hids[MAX_HID_DEVICES];
 
 public:
-    void Ready()
-    {
-    printf("HIDBluetooth %d\n",sizeof(HIDBluetooth));
-         memset(_hids,0,sizeof(_hids));
+    void Ready() {
+        printf("HIDBluetooth %d\n",sizeof(HIDBluetooth));
+        memset(_hids,0,sizeof(_hids));
         Inquiry();
 
     }
 
     //  We have connected to a device
-    void ConnectionComplete(HCI* hci, connection_info* info)
-    {
-    printf("ConnectionComplete ");
+    void ConnectionComplete(HCI* hci, connection_info* info) {
+        printf("ConnectionComplete ");
         BD_ADDR* a = &info->bdaddr;
         printf(a);
         BTDevice* bt = hci->Find(a);
@@ -257,23 +242,19 @@
             hid->Open(a,&bt->_info);
     }
 
-    HIDBluetooth* NewHIDBluetooth()
-    {
+    HIDBluetooth* NewHIDBluetooth() {
         for (int i = 0; i < MAX_HID_DEVICES; i++)
             if (!_hids[i].InUse())
                 return _hids+i;
         return 0;
     }
 
-    void ConnectDevices()
-    {
+    void ConnectDevices() {
         BTDevice* devs[8];
         int count = gHCI->GetDevices(devs,8);
-        for (int i = 0; i < count; i++)
-        {
+        for (int i = 0; i < count; i++) {
             printfBytes("DEVICE CLASS",devs[i]->_info.dev_class,3);
-            if (devs[i]->_handle == 0)
-            {
+            if (devs[i]->_handle == 0) {
                 BD_ADDR* bd = &devs[i]->_info.bdaddr;
                 printf("Connecting to ");
                 printf(bd);
@@ -283,11 +264,9 @@
         }
     }
 
-    const char* ReadLine()
-    {
+    const char* ReadLine() {
         int i;
-        for (i = 0; i < 255; )
-        {
+        for (i = 0; i < 255; ) {
             USBLoop();
             int c = GetConsoleChar();
             if (c == -1)
@@ -300,53 +279,48 @@
         return _line;
     }
 
-    void Inquiry()
-    {
+    void Inquiry() {
         printf("Inquiry..\n");
         gHCI->Inquiry();
     }
 
-    void List()
-    {
-        #if 0
+    void List() {
+#if 0
         printf("%d devices\n",_deviceCount);
-        for (int i = 0; i < _deviceCount; i++)
-        {
+        for (int i = 0; i < _deviceCount; i++) {
             printf(&_devices[i].info.bdaddr);
             printf("\n");
         }
-        #endif
+#endif
     }
 
-    void Connect()
-    {
+    void Connect() {
         ConnectDevices();
     }
 
-    void Disconnect()
-    {
+    void Disconnect() {
         gHCI->DisconnectAll();
     }
 
-    void CloseMouse()
-    {
+    void CloseMouse() {
     }
 
-    void Quit()
-    {
+    void Quit() {
         CloseMouse();
     }
 
-    void Run()
-    {
-        for(;;)
-        {
+    void Run() {
+        for(;;) {
             const char* cmd = "";
             USBLoop();
             if (IsConsoleReadable())
                 cmd = ReadLine();
 
-            if ((strcmp(cmd,"s") == 0) || (mySW == 0)) {
+            if (mySW2 == 0) {
+                myLED1 = 1;
+                PlaySMF("/local/test.mid");
+                myLED1 = 0;
+            } else if ((strcmp(cmd,"s") == 0) || (mySW1 == 0)) {
                 myLED1 = 1;
                 Miku(3);
                 wait(0.001);
@@ -354,14 +328,14 @@
                 wait(0.8);
                 NoteOff(0, 72, 0x7f);
                 wait(0.001);
- 
+
                 Miku(124);
                 wait(0.001);
                 NoteOn(0, 74, 0x7f);
                 wait(0.5);
                 NoteOff(0, 74, 0x7f);
                 wait(0.001);
- 
+
                 Miku(79);
                 wait(0.001);
                 NoteOn(0, 76, 0x7f);
@@ -376,8 +350,7 @@
                 wait(0.5);
                 NoteOff(0, 76, 0x7f);
                 myLED1 = 0;
-            } else 
-            if (strcmp(cmd,"scan") == 0 || strcmp(cmd,"inquiry") == 0)
+            } else if (strcmp(cmd,"scan") == 0 || strcmp(cmd,"inquiry") == 0)
                 Inquiry();
             else if (strcmp(cmd,"ls") == 0)
                 List();
@@ -385,8 +358,7 @@
                 Connect();
             else if (strcmp(cmd,"disconnect") == 0)
                 Disconnect();
-            else if (strcmp(cmd,"q")== 0)
-            {
+            else if (strcmp(cmd,"q")== 0) {
                 Quit();
                 break;
             } else if (*cmd == 0) {
@@ -403,8 +375,7 @@
 
 static int HciCallback(HCI* hci, HCI_CALLBACK_EVENT evt, const u8* data, int len)
 {
-    switch (evt)
-    {
+    switch (evt) {
         case CALLBACK_READY:
             printf("CALLBACK_READY\n");
             gApp.Ready();
@@ -421,14 +392,13 @@
             gApp.ConnectDevices();
             break;
 
-        case CALLBACK_REMOTE_NAME:
-            {
-                BD_ADDR* addr = (BD_ADDR*)data;
-                const char* name = (const char*)(data + 6);
-                printf(addr);
-                printf(" % s\n",name);
-            }
-            break;
+        case CALLBACK_REMOTE_NAME: {
+            BD_ADDR* addr = (BD_ADDR*)data;
+            const char* name = (const char*)(data + 6);
+            printf(addr);
+            printf(" % s\n",name);
+        }
+        break;
 
         case CALLBACK_CONNECTION_COMPLETE:
             gApp.ConnectionComplete(hci,(connection_info*)data);
@@ -438,8 +408,7 @@
 }
 
 //  these should be placed in the DMA SRAM
-typedef struct
-{
+typedef struct {
     u8 _hciBuffer[MAX_HCL_SIZE];
     u8 _aclBuffer[MAX_ACL_SIZE];
 } SRAMPlacement;
@@ -468,3 +437,428 @@
     USBInit();
     gApp.Run();
 }
+
+/* For Handling Standard Midi File */
+struct midiPacket {
+    unsigned short delta;
+    unsigned short len;
+    unsigned char *p;
+};
+
+class midiHeader
+{
+private:
+    char magic[4];
+    unsigned long sz;
+public:
+    unsigned short fmt;
+    unsigned short tracks;
+    unsigned short delta;
+
+    void readFile(FILE *fp);
+    int check();
+};
+
+class midiTrack
+{
+private:
+    char magic[4];
+    unsigned char run_status;
+    unsigned char last_len;
+    unsigned char buf[128];
+public:
+    unsigned long tempo;
+    unsigned long sz;
+    unsigned char *dat;
+    unsigned char *cur;
+
+    midiTrack() {
+        tempo = 1000*1000L;
+        dat = NULL;
+    }
+    ~midiTrack() {
+        if (dat != NULL) delete dat;
+    }
+    void dump();
+    bool isEnd() {
+        if (cur < (dat + sz)) return false;
+        return true;
+    }
+    struct midiPacket next();
+    void readFile(FILE *fp);
+    void rewind() {
+        cur = dat;
+    }
+};
+
+unsigned long read32(FILE *fp);
+unsigned short read16(FILE *fp);
+
+/* ------------------------- Functions ---------------------- */
+unsigned long read32(FILE *fp)
+{
+    int i;
+    unsigned long ret = 0;
+
+    for (i = 0, ret = 0; i < 4; i ++) {
+        ret = (ret << 8) | (unsigned char)fgetc(fp);
+    }
+    return ret;
+}
+
+unsigned short read16(FILE *fp)
+{
+    int i;
+    unsigned short ret = 0;
+
+    for (i = 0, ret = 0; i < 2; i ++) {
+        ret = (ret << 8) | (unsigned char)fgetc(fp);
+    }
+    return ret;
+}
+
+int midiHeader::check()
+{
+    if (strncmp(magic, "MThd", 4) != 0 || sz != 6) {
+        return 1;
+    }
+    return 0;
+}
+
+void midiHeader::readFile(FILE *fp)
+{
+    fread(magic, 4, 1, fp);
+    sz = read32(fp);
+    fmt = read16(fp);
+    tracks = read16(fp);
+    delta = read16(fp);
+}
+
+void midiTrack::dump()
+{
+    unsigned char *p = dat, *e = dat + sz;
+    unsigned long delta;
+
+    while (p < e) {
+        delta = 0;
+        while ((*p & 0x80) != 0) {
+            delta = (delta << 7) | (*p & 0x7f);
+            p ++;
+        }
+        delta = (delta << 7) | *p;
+        p ++;
+
+        fprintf(stderr, "%lu: ", delta);
+
+        unsigned char evt = (*p & 0xf0);
+        switch (evt) {
+            case 0xd0:
+                /* 2bytes events */
+                fprintf(stderr, "%02x %02x\n", *p, *(p+1));
+                p += 2;
+                last_len = 2;
+                break;
+
+            case 0x80:
+            case 0x90:
+            case 0xa0:
+            case 0xe0:
+                /* 3bytes events */
+                fprintf(stderr, "%02x %02x %02x \n", *p, *(p+1), *(p+2));
+                p += 3;
+                last_len = 3;
+                break;
+
+            case 0xb0:
+                /* 3-4bytes events */
+                if (*(p+1) == 0x7e && *(p+2) == 0x00 && *(p+3) == 0x04) {
+                    /* 4bytes */
+                    fprintf(stderr, "%02x %02x %02x %02x \n", *p, *(p+1), *(p+2), *(p+3));
+                    p += 4;
+                } else {
+                    /* 3bytes events */
+                    fprintf(stderr, "%02x %02x %02x \n", *p, *(p+1), *(p+2));
+                    p += 3;
+                }
+                last_len = 0;
+                break;
+
+            case 0xf0:
+                if (*p == 0xff) {
+                    /* meta events */
+                    if (*(p+1) == 0x51 && *(p+2) == 0x03) {
+                        tempo = (*(p+3)<<16) | (*(p+4)<<8) | *(p+5);
+                        fprintf(stderr, "(Tempo: %lu)", tempo);
+                    }
+                    fprintf(stderr, "%02x %02x %02x ", *p, *(p+1), *(p+2));
+                    unsigned char len = *(p+2);
+                    p += 3;
+                    for (int i=0; i<len; i++) {
+                        fprintf(stderr, "%02x ", *(p++));
+                    }
+                    fprintf(stderr, "\n");
+                } else {
+                    /* SysEx event (variable length) */
+                    fprintf(stderr, "%02x %02x ", *p, *(p+1));
+                    unsigned char len = *(p+1);
+                    p += 2;
+                    for (int i=0; i<len; i++) {
+                        fprintf(stderr, "%02x ", *(p++));
+                    }
+                    fprintf(stderr, "\n");
+                }
+                last_len = 0;
+                break;
+
+            default:
+                /* running status */
+                for (int i=1; i<last_len; i++) {
+                    fprintf(stderr, "%02x ", *(p++));
+                }
+                fprintf(stderr, "\n");
+                break;
+        }
+    }
+}
+
+struct midiPacket midiTrack::next() {
+    struct midiPacket ret;
+    unsigned char *p = cur, *e = dat + sz, *q = buf;
+    unsigned long delta;
+
+    ret.len = 0;
+    if (cur >= dat + sz)
+        return ret;
+
+    delta = 0;
+    while ((*p & 0x80) != 0) {
+        delta = (delta << 7) | (*p & 0x7f);
+        p ++;
+    }
+    delta = (delta << 7) | *p;
+    p ++;
+    ret.delta = delta;
+    ret.p = buf;
+
+    while (p < e) {
+        unsigned char evt = (*p & 0xf0);
+        switch (evt) {
+            case 0xd0:
+                /* 2bytes events */
+                run_status = *p;
+                *(q++) = evt >> 4 | (0x00);
+                memcpy(q, p, 2);
+                p += 2;
+                q += 2;
+                *(q++) = 0;
+                ret.len += 4;
+                last_len = 2;
+                break;
+
+            case 0x80:
+            case 0x90:
+            case 0xa0:
+            case 0xe0:
+                run_status = *p;
+                *(q++) = evt >> 4 | (0x00);
+                memcpy(q, p, 3);
+                p += 3;
+                q += 3;
+                ret.len += 4;
+                last_len = 3;
+                break;
+
+            case 0xb0:
+                run_status = *p;
+                *(q++) = evt >> 4 | (0x00);
+                /* 3-4bytes events */
+                if (*(p+1) == 0x7e && *(p+2) == 0x00 && *(p+3) == 0x04) {
+                    /* 4bytes */
+                    memcpy(q, p, 4);
+                    p += 4;
+                    q += 4;
+                    ret.len += 5;
+                } else {
+                    /* 3bytes events */
+                    memcpy(q, p, 3);
+                    p += 3;
+                    q += 3;
+                    ret.len += 4;
+                }
+                last_len = 0;
+                break;
+
+            case 0xf0: {
+                unsigned char len;
+                if (*p == 0xff) {
+                    /* meta events */
+                    if (*(p+1) == 0x51 && *(p+2) == 0x03) {
+                        tempo = (*(p+3)<<16) | (*(p+4)<<8) | *(p+5);
+                    }
+                    len = *(p+2) + 3;
+#if 0
+                    memcpy(q, p, len);
+                    p += len;
+                    q += len;
+                    ret.len += len;
+#else
+                    /* just ignore */
+                    p += len;
+#endif
+                } else {
+                    /* SysEx event (variable length) */
+                    len = *(p+1) + 1;
+                    unsigned char buf_[127], *s;
+
+                    buf_[0] = *p;
+                    memcpy(buf_+1, p+2, len);
+                    s = buf_;
+                    p += len + 1;
+
+                    while (len > 3) {
+                        *(q++) = 0x4 | 0x00;
+                        memcpy(q, s, 3);
+                        q += 3;
+                        s += 3;
+                        ret.len += 4;
+                        len -= 3;
+                    }
+                    switch (len) {
+                        case 3:
+                            *(q++) = 0x7 | 0x00;
+                            memcpy(q, s, 3);
+                            q += 3;
+                            ret.len += 4;
+                            break;
+                        case 2:
+                            *(q++) = 0x6 | 0x00;
+                            memcpy(q, s, 2);
+                            q += 2;
+                            *(q++) = 0;
+                            ret.len += 4;
+                            break;
+                        case 1:
+                            *(q++) = 0x5 | 0x00;
+                            memcpy(q, s, 1);
+                            q += 1;
+                            *(q++) = 0;
+                            *(q++) = 0;
+                            ret.len += 4;
+                            break;
+                    }
+                }
+            }
+            last_len = 0;
+            break;
+
+            default:
+                /* running status */
+                *(q++) = run_status >> 4;
+                *(q++) =                 run_status;
+                memcpy(q, p, last_len);
+                p += last_len;
+                q += last_len;
+                ret.len += (last_len+2);
+        }
+        if (*p != 0) /* Breaks if delta is not 0 */
+            break;
+        p ++;
+    }
+    cur = p;
+
+    return ret;
+}
+
+void midiTrack::readFile(FILE *fp)
+{
+    fread(magic, 4, 1, fp);
+    sz = read32(fp);
+    dat = new unsigned char[sz];
+    fread(dat, 1, sz, fp);
+    cur = dat;
+}
+
+int PlaySMF(char *fname)
+{
+    FILE *fp;
+
+    if ((fp = fopen(fname, "r")) == NULL) {
+        fprintf(stderr, "Could not open file %s.\n", fname);
+        return 1;
+    }
+
+    struct midiHeader *mh = new midiHeader;
+    mh->readFile(fp);
+
+    if (mh->check() != 0) {
+        fprintf(stderr, "Not a MIDI file.\n");
+        delete mh;
+        fclose(fp);
+        return 1;
+    }
+
+    midiTrack* trks[32] = {NULL};
+
+    for (int i=0; i<mh->tracks; i++) {
+        trks[i] = new midiTrack;
+        trks[i]->readFile(fp);
+    }
+    fclose(fp);
+
+    for (int i=0; i<mh->tracks; i++) {
+        if (trks[i] == NULL)
+            continue;
+        trks[i]->rewind();
+    }
+
+    for (;;) {
+        struct midiPacket pkt[32];
+        bool songEnd = true;
+
+        for (int i=0; i<mh->tracks; i++) {
+            pkt[i].len = 0;
+
+            if (trks[i] == NULL)
+                continue;
+
+            if (!trks[i]->isEnd()) {
+                pkt[i] = trks[i]->next();
+                songEnd = false;
+            }
+        }
+        if (songEnd)
+            break;
+
+        for (int t=0; ; t += 10) {
+            USBLoop();
+            bool toNext = true;
+            for (int i=0; i<mh->tracks; i++) {
+                if (pkt[i].len == 0) {
+                    continue;
+                }
+                toNext = false;
+                if (t >= pkt[i].delta) {
+                    fprintf(stderr, "%d[%d] ", t, i);
+                    for (int j=0; j<pkt[i].len; j++) {
+                        fprintf(stderr, "%02x ", pkt[i].p[j]);
+                    }
+                    fprintf(stderr, "\n");
+                    USBBulkTransfer(NSX39_device, NSX39_endpoint,
+                                    pkt[i].p, pkt[i].len, NULL, NULL);
+                    pkt[i].len = 0;
+                }
+            }
+            if (toNext)
+                break;
+//    fprintf(stderr, "%f ", (float)trks[0]->tempo/(float)mh->delta/1000000.0);
+            wait((float)trks[0]->tempo/(float)mh->delta/100000.0);
+        }
+    }
+
+    for (int i=0; i<mh->tracks; i++) {
+        delete trks[i];
+    }
+    delete mh;
+
+    return 0;
+}