tuning-core 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,260 @@
1
- # tuning-core
1
+ # New Tonality Tuning Core
2
2
 
3
- To install dependencies:
3
+ A set of primitive classes for representing harmonics, spectrum, and frequency ratios using exact rational number arithmetic. These classes are used across the New Tonality project ecosystem, including:
4
+
5
+ - **[new-tonality-website-v3](https://github.com/new-tonality-project/new-tonality-website-v3)** - Main website and web tools
6
+ - **[sethares-dissonance](https://github.com/new-tonality-project/sethares-dissonance)** - Dissonance curve calculations
7
+ - **[set-consonance](https://github.com/new-tonality-project/set-consonance)** - Set-theorietic consonance analysis tools
8
+
9
+ The primitives provide a foundation for precise tuning mathematics, avoiding floating-point errors through the use of `Fraction.js` for exact rational number representation.
10
+
11
+ ## Contributing
12
+
13
+ Found a bug or have a feature request? Please don't hesitate to reach out!
14
+
15
+ - **GitHub Issues**: Report bugs or request features by opening an issue on the [GitHub repository](https://github.com/new-tonality-project/tuning-core)
16
+ - **Email**: Send feedback directly to [support@newtonality.net](mailto:support@newtonality.net)
17
+
18
+ Your feedback helps improve the New Tonality Tuning Core for everyone!
19
+
20
+ ## Building and Publishing
21
+
22
+ **Build** the package (compiles TypeScript to `dist/`):
4
23
 
5
24
  ```bash
6
- bun install
25
+ bun run build
7
26
  ```
8
27
 
9
- To run:
28
+ **Publish** to npm (builds first, then publishes with public access):
10
29
 
11
30
  ```bash
12
- bun run index.ts
31
+ bun run publish
32
+ ```
33
+
34
+ To bump the version before publishing, use `npm version`:
35
+
36
+ ```bash
37
+ npm version patch # 0.1.0 → 0.1.1
38
+ npm version minor # 0.1.0 → 0.2.0
39
+ npm version major # 0.1.0 → 1.0.0
40
+ ```
41
+
42
+ ## Usage Examples
43
+
44
+ ### Frequency and Interval Representation
45
+
46
+ All frequencies and intervals are handled as (Fraction.js)[https://www.npmjs.com/package/fraction.js?activeTab=readme] objects internally, which allows for exact representation of rational numbers. This is crucial for precise tuning mathematics and avoids floating-point errors. However that may lead to performance issues due to conversion of very percise floating point numbers like 1.0001230576.
47
+
48
+ You can provide frequencies and intervals using any of the following `FractionInput` types,
49
+ it will automatically be converted to Fraction object interanlly.
50
+
51
+ ```ts
52
+ new Harmonic(100.25) // 100.25 Hz
53
+ new Harmonic("100.25") // 100.25 Hz
54
+ new Harmonic("100 1/4") // 100.25 Hz
55
+ new Harmonic(new Fraction(100.25)) // 100.25 Hz
56
+
57
+ // working with intervals:
58
+
59
+ const h = new Harmonic(100)
60
+
61
+ const perfectFifth = h.toTransposed("3/2") // 150 Hz
62
+ const perfectFourth = h.toTransposed([4, 3]) // 133.33.. Hz
63
+ const majorThird = h.toTransposed({ n: 5, d: 4 }) // 125 Hz
64
+ const perfectOctave = h.toTransposed(2) // 200 Hz
65
+ ```
66
+
67
+ If you already have a Fraction object, it is also a valid input:
68
+
69
+ ```ts
70
+ import Fraction from 'fraction.js';
71
+
72
+ const f = new Fraction("4/3")
73
+ const h = new Harmonic(440).transpose(f) // OK
74
+ ```
75
+
76
+ **Note**: When using string fractions (e.g., `"3/2"`), the fraction is automatically simplified. For example, `"6/4"` becomes `"3/2"`.
77
+
78
+ ### Harmonic class
79
+
80
+ ```ts
81
+ import { Harmonic } from 'tuning-core';
82
+
83
+ // Create harmonic with individual parameters
84
+ // Frequencies represent partials (e.g., from harmonic series: 110, 220, 330 Hz)
85
+ const h1 = new Harmonic(220, 0.5, 0); // 220 Hz, amplitude 0.5
86
+ const h2 = new Harmonic("330", 0.8); // 330 Hz, phase defaults to 0
87
+ const h3 = new Harmonic("110 1/4"); // 110.25 Hz, amplitude defaults to 1
88
+
89
+ // Access properties (read-only)
90
+ console.log(h1.frequency); // Fraction obj
91
+ console.log(h1.frequencyStr); // "220"
92
+ console.log(h1.frequencyNum); // 220
93
+ console.log(h1.amplitude); // 0.5
94
+ console.log(h1.phase); // 0
95
+
96
+ // Modify harmonic (methods can be chained)
97
+ h1.setAmplitude(0.7).setPhase(Math.PI);
98
+ h1.scale(0.5); // Scale amplitude by 0.5
99
+
100
+ // Transpose using musical intervals (ratios) in place
101
+ h1.transpose('3/2'); // Transpose by perfect fifth (multiply frequency by 3/2)
102
+ h1.transpose('4/3'); // Transpose by perfect fourth (multiply frequency by 4/3)
103
+
104
+ // Create several harmonics using transposition
105
+ const h2 = h1.toTransposed("3/2") // perfect fifth
106
+ const copy = h1.clone()
107
+
108
+ // Serialization
109
+ const h = new Harmonic(100, 0.5, Math.PI)
110
+ const harmonicData: HarmonicData = h.toJSON()
111
+ console.log(harmonicData)
112
+ // Output: { frequency: '100', amplitude: 0.5, phase: 3.141592653589793 }
113
+
114
+ // serialized data can be used to counstruct a new Harmonic
115
+ const deserializedharmonic = new Harmonic(harmonicData)
116
+ ```
117
+
118
+ ### Spectrum class
119
+
120
+ Spectrum is a collection of individual Harmonics. Under the hood it is a Map with keys that are frequency strings, that is done to avoid duplicates. Adding new harmonic with existing frequency will override the existing harmonic.
121
+
122
+ ```ts
123
+ import { Spectrum } from 'tuning-core';
124
+
125
+ // Create empty spectrum
126
+ const s1 = new Spectrum();
127
+
128
+ // Add harmonics using various methods
129
+ // Frequencies represent partials (e.g., from harmonic series: 110, 220, 330 Hz)
130
+ s1.add(110, 1.0); // Add by frequency (Hz)
131
+ s1.add(220, 0.5, Math.PI); // Add with phase
132
+ s1.add(new Harmonic(330, 0.33)); // Add Harmonic object
133
+ s1.add({ frequency: '440', amplitude: 0.6, phase: 0 }); // Add HarmonicData
134
+
135
+ // Query spectrum
136
+ s1.has(220); // Check if harmonic exists
137
+ s1.get(220); // Get harmonic (returns Harmonic | undefined)
138
+ s1.size; // Number of harmonics
139
+ s1.isEmpty(); // Check if empty
140
+
141
+ // Modify spectrum
142
+ s1.remove(220); // Remove harmonic
143
+ s1.scaleAmplitudes(0.5); // Scale all amplitudes
144
+ s1.clear(); // Remove all harmonics
145
+ s1.transposeHarmonic(220, "3/2"); // Transposes a single harmonic a fifth up
146
+
147
+ // Transpose using musical intervals (ratios)
148
+ s1.transpose('3/2'); // Transpose all harmonics by perfect fifth (mutable)
149
+ s1.transpose('4/3'); // Transpose all harmonics by perfect fourth
150
+
151
+ // Get harmonics
152
+ const harmonics = s1.getHarmonics(); // Returns sorted array
153
+ const lowest = s1.getLowestHarmonic();
154
+ const highest = s1.getHighestHarmonic();
155
+
156
+ // Clone and immutable operations
157
+ const s3 = s1.clone(); // Create independent copy
158
+ const s4 = s1.toTransposed('3/2'); // Create transposed copy (immutable) - perfect fifth
159
+
160
+ // Serialization
161
+ const spectrum = new Speacrum()
162
+
163
+ spectrum.add(110, 1, 0)
164
+ spectrum.add(220, 0.5, 1)
165
+
166
+ const spectrumData: SpectrumData = spectrum.toJSON()
167
+ console.log(spectrumData)
168
+ // output: [
169
+ // { frequency: 110, amplitude: 1, phase: 0 },
170
+ // { frequency: 220, amplitude: 0.5, phase: 1 }
171
+ // ]
172
+
173
+ // Deserialisation
174
+ const s2 = new Spectrum(spectrumData); // OK
13
175
  ```
14
176
 
15
- This project was created using `bun init` in bun v1.2.15. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
177
+ ### Spectrum static methods
178
+
179
+ A set of convenience factory methods to create common spectra such as harmonic series and others
180
+
181
+ ```ts
182
+ import { Spectrum } from 'tuning-core';
183
+
184
+ // Create harmonic series (frequency ratios: 1, 2, 3, 4, ...)
185
+ // These represent the harmonic series relative to a fundamental
186
+ const harmonicSeries = Spectrum.harmonicSeries(8);
187
+ // Creates spectrum with ratios 1, 2, 3, 4, 5, 6, 7, 8
188
+ // Amplitudes follow natural decay: 1, 0.5, 0.333, 0.25, ...
189
+
190
+ // Create spectrum from absolute frequencies (Hz)
191
+ const spectrum = Spectrum.fromFrequencies(
192
+ [110, 220, 330, 440], // Frequencies in Hz (harmonic series)
193
+ [1.0, 0.5, 0.33, 0.25], // Amplitudes
194
+ [0, 0, 0, 0] // Phases
195
+ );
196
+ ```
197
+
198
+ ### Performance Considerations
199
+
200
+ The performance of `Fraction.js` constructor varies significantly depending on the input type and ratio complexity. Benchmarks show different characteristics for integers vs. simple vs. complex ratios:
201
+
202
+ **Integers (e.g., `440`) measured for 100_000_000 iterations:**
203
+ | Input Type | Time per Operation | Relative Speed |
204
+ |------------|-------------------|----------------|
205
+ | **Float** `440` | 88 ns | 1.00x (fastest) |
206
+ | **Array** `[440, 1]` | 114 ns | 1.30x |
207
+ | **Object** `{n: 440, d: 1}` | 114 ns | 1.30x |
208
+ | **Fraction (copying)** | 143 ns | 1.63x |
209
+ | **String** `"440"` | 237 ns | 2.70x (slowest) |
210
+
211
+ **Simple Ratios (e.g., `3/2`) measured for 100_000_000 iterations:**
212
+ | Input Type | Time per Operation | Relative Speed |
213
+ |------------|-------------------|----------------|
214
+ | **Object** `{n: 3, d: 2}` | 131 ns | 1.00x (fastest) |
215
+ | **Array** `[3, 2]` | 132 ns | 1.01x |
216
+ | **Fraction (copying)** | 160 ns | 1.22x |
217
+ | **Float** `1.5` | 231 ns | 1.77x |
218
+ | **String** `"3/2"` | 321 ns | 2.45x (slowest) |
219
+
220
+ **Complex Ratios (e.g., `12345/6789`) measured for 10_000_000 iterations:**
221
+ | Input Type | Time per Operation | Relative Speed |
222
+ |------------|-------------------|----------------|
223
+ | **Fraction (copying)** | 279 ns | 1.00x (fastest) |
224
+ | **Object** `{n, d}` | 286 ns | 1.02x |
225
+ | **Array** `[n, d]` | 287 ns | 1.03x |
226
+ | **String** `"n/d"` | 506 ns | 1.81x |
227
+ | **Float** `1.818...` | 2,826 ns | **10.12x** (slowest) |
228
+
229
+ **Key Recommendations:**
230
+
231
+ 1. **For integers, floats are fastest**: When working with whole numbers (e.g., `440` Hz), using a float/number directly is the fastest option (~1.3x faster than array/object). However, arrays/objects are still very fast and provide consistency.
232
+
233
+ 2. **Use arrays or objects for ratios**: For both simple and complex ratios, array `[numerator, denominator]` or object `{n: numerator, d: denominator}` formats consistently provide the best performance. They are the fastest option for simple ratios and nearly as fast as copying for complex ratios.
234
+
235
+ 3. **Avoid floats for complex intervals**: Float input becomes **~10x slower** for complex ratios because `Fraction.js` must convert the decimal to a rational approximation. For simple ratios, floats are better than strings but still slower than array/object.
236
+
237
+ 4. **String parsing overhead**: String fractions add parsing overhead (~2.7x slower for integers, ~2.5x for simple ratios, ~1.8x for complex ratios). The overhead is not that great so they can be used for improved readability, but avoid them for performance-critical code.
238
+
239
+ 5. **Reuse Fraction objects when possible**: Copying an existing `Fraction` object has comparable performance to construction from array or object
240
+
241
+ **Example - Performance Best Practices:**
242
+
243
+ ```ts
244
+ // ✅ FASTEST FOR INTEGERS: Floats are fastest for whole numbers
245
+ const fastestInt = new Harmonic(440); // Float input is fastest for integers
246
+
247
+ // ✅ FASTEST FOR RATIOS: Use array/object for best performance
248
+ const fastest1 = new Harmonic(440).transpose([3, 2]); // Simple ratio
249
+ const fastest2 = new Harmonic(440).transpose({ n: 3, d: 2 }); // Simple ratio
250
+ const fastest3 = new Harmonic(440).transpose([12345, 6789]); // Complex ratio
251
+
252
+ // ✅ GOOD: String fractions for readability (acceptable performance)
253
+ const readable = new Harmonic(440).transpose("3/2"); // ~2.5x slower than array
254
+
255
+ // ⚠️ AVOID: Floats for complex ratios (very slow)
256
+ const slowComplex = new Harmonic(440).transpose(1.8181818181818182); // ~10x slower
257
+
258
+ // ⚠️ ACCEPTABLE: Floats for simple ratios (but array/object still faster)
259
+ const acceptable = new Harmonic(440).transpose(1.5); // ~1.8x slower than array
260
+ ```
@@ -0,0 +1,92 @@
1
+ import Fraction, { type FractionInput } from 'fraction.js';
2
+ import { type HarmonicData } from '../lib';
3
+ /**
4
+ * Represents a single partial in an arbitrary spectrum.
5
+ * The name Harmonic does not imply a harmonic series but chosen over
6
+ * Partial to avoid coflict with the TypeScript's Partial type.
7
+ * Mutable for better real-time performance.
8
+ * Frequencies are stored as rational numbers for exact tuning mathematics.
9
+ *
10
+ * The constructor accepts either a HarmonicData object or individual parameters.
11
+ *
12
+ * @example
13
+ * // Using HarmonicData object
14
+ * const harmonic1 = new Harmonic({
15
+ * frequency: 440,
16
+ * amplitude: 0.5,
17
+ * phase: Math.PI
18
+ * });
19
+ *
20
+ * @example
21
+ * // Using individual parameters
22
+ * const harmonic2 = new Harmonic(440, 0.5, Math.PI);
23
+ * const harmonic3 = new Harmonic("440"); // amplitude defaults to 1, phase defaults to 0
24
+ * const harmonic4 = new Harmonic("440 1/3"); // frequency as a string with whole and fractional part
25
+ * const harmonic4 = new Harmonic([107, 100]); // frequency as a fraction of two integers 107/100
26
+ * // Note that frequency is treated as a rational number from Fraction.js lib
27
+ * // so it accepts FractionInput type
28
+ */
29
+ export declare class Harmonic {
30
+ private _frequency;
31
+ private _amplitude;
32
+ private _phase;
33
+ /**
34
+ * Get frequency as a Fraction
35
+ */
36
+ get frequency(): Fraction;
37
+ /**
38
+ * Get frequency as a float number
39
+ */
40
+ get frequencyNum(): number;
41
+ /**
42
+ * Get frequency as a simplified fraction string (e.g., "3/2")
43
+ */
44
+ get frequencyStr(): string;
45
+ /**
46
+ * Get amplitude (0-1 normalized)
47
+ */
48
+ get amplitude(): number;
49
+ /**
50
+ * Get phase (0-2π radians)
51
+ */
52
+ get phase(): number;
53
+ constructor(data: HarmonicData);
54
+ constructor(frequency: FractionInput, amplitude?: number, phase?: number);
55
+ private validate;
56
+ /**
57
+ * Sets frequency to the provided value
58
+ */
59
+ setFrequency(frequency: FractionInput): this;
60
+ /**
61
+ * Sets amplitude (0-1)
62
+ */
63
+ setAmplitude(amplitude: number): this;
64
+ /**
65
+ * Sets phase (0-2π)
66
+ */
67
+ setPhase(phase: number): this;
68
+ /**
69
+ * Multiply frequency by a ratio (for transposition)
70
+ */
71
+ transpose(ratio: FractionInput): this;
72
+ /**
73
+ * Multiply frequency by a ratio (for transposition)
74
+ */
75
+ toTransposed(ratio: FractionInput): Harmonic;
76
+ /**
77
+ * Scale amplitude by a factor
78
+ */
79
+ scale(factor: number): this;
80
+ /**
81
+ * Create a copy of this harmonic
82
+ */
83
+ clone(): Harmonic;
84
+ /**
85
+ * Compare frequencies (for sorting)
86
+ */
87
+ compareFrequency(other: Harmonic): number;
88
+ /**
89
+ * Serializes the harmonic to a HarmonicData object
90
+ */
91
+ toJSON(): HarmonicData;
92
+ }
@@ -0,0 +1,164 @@
1
+ import Fraction, {} from 'fraction.js';
2
+ import { isHarmonicData } from '../lib';
3
+ /**
4
+ * Represents a single partial in an arbitrary spectrum.
5
+ * The name Harmonic does not imply a harmonic series but chosen over
6
+ * Partial to avoid coflict with the TypeScript's Partial type.
7
+ * Mutable for better real-time performance.
8
+ * Frequencies are stored as rational numbers for exact tuning mathematics.
9
+ *
10
+ * The constructor accepts either a HarmonicData object or individual parameters.
11
+ *
12
+ * @example
13
+ * // Using HarmonicData object
14
+ * const harmonic1 = new Harmonic({
15
+ * frequency: 440,
16
+ * amplitude: 0.5,
17
+ * phase: Math.PI
18
+ * });
19
+ *
20
+ * @example
21
+ * // Using individual parameters
22
+ * const harmonic2 = new Harmonic(440, 0.5, Math.PI);
23
+ * const harmonic3 = new Harmonic("440"); // amplitude defaults to 1, phase defaults to 0
24
+ * const harmonic4 = new Harmonic("440 1/3"); // frequency as a string with whole and fractional part
25
+ * const harmonic4 = new Harmonic([107, 100]); // frequency as a fraction of two integers 107/100
26
+ * // Note that frequency is treated as a rational number from Fraction.js lib
27
+ * // so it accepts FractionInput type
28
+ */
29
+ export class Harmonic {
30
+ _frequency; // Frequency as ratio (e.g., 3/2 for perfect fifth)
31
+ _amplitude; // 0-1 normalized
32
+ _phase; // 0-2π radians
33
+ /**
34
+ * Get frequency as a Fraction
35
+ */
36
+ get frequency() {
37
+ return this._frequency;
38
+ }
39
+ /**
40
+ * Get frequency as a float number
41
+ */
42
+ get frequencyNum() {
43
+ return this._frequency.valueOf();
44
+ }
45
+ /**
46
+ * Get frequency as a simplified fraction string (e.g., "3/2")
47
+ */
48
+ get frequencyStr() {
49
+ return this._frequency.toFraction();
50
+ }
51
+ /**
52
+ * Get amplitude (0-1 normalized)
53
+ */
54
+ get amplitude() {
55
+ return this._amplitude;
56
+ }
57
+ /**
58
+ * Get phase (0-2π radians)
59
+ */
60
+ get phase() {
61
+ return this._phase;
62
+ }
63
+ constructor(frequencyOrData, amplitude = 1, phase = 0) {
64
+ if (isHarmonicData(frequencyOrData)) {
65
+ // HarmonicData case
66
+ this._frequency = new Fraction(frequencyOrData.frequency);
67
+ this._amplitude = frequencyOrData.amplitude;
68
+ this._phase = frequencyOrData.phase;
69
+ }
70
+ else {
71
+ // Original case: frequency, amplitude, phase
72
+ this._frequency = new Fraction(frequencyOrData);
73
+ this._amplitude = amplitude ?? 1;
74
+ this._phase = phase ?? 0;
75
+ }
76
+ this.validate();
77
+ }
78
+ validate() {
79
+ if (this._frequency.compare(0) <= 0) {
80
+ throw new Error('Frequency must be positive');
81
+ }
82
+ if (this._amplitude < 0 || this._amplitude > 1) {
83
+ throw new Error('Amplitude must be between 0 and 1');
84
+ }
85
+ if (this._phase < 0 || this._phase >= 2 * Math.PI) {
86
+ throw new Error('Phase must be between 0 and 2π');
87
+ }
88
+ }
89
+ /**
90
+ * Sets frequency to the provided value
91
+ */
92
+ setFrequency(frequency) {
93
+ this._frequency = new Fraction(frequency);
94
+ if (this._frequency.compare(0) <= 0) {
95
+ throw new Error('Frequency must be positive');
96
+ }
97
+ return this;
98
+ }
99
+ /**
100
+ * Sets amplitude (0-1)
101
+ */
102
+ setAmplitude(amplitude) {
103
+ if (amplitude < 0 || amplitude > 1) {
104
+ throw new Error('Amplitude must be between 0 and 1');
105
+ }
106
+ this._amplitude = amplitude;
107
+ return this;
108
+ }
109
+ /**
110
+ * Sets phase (0-2π)
111
+ */
112
+ setPhase(phase) {
113
+ if (phase < 0 || phase >= 2 * Math.PI) {
114
+ throw new Error('Phase must be between 0 and 2π');
115
+ }
116
+ this._phase = phase;
117
+ return this;
118
+ }
119
+ /**
120
+ * Multiply frequency by a ratio (for transposition)
121
+ */
122
+ transpose(ratio) {
123
+ this._frequency = this._frequency.mul(ratio);
124
+ return this;
125
+ }
126
+ /**
127
+ * Multiply frequency by a ratio (for transposition)
128
+ */
129
+ toTransposed(ratio) {
130
+ return this.clone().transpose(ratio);
131
+ }
132
+ /**
133
+ * Scale amplitude by a factor
134
+ */
135
+ scale(factor) {
136
+ this._amplitude = Math.max(0, Math.min(1, this._amplitude * factor));
137
+ return this;
138
+ }
139
+ /**
140
+ * Create a copy of this harmonic
141
+ */
142
+ clone() {
143
+ return new Harmonic(this._frequency, this._amplitude, this._phase);
144
+ }
145
+ /**
146
+ * Compare frequencies (for sorting)
147
+ */
148
+ compareFrequency(other) {
149
+ return this._frequency.compare(other._frequency);
150
+ }
151
+ /**
152
+ * Serializes the harmonic to a HarmonicData object
153
+ */
154
+ toJSON() {
155
+ return {
156
+ frequency: {
157
+ n: Number(this._frequency.n),
158
+ d: Number(this._frequency.d),
159
+ },
160
+ amplitude: this._amplitude,
161
+ phase: this._phase,
162
+ };
163
+ }
164
+ }
@@ -0,0 +1,157 @@
1
+ import Fraction, { type FractionInput } from 'fraction.js';
2
+ import { Spectrum } from './Spectrum';
3
+ /**
4
+ * Represents a set of frequency ratios (intervals).
5
+ * Uses Map to ensure no duplicate ratios.
6
+ * Useful for building tuning systems, scales, and analyzing interval collections.
7
+ */
8
+ export declare class IntervalSet {
9
+ private ratios;
10
+ constructor(ratios?: FractionInput[]);
11
+ /**
12
+ * Add a ratio to the set.
13
+ * If ratio already exists, it replaces the existing one.
14
+ */
15
+ add(ratio: FractionInput): this;
16
+ /**
17
+ * Check if the set contains this ratio
18
+ */
19
+ has(ratio: FractionInput): boolean;
20
+ /**
21
+ * Remove a ratio from the set
22
+ * @returns true if ratio was removed, false if it didn't exist
23
+ */
24
+ delete(ratio: FractionInput): this;
25
+ /**
26
+ * Get a ratio from the set
27
+ * @returns Fraction object or undefined if not found
28
+ */
29
+ get(ratio: FractionInput): Fraction | undefined;
30
+ /**
31
+ * Number of ratios in the set
32
+ */
33
+ get size(): number;
34
+ /**
35
+ * Check if the set is empty
36
+ */
37
+ isEmpty(): boolean;
38
+ /**
39
+ * Get all ratios as an array
40
+ */
41
+ getRatios(): Fraction[];
42
+ /**
43
+ * Get the smallest ratio in the set
44
+ */
45
+ min(): Fraction | undefined;
46
+ /**
47
+ * Get the largest ratio in the set
48
+ */
49
+ max(): Fraction | undefined;
50
+ /**
51
+ * Clear all ratios from the set
52
+ */
53
+ clear(): this;
54
+ /**
55
+ * Create a copy of this interval set
56
+ */
57
+ clone(): IntervalSet;
58
+ /**
59
+ * Iterate over all ratios
60
+ */
61
+ forEach(callback: (ratio: Fraction, key: string) => void): void;
62
+ /**
63
+ * Convert to Spectrum with equal amplitudes
64
+ * @param fundamentalHz - Optional fundamental frequency in Hz
65
+ * @param amplitude - Amplitude for all harmonics (default 1)
66
+ */
67
+ toSpectrum(amp: (ratio: Fraction, index: number) => number): Spectrum;
68
+ /**
69
+ * Get ratios as array of strings
70
+ */
71
+ toStrings(): string[];
72
+ /**
73
+ * Get ratios as array of decimal numbers
74
+ */
75
+ toNumbers(): number[];
76
+ /**
77
+ * Check if this set equals another set
78
+ */
79
+ equals(other: IntervalSet): boolean;
80
+ /**
81
+ * Calculate GCD of all ratios
82
+ */
83
+ private getGCD;
84
+ /**
85
+ * Convert an array of ratios to an array of intervals (differences between consecutive ratios).
86
+ * The first interval is always 1, and each subsequent interval is the current ratio divided by the previous ratio.
87
+ *
88
+ * @param ratios - Array of Fraction objects representing ratios
89
+ * @returns Array of Fraction objects representing intervals, starting with 1
90
+ *
91
+ * @example
92
+ * // If ratios are [1, 5/4, 3/2, 2], intervals will be [1, 5/4, 6/5, 4/3]
93
+ * const ratios = [new Fraction(1), new Fraction(5, 4), new Fraction(3, 2), new Fraction(2)];
94
+ * const intervals = getSweepIntervals(ratios);
95
+ */
96
+ toSweepIntervals(): Fraction[];
97
+ minMax(min: FractionInput, max: FractionInput): this;
98
+ /**
99
+ * Generate all unique ratios between min and max with denominators up to maxDenominator.
100
+ *
101
+ * This creates all possible fractions n/d where:
102
+ * - min <= n/d <= max
103
+ * - 1 <= d <= maxDenominator
104
+ * - 1 <= n
105
+ * - n and d are coprime (simplified fractions only)
106
+ *
107
+ * The Map in IntervalSet automatically handles deduplication of equivalent fractions.
108
+ *
109
+ * @param min - Minimum ratio (inclusive)
110
+ * @param max - Maximum ratio (inclusive)
111
+ * @param maxDenominator - Maximum denominator for generated fractions
112
+ *
113
+ * @example
114
+ * // All simple ratios in one octave
115
+ * IntervalSetFactory.range(1, 2, 5)
116
+ * // Returns: 1/1, 6/5, 5/4, 4/3, 3/2, 8/5, 5/3, 2/1
117
+ *
118
+ * @example
119
+ * // Fine divisions for analysis (100 steps per octave)
120
+ * IntervalSetFactory.range(1, 2, 100)
121
+ *
122
+ * @example
123
+ * // Ratios across 1.5 octaves
124
+ * IntervalSetFactory.range(1, 3, 12)
125
+ */
126
+ static range(min: FractionInput, max: FractionInput, maxDenominator: number): IntervalSet;
127
+ /**
128
+ * Generate affinitive tuning intervals that contain all ratios between harmonics of two spectra.
129
+ * For each pair of harmonics (one from each spectrum), calculates the ratio between them.
130
+ * Useful for analyzing harmonic relationships and consonance between two sounds.
131
+ *
132
+ * @param spectrum1 - First spectrum
133
+ * @param spectrum2 - Second spectrum
134
+ * @returns IntervalSet containing all unique ratios between the spectra's harmonics
135
+ */
136
+ static affinitive(context: Spectrum, complement: Spectrum): IntervalSet;
137
+ /**
138
+ * Add intermediate intervals between existing ratios to create a smooth curve.
139
+ * Uses logarithmic spacing to ensure perceptually uniform distribution.
140
+ * A gap of 1→2 gets the same density as 2→4 (both are octaves).
141
+ *
142
+ * @param maxGapCents - Maximum allowed gap in cents (e.g., 10 for 10-cent steps)
143
+ * 1200 cents = 1 octave, 100 cents ≈ 1 semitone
144
+ * @returns this (for chaining)
145
+ *
146
+ * @example
147
+ * const intervals = new IntervalSet([1, 2, 4]);
148
+ * intervals.densify(100); // Max 100-cent (1 semitone) gaps
149
+ * // Adds same number of points between 1→2 as between 2→4
150
+ *
151
+ * @example
152
+ * // For dissonance curves - logarithmic spacing
153
+ * const extrema = IntervalSet.affinitive(spectrum1, spectrum2);
154
+ * extrema.densify(10); // ~10-cent resolution for smooth curve
155
+ */
156
+ densify(maxGapCents: number): this;
157
+ }