sclass PianoSampler { macro dprint { if (debug) printVars } bool debug; int channels = 1; double sampleRate = 48000; double currentSample; IQuerySound sound; IAudioSample audio; SlidingWindow window; long haarFeaturesChecked; persistable class Oscillator extends HasKey { double currentPeriodStart; // all recorded intensities new TreeMap> intensities; double interval; // in samples *(Frequency f) { super(f); interval = frequency().interval()*sampleRate; } Frequency frequency() { ret key(); } double interval() { ret interval; } void record_impl(DoubleRange r, Channels c) { intensities.put(r, c); } void advancePeriodToRange(DoubleRange timeRange) { double end = currentPeriodStart + interval; int phaseShifts = ifloor((timeRange.start-end)/interval); if (phaseShifts = 0) ret; setStart(currentPeriodStart + phaseShifts*interval); } // we span a total range of interval*1.25 because of the sin/cos thing void measure(DoubleRange timeRange) { DoubleRange p1 = doubleRangeWithLength(phaseMinus90(), interval); p1 = intersectDoubleRanges(p1, timeRange); double re = 0; if (!isEmpty(p1)) { AudioHaarFeature haar = new(audio, p1); re = haar!; } DoubleRange p2 = doubleRangeWithLength(currentPeriodStart, interval); p2 = intersectDoubleRanges(p2, timeRange); double im = 0; if (!isEmpty(p2)) { AudioHaarFeature haar = new(audio, p2); im = haar!; } record(this, p1 /*TODO*/, complexChannels(re, im)); haarFeaturesChecked += 2; } void dropMeasurementsLeftOf(double t) { while ping (true) { var r = firstKey(intensities); if (r != null && r.end() <= t) popFirst(intensities); else break; } } void initial(double currentSample) { setStart(currentSample + startPhase()); } void moveTo(double time) { currentPeriodStart = time; double next = phase360(); dprint("moveTo", +this, +next); completionsForSample.put(next, this); } double startPhase() { ret phase90(); } double quarterPhase() { ret interval()*.25; } double phaseMinus90() { ret currentPeriodStart - interval()*.25; } double phase0() { ret currentPeriodStart; } double phase90() { ret currentPeriodStart + interval()*.25; } double phase180() { ret currentPeriodStart + interval()*.5; } double phase270() { ret currentPeriodStart + interval()*.75; } double phase360() { ret currentPeriodStart + interval(); } double phase450() { ret currentPeriodStart + interval()*1.25; } } // end of Oscillator TreeHasKeyMap oscillators = new TreeHasKeyMap(map(f -> new Oscillator(f), pianoFrequencies88())); new TreeMultiMap completionsForSample; *() {} *(IAudioSample audio, double *currentSample) { setAudio(audio); } *(IAudioSample audio) { setAudio(audio); } // start at time 0 void setAudio(IAudioSample audio) { this.audio = audio; window = optCast SlidingWindow(audio); } void verboseOnRecordAdded() { onRecordAdded(lambda1 print); } int nFrequencies() { ret oscillators.size(); } void startOscillators { for (o : oscillators) o.initial(currentSample); } void stepTo(DoubleRange timeRange) { while ping (true) { Double t = firstKey(completionsForSample); if (t != null && t <= timeRange.start) { var list = completionsForSample.getAndClear(t); dprint(+t, +list); for (Oscillator o : list) { o.measure(timeRange); o.relocate(t); } } else break; } } void record(Oscillator o, DoubleRange r, Channels c) { if (o == null) ret; o.record_impl(r, c); onRecordAdded(new RecordAdded(o, r, c)); } record RecordAdded(Oscillator oscillator, DoubleRange period, Channels intensity) {} transient event interface OnRecordAdded as onRecordAdded shipping RecordAdded; Frequency lowestFrequency() { ret oscillators.firstKey(); } Frequency highestFrequency() { ret oscillators.lastKey(); } int minWindowSize() { ret iceil(sampleRate*lowestFrequency().interval()); } void makeSmallestWindow(IQuerySound sound) { this.sound = sound; setAudio(new SlidingWindow(channels, sampleRate, sound, 0, minWindowSize()); } double windowSize() { ret window.length(); } S stats() { ret renderVars(+audio, +haarFeaturesChecked); } IIntegralImage imageColumn(DoubleRange timeRange) { int n = nFrequencies(); var l = reversedList(oscillators); double[] col = new[n]; for y to n: { var l2 = l.get(y).intensities; new Average avg; forEach(l2, (r, val) -> { double amplitude = val.first().abs(); // if only a part is in the time range double actualDuration = l(intersectDoubleRanges(r, timeRange)); avg.add(amplitude, actualDuration); }); col[y] = avg!; } double[] col2 = normalizeDoubles(col, 255); ret bwIntegralImageFromFunction(1, n, (x, y) -> ifloor(col2[y])); } // how much horizontal area have we covered L coverageByFreq() { ret mapReversed(oscillators, o -> totalLengthOfDoubleRanges(keys(o.intensities))); } void stepRight(double shift) { assertNotNull(+window); var r = window.bounds(); window.shiftRight(max(1, ifloor(shift))); for (o : oscillators) o.dropMeasurementsLeftOf(window.start()); stepTo(window.end()); } DoubleRange windowBounds() { ret window.bounds(); } }