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; *(Frequency f) { super(f); } Frequency frequency() { ret key(); } double interval() { ret frequency().interval()*sampleRate; } void record_impl(DoubleRange r, Channels c) { intensities.put(r, c); } void measure { double start = phaseMinus90(), end = start + interval(); AudioHaarFeature haar = new(audio, start, end); var re = haar!; haar = new AudioHaarFeature(audio, currentPeriodStart, currentPeriodStart + interval()); var im = haar!; record(this, doubleRange(start, end), complexChannels(re, im)); haarFeaturesChecked += 2; } void start(double currentSample) { relocate(currentSample + startPhase()); } void relocate(double time) { currentPeriodStart = time; double next = phase360(); dprint("relocate", +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; } } 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.start(currentSample); } void stepTo(double time) { while ping (true) { Double t = firstKey(completionsForSample); if (t != null && t <= time) { var list = completionsForSample.getAndClear(t); dprint(+t, +list); for (Oscillator o : list) { o.measure(); 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); } IBWntegralImage imageColumn() { int n = nFrequencies(); var l = reversedList(oscillators); double[] col = new[n]; for y to n: { var l2 = l.get(y).intensities; new Average avg; for (DoubleRange r, Channel val : l2) { double amplitude = first(val).abs(); avg.add(amplitude, r.length()); } col[y] = avg!; } col = normalizeDoubles(col, 255); ret bwIntegralImageFromFunction(1, n, (x, y) -> ifloor(col[y])); } // how much horizontal area have we covered L coverageByFreq() { ret mapReversed(oscillators, o -> totalLengthOfDoubleRanges(keys(o.intensities))); } }