/* Air Harp: * Capture video frames and perform background subtraction to * isolate moving objects (people). Monitor various regions * of the camera's visual field for activity, playing notes or * modifying midi channels based on active areas */ #include #include #include #include #include #include #include #include #include #include struct vPixel { char a; char r; char g; char b; } vFrame[720][720]; /* big in case you want to go full size... */ class camaraframe { public: VLServer svr; VLPath path; VLNode src, drn; VLControlValue val; VLBuffer buffer; VLInfoPtr info; char *dataPtr; int xsize; int ysize; int c, have_frame; long win; camaraframe(){ printf("initing cameraframe\n"); // GLXconfig t1= {GLX_NORMAL, GLX_RGB, NULL}; #if 0 if(!GLXlink(d.display, GLXgetconfig(d.display,d.screen_num,&t1))) exit(0); #endif // if(printf("f%df\n",GLXwinset(d.display,d.win))) printf("couldn't\n"); printf("passed winset\n"); foreground(); /* Connect to the daemon */ if (!(svr = vlOpenVideo(""))) exit(0); /* Set up a drain node in memory */ drn = vlGetNode(svr, VL_DRN, VL_MEM, VL_ANY); /* Set up a source node on any video source */ src = vlGetNode(svr, VL_SRC, VL_VIDEO, VL_ANY); /* Create a path using the first device that will support it */ path = vlCreatePath(svr, VL_ANY, src, drn); /* Set up the hardware for and define the usage of the path */ if ((vlSetupPaths(svr, (VLPathList)&path, 1, VL_SHARE, VL_SHARE)) < 0) exit(0); /* Set the packing to RGB */ val.intVal = VL_PACKING_RGB_8; vlSetControl(svr, path, drn, VL_PACKING, &val); /* Set screen size to fraction of normal */ val.fractVal.numerator = 1; val.fractVal.denominator = 2; vlSetControl(svr, path, drn, VL_ZOOM, &val); /* Get the video size */ vlGetControl(svr, path, drn, VL_SIZE, &val); xsize = val.xyVal.x; ysize = val.xyVal.y; /* Set up and open a GL window to display the data */ prefsize(xsize, ysize ); win = winopen("Filter Window"); RGBmode(); pixmode(PM_TTOB, 1); gconfig(); /* Create and register a buffer for 4 frames */ buffer = vlCreateBuffer(svr, path, drn, 4 ); if (buffer == NULL ) exit(0); vlRegisterBuffer(svr, path, drn, buffer); /* Begin the data transfer */ if (vlBeginTransfer(svr, path, 0, NULL)) exit(0); have_frame = 0; printf("done initing cameraframe\n"); } void display(){ if(have_frame == 0) return; lrectwrite(0, 0, xsize-1, ysize-1, (ulong *)dataPtr); } void getFrame(){ if(have_frame == 1) putFrame(); do { info = vlGetNextValid(svr, buffer); } while (!info); dataPtr = (char*) vlGetActiveRegion(svr, buffer, info); have_frame = 1; } void putFrame(){ if(have_frame) vlPutFree(svr, buffer); have_frame = 0; } ~camaraframe(){ vlEndTransfer(svr, path); /* Cleanup before exiting */ vlDeregisterBuffer(svr, path, drn, buffer); vlDestroyBuffer(svr, buffer); vlDestroyPath(svr, path); vlCloseVideo(svr); } }; void main(int argc, char** argv){ char *temp; char *display, *displayPtr, *displayEnd; char *currentPtr; char *background,*backgroundPtr; size_t cam1datasize; camaraframe cam1; /* this opens its own window */ float test; int val,note; int i,j,k,n; /* loop counters */ int keyboard[80]; char *intfcname; /* Name of the interface to send to */ MDport port; /* Port connected to named interface */ int num_intfcs; /* Number of configured interfaces */ MDevent event; /* A MIDI event structure */ int channel = 0; /* MIDI channel to play on */ unsigned long long now; /* UST of current time */ long long stamp = 0; /* The timestamp for the next event */ /* setup video window size */ cam1datasize = cam1.xsize * cam1.ysize * 4; setpriority(PRIO_PROCESS, 0, -20); printf("XY size %d %d\n", cam1.xsize, cam1.ysize); /* setup video memory */ display = ( char*)calloc(cam1datasize,sizeof( long)); if(display == NULL) { fprintf(stderr,"Unable to alloc display\n"); exit(0); } displayEnd = display + cam1datasize; /* Initialize the MIDI library. */ num_intfcs = mdInit(); if (num_intfcs == 0) { fprintf(stderr, "No MIDI interfaces configured.\n"); exit(1); } /* Open default (internal) midi interface */ intfcname = NULL; if ((port = mdOpenOutPort(intfcname)) == NULL) { fprintf(stderr, "Cannot open MIDI interface '%s'for output.\n", intfcname); exit(1); } /* Reckon time in ticks relative to the current time. */ mdSetStampMode(port, MD_RELATIVETICKS); /* We now establish the correspondence between real-time (measured * in UST) and ticks. We do this by figuring out what time it is * now and telling the system that the tick origin is now. */ dmGetUST(&now); mdSetStartPoint(port, (long long) now, 0); /* Make the duration of one tick be 30 milliseconds (which is equivalent * to 30,000 microseconds, which is actually the unit taken by mdSetTempo) */ mdSetDivision(port, 1); mdSetTempo(port, 30000); /* send instrument type */ event.stamp = stamp; stamp++; event.msg[0] = MD_PROGRAMCHANGE|channel; event.msg[1] = 9; /* glockenspiel - instrument #10 (numbering starts at 0)*/ mdSend(port, &event, 1); /* prepare to enter backsub loop */ cam1.getFrame(); background = cam1.dataPtr; cam1.have_frame = 0; while(1) { cam1.getFrame(); currentPtr = cam1.dataPtr; backgroundPtr = background; displayPtr = display; currentPtr++; /* skip alpha channel --> ARGB data packing */ backgroundPtr++; displayPtr++; while(displayPtr < displayEnd) { /* do the RGB background subtraction */ *(displayPtr) = (char)abs(*(backgroundPtr) - *(currentPtr)); currentPtr++; backgroundPtr++; displayPtr++; *(displayPtr) = (char)abs(*(backgroundPtr) - *(currentPtr)); currentPtr++; backgroundPtr++; displayPtr++; *(displayPtr) = (char)abs(*(backgroundPtr) - *(currentPtr)); currentPtr+=2; /* skip alpha channel */ backgroundPtr+=2; displayPtr+=2; } /* end while */ /* populate vFrame matrix */ displayPtr = display; for(i = 0 ; i < cam1.ysize; i++) for (j = 0; j < cam1.xsize; j++) { vFrame[i][j].a = *displayPtr; displayPtr++; vFrame[i][j].r = *displayPtr; displayPtr++; vFrame[i][j].g = *displayPtr; displayPtr++; vFrame[i][j].b = *displayPtr; displayPtr++; } /* end for */ /* Dump current video frame to screen */ lrectwrite(0, 0, cam1.xsize-1, cam1.ysize-1, (ulong *)display); /* Anylize video for motion: * collect active pixels in note regions on the left edge of video frame * as if the keyboard were turned on its side... */ /* clear keyboard array */ for(i = 0; i < 80; i++) { keyboard[i] = 0; } n = 0; /* start loop - 80 keys (not 88), each 4 pixels tall, by 20 pixels wide * so sue me - 80 was a nicer number to work with... */ for(k = 0; k < cam1.xsize; k+=4) { for(i = k; i < k+4; i++) { for (j = 0; j < 20; j++) { test = (vFrame[i][j].r + vFrame[i][j].g + vFrame[i][j].b)/3.0; if (test > 30.0) keyboard[n]++; } /* end for */ } /* end for */ n++; } /* end for */ /* find note region with largest number of active pixels - play that note... */ val = 0; note = 0; for(i = 0; i < 80; i++) { if (keyboard[i] > val) { val = keyboard[i]; note = i; } /* end if */ } /* end for */ if (val > 0) { /* send note on */ event.stamp = stamp; stamp++; event.msg[0] = MD_NOTEON|channel; event.msg[1] = note; event.msg[2] = 100; mdSend(port, &event, 1); /* Now send a note off */ event.stamp = stamp; stamp += 2; event.msg[0] = MD_NOTEOFF|channel; event.msg[1] = note; event.msg[2] = 0; mdSend(port, &event, 1); /* truth be known, a NOTEOFF is just a NOTEON with a zero velocity... */ } /* end if */ /* update video frames - copy current into background and * setup for next iteration. */ temp = cam1.dataPtr; cam1.dataPtr = background; background = temp; } /* end while */ } /* end main */