(clips_matrix[0].length/2)){ lineMap.push(x); } } //lineMap now contains all the rows that are probably lines //Unless the camera is too far away, there should be at least 2 or 3 rows for a staff var lineFlag:Boolean=true; var lineScale:int =1; var lineScalehigh:int=0; for (var x:int = 0;xlineScalehigh)lineScalehigh=lineScale; lineScale++; } else if(lineScale>1){ lineFlag=false; } } // printConsole("Row " +lineMap[x]+" is part of a staff\n"); } // printConsole("Staff is covered by " +lineScale+" picture elements\n"); var staffFoundFlag:Boolean = false; //Determine the scale for (var x:int = 0;x", thresholds, color, maskColor, false); var staffBitmap:Bitmap = new Bitmap(staffBitmapData); var staffHolder:UIComponent = new UIComponent(); staffHolder.addChild(staffBitmap); var diameter:int = (clips_matrix[0][0].height)*scale/4; var radius:int = diameter/2; //a note is generally 1/4th the height of the staff (B step is center) var multiplier:int = 8; //double diameter for checks //fiddle with this staffBitmapData = removeWholeLines(staffBitmapData,scale); var result:BitmapData = new BitmapData(diameter+multiplier,diameter+multiplier);//(diameter, // diameter, // false, // 0x808080); //this approach is VERY brute force for(var y:int = 0; y0.25){ //Add the note to the staff object //clip height * (scale/4) is the magic scaling distance (4 spaces between staff lines) //Note : For semitone scaling, multiply the magic distance by 2, this decreases accuracy by a lot but allows semitones var notePitch:int = y/(clips_matrix[0][0].height*(scale/4)); printConsole ("notePitch is "+notePitch); staff.addNote(4,notePitch); printConsole("this circle may be a note, it's at "+x+","+y+" with insidecounter at "+insideCounter+" and outside at "+outsideCounter); //following code draws copies of detected notes on top of the original webcam feed, but doesn't line up with the original image due to size constraints //this dummpyholder is never used var tempBitmap:Bitmap = new Bitmap(result.clone()); var tempSprite:Sprite = new Sprite(); tempSprite.addChild(tempBitmap); tempSprite.x=x+diameter+multiplier/2; tempSprite.y=y+diameter+multiplier/2; var tempHolder:UIComponent = new UIComponent(); tempHolder.addChild(tempSprite); //pnlWebcam.addChild(tempHolder); var circle:Shape = new Shape(); var xPos:Number = multiplier/2+x+diameter/2; var yPos:Number = multiplier/2+y+diameter/2; var radius2:Number = diameter/2; circle.graphics.beginFill(0x20FF8800); circle.graphics.drawCircle(xPos, yPos, radius2); staffHolder.addChild(circle); } } } pnlSnapshot.addChild(staffHolder); currentStaff = staff; } public function detectLines():void{ /** * Takes a snapshot from the camera feed and samples the bitmap * **/ var bitmap:Bitmap = takeSnapshot(); globalSnapshot = bitmap; //Apply convolution filter var bitmapdata:BitmapData = applyFilterToBitmapData(bitmap.bitmapData); var interpretbitmap:Bitmap = new Bitmap(bitmapdata); searchGlyphs(interpretbitmap,threshold); } public function searchGlyphs(interpretbitmap:Bitmap,thresholdparam:int):void{ var data:BitmapData = interpretbitmap.bitmapData; clips_matrix = new Array(); clips_array = new Array(); var rows:int=20; var columns:int=20; var ww:int=Math.ceil(interpretbitmap.width/rows); var hh:int=Math.ceil(interpretbitmap.height/columns); var mat:Matrix; var bitmap_data:BitmapData; var point:Point; var clip:actionscript_omr_lib_PictureElement; var interpretHolder:UIComponent = new UIComponent(); for(var i:int=0;i < columns;i++) { clips_matrix[i] = new Array(); for(var j:int=0;j < rows;j++) { bitmap_data=new BitmapData(ww,hh,true,0xFFFFFFFF); mat=interpretbitmap.transform.matrix; mat.translate(-ww*j,-hh*i); bitmap_data.draw(interpretbitmap,mat); clip=new actionscript_omr_lib_PictureElement(); clips_array.push(clip); interpretHolder.addChild(clip); //uncomment next line to show white lines around clips, good for seeing sample size // point=new Point(interpretbitmap.x+ww*j+1*j,interpretbitmap.y+hh*i+1*i); point=new Point(interpretbitmap.x+ww*j,interpretbitmap.y+hh*i); var bitmap:Bitmap=new Bitmap(bitmap_data); var pt:Point = new Point(0, 0); var rect:Rectangle = new Rectangle(0, 0, ww, hh); var threshold:uint = (thresholdparam*(16*16))+thresholdparam*(16*16*16*16)+thresholdparam+4278190080; var color:uint = 0x00000000; var maskColor:uint = 0xFFFFFFF; var pixelCount:int=bitmap_data.threshold(bitmap_data, rect, pt, ">", threshold, color, maskColor, false); //Count pixels, if there are more than thirty over the threshold, the clip is relevant if(pixelCount>30){ //todo If the whole screen is 'musically relevant' there is no paper //check for staff lines if(checkForLines(bitmap_data)){ //Add green overlay for visual presentation var overlay_data3:BitmapData = new BitmapData(ww,hh,true,0xFF00FF00); clip.addChild(new Bitmap(overlay_data3)); //Tell clip it's a line clip.setLine(); } //compare to bitmap for glyp search -shouldn't be else clause- //TODO not currently using this //else if(compareBitmaps(new Bitmap(bitmap_data),glyph.getQuaver())>20){ //Add blue overlay for visual presentation // var overlay_data2:BitmapData = new BitmapData(ww,hh,true,0xFF0000FF); // clip.addChild(new Bitmap(overlay_data2)); //Tell clip it's a note // clip.setNote(); //} else{ //Nothing fancy about this potential //Add red overlay for visual presentation var overlay_data:BitmapData = new BitmapData(ww,hh,true,0xFFFF0000); clip.addChild(new Bitmap(overlay_data)); } } //add the clip clip.addChild(bitmap); clip.x=point.x; clip.y=point.y; //add the clip to clips matrix clips_matrix[i][j] = clip; //end for loop j } //end for loop i } //Add the overlays and clips to Interpretation panel pnlInterpretation.removeAllChildren(); pnlInterpretation.addChild(interpretHolder); } public function removeWholeLines(bitmap:BitmapData,scale:int):BitmapData{ //removes whole lines from bitmap for(var x:int = 0;x<=bitmap.height;x++){ //do in chunks of 10 ---arbitrary number, make variable //for(var y:int = 0;y<=bitmap.height/5;y++){ // //} var rect:Rectangle = new Rectangle(0,x,bitmap.width,1); var array1:ByteArray = bitmap.getPixels(rect); array1.position=0; var flag:Boolean=false; var counter:int=0; var highcounter:int=0; var linelimit:int = scale*3; while(array1.bytesAvailable){ if(flag){counter++; if(counter>linelimit){ array1.position=0; while(array1.bytesAvailable){ array1.writeUnsignedInt(0xFF000000); } } } if(array1.bytesAvailable){ if(array1.readUnsignedInt()==0x00000000){ flag=true } else{ flag=false; if(highcounterminimum){} } return bitmap; } public function removeLines(bitmap:BitmapData):BitmapData{ //removes lines from bitmap for(var x:int = 0;x<=bitmap.height;x++){ //do in chunks of 10 ---arbitrary number, make variable //for(var y:int = 0;y<=bitmap.height/5;y++){ // //} var rect:Rectangle = new Rectangle(0,x,bitmap.width,1); var array1:ByteArray = bitmap.getPixels(rect); array1.position=0; var flag:Boolean=false; var counter:int=0; var highcounter:int=0; var linelimit = 20; while(array1.bytesAvailable){ if(flag){counter++; if(counter>linelimit){ array1.position-=linelimit*4; //times four, Uint is 32 bytes for(var x:int = 0; xminimum){} } return bitmap; } public function checkForLines(bitmap:BitmapData):Boolean{ //Returns >0 score if an uninterrupted line longer than minimum was found //create scanlines //TODO something wrong with the first line scanned var minimum:int = 10; var score:int = 0; for(var x:int = 0;x<=bitmap.height;x++){ var rect:Rectangle = new Rectangle(0,x,bitmap.width,1); var array1:ByteArray = bitmap.getPixels(rect); array1.position=0; var flag:Boolean=false; var counter:int=0; var highcounter:int=0; while(array1.bytesAvailable){ //flag never true, color matched probably incorrect !!!!!!!!!!!!!!!!!!!!!!!!!!!!<<<<------------ na voetbal if(flag){counter++;} if(array1.readUnsignedInt()==0x00000000){ flag=true } else{ flag=false; if(highcounterminimum)score++ } return score>0; } public function compareBitmaps(bitmap1:Bitmap,bitmap2:Bitmap):int{ //cue bitmap comparison algorithm, I wish flex had this built in if(bitmap1.height != bitmap2.height || bitmap1.width != bitmap2.width){ //TODO : Scale printConsole("bitmaps are not of equal size"); return 0; } var score:int=0; var rect:Rectangle = new Rectangle(0,0,bitmap1.width,bitmap1.height) var array1:ByteArray = bitmap1.bitmapData.getPixels(rect); var array2:ByteArray = bitmap2.bitmapData.getPixels(rect); //printConsole("array length :"+array1.length+"array position"+array1.position); array1.position=0; array2.position=0; while(array1.bytesAvailable){ //printConsole("byte checked"); if(array1.readUnsignedInt()==array2.readUnsignedInt()) score++; } return (score/(bitmap1.height*bitmap2.width))*100; } public function takeSnapshot():Bitmap{ var snapshotHolder:UIComponent = new UIComponent(); var snapshot:BitmapData = new BitmapData(pnlWebcam.video.width, pnlWebcam.video.height, true); snapshotHolder.y =10; //pnlSnapshot.addChild(snapshotHolder); snapshot.draw(pnlWebcam.video); //applyFilterToBitmap(snapshotbitmap); pnlSnapshot.visible = true; var snapshotbitmap:Bitmap = new Bitmap(snapshot); snapshotHolder.addChild(applyFilterToBitmap(new Bitmap(snapshot))); pnlSnapshot.addChild(snapshotHolder); return snapshotbitmap; false } function applyFilterToBitmapData(bitmapdata:BitmapData):BitmapData{ // Create the convolution matrix. //var matrix:Array = [0, 1, 0, // 1, -0.5, 1, // 0, 1, 0]; var matrix:Array = [0, -1, 0, -1,4, -1, 0, -1, 0]; var convolution:ConvolutionFilter = new ConvolutionFilter(); convolution.matrixX = 3; convolution.matrixY = 3; convolution.matrix = matrix; convolution.divisor = 1; bitmapdata.applyFilter(bitmapdata,new Rectangle(0,0,pnlWebcam.video.width,pnlWebcam.video.height),new Point(0,0), convolution); return bitmapdata; } function applyFilterToBitmap(bitmap:Bitmap):Bitmap{ // Create the convolution matrix. var matrix:Array = [0, -1, 0, -1, 4, -1, 0, -1, 0]; var convolution:ConvolutionFilter = new ConvolutionFilter(); convolution.matrixX = 3; convolution.matrixY = 3; convolution.matrix = matrix; convolution.divisor = 1; bitmap.filters = [convolution]; return bitmap; } function applyFilter():void { // Create the convolution matrix. var matrix:Array = [0, -1, 0, -1, 4, -1, 0, -1, 0]; var convolution:ConvolutionFilter = new ConvolutionFilter(); convolution.matrixX = 3; convolution.matrixY = 3; convolution.matrix = matrix; convolution.divisor = 1; pnlWebcam.video.filters = [convolution]; } public function printConsole(string:String):void{ var oldString:String = consolelabel.text; consolelabel.text=oldString+":"+string+"\n"; } private function handlePlayback(event:ItemClickEvent):void{ if(event.currentTarget.selectedValue=="wave"){ Alert.show("Warning, the SineWave Generator has timing issues, isn't easy on the ears and has a tendency to fail stop commands. But at least it's pitch-perfect!"); playBack = "wave"; } if(event.currentTarget.selectedValue=="piano"){ playBack = "piano"; } } private function showHelp(){ dragPanel.visible="true"; var helpText:Text = new Text(); dragPanel.horizontalScrollPolicy="off"; //helpText.maxWidth = 300; helpText.width=300; helpText.text = "Welcome to the Camera Music Interpreter.\n\n To interpret sheet music, hold it directly in front of the camera. Make sure to hold the sheet at an appropriate distance (You should be able to see a sharp representation in the Camera Stream pane).\n While the sheet is in view of the camera, hit the Draw Interpreted Lines button. This will make a snapshot and apply the necessary filters. The snapshot will be displayed in the Interpretation pane.\n If the snapshot has too much noise (usually most clip elements are green in that case), lower the noise threshold by moving the slider on top of the screen to the left and hit the Draw Interpreted Lines button again. If the musical staff is mostly green and there is little green outside of the staffs, the noise threshold is correct. Now hit the Interpret Clips button. If the program has deteced a musical staff in the Interpretation image, a button will appear next to that staff, on the right of the Interpretation pane. Click this button to interpret that staff. A represesntation of the staff will be displayed in the Notes pane in the lower right corner. Now, you can play back the interpreted notes by selecting a playback method (the default is piano) and hitting Play. "; dragPanel.addChild(helpText); } //write code here ]]>