Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions src/bundles/plotly/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,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
Expand All @@ -385,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
Expand All @@ -402,7 +402,7 @@ 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) => {
Expand Down Expand Up @@ -479,7 +479,8 @@ export const draw_sound_2d = (sound: Sound) => {
};

/**
* Visualizes frequency-domain samples of a sound on a 2d line graph
* 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) => {
Expand Down
160 changes: 156 additions & 4 deletions src/bundles/sound_fft/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const FS: number = 44100;

// CONVERSION

export function time_to_frequency(time_samples: TimeSamples): FrequencySamples {
function time_to_frequency(time_samples: TimeSamples): FrequencySamples {
const n = time_samples.length;
const fft = new FFT(n);

Expand All @@ -52,7 +52,7 @@ export function time_to_frequency(time_samples: TimeSamples): FrequencySamples {
return frequencyDomain;
}

export function frequency_to_time(frequency_samples: FrequencySamples): TimeSamples {
function frequency_to_time(frequency_samples: FrequencySamples): TimeSamples {
const n = frequency_samples.length;
const fft = new FFT(n);

Expand Down Expand Up @@ -85,7 +85,7 @@ function next_power_of_2(x: number): number {
}

// Pad to power-of-2
export function sound_to_time_samples(sound: Sound): TimeSamples {
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);
Expand All @@ -98,7 +98,7 @@ export function sound_to_time_samples(sound: Sound): TimeSamples {
return sample;
}

export function time_samples_to_sound(time_samples: TimeSamples): Sound {
function time_samples_to_sound(time_samples: TimeSamples): Sound {
const duration = time_samples.length / FS;
return make_sound((t) => {
const index = t * FS;
Expand Down Expand Up @@ -130,13 +130,43 @@ function list_to_frequency(frequency_list: FrequencyList): FrequencySamples {
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);
Expand All @@ -154,24 +184,88 @@ 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);
Expand All @@ -189,6 +283,26 @@ export function low_pass_filter(frequency: number): Filter {
};
}

/**
* 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);
Expand All @@ -206,6 +320,27 @@ export function high_pass_filter(frequency: number): Filter {
};
}

/**
* 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) {
Expand All @@ -215,6 +350,23 @@ export function combine_filters(filters: List): Filter {
}

// 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);
Expand Down
18 changes: 16 additions & 2 deletions src/bundles/sound_fft/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
/**
* This provides extra functionality through the use of FFT.
* 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.
*
* Additional details needed.
* 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
Expand Down