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: | 274 / 534 |
Version history: | 83 change(s) |
Referenced in: | [show references] |