Libraryless. Click here for Pure Java version (11208L/67K).
| 1 | sclass ContinuousOscillators {
 | 
| 2 |   macro dprint { if (debug) printVars } bool debug;
 | 
| 3 | |
| 4 | int channels = 1; | 
| 5 | double sampleRate = 48000; | 
| 6 | double currentSample; | 
| 7 | IQuerySound sound; | 
| 8 | IAudioSample audio; | 
| 9 | SlidingWindow window; | 
| 10 | bool renderUsingSine; // or square wave | 
| 11 | bool realOnly, imagOnly; // for testing | 
| 12 | double maxRelFreq = 0.25; // maximum oscillator frequency relative to sample rate | 
| 13 | |
| 14 | long haarFeaturesChecked; | 
| 15 | |
| 16 |   persistable class Oscillator extends HasKey<Frequency> {
 | 
| 17 | double currentPeriodStart; | 
| 18 | // all recorded intensities | 
| 19 | new TreeMap<DoubleRange, Channels<Complex>> intensities; | 
| 20 | double interval; // length of period in samples | 
| 21 | |
| 22 |     *(Frequency f) {
 | 
| 23 | super(f); | 
| 24 | interval = frequency().interval()*sampleRate; | 
| 25 | } | 
| 26 | |
| 27 |     Frequency frequency() { ret key(); }
 | 
| 28 |     double interval() { ret interval; }
 | 
| 29 | |
| 30 |     void record_impl(DoubleRange r, Channels<Complex> c) {
 | 
| 31 | intensities.put(r, c); | 
| 32 | } | 
| 33 | |
| 34 |     void measure() {
 | 
| 35 | DoubleRange p1 = doubleRangeWithLength(currentPeriodStart, interval); | 
| 36 | AudioHaarFeature haar = new(audio, p1); | 
| 37 | record(this, p1, haar.getComplex()); | 
| 38 | haarFeaturesChecked += 2; | 
| 39 | schuleNextOscillation(); | 
| 40 | } | 
| 41 | |
| 42 |     void initial() {
 | 
| 43 | completionsForSample.put(currentPeriodStart, this); | 
| 44 | } | 
| 45 | |
| 46 |     void schuleNextOscillation() {
 | 
| 47 | completionsForSample.put(currentPeriodStart = phase360(), this); | 
| 48 | } | 
| 49 | |
| 50 |     double phase360() { ret currentPeriodStart + interval(); }
 | 
| 51 | } // end of Oscillator | 
| 52 | |
| 53 | TreeHasKeyMap<Frequency, Oscillator> oscillators; | 
| 54 | new TreeMultiMap<Double, Oscillator> completionsForSample; | 
| 55 | |
| 56 |   *() {}
 | 
| 57 |   *(IAudioSample audio, double *currentSample) { setAudio(audio); }
 | 
| 58 |   *(IAudioSample audio) { setAudio(audio); } // start at time 0
 | 
| 59 | |
| 60 |   void setFrequencies(Iterable<Frequency> frequencies) {
 | 
| 61 | oscillators = new TreeHasKeyMap<Frequency, Oscillator>(map(f -> new Oscillator(f), filter(frequencies, | 
| 62 | f -> between(f!, 1, sampleRate*maxRelFreq)))); | 
| 63 | } | 
| 64 | |
| 65 |   void setAudio(IAudioSample audio) {
 | 
| 66 | this.audio = audio; | 
| 67 | window = optCast SlidingWindow(audio); | 
| 68 | } | 
| 69 | |
| 70 |   int nFrequencies() { ret oscillators.size(); }
 | 
| 71 | |
| 72 |   void startOscillators {
 | 
| 73 | for (o : oscillators) o.initial(); | 
| 74 | } | 
| 75 | |
| 76 |   void stepTo(DoubleRange timeRange) {
 | 
| 77 |     while ping (true) {
 | 
| 78 | Double t = firstKey(completionsForSample); | 
| 79 |       if (t != null && t <= timeRange.end) {
 | 
| 80 | var list = completionsForSample.getAndClear(t); | 
| 81 | dprint(+t, +list); | 
| 82 | for (Oscillator o : list) | 
| 83 | o.measure(); | 
| 84 | } else | 
| 85 | break; | 
| 86 | } | 
| 87 | } | 
| 88 | |
| 89 |   void record(Oscillator o, DoubleRange r, Channels<Complex> c) {
 | 
| 90 | if (o == null) ret; | 
| 91 | o.record_impl(r, c); | 
| 92 | } | 
| 93 | |
| 94 |   Frequency lowestFrequency() { ret oscillators.firstKey(); }
 | 
| 95 |   Frequency highestFrequency() { ret oscillators.lastKey(); }
 | 
| 96 | |
| 97 |   int minWindowSize() {
 | 
| 98 | ret iceil(sampleRate*lowestFrequency().interval()); | 
| 99 | } | 
| 100 | |
| 101 |   void makeSmallestWindow(IQuerySound sound) {
 | 
| 102 | this.sound = sound; | 
| 103 | setAudio(new SlidingWindow(channels, sampleRate, sound, 0, minWindowSize()); | 
| 104 | } | 
| 105 | |
| 106 |   void makeFullWindow(IQuerySound sound, IntRange r) {
 | 
| 107 | this.sound = sound; | 
| 108 | setAudio(new SlidingWindow(channels, sampleRate, sound, | 
| 109 | r.start, r.length())); | 
| 110 | } | 
| 111 | |
| 112 |   double windowSize() { ret window.length(); }
 | 
| 113 | |
| 114 |   S stats() { ret renderVars(+audio, +haarFeaturesChecked); }
 | 
| 115 | |
| 116 | // how much horizontal area have we covered | 
| 117 |   L<Double> coverageByFreq() {
 | 
| 118 | ret mapReversed(oscillators, o -> totalLengthOfDoubleRanges(keys(o.intensities))); | 
| 119 | } | 
| 120 | |
| 121 |   DoubleRange windowBounds() { ret window.bounds(); }
 | 
| 122 | |
| 123 |   IIntegralImage imageColumn(DoubleRange timeRange) {
 | 
| 124 | int n = nFrequencies(); | 
| 125 | double[] col = rawImageColumn(timeRange); | 
| 126 | double[] col2 = normalizeDoubles(col, 255); | 
| 127 | ret bwIntegralImageFromFunction(1, n, (x, y) -> ifloor(col2[y])); | 
| 128 | } | 
| 129 | |
| 130 |   double[] rawImageColumn(DoubleRange timeRange) {
 | 
| 131 | int n = nFrequencies(); | 
| 132 | var l = reversedList(oscillators); | 
| 133 | double[] col = new[n]; | 
| 134 |     for y to n: {
 | 
| 135 | TreeMap<DoubleRange, Channels<Complex>> l2 = l.get(y).intensities; | 
| 136 | |
| 137 | // find right period | 
| 138 | DoubleRange period = l2.floorKey(timeRange); | 
| 139 | if (period == null) continue; | 
| 140 | |
| 141 | double amplitude = l2.get(period).first().abs(); | 
| 142 | col[y] = amplitude; | 
| 143 | } | 
| 144 | |
| 145 | ret col; | 
| 146 | } | 
| 147 | |
| 148 |   MakesBufferedImage imageForWindow(double pixelsPerSecond) {
 | 
| 149 | new L<IIntegralImage> images; | 
| 150 | double len = windowBounds().length()/sampleRate; | 
| 151 | int pixels = iround(len*pixelsPerSecond); | 
| 152 |     for i to pixels: {
 | 
| 153 | DoubleRange range = doubleRange( | 
| 154 | i/pixelsPerSecond*sampleRate+windowBounds().start(), | 
| 155 | (i+1)/pixelsPerSecond*sampleRate+windowBounds().start()); | 
| 156 | //print(+range); | 
| 157 | images.add(imageColumn(range)); | 
| 158 | } | 
| 159 | |
| 160 | ret mergeBWImagesHorizontally(map toBWImage(images), spacing := 0); | 
| 161 | } | 
| 162 | |
| 163 |   double[] toAudio(IntRange sampleRange, Iterable<Oscillator> oscillatorsToRender default oscillators) {
 | 
| 164 | double[] samples = new[l(sampleRange)]; | 
| 165 | |
| 166 |     class PerFreq {
 | 
| 167 | Oscillator o; | 
| 168 | DoubleRange period; | 
| 169 | Channels<Complex> intensity; | 
| 170 | |
| 171 |       *(Oscillator *o) {
 | 
| 172 | setPeriod(o.intensities.floorKey(toDoubleRange(sampleRange))); | 
| 173 |         //printVars("PerFreq", f := o.frequency(), +sampleRange, +period, firstPeriod := firstKey(o.intensities));
 | 
| 174 | } | 
| 175 | |
| 176 |       void setPeriod(DoubleRange period) {
 | 
| 177 | this.period = period; | 
| 178 | intensity = period == null ?: o.intensities.get(period); | 
| 179 | } | 
| 180 | |
| 181 |       void nextPeriod(double t) {
 | 
| 182 | while (period != null && t >= period.end) | 
| 183 | setPeriod(o.intensities.higherKey(period)); | 
| 184 | } | 
| 185 | } | 
| 186 | |
| 187 | L<PerFreq> perFreqs = map(oscillatorsToRender, o -> new PerFreq(o)); | 
| 188 | |
| 189 |     for i over samples: {
 | 
| 190 | double t = sampleRange.start+i; // t is time in samples | 
| 191 | double sum = 0; | 
| 192 |       for (pf : perFreqs) {
 | 
| 193 | pf.nextPeriod(t); | 
| 194 | bool shouldPrint = toAudio_shouldPrint(i, l(samples)); | 
| 195 | |
| 196 |         if (pf.intensity != null) {
 | 
| 197 | Complex c = div(pf.intensity.first(), l(pf.period)); | 
| 198 | double t2 = t-pf.period.start; | 
| 199 | double frac = t2/pf.o.interval; | 
| 200 | //double phiRE = frac*pi()*2; | 
| 201 | //double phiIM = phiRE+pi()/2; | 
| 202 | if (shouldPrint) | 
| 203 | printVars(f := pf.o.frequency(), +i, interval := formatDouble1(pf.o.interval)/*, interval2 := l(pf.period)*/, +t, +t2, frac := formatDouble2(frac), | 
| 204 | c := renderComplexWithAngle(div(c, 32768))); | 
| 205 | if (realOnly) c = complex(c.re()); | 
| 206 | else if (imagOnly) c = complex(0, c.im()); | 
| 207 | sum += renderFrequencySample(c, frac); | 
| 208 | } else | 
| 209 | if (shouldPrint) | 
| 210 | printVars(f := pf.o.frequency(), +i, interval := pf.o.interval, +t, c := 0); | 
| 211 | |
| 212 | } | 
| 213 | |
| 214 | samples[i] = sum; | 
| 215 | } | 
| 216 | |
| 217 | ret samples; | 
| 218 | } | 
| 219 | |
| 220 |   double renderFrequencySample(Complex c, double frac) {
 | 
| 221 | ret (renderUsingSine | 
| 222 | ? new RenderFrequencySample_Sine | 
| 223 | : new RenderFrequencySample_SquareWave).get(c, frac); | 
| 224 | } | 
| 225 | |
| 226 |   swappable bool toAudio_shouldPrint(int i, int n) { false; }
 | 
| 227 | } | 
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: | 499 / 792 | 
| Version history: | 83 change(s) | 
| Referenced in: | [show references] |