Libraryless. Click here for Pure Java version (11208L/67K).
sclass ContinuousOscillators { macro dprint { if (debug) printVars } bool debug; int channels = 1; double sampleRate = 48000; double currentSample; IQuerySound sound; IAudioSample audio; SlidingWindow window; bool renderUsingSine; // or square wave bool realOnly, imagOnly; // for testing double maxRelFreq = 0.25; // maximum oscillator frequency relative to sample rate long haarFeaturesChecked; persistable class Oscillator extends HasKey<Frequency> { double currentPeriodStart; // all recorded intensities new TreeMap<DoubleRange, Channels<Complex>> intensities; double interval; // length of period in samples *(Frequency f) { super(f); interval = frequency().interval()*sampleRate; } Frequency frequency() { ret key(); } double interval() { ret interval; } void record_impl(DoubleRange r, Channels<Complex> c) { intensities.put(r, c); } void measure() { DoubleRange p1 = doubleRangeWithLength(currentPeriodStart, interval); AudioHaarFeature haar = new(audio, p1); record(this, p1, haar.getComplex()); haarFeaturesChecked += 2; schuleNextOscillation(); } void initial() { completionsForSample.put(currentPeriodStart, this); } void schuleNextOscillation() { completionsForSample.put(currentPeriodStart = phase360(), this); } double phase360() { ret currentPeriodStart + interval(); } } // end of Oscillator TreeHasKeyMap<Frequency, Oscillator> oscillators; new TreeMultiMap<Double, Oscillator> completionsForSample; *() {} *(IAudioSample audio, double *currentSample) { setAudio(audio); } *(IAudioSample audio) { setAudio(audio); } // start at time 0 void setFrequencies(Iterable<Frequency> frequencies) { oscillators = new TreeHasKeyMap<Frequency, Oscillator>(map(f -> new Oscillator(f), filter(frequencies, f -> between(f!, 1, sampleRate*maxRelFreq)))); } void setAudio(IAudioSample audio) { this.audio = audio; window = optCast SlidingWindow(audio); } int nFrequencies() { ret oscillators.size(); } void startOscillators { for (o : oscillators) o.initial(); } void stepTo(DoubleRange timeRange) { while ping (true) { Double t = firstKey(completionsForSample); if (t != null && t <= timeRange.end) { var list = completionsForSample.getAndClear(t); dprint(+t, +list); for (Oscillator o : list) o.measure(); } else break; } } void record(Oscillator o, DoubleRange r, Channels<Complex> c) { if (o == null) ret; o.record_impl(r, c); } 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()); } void makeFullWindow(IQuerySound sound, IntRange r) { this.sound = sound; setAudio(new SlidingWindow(channels, sampleRate, sound, r.start, r.length())); } double windowSize() { ret window.length(); } S stats() { ret renderVars(+audio, +haarFeaturesChecked); } // how much horizontal area have we covered L<Double> coverageByFreq() { ret mapReversed(oscillators, o -> totalLengthOfDoubleRanges(keys(o.intensities))); } DoubleRange windowBounds() { ret window.bounds(); } IIntegralImage imageColumn(DoubleRange timeRange) { int n = nFrequencies(); double[] col = rawImageColumn(timeRange); double[] col2 = normalizeDoubles(col, 255); ret bwIntegralImageFromFunction(1, n, (x, y) -> ifloor(col2[y])); } double[] rawImageColumn(DoubleRange timeRange) { int n = nFrequencies(); var l = reversedList(oscillators); double[] col = new[n]; for y to n: { TreeMap<DoubleRange, Channels<Complex>> l2 = l.get(y).intensities; // find right period DoubleRange period = l2.floorKey(timeRange); if (period == null) continue; double amplitude = l2.get(period).first().abs(); col[y] = amplitude; } ret col; } MakesBufferedImage imageForWindow(double pixelsPerSecond) { new L<IIntegralImage> images; double len = windowBounds().length()/sampleRate; int pixels = iround(len*pixelsPerSecond); for i to pixels: { DoubleRange range = doubleRange( i/pixelsPerSecond*sampleRate+windowBounds().start(), (i+1)/pixelsPerSecond*sampleRate+windowBounds().start()); //print(+range); images.add(imageColumn(range)); } ret mergeBWImagesHorizontally(map toBWImage(images), spacing := 0); } double[] toAudio(IntRange sampleRange, Iterable<Oscillator> oscillatorsToRender default oscillators) { double[] samples = new[l(sampleRange)]; class PerFreq { Oscillator o; DoubleRange period; Channels<Complex> intensity; *(Oscillator *o) { setPeriod(o.intensities.floorKey(toDoubleRange(sampleRange))); //printVars("PerFreq", f := o.frequency(), +sampleRange, +period, firstPeriod := firstKey(o.intensities)); } void setPeriod(DoubleRange period) { this.period = period; intensity = period == null ?: o.intensities.get(period); } void nextPeriod(double t) { while (period != null && t >= period.end) setPeriod(o.intensities.higherKey(period)); } } L<PerFreq> perFreqs = map(oscillatorsToRender, o -> new PerFreq(o)); for i over samples: { double t = sampleRange.start+i; // t is time in samples double sum = 0; for (pf : perFreqs) { pf.nextPeriod(t); bool shouldPrint = toAudio_shouldPrint(i, l(samples)); if (pf.intensity != null) { Complex c = div(pf.intensity.first(), l(pf.period)); double t2 = t-pf.period.start; double frac = t2/pf.o.interval; //double phiRE = frac*pi()*2; //double phiIM = phiRE+pi()/2; if (shouldPrint) printVars(f := pf.o.frequency(), +i, interval := formatDouble1(pf.o.interval)/*, interval2 := l(pf.period)*/, +t, +t2, frac := formatDouble2(frac), c := renderComplexWithAngle(div(c, 32768))); if (realOnly) c = complex(c.re()); else if (imagOnly) c = complex(0, c.im()); sum += renderFrequencySample(c, frac); } else if (shouldPrint) printVars(f := pf.o.frequency(), +i, interval := pf.o.interval, +t, c := 0); } samples[i] = sum; } ret samples; } double renderFrequencySample(Complex c, double frac) { ret (renderUsingSine ? new RenderFrequencySample_Sine : new RenderFrequencySample_SquareWave).get(c, frac); } swappable bool toAudio_shouldPrint(int i, int n) { false; } }
Began life as a copy of #1032970
download show line numbers debug dex old transpilations
Travelled to 3 computer(s): bhatertpkbcr, mowyntqkapby, mqqgnosmbjvj
No comments. add comment
Snippet ID: | #1033245 |
Snippet name: | ContinuousOscillators [dev.] |
Eternal ID of this version: | #1033245/84 |
Text MD5: | aaff9cf861799e1a08918cea099f79b7 |
Transpilation MD5: | 697ec689cfcf22ffc1a4243c942e8541 |
Author: | stefan |
Category: | javax / audio analysis |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2021-10-18 03:23:13 |
Source code size: | 7291 bytes / 227 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 273 / 532 |
Version history: | 83 change(s) |
Referenced in: | #1033271 - ContinuousOscillators_v2 - with deleting recognized frequencies [dev.] #1034167 - Standard Classes + Interfaces (LIVE, continuation of #1003674) |