Transpiled version (11198L) is out of date.
1 | // TODO: declare audio ended so we can ignore any incomplete bass frequencies |
2 | |
3 | sclass ContinuousOscillators_v2 extends Meta { |
4 | macro dprint { if (debug) printVars } bool debug; |
5 | |
6 | settable bool bassFirst; |
7 | settable bool printSubtractions; |
8 | int channels = 1; |
9 | settable double sampleRate = 48000; |
10 | double currentSample; |
11 | IAudioSample audio; // integrated audio sample |
12 | SubtractedAudio subtractedAudio; // audio with deleted frequencies |
13 | settable bool keepRendered; |
14 | transient double[] rendered; |
15 | |
16 | bool renderUsingSine; // or square wave |
17 | bool realOnly, imagOnly; // for testing |
18 | double maxRelFreq = 0.5; // maximum oscillator frequency relative to sample rate |
19 | |
20 | event escalating(Oscillator o); |
21 | |
22 | long haarFeaturesChecked; |
23 | |
24 | persistable class Oscillator extends HasKey<Frequency> { |
25 | double currentPeriodStart; |
26 | // all recorded intensities |
27 | new TreeMap<DoubleRange, Channels<Complex>> intensities; |
28 | double interval; // length of period in samples |
29 | |
30 | *(Frequency f) { |
31 | super(f); |
32 | interval = frequency().interval()*sampleRate; |
33 | } |
34 | |
35 | Frequency frequency() { ret key(); } |
36 | double interval() { ret interval; } |
37 | |
38 | scaffolded void record_impl(DoubleRange r, Channels<Complex> c) { |
39 | intensities.put(r, c); |
40 | subtractMeasured(r, c); |
41 | currentPeriodStart = phase360(); |
42 | if (!bassFirst) escalate(); |
43 | } |
44 | |
45 | void escalate { |
46 | escalating(this); |
47 | rcallF(oscillators.higher(this), o -> { o?.process(currentPeriodStart); }); |
48 | } |
49 | |
50 | void subtractMeasured(DoubleRange r, Channels<Complex> c) { |
51 | renderer from method; |
52 | if (printSubtractions) { |
53 | print("subtract " + r + " = " + c); |
54 | enableScaffolding(renderer); |
55 | } |
56 | renderer.subtractPeriodFrom(subtractedAudio, r, frequency(), c.getSingleton()); |
57 | renderer().addPeriodTo(rendered, r, frequency(), c.getSingleton()); |
58 | } |
59 | |
60 | scaffolded void process(double upTo) { |
61 | //printVars(+phase360(), +upTo); |
62 | while (phase360() <= upTo) |
63 | measure(); |
64 | subtractedAudioSanityCheck(); |
65 | escalate(); |
66 | } |
67 | |
68 | scaffolded void measure() { |
69 | DoubleRange p1 = doubleRangeWithLength(currentPeriodStart, interval); |
70 | AudioHaarFeature haar = new(relevantAudio(), p1); |
71 | haarFeaturesChecked += 2; |
72 | record(this, p1, haar.getComplex()); |
73 | } |
74 | |
75 | double phase360() { ret currentPeriodStart + interval(); } |
76 | } // end of Oscillator |
77 | |
78 | IAudioSample relevantAudio() { ret subtractedAudio; } |
79 | |
80 | TreeHasKeyMap<Frequency, Oscillator> oscillators; |
81 | |
82 | *() {} |
83 | *(IAudioSample audio, double *currentSample) { setAudio(audio); } |
84 | *(IAudioSample audio) { setAudio(audio); } // start at time 0 |
85 | |
86 | void setFrequencies(Iterable<Frequency> frequencies) { |
87 | oscillators = new TreeHasKeyMap<Frequency, Oscillator>(map(f -> new Oscillator(f), filter(frequencies, |
88 | f -> between(f!, 1, sampleRate*maxRelFreq)))); |
89 | } |
90 | |
91 | void setAudio(IAudioSample audio) { |
92 | this.audio = audio; |
93 | subtractedAudio = new SubtractedAudio(audio); |
94 | if (keepRendered) rendered = new double[iceil(audio.length())]; |
95 | assertEquals("bounds", audio.bounds(), subtractedAudio.bounds()); |
96 | } |
97 | |
98 | int nFrequencies() { ret oscillators.size(); } |
99 | |
100 | scaffolded void stepTo(DoubleRange timeRange) { |
101 | var o = first(oscillators); |
102 | o?.process(timeRange.end); |
103 | } |
104 | |
105 | void record(Oscillator o, DoubleRange r, Channels<Complex> c) { |
106 | if (o == null) ret; |
107 | o.record_impl(r, c); |
108 | } |
109 | |
110 | Frequency lowestFrequency() { ret oscillators.firstKey(); } |
111 | Frequency highestFrequency() { ret oscillators.lastKey(); } |
112 | |
113 | int minWindowSize() { |
114 | ret iceil(sampleRate*lowestFrequency().interval()); |
115 | } |
116 | |
117 | S stats() { ret renderVars(+audio, +haarFeaturesChecked); } |
118 | |
119 | // how much horizontal area have we covered |
120 | L<Double> coverageByFreq() { |
121 | ret mapReversed(oscillators, o -> totalLengthOfDoubleRanges(keys(o.intensities))); |
122 | } |
123 | |
124 | IIntegralImage imageColumn(DoubleRange timeRange) { |
125 | int n = nFrequencies(); |
126 | double[] col = rawImageColumn(timeRange); |
127 | double[] col2 = normalizeDoubles(col, 255); |
128 | ret bwIntegralImageFromFunction(1, n, (x, y) -> ifloor(col2[y])); |
129 | } |
130 | |
131 | double[] rawImageColumn(DoubleRange timeRange) { |
132 | int n = nFrequencies(); |
133 | var l = reversedList(oscillators); |
134 | double[] col = new[n]; |
135 | for y to n: { |
136 | TreeMap<DoubleRange, Channels<Complex>> l2 = l.get(y).intensities; |
137 | |
138 | // find right period |
139 | DoubleRange period = l2.floorKey(timeRange); |
140 | if (period == null) continue; |
141 | |
142 | double amplitude = l2.get(period).first().abs(); |
143 | col[y] = amplitude; |
144 | } |
145 | |
146 | ret col; |
147 | } |
148 | |
149 | MakesBufferedImage image(DoubleRange r, double pixelsPerSecond) { |
150 | new L<IIntegralImage> images; |
151 | double len = r.length()/sampleRate; |
152 | int pixels = iround(len*pixelsPerSecond); |
153 | for i to pixels: { |
154 | DoubleRange range = doubleRange( |
155 | i/pixelsPerSecond*sampleRate+r.start(), |
156 | (i+1)/pixelsPerSecond*sampleRate+r.start()); |
157 | //print(+range); |
158 | images.add(imageColumn(range)); |
159 | } |
160 | |
161 | ret mergeBWImagesHorizontally(map toBWImage(images), spacing := 0); |
162 | } |
163 | |
164 | double[] toAudio(IntRange sampleRange, Iterable<Oscillator> oscillatorsToRender default oscillators) { |
165 | double[] samples = new[l(sampleRange)]; |
166 | |
167 | class PerFreq { |
168 | Oscillator o; |
169 | DoubleRange period; |
170 | Channels<Complex> intensity; |
171 | |
172 | *(Oscillator *o) { |
173 | setPeriod(o.intensities.floorKey(toDoubleRange(sampleRange))); |
174 | //printVars("PerFreq", f := o.frequency(), +sampleRange, +period, firstPeriod := firstKey(o.intensities)); |
175 | } |
176 | |
177 | void setPeriod(DoubleRange period) { |
178 | this.period = period; |
179 | intensity = period == null ?: o.intensities.get(period); |
180 | } |
181 | |
182 | void nextPeriod(double t) { |
183 | while (period != null && t >= period.end) |
184 | setPeriod(o.intensities.higherKey(period)); |
185 | } |
186 | } |
187 | |
188 | L<PerFreq> perFreqs = map(oscillatorsToRender, o -> new PerFreq(o)); |
189 | |
190 | for i over samples: { |
191 | double t = sampleRange.start+i; // t is time in samples |
192 | double sum = 0; |
193 | for (pf : perFreqs) { |
194 | pf.nextPeriod(t); |
195 | bool shouldPrint = toAudio_shouldPrint(i, l(samples)); |
196 | |
197 | if (pf.intensity != null) { |
198 | sum += renderer().renderPeriodSample(pf.period, pf.o.frequency(), pf.intensity.first(), t, shouldPrint); |
199 | } else |
200 | if (shouldPrint) |
201 | printVars(f := pf.o.frequency(), +i, interval := pf.o.interval, +t, c := 0); |
202 | |
203 | } |
204 | |
205 | samples[i] = sum; |
206 | } |
207 | |
208 | ret samples; |
209 | } |
210 | |
211 | RenderFrequencySample renderer() { |
212 | ret renderUsingSine |
213 | ? new RenderFrequencySample_Sine |
214 | : new RenderFrequencySample_SquareWave; |
215 | } |
216 | |
217 | swappable bool toAudio_shouldPrint(int i, int n) { false; } |
218 | |
219 | void subtractedAudioSanityCheck() { |
220 | if (rendered == null) ret; |
221 | int n = l(rendered); |
222 | for i to n: { |
223 | reimmutable i; |
224 | assertDoublesVeryClose_warnOnly(-> "Sample " + i, |
225 | subtractedAudio.sampleSum(0, i, i+1), |
226 | audio.sampleSum(0, i, i+1)-rendered[i], |
227 | 1e-5); |
228 | } |
229 | } |
230 | } |
Began life as a copy of #1033245
download show line numbers debug dex old transpilations
Travelled to 3 computer(s): bhatertpkbcr, mowyntqkapby, mqqgnosmbjvj
No comments. add comment
Snippet ID: | #1033271 |
Snippet name: | ContinuousOscillators_v2 - with deleting recognized frequencies [dev.] |
Eternal ID of this version: | #1033271/52 |
Text MD5: | 938ebeefa64e2df5833e25c076b7364a |
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 11:56:37 |
Source code size: | 7388 bytes / 230 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 243 / 475 |
Version history: | 51 change(s) |
Referenced in: | [show references] |