private void end(boolean initAfter) { COLLECTING = false; if (model.plottingSelected()) { plotterYIN.stop(); plotterYIN.setVisible(false); plotterMPM.stop(); plotterMPM.setVisible(false); plotterBUFFER.stop(); plotterBUFFER.setVisible(false); } showStatistics(); model.firePropertyChange(ControllerEngine.START_STOP_PROCESSING_BUTTON_PROPERTY, "stop", "rec"); model.firePropertyChange(ControllerEngine.INPUTLEVEL_PROPERTY, -1, 0); reset(); if (initAfter && !model.isEvaluating()) model.initProcessing(null); }
/** in loop: get buffer from queue and call detectPitchAndCollect() */ public void run() { // fft.start(); //TODO fft // System.out.println("starte collector, TIME: " + // (System.currentTimeMillis()-jAM.GLOBAL_TIMESTAMP) ); if (model.plottingSelected()) { plotterYIN = new SimplePlotterFrame("YIN Buffer Plotter", 200); plotterYIN.start(); plotterMPM = new SimplePlotterFrame("MPM", 400); plotterMPM.start(); plotterBUFFER = new SimplePlotterFrame("Audio Buffer Plotter", 600); plotterBUFFER.start(); } // detectionStartTime=System.currentTimeMillis(); try { while (thread != null) { // long start = System.currentTimeMillis(); // get data: block if queue is empty float[] audioFloatBuffer = queue.get(); // check end-of-stream marker if (audioFloatBuffer == null) { // System.out.println("COLLECTOR: buffer is null!"); end(true); return; } double level = jAMUtils.soundPressureLevel(audioFloatBuffer); // TODO level (dB) to percent ? // System.out.println(Math.round(1000000000*Math.pow(10,level/20))/10000000 + "%"); countSamples += audioFloatBuffer.length - overlap; // ----- ok wir haben nun einen buffer aus der queue geholt // ----- detectPitchAndCollect(audioFloatBuffer, level); // TODO doc evaluation: auf intel 2 core blabla zB 3ms fuer // detectPitchAndCollect() -> diesen pipeline schritt // System.out.println("===TIME FOR ONE STEP===> " + // (System.currentTimeMillis()-start) + "ms"); } // end(); } catch (InterruptedException e) { // e.printStackTrace(); System.err.println("catched interr exc in collector.run()"); } }
/** show some statistics at the end of the collector process */ public void showStatistics() { // TODO nur fuer mich die folgenden stats...haben in eval // erstmal! nix verloren String stat = ""; if (!model.isEvaluating()) { stat += "\n****************************** START STATS ******************************\n"; stat += "!ALL! DETECTED NOTES AND THEIR LENGTHS IN ms: (just for stats)\n"; stat += midiKeySammlerInsgesamt.toString() + "\n\n"; stat += "AbcNotes-Backup: \n" + notesAsString + "\n"; stat += "YIN/MPM - STATS:\n"; float sum = yin_cnt + mpm_cnt; stat += "YIN: " + yin_cnt / sum * 100 + "% - MPM: " + mpm_cnt / sum * 100 + "%\n"; stat += "****************************** END STATS ******************************\n\n"; jAM.log(stat, false); } else evaluator.evaluateCurrentTranscription(evaluationSammler, PITCHDETECTOR, notesAsString); }
private void detectRest(float duration) { float noteDur = midiKeysRests.size() * duration; // NE PAUSE MUSS MIND NE 16tel lang sein, sonst wird einfach ignoriert! int min = timeFor16thNote - MINIMUM_DURATION; // MINIMUM_DURATION; //timeFor16thNote - MINIMUM_DURATION; // // -MINIMUM_DURATION; if (noteDur < min) { if (!model.isEvaluating() && jAM.SYSOUT) System.out.println( "NoteCollectorWorker: ########## IGNORED: " + noteDur + "ms OF RESTS! - min: " + min + " midiKeysRests: " + midiKeysRests); midiKeysRests.clear(); return; } addNoteOrRest(0, noteDur); }
private Vector<Double> getMostDetectedNoteInSequence( Vector<Float[]> midiKeysAndLevels, float durationOfOneNote) { int midiKey = 0; int cnt = 0; Vector<Double> ret = new Vector<Double>(); float[] midiKeys = new float[midiKeysAndLevels.size()]; double[] levels = new double[midiKeysAndLevels.size()]; // 1. erst zŠhlen: int[] modeArray = new int[128]; // 128 midiKeys ! // copy from Vector<Float[]> to float[] for (int i = 0; i < midiKeys.length; i++) { midiKeys[i] = midiKeysAndLevels.get(i)[0]; levels[i] = Math.abs(midiKeysAndLevels.get(i)[1]); // betrag, dann nach maxima suchen } for (int i = 0; i < midiKeys.length; i++) { modeArray[(int) midiKeys[i]] += 1; // count } // 2. dann den Haeufigsten suchen for (int i = 0; i < modeArray.length; i++) { if (modeArray[i] > cnt) { cnt = modeArray[i]; midiKey = i; } } // System.out.println(midiKey + " occures " + cnt + " times"); // return midiKey; // first add taken MidiKey ret.add((double) midiKey); /** * minima/maxima: idea: if there are local minima in the levelVector of this notesequence then * we can assume that the SAME note was played more than one time! */ // find extrema in RMS Levels boolean minima = false; Vector<double[]> extremwerte = jAMUtils.detectExtremum(levels, delta, minima); // sysout if (!model.isEvaluating() && extremwerte.size() > 0) { System.out.print("EXTREMWERTE " + (minima ? " MINIMA " : " MAXIMA ") + " ===> ["); for (int i = 0; i < extremwerte.size(); i++) { System.out.print("(" + extremwerte.get(i)[0] + "," + extremwerte.get(i)[1] + "), "); } System.out.println("] durationOfOneNote: " + durationOfOneNote); } // midiKey, notevalue1, notevalue2 usw... // return i.e. [60, 512, 511..] // then add possible durations if (extremwerte.size() > 0) { double startX = 0; double endX = 0; for (int i = 0; i < extremwerte.size(); i++) { endX = extremwerte.get(i)[0]; // nich beim Tiefpunkt sondern ungefaehr beim Naechsten ONSET! // ret.add( durationOfOneNote * (endX-startX) ); // //ignoriere extremwerte am Ende, denn dies kommt sehr oft vor! also nur die ersten X% float X = (levels.length * 75.0f / 100); if (endX < X) { ret.add(durationOfOneNote * (endX - startX)); } startX = endX; // -2; } // nur wenn mind. eine duration geaddet wurde noch zum Ende: if (ret.size() > 1) { endX = levels.length; ret.add(durationOfOneNote * (endX - startX)); } } return ret; }
private void addNoteOrRest(int midiKey, float noteDur) { // int noteLength = mapNoteDurationToBPM_8tel(noteDur); int noteLength = mapNoteDurationToBPM_16tel(noteDur); // ggf 2 Noten malen (mit bogen!) bei: 5 7 9 10 11 13 14 15 (bei 8tel: // 10 und 14) switch (noteLength) { case 5: convertToABC(midiKey, 4, midiKey == 0 ? "" : "-"); convertToABC(midiKey, 1, ""); break; case 7: convertToABC(midiKey, 6, midiKey == 0 ? "" : "-"); convertToABC(midiKey, 1, ""); break; case 9: convertToABC(midiKey, 8, midiKey == 0 ? "" : "-"); convertToABC(midiKey, 1, ""); break; case 10: convertToABC(midiKey, 8, midiKey == 0 ? "" : "-"); convertToABC(midiKey, 2, ""); break; case 11: convertToABC(midiKey, 8, midiKey == 0 ? "" : "-"); convertToABC(midiKey, 3, ""); break; case 13: convertToABC(midiKey, 12, midiKey == 0 ? "" : "-"); convertToABC(midiKey, 1, ""); break; case 14: convertToABC(midiKey, 12, midiKey == 0 ? "" : "-"); convertToABC(midiKey, 2, ""); break; case 15: convertToABC(midiKey, 12, midiKey == 0 ? "" : "-"); convertToABC(midiKey, 3, ""); break; default: convertToABC(midiKey, noteLength, ""); break; } if (!model.isEvaluating()) model.updateScore(notesAsString); if (midiKey == 0) { if (!model.isEvaluating() && jAM.SYSOUT) // jAM.log("NoteCollectorWorker: ==> ENTSCHEIDUNG REST: ("+noteLength+") noteDur: // "+noteDur+" - based on: " // + midiKeysRests, false); System.err.println( timestamp() + " ==> ENTSCHEIDUNG REST: (" + noteLength + ") noteDur: " + noteDur + " - based on 'RESTS':" + midiKeysRests); } else { if (!model.isEvaluating() && jAM.SYSOUT) { // jAM.log("NoteCollectorWorker: ==> ENTSCHEIDUNG NOTE: "+midiKey+"("+noteLength+") noteDur: // "+noteDur+" - based on: " // + midiKeysNotes, false); String s = ""; for (int i = 0; i < midiKeysAndLevels.size(); i++) { s += (midiKeysAndLevels.get(i)[0] + ","); } s += "\n"; System.err.print( timestamp() + " ==> ENTSCHEIDUNG NOTE: " + midiKey + "(" + noteLength + ") noteDur: " + noteDur + " - based on: " + s); } } // immer beide loeschen, sonst werden features gesammelt, welche schon vor langer zeit auftraten midiKeysAndLevels.clear(); midiKeysRests.clear(); // wir brauchen midiKey und Notenwert(zwischen 1-16) evaluationSammler.add(midiKey); evaluationSammler.add(noteLength); }
/** * <b>This is the most important function of the collector pipeline</b> First we detect the pitch * of the audioFloatBuffer using YIN or MPM.<br> * Then the collector process starts, checking onsets and offsets based on midiKeys and dB, * collects<br> * and convert these pich vectors to abc-notes based on the bpm, samplerate buffersize and * bufferoverlap.<br> */ private void detectPitchAndCollect(float[] audioFloatBuffer, double level) { int midiKey; String note; // TODO pitchInHertz = getBestPitch(audioFloatBuffer); // ---------- ENTSCHEIDUNG ---------- // pitchInHertz=mpm_pitch; pitch = Pitch.getInstance(PitchUnit.HERTZ, (double) pitchInHertz); midiKey = (int) pitch.getPitch( PitchUnit.MIDI_KEY); // PitchConverter.hertzToMidiKey((double)pitchInHertz); if (model.plottingSelected()) { plotterYIN.setData(yin.getCurrentBuffer()); plotterYIN.setInfoString("CURRENT PITCH: " + pitchInHertz + "Hz PROB: " + pitch_probability); plotterMPM.setData(mpm.getCurrentBuffer()); plotterMPM.setInfoString("CURRENT PITCH: " + pitchInHertz + "Hz PROB: " + pitch_probability); plotterBUFFER.setData(audioFloatBuffer); plotterBUFFER.setInfoString("level: " + level); } // nach dem pitch erkannt wurde muss ggf. entsprechend dem Instrument // transponiert werden if (model.getTransposeRecIndex() == 1) { // Bb Clarinet: 2 halftonesteps up pitch.convertPitch(2); pitchInHertz = (float) pitch.getPitch(PitchUnit.HERTZ); midiKey = PitchConverter.hertzToMidiKey((double) pitchInHertz); } String[] arr = pitch.getBaseNote(pitchInHertz).split(" "); String note2 = arr[0]; int oktave = Integer.parseInt(arr[1]); // note = pitch.noteName(); String str = ""; // formatted Info output to GUI str = pitchInHertz == -1 ? "NO PITCH DETECTED" : note + " at " + String.format("%.5g%n", pitchInHertz) + "Hz - IDEAL: " + String.format("%.5g%n", pitch.getIdealFreq(note2, oktave)) + "Hz - PROB: " + String.format("%.5g%n", pitch_probability) + "%"; model.firePropertyChange(ControllerEngine.INFO_LABEL_PROPERTY, "", str); countMSprocessing += (audioFloatBuffer.length - overlap) / audioSampleRate * 1000.0f; float duration = (bufferSize - overlap) / audioSampleRate * 1000.0f; // finally: // collect(audioFloatBuffer, midiKey, duration, level, note); // ------------------------- begin collector process ------------------------- // ----- sammel ALLE erkannten Noten fuer statistiken ----- if (midiKeySammlerInsgesamt.get(midiKey) != null) { midiKeySammlerInsgesamt.put(midiKey, midiKeySammlerInsgesamt.get(midiKey) + duration); } else { midiKeySammlerInsgesamt.put(midiKey, duration); } // TODO level neuer Ansatz ? zusaetzliche offset/Onset bedingung // levels.add(level); // // boolean minima=true; // Vector<double[]>extremwerte = jAMUtils.detectExtremum(levels, delta, minima); // // //sysout // if(!model.isEvaluating() && extremwerte.size()==1) { //kann so immer nur 1 finden //// jAMUtils.printArray(levels); // String str=""; // for (int i = 0; i < extremwerte.size(); i++) { // str+= "(" + extremwerte.get(i)[0] + ","+extremwerte.get(i)[1] + "),"; // } // System.out.println("============> EXTREM!!!: " + str + " at levels: " + levels); // System.out.println("midiKey: " + midiKey); // // levels.clear(); //wenn ein minima entdeckt: offset und dann leeren ? // } /** * * wenn eine note zb 200ms lang, dann -> 16tel aber die 50 ms muessen beim NŠchsten Mal * ignoriert werden wenn dies pausen entspricht !!! sonst kann sein dass zB z(3) gemalt wird * obwohl z(2) !!! * * <p>if(msToIgnore>0 && !ONSET) { //MINIMUM_DURATION dur+=duration; if(dur<msToIgnore) return; * else { System.out.println( "============================================>>>>>> IGNORED: " + * dur + "ms OF DATA - msToIgnore: " + msToIgnore); dur=0; msToIgnore=0; } } */ boolean silence = jAMUtils.isSilence(audioFloatBuffer, MINIMUM_LEVEL); // 1. OFFSET basierend auf neuer note! if ((ONSET && midiKey > 0 && !silence /* level > MINIMUM_LEVEL*/) && (midiKey != lastTakenMidiKey)) { // es kommt ne andere /Note/ // System.out.println("POSSIBLE NEW NOTE: "+ midiKey); if (newNoteCount == 0) // die erste "andere" Note newNoteCount++; else if (midiKey == lastDetectedMidiKey) newNoteCount++; if (newNoteCount > (int) (MINIMUM_DURATION / duration)) { // 60/25.01 --> 2 also mind. 3 nehmen! wie sonst auch ONSET = false; // dann macht er jetzt unten ne Entscheidung und beim next Mal fŠngt er an die // neue note zu collecten NEW_NOTE_ONSET = true; // TODO sinn? newNoteCount = 0; // sysout if (!model.isEvaluating() && jAM.SYSOUT) System.out.println( timestamp() + " ================================================== OFFSET NEWNOTE: " + midiKey + " could be possible new note"); } // 2. ONSET } else if (!ONSET && midiKey > 0 && !silence /*&& level > MINIMUM_LEVEL*/) { newNoteCount = 0; // gibts diese note schon? und kam sie beim letzten Mal? if (midiKeySammler.get(midiKey) != null && midiKey == lastDetectedMidiKey) { midiKeySammler.put(midiKey, midiKeySammler.get(midiKey) + duration); if (midiKeySammler.get(midiKey) > MINIMUM_DURATION) { // // sysout if (!model.isEvaluating() && jAM.SYSOUT) System.out.println( timestamp() + " ================================================== ONSET - Entscheidung basiert auf: " + midiKeySammler); ONSET = true; // wir kšnnen davon ausgehen dass "note" die lastTakenNote // wird, da sie alle Bedingungen fuer ein note-OFFSET erfŸllt // Problem: es kann sein dass zb 3 buffer A1 kommen, das is // ne new note bedingung // der naechst kommt dann hier rein und is aber diesmal A3 // also lastTakenNote="A3" anstatt A1 ??? lastTakenMidiKey = midiKey; midiKeySammler.clear(); } } else { midiKeySammler.put(midiKey, duration); } // 3. OFFSET basierend auf Pause } else if (silence || midiKey == 0 /*|| level < MINIMUM_LEVEL*/) { // sonst ist alles ne Pause: kein pitch und auch // level < MIN_LEVEL /** * gibts diese Pause schon? und kam sie beim letzten Mal? ob sie beim letzten Mal kam is * unwichtig es mŸssen die Pausen gezŠhlt werden! BSP: 60,60,60,0,0,0,0,55,0,0,0, --> die 55 * MUSS mitgezaehlt werden ! ??? stimmt das? */ if (midiKeySammler.get(0) != null && lastDetectedMidiKey == 0) { midiKeySammler.put(0, midiKeySammler.get(0) + duration); if (midiKeySammler.get(0) > MINIMUM_DURATION) { if (ONSET) { if (!model.isEvaluating() && jAM.SYSOUT) System.out.println( timestamp() + " ================================================== OFFSET - Entscheidung basiert auf: " + midiKeySammler); ONSET = false; // jetzt ist wieder vorbei NEW_NOTE_ONSET = false; } midiKeySammler.clear(); } } else { midiKeySammler.put(0, duration); } } // ----- wenn nun ONSET==true anfangen zu sammeln bis ONSET==false! if (ONSET) { if (midiKeysRests.size() > 0 && !NEW_NOTE_ONSET) { // hier sind nun pausen in der off Phase gezaehlt worden detectRest(duration); } else { // sonst: sammle noten // hier midiKey und level speichern: midiKeysAndLevels.add(new Float[] {(float) midiKey, (float) level}); } } else { // wenn ein OFFSET und noten sind vorhanden: entscheidung! if (midiKeysAndLevels.size() > 0) { detectNote(duration); } else // sonst sammple pausen midiKeysRests.add(midiKey); } lastDetectedMidiKey = midiKey; // TODO fft // fft(audioFloatBuffer); // TODO sysout if (!model.isEvaluating() && jAM.SYSOUT) { if (pitchInHertz == -1) System.out.println( timestamp() + "\t" + "--> Rest: " + note + (note.length() == 2 ? "\t" : "") + "\t\t midiKey: " + midiKey + "\t RMS: " + String.format("%.2f", level) + " Zustand: " + (ONSET ? "ONSET " : "OFFSET ") + (silence ? "SILENCE" : "NO SILENCE")); else System.out.println( timestamp() + "\t" + "--> Note: " + note + (note.length() == 2 ? "\t" : "") + "\t midiKey: " + midiKey + "\t RMS: " + String.format("%.2f", level) + " Zustand: " + (ONSET ? "ONSET " : "OFFSET ") + (silence ? "SILENCE" : "NO SILENCE")); } }
public void convertToABC(int midiKey, int notenWert, String bindeBogenSameNotes) { String NOTE = ""; // wenn zB C10 erkannt oder irgendwas ausserhalb meines bereichs -> // Pause! if (midiKey < 36 || midiKey > 96) // von C2 bis C7 NOTE = "z"; else { // 1. Wenn midiKey in Tonleiter: map to right note (b ein // hoch(Bb->B), # ein runter(F#->F)) boolean inTonleiter = jAMUtils.inTonleiter(midiKey, tonleiter); // System.out.println("====>" + midiKey + FLAT_KEY + " - "+ inTonleiter); if (FLAT_KEY && inTonleiter && jAMUtils.ABC_NOTES_FLAT[midiKey].contains("_")) midiKey += 1; else if (!FLAT_KEY && inTonleiter && jAMUtils.ABC_NOTES_SHARP[midiKey].contains("^")) midiKey -= 1; // System.out.println("====>" + ABC_NOTES_FLAT[midiKey].contains("_")); // System.out.println("====>" + midiKey); // wenn nich in Tonleiter wird einfach sharp gemalt! NOTE = jAMUtils.ABC_NOTES_SHARP[midiKey]; // old: // NOTE = FLAT_KEY ? ABC_NOTES_FLAT[midiKey] : // ABC_NOTES_SHARP[midiKey]; } String PREFIX = ""; // zB "(" oder auch ")" dann: ")A4" kann evtl sein! String POSTFIX = ""; // zB ")" oder "|" oder "\n" lenge += notenWert; POSTFIX += bindeBogenSameNotes; // is entweder "" oder "-" // TODO stimmt das alles? if (lenge >= OBEN * 4 && takte < UNTEN) { // System.out.println("=========================================== EIN TAKT VORBEI"); POSTFIX += "|"; lenge = 0; takte++; model.firePropertyChange(ControllerEngine.SCROLL_DOWN_PROPERTY, -1, 100); } else if (lenge >= OBEN * 4 && takte == UNTEN) { // System.out.println("=========================================== 4 TAKTE VORBEI // ==========================================="); POSTFIX += "\n"; takte = 1; lenge = 0; } /* * imSelbenTakt(letzte UND! NOTE) && TODO ob im selben Takt wird * noch ignoriert! */ // wenn letzte und diese unabghŠngig von #/b DIESELBE IST: if ((letzteNote.replace("_", "").equals(NOTE.replace("_", "")) || letzteNote.replace("^", "").equals(NOTE.replace("^", ""))) && // wenn letzte nun b oder # hatte und DIESE NICHT: NATURAL SIGN! (letzteNote.contains("^") && !NOTE.contains("^")) || (letzteNote.contains("_") && !NOTE.contains("_"))) PREFIX = "="; // natural sign notesAsString += (PREFIX + NOTE + notenWert + POSTFIX); letzteNote = NOTE; }