diff --git a/modules.json b/modules.json index f8a3abf08f..042a83fe36 100644 --- a/modules.json +++ b/modules.json @@ -47,6 +47,9 @@ "Sound" ] }, + "sound_fft": { + "tabs": [] + }, "scrabble": { "tabs": [] }, @@ -122,4 +125,4 @@ "Unittest" ] } -} \ No newline at end of file +} diff --git a/package.json b/package.json index b69d165677..e311510eac 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "@jscad/stl-serializer": "2.1.11", "ace-builds": "^1.25.1", "classnames": "^2.3.1", + "fft.js": "^4.0.4", "gl-matrix": "^3.3.0", "js-slang": "^1.0.81", "lodash": "^4.17.21", diff --git a/src/bundles/plotly/functions.ts b/src/bundles/plotly/functions.ts index afcceb5e0f..3cf841ea42 100644 --- a/src/bundles/plotly/functions.ts +++ b/src/bundles/plotly/functions.ts @@ -4,8 +4,13 @@ */ import context from 'js-slang/context'; +import { list_to_vector } from 'js-slang/dist/stdlib/list'; import Plotly, { type Data, type Layout } from 'plotly.js-dist'; import type { Sound } from '../sound/types'; +import type { + AugmentedSample, + FrequencyList, +} from '../sound_fft/types'; import { generatePlot } from './curve_functions'; import { type Curve, @@ -14,7 +19,7 @@ import { DrawnPlot, type ListOfPairs } from './plotly'; -import { get_duration, get_wave, is_sound } from './sound_functions'; +import { get_duration, get_wave, is_sound, get_magnitude } from './sound_functions'; const drawnPlots: (CurvePlot | DrawnPlot)[] = []; @@ -355,10 +360,10 @@ export const draw_connected_3d = createPlotFunction( /** * Returns a function that turns a given Curve into a Drawing, by sampling the - * Curve at num sample points. The Drawing consists of isolated points, and does not connect them. - * When a program evaluates to a Drawing, the Source system displays it graphically, in a window, + * Curve at `num` sample points. The Drawing consists of isolated points, and does not connect them. + * When a program evaluates to a Drawing, the Source system displays it graphically, in a window. * - * * @param num determines the number of points, lower than 65535, to be sampled. + * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type 2D Curve → Drawing * @example @@ -380,10 +385,10 @@ export const draw_points_2d = createPlotFunction( /** * Returns a function that turns a given 3D Curve into a Drawing, by sampling the - * 3D Curve at num sample points. The Drawing consists of isolated points, and does not connect them. - * When a program evaluates to a Drawing, the Source system displays it graphically, in a window, + * 3D Curve at `num` sample points. The Drawing consists of isolated points, and does not connect them. + * When a program evaluates to a Drawing, the Source system displays it graphically, in a window. * - * * @param num determines the number of points, lower than 65535, to be sampled. + * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type 3D Curve → Drawing * @example @@ -397,11 +402,12 @@ export const draw_points_3d = createPlotFunction( ); /** - * Visualizes the sound on a 2d line graph + * Visualizes the given sound on a 2D line graph (amplitude vs time). * @param sound the sound which is to be visualized on plotly */ export const draw_sound_2d = (sound: Sound) => { const FS: number = 44100; // Output sample rate + const POINT_LIMIT: number = 44100; // Maximum number of points to be plotted if (!is_sound(sound)) { throw new Error( `draw_sound_2d is expecting sound, but encountered ${sound}` @@ -425,10 +431,19 @@ export const draw_sound_2d = (sound: Sound) => { const x_s: number[] = []; const y_s: number[] = []; - - for (let i = 0; i < channel.length; i += 1) { + const insert_point = (i: number) => { x_s.push(time_stamps[i]); y_s.push(channel[i]); + }; + + if (channel.length <= POINT_LIMIT) { + for (let i = 0; i < channel.length; i += 1) { + insert_point(i); + } + } else { + for (let i = 0; i < POINT_LIMIT; i += 1) { + insert_point(Math.round(channel.length / POINT_LIMIT * i)); + } } const plotlyData: Data = { @@ -463,6 +478,70 @@ export const draw_sound_2d = (sound: Sound) => { } }; +/** + * Visualizes the frequency-domain samples of the given sound on a 2D line graph + * (magnitude vs frequency). + * @param frequencies the frequency-domain samples of a sound to be visualized on plotly + */ +export const draw_sound_frequency_2d = (frequencies: FrequencyList) => { + const FS: number = 44100; // Output sample rate + const POINT_LIMIT: number = 44100; // Maximum number of points to be plotted + + const x_s: number[] = []; + const y_s: number[] = []; + const frequencies_arr: AugmentedSample[] = list_to_vector(frequencies); + const len: number = frequencies_arr.length; + + const insert_point = (i: number) => { + const bin_freq: number = i * FS / len; + const sample: AugmentedSample = frequencies_arr[i]; + const magnitude: number = get_magnitude(sample); + + x_s.push(bin_freq); + y_s.push(magnitude); + } + + if (len <= POINT_LIMIT) { + for (let i = 0; i < len; i += 1) { + insert_point(i); + } + } else { + for (let i = 0; i < POINT_LIMIT; i += 1) { + insert_point(Math.round(len / POINT_LIMIT * i)); + } + } + + const plotlyData: Data = { + x: x_s, + y: y_s + }; + const plot = new CurvePlot( + draw_new_curve, + { + ...plotlyData, + type: 'scattergl', + mode: 'lines', + line: { width: 0.5 } + } as Data, + { + xaxis: { + type: 'linear', + title: 'Frequency', + anchor: 'y', + position: 0, + rangeslider: { visible: true } + }, + yaxis: { + type: 'linear', + visible: false + }, + bargap: 0.2, + barmode: 'stack' + } + ); + if (drawnPlots) drawnPlots.push(plot); +}; + function draw_new_curve(divId: string, data: Data, layout: Partial) { Plotly.react(divId, [data], layout); } diff --git a/src/bundles/plotly/index.ts b/src/bundles/plotly/index.ts index e588d53add..9960de1c98 100644 --- a/src/bundles/plotly/index.ts +++ b/src/bundles/plotly/index.ts @@ -10,5 +10,6 @@ export { draw_connected_3d, draw_points_2d, draw_points_3d, - draw_sound_2d + draw_sound_2d, + draw_sound_frequency_2d } from './functions'; diff --git a/src/bundles/plotly/sound_functions.ts b/src/bundles/plotly/sound_functions.ts index 9d46078f65..0a50c685c6 100644 --- a/src/bundles/plotly/sound_functions.ts +++ b/src/bundles/plotly/sound_functions.ts @@ -4,6 +4,7 @@ import { is_pair } from 'js-slang/dist/stdlib/list'; import type { Sound, Wave } from '../sound/types'; +import type { FrequencySample, AugmentedSample } from '../sound_fft/types'; export function is_sound(x: any): x is Sound { return ( is_pair(x) @@ -31,3 +32,21 @@ export function get_wave(sound: Sound): Wave { export function get_duration(sound: Sound): number { return tail(sound); } +/** + * Accesses the magnitude of a given frequency sample. + * + * @param frequency_sample given frequency sample + * @return the magnitude of the frequency sample + */ +function get_magnitude_fs(frequency_sample: FrequencySample): number { + return head(frequency_sample); +} +/** + * Accesses the magnitude of a given augmented sample. + * + * @param augmented_sample given augmented sample + * @return the magnitude of the augmented sample + */ +export function get_magnitude(augmented_sample: AugmentedSample): number { + return get_magnitude_fs(tail(augmented_sample)); +} diff --git a/src/bundles/sound/functions.ts b/src/bundles/sound/functions.ts index 53a9966a4d..92a60d982b 100644 --- a/src/bundles/sound/functions.ts +++ b/src/bundles/sound/functions.ts @@ -16,7 +16,7 @@ import type { Sound, SoundProducer, SoundTransformer, - AudioPlayed + AudioPlayed, } from './types'; // Global Constants and Variables diff --git a/src/bundles/sound_fft/functions.ts b/src/bundles/sound_fft/functions.ts new file mode 100644 index 0000000000..613b4e068f --- /dev/null +++ b/src/bundles/sound_fft/functions.ts @@ -0,0 +1,386 @@ +import FFT from 'fft.js'; +import { + pair, + head, + tail, + set_tail, + accumulate, + list_to_vector, + type List +} from 'js-slang/dist/stdlib/list'; +import type { Sound } from '../sound/types'; +import { vector_to_list } from './list'; +import { + make_sound, + get_wave, + get_duration +} from './sound_functions'; +import type { + TimeSamples, + FrequencySample, + AugmentedSample, + FrequencySamples, + FrequencyList, + Filter +} from './types'; + +// TODO: Export FS from 'sound', then import it here. +// We cannot import yet since we didn't export it. +const FS: number = 44100; + +// CONVERSION + +function time_to_frequency(time_samples: TimeSamples): FrequencySamples { + const n = time_samples.length; + const fft = new FFT(n); + + const flatDomain = fft.createComplexArray(); + const fullSamples = fft.createComplexArray(); + + fft.toComplexArray(time_samples, fullSamples); + fft.transform(flatDomain, fullSamples); + + const frequencyDomain = new Array(n); + for (let i = 0; i < n; i++) { + const real = flatDomain[i * 2]; + const imag = flatDomain[i * 2 + 1]; + const magnitude = Math.sqrt(real * real + imag * imag); + const phase = Math.atan2(imag, real); + frequencyDomain[i] = pair(magnitude, phase); + } + + return frequencyDomain; +} + +function frequency_to_time(frequency_samples: FrequencySamples): TimeSamples { + const n = frequency_samples.length; + const fft = new FFT(n); + + const timeDomain = fft.createComplexArray(); + const flatDomain = fft.createComplexArray(); + + for (let i = 0; i < n; i++) { + const magnitude = get_magnitude_fs(frequency_samples[i]); + const phase = get_phase_fs(frequency_samples[i]); + const real = magnitude * Math.cos(phase); + const imag = magnitude * Math.sin(phase); + flatDomain[i * 2] = real; + flatDomain[i * 2 + 1] = imag; + } + + fft.inverseTransform(timeDomain, flatDomain); + + const timeSamples = new Array(n); + fft.fromComplexArray(timeDomain, timeSamples); + return timeSamples; +} + +function next_power_of_2(x: number): number { + const lowerPowerOf2: number = 1 << 31 - Math.clz32(x); + if (lowerPowerOf2 == x) { + return lowerPowerOf2; + } else { + return lowerPowerOf2 * 2; + } +} + +// Pad to power-of-2 +function sound_to_time_samples(sound: Sound): TimeSamples { + const baseSize = Math.ceil(FS * get_duration(sound)); + const sampleSize = next_power_of_2(baseSize); + const wave = get_wave(sound); + + const sample = new Array(sampleSize); + for (let i = 0; i < sampleSize; i += 1) { + sample[i] = wave(i / FS); + } + + return sample; +} + +function time_samples_to_sound(time_samples: TimeSamples): Sound { + const duration = time_samples.length / FS; + return make_sound((t) => { + const index = t * FS; + const lowerIndex = Math.floor(index); + const upperIndex = lowerIndex + 1; + const ratio = index - lowerIndex; + const upper = time_samples[upperIndex] ? time_samples[upperIndex] : 0; + const lower = time_samples[lowerIndex] ? time_samples[lowerIndex] : 0; + return lower * (1 - ratio) + upper * ratio; + }, duration); +} + +function frequency_to_list(frequency_samples: FrequencySamples): FrequencyList { + const len = frequency_samples.length; + const augmented_samples: AugmentedSample[] = new Array(len); + for (let i = 0; i < len; i++) { + augmented_samples[i] = pair(i * FS / len, frequency_samples[i]); + } + const frequency_list: FrequencyList = vector_to_list(augmented_samples); + return frequency_list; +} + +function list_to_frequency(frequency_list: FrequencyList): FrequencySamples { + const augmented_samples: AugmentedSample[] = list_to_vector(frequency_list); + const frequency_samples: FrequencySamples = new Array(augmented_samples.length); + for (let i = 0; i < augmented_samples.length; i++) { + frequency_samples[i] = tail(augmented_samples[i]); + } + return frequency_samples; +} + +/** + * Returns the frequency-domain representation of the given Sound. + * + * The length of the returned list is the smallest power of 2 that is larger + * than or equal to the duration of the sound multiplied by the sampling + * rate (44,100). + * + * Converting a Sound to its frequency-domain representation and back + * may produce a Sound with a longer duration. + * @param sound given Sound + * @returns the frequency-domain representation of the given Sound + * @example const f = sound_to_frequency(sine_sound(440, 1)); + */ +export function sound_to_frequency(sound: Sound): FrequencyList { + const time_samples: TimeSamples = sound_to_time_samples(sound); + const frequency_samples: FrequencySamples = time_to_frequency(time_samples); + const frequency_list: FrequencyList = frequency_to_list(frequency_samples); + return frequency_list; +} + +/** + * Returns the Sound with the given frequency-domain representation. + * + * The duration of the returned Sound in seconds is the length of the + * frequency-domain representation (which is a power of 2), divided by the + * sampling rate (44,100). + * + * Converting a Sound to its frequency-domain representation and back + * may produce a Sound with a longer duration. + * @param frequency_list given frequency-domain representation + * @returns the Sound with the given frequency-domain representation + * @example + * ```typescript + * const f = sound_to_frequency(sine_sound(440, 1)); + * const s = frequency_to_sound(f); + * ``` + */ +export function frequency_to_sound(frequency_list: FrequencyList): Sound { + const frequency_samples: FrequencySamples = list_to_frequency(frequency_list); + const time_samples: TimeSamples = frequency_to_time(frequency_samples); + const sound: Sound = time_samples_to_sound(time_samples); + return sound; +} + +// FREQUENCY, MAGNITUDE and PHASE + +function get_magnitude_fs(frequency_sample: FrequencySample): number { + return head(frequency_sample); +} + +function get_phase_fs(frequency_sample: FrequencySample): number { + return tail(frequency_sample); +} + +/** + * Returns the frequency of a given sample in a frequency-domain representation. + * @param augmented_sample a sample in a frequency-domain representation + * @returns frequency + * @example + * ```typescript + * const f = sound_to_frequency(sine_sound(440, 1)); + * get_frequency(head(f)); + * ``` + */ +export function get_frequency(augmented_sample: AugmentedSample): number { + return head(augmented_sample); +} + +/** + * Returns the magnitude of a given sample in a frequency-domain representation. + * @param augmented_sample a sample in a frequency-domain representation + * @returns magnitude + * @example + * ```typescript + * const f = sound_to_frequency(sine_sound(440, 1)); + * get_magnitude(head(f)); + * ``` + */ +export function get_magnitude(augmented_sample: AugmentedSample): number { + return get_magnitude_fs(tail(augmented_sample)); +} + +/** + * Returns the phase of a given sample in a frequency-domain representation. + * @param augmented_sample a sample in a frequency-domain representation + * @returns phase + * @example + * ```typescript + * const f = sound_to_frequency(sine_sound(440, 1)); + * get_phase(head(f)); + * ``` + */ +export function get_phase(augmented_sample: AugmentedSample): number { + return get_phase_fs(tail(augmented_sample)); +} + +/** + * Returns a frequency sample with the given parameters. + * @param frequency frequency of the constructed element + * @param magnitude magnitude of the constructed element + * @param phase phase of the constructed element + * @returns a frequency sample + * @example + * ```typescript + * const mute_sample = sample => make_augmented_sample( + * get_frequency(sample), + * 0, + * get_phase(sample)); + * ``` + */ +export function make_augmented_sample(frequency: number, magnitude: number, phase: number): AugmentedSample { + return pair(frequency, pair(magnitude, phase)); +} + +// FILTER CREATION + +/** + * Makes a low pass filter with the given frequency threshold. Frequencies + * below the threshold will pass through the filter. Other frequencies will be + * removed. + * + * The filter is a function that takes in a frequency-domain representation and + * returns another frequency-domain representation. + * @param frequency threshold frequency + * @returns a low pass filter + * @example + * ```typescript + * const s1 = simultaneously(list( + * sine_sound(400, 1), + * sine_sound(2000, 1))); + * const f1 = sound_to_frequency(s1); + * const f2 = low_pass_filter(1000)(f1); + * const s2 = frequency_to_sound(f2); + * play(s2); + * ``` + */ +export function low_pass_filter(frequency: number): Filter { + return (freq_list: FrequencyList) => { + const freq_domain: AugmentedSample[] = list_to_vector(freq_list); + for (let i = 0; i < freq_domain.length; i++) { + const sample_freq: number = get_frequency(freq_domain[i]); + if (sample_freq > frequency && sample_freq < FS - frequency) { + freq_domain[i] = make_augmented_sample( + sample_freq, + 0, + get_phase(freq_domain[i]) + ); + } + } + return vector_to_list(freq_domain); + }; +} + +/** + * Makes a high pass filter with the given frequency threshold. Frequencies + * above the threshold will pass through the filter. Other frequencies will be + * removed. + * + * The filter is a function that takes in a frequency-domain representation and + * returns another frequency-domain representation. + * @param frequency threshold frequency + * @returns a high pass filter + * @example + * ```typescript + * const s1 = simultaneously(list( + * sine_sound(400, 1), + * sine_sound(2000, 1))); + * const f1 = sound_to_frequency(s1); + * const f2 = high_pass_filter(1000)(f1); + * const s2 = frequency_to_sound(f2); + * play(s2); + * ``` + */ +export function high_pass_filter(frequency: number): Filter { + return (freq_list: FrequencyList) => { + const freq_domain: AugmentedSample[] = list_to_vector(freq_list); + for (let i = 0; i < freq_domain.length; i++) { + const sample_freq: number = get_frequency(freq_domain[i]); + if (sample_freq < frequency || sample_freq > FS - frequency) { + freq_domain[i] = make_augmented_sample( + sample_freq, + 0, + get_phase(freq_domain[i]) + ); + } + } + return vector_to_list(freq_domain); + }; +} + +/** + * Makes a new filter that is the result of combining all filters in the given + * list. Passing frequencies through the new filter produces the same result as + * passing frequencies through `head(filters)`, then through + * `head(tail(filters))`, and so on. + * + * The filter is a function that takes in a frequency-domain representation and + * returns another frequency-domain representation. + * @param frequency threshold frequency + * @returns a filter + * @example + * ```typescript + * const band_filter = combine_filters(list( + * high_pass_filter(870), + * low_pass_filter(890))); + * const f1 = sound_to_frequency(noise_sound(1)); + * const f2 = band_filter(f1); + * const s2 = frequency_to_sound(f2); + * play(s2); // compare with play(sine_sound(880, 1)); + * ``` + */ +export function combine_filters(filters: List): Filter { + const nullFilter = (x: any) => x; + function combine(f1: Filter, f2: Filter) { + return (frequencyDomain: FrequencyList) => f1(f2(frequencyDomain)); + } + return accumulate(combine, nullFilter, filters); +} + +// FILTER SOUND +/** + * Transforms the given Sound by converting it to its frequency-domain + * representation, applying the given frequency filter, converting back into a + * Sound and truncating it so that its duration is the same as the original + * Sound. + * @param sound the Sound to transform + * @param filter the frequency filter to apply + * @returns the transformed Sound + * @example + * ```typescript + * const s1 = simultaneously(list( + * sine_sound(400, 1), + * sine_sound(2000, 1))); + * const s2 = filter_sound(s1, low_pass_filter(1000)); + * play(s2); + * ``` + */ +export function filter_sound(sound: Sound, filter: Filter): Sound { + const original_duration = get_duration(sound); + const original_size = Math.ceil(FS * original_duration); + const frequency_list_before = sound_to_frequency(sound); + const frequency_list_after = filter(frequency_list_before); + const frequency_samples = list_to_frequency(frequency_list_after); + + for (let i = original_size; i < frequency_samples.length; i++) { + frequency_samples[i] = pair(0, 0); + } + + const final_list = frequency_to_list(frequency_samples); + const final_sound = frequency_to_sound(final_list); + set_tail(final_sound, original_duration); + + return final_sound; +} diff --git a/src/bundles/sound_fft/index.ts b/src/bundles/sound_fft/index.ts new file mode 100644 index 0000000000..c59a8d7393 --- /dev/null +++ b/src/bundles/sound_fft/index.ts @@ -0,0 +1,38 @@ +/** + * The `sound_fft` module provides functions for converting Source Sounds to + * their frequency-domain representations and vice versa, as well as processing + * Sounds in the frequency domain. + * + * For more information about Sounds in Source, see the `sound` module. + * + * In this module, the frequency-domain representation of a Sound is a list + * in which each element is an AugmentedSample. An AugmentedSample describes + * the magnitude, phase and frequency of a sample in the frequency domain. + * + * Sound processing in the frequency domain is done via frequency filters, + * known as Filters. A Filter is a function that takes in a list of + * AugmentedSamples and returns another list of AugmentedSamples. Hence, a + * Filter can be applied to a frequency-domain representation to transform it. + * + * The conversion between Sounds and frequency-domain representations is + * implemented using FFT (Fast Fourier Transform). + * + * @module sound_fft + * @author Tran Gia Huy + * @author Stuart Lim Yi Xiong + */ +export { + low_pass_filter, + high_pass_filter, + combine_filters, + + get_magnitude, + get_phase, + get_frequency, + make_augmented_sample, + + frequency_to_sound, + sound_to_frequency, + + filter_sound +} from './functions'; diff --git a/src/bundles/sound_fft/list.ts b/src/bundles/sound_fft/list.ts new file mode 100644 index 0000000000..4208a70826 --- /dev/null +++ b/src/bundles/sound_fft/list.ts @@ -0,0 +1,30 @@ +// list.js: Supporting lists in the Scheme style, using pairs made +// up of two-element JavaScript array (vector) + +// Author: Martin Henz + +// Note: this library is used in the externalLibs of cadet-frontend. +// It is distinct from the LISTS library of Source §2, which contains +// primitive and predeclared functions from Chapter 2 of SICP JS. + +// The version of the list library in sound_fft contains a different +// implementation of vector_to_list, which is required for sound_fft +// to function. + +// pair constructs a pair using a two-element array +// LOW-LEVEL FUNCTION, NOT SOURCE +export function pair(x, xs): [any, any] { + return [x, xs]; +} + +// vector_to_list returns a list that contains the elements of the argument vector +// in the given order. +// vector_to_list throws an exception if the argument is not a vector +// LOW-LEVEL FUNCTION, NOT SOURCE +export function vector_to_list(vector) { + let result: any = null; + for (let i = vector.length - 1; i >= 0; i = i - 1) { + result = pair(vector[i], result); + } + return result; +} diff --git a/src/bundles/sound_fft/sound_functions.ts b/src/bundles/sound_fft/sound_functions.ts new file mode 100644 index 0000000000..d718af4675 --- /dev/null +++ b/src/bundles/sound_fft/sound_functions.ts @@ -0,0 +1,56 @@ +import { + head, + tail, + pair, + is_pair +} from 'js-slang/dist/stdlib/list'; +import type { Sound, Wave } from '../sound/types'; + +export function is_sound(x: any): x is Sound { + return ( + is_pair(x) + && typeof get_wave(x) === 'function' + && typeof get_duration(x) === 'number' + ); +} + +/** + * Makes a Sound with given wave function and duration. + * The wave function is a function: number -> number + * that takes in a non-negative input time and returns an amplitude + * between -1 and 1. + * + * @param wave wave function of the Sound + * @param duration duration of the Sound + * @return with wave as wave function and duration as duration + * @example const s = make_sound(t => Math_sin(2 * Math_PI * 440 * t), 5); + */ +export function make_sound(wave: Wave, duration: number): Sound { + if (duration < 0) { + throw new Error('Sound duration must be greater than or equal to 0'); + } + + return pair((t: number) => (t >= duration ? 0 : wave(t)), duration); +} + +/** + * Accesses the wave function of a given Sound. + * + * @param sound given Sound + * @return the wave function of the Sound + * @example get_wave(make_sound(t => Math_sin(2 * Math_PI * 440 * t), 5)); // Returns t => Math_sin(2 * Math_PI * 440 * t) + */ +export function get_wave(sound: Sound): Wave { + return head(sound); +} + +/** + * Accesses the duration of a given Sound. + * + * @param sound given Sound + * @return the duration of the Sound + * @example get_duration(make_sound(t => Math_sin(2 * Math_PI * 440 * t), 5)); // Returns 5 + */ +export function get_duration(sound: Sound): number { + return tail(sound); +} diff --git a/src/bundles/sound_fft/types.ts b/src/bundles/sound_fft/types.ts new file mode 100644 index 0000000000..dfa7ae0010 --- /dev/null +++ b/src/bundles/sound_fft/types.ts @@ -0,0 +1,8 @@ +import type { Pair, List } from 'js-slang/dist/stdlib/list'; + +export type TimeSamples = Array; +export type FrequencySample = Pair; +export type AugmentedSample = Pair; +export type FrequencySamples = Array; +export type FrequencyList = List; // containing AugmentedSample +export type Filter = (freq: FrequencyList) => FrequencyList; diff --git a/yarn.lock b/yarn.lock index 8873a5181d..1b6c55a801 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4979,6 +4979,13 @@ __metadata: languageName: node linkType: hard +"fft.js@npm:^4.0.4": + version: 4.0.4 + resolution: "fft.js@npm:4.0.4" + checksum: 10c0/ea9bf9df38204281078629e32e0176035e8121410bb8db67b5544520faa6f567734774afb87eafc31cb43339995295d3d72202917b4f43942be4eca8dd54b226 + languageName: node + linkType: hard + "file-entry-cache@npm:^8.0.0": version: 8.0.0 resolution: "file-entry-cache@npm:8.0.0" @@ -7720,6 +7727,7 @@ __metadata: eslint-plugin-jsx-a11y: "npm:^6.10.2" eslint-plugin-react: "npm:^7.37.4" eslint-plugin-react-hooks: "npm:^5.1.0" + fft.js: "npm:^4.0.4" gl-matrix: "npm:^3.3.0" globals: "npm:^16.0.0" http-server: "npm:^14.0.0"