class Digiproc::AnalogSignal

Class for performing actions upon an Analog, Continuous Time signal defined by an equation Initialized with a lambda or proc, the signal is sampled, companded, quantized, and can be turned into a `Digiproc::DigitalSignal`

Attributes

companding_strategy[RW]
eqn[R]
quant_max[RW]
quant_min[RW]
quantization_bits[RW]
quantized_samples[R]
raw_samples[R]
sample_rate[RW]
signal[RW]
size[RW]

Public Class Methods

new(eqn: ,sample_rate: 0.0001, size: 100, companding_strategy: nil, quantization_bits: Float::INFINITY, quant_max: nil, quant_min: nil) click to toggle source

See examples/analog_signals/companding for full options in initializer

fn = ->(x){ Math.sin(x) }
asig = Digiproc::AnalogSignal.new(eqn: fn)
# File lib/signals/analog_signal.rb, line 13
def initialize(eqn: ,sample_rate: 0.0001, size: 100, companding_strategy: nil, quantization_bits: Float::INFINITY, quant_max: nil, quant_min: nil)
    @eqn, @sample_rate, @size, @quantization_bits, @quant_max, @quant_min = eqn, sample_rate, size, quantization_bits, quant_max, quant_min
    @signal = eqn
    @companding_strategy = (companding_strategy.is_a?(Class) and !!companding_strategy) ? companding_strategy.new : companding_strategy 
end

Public Instance Methods

digitize() click to toggle source

No arguments, outputs [Array] digital samples Will sample, compand (compress, expand), and quantize samples (companding and quantizing optional) See analog_signals examples

# File lib/signals/analog_signal.rb, line 23
def digitize
    samples = sample
    @raw_samples = samples
    samples = compress(samples) if not companding_strategy.nil?
    samples = quantize(samples, self.quantization_bits, self.quant_max, self.quant_min) if self.quantization_bits < Float::INFINITY
    samples = expand(samples) if not companding_strategy.nil?
    @quantized_samples = samples
    samples
end
normalized_quantization_rms_error() click to toggle source

Returns [Float] normalized quantization RMS error. See examples/analog_signal/companding

# File lib/signals/analog_signal.rb, line 41
def normalized_quantization_rms_error
    total = 0
    x_max = self.raw_samples.max
    self.raw_samples.each_with_index do |x, i|
        total += ((x.to_f - quantized_samples[i]) / x_max) ** 2
    end
    (1.0 / size) * (total ** (0.5))
end
to_ds() click to toggle source

Returns a Digiproc::DigitalSignal of the digital signals

# File lib/signals/analog_signal.rb, line 35
def to_ds
    Digiproc::DigitalSignal.new(data: digitize)
end

Private Instance Methods

center_of(max, min) click to toggle source

Return center [Float]

# File lib/signals/analog_signal.rb, line 133
def center_of(max, min)
    (max + min) / 2.0
end
compress(samples) click to toggle source

Compress samples for companding before quantization

# File lib/signals/analog_signal.rb, line 86
def compress(samples)
    self.companding_strategy.compress(samples)
end
expand(samples) click to toggle source

Expand samples for companding after quantization

# File lib/signals/analog_signal.rb, line 92
def expand(samples)
    self.companding_strategy.expand(samples)
end
map_to_eqn(starting_max, starting_min, target_max, target_min) click to toggle source

Return a lambda function which will map a dataset to a target maximum and minimum

# File lib/signals/analog_signal.rb, line 121
def map_to_eqn(starting_max, starting_min, target_max, target_min)
    target_center = center_of(target_max, target_min)
    target_range = range_of(target_max, target_min)
    starting_center = center_of(starting_max, starting_min) 
    starting_range = range_of(starting_max.to_f, starting_min)
    center_map = target_center - starting_center
    range_map = target_range / starting_range
    ->(n){ (n - starting_center) * range_map + target_center}
end
map_to_unit_centered(samples, max, min) click to toggle source

Map values to range -1 to 1

# File lib/signals/analog_signal.rb, line 111
def map_to_unit_centered(samples, max, min)
    center = center_of(max, min)
    range = range_of(max, min)
    a_range = range / 2.0
    mapping = ->(n){ (n - center) / a_range }
    process(samples, mapping)
end
process(inputs , equation) click to toggle source

Helper for processing an array of values with a lambda function

# File lib/signals/analog_signal.rb, line 105
def process(inputs , equation)
    inputs.map{ |n| equation.call(n) }
end
quantize(samples, bits, max, min) click to toggle source

Quantize signals to a certain number of bits over a specified range

Args

samples
Array

input data

bits
Integer

number of bits used to reprisent each sample

max

maximum possible sample values (to be reprisented by max bits in quantization)

min

minimum possible sample value (to be reprisented by minimum bits value in quantization)

bit quantization is mapped back to original max and min values, returning what they would look like upon attempted reconstruction of the analog signal.

# File lib/signals/analog_signal.rb, line 61
def quantize(samples, bits, max, min)
    sample_bits = quantize_bit_reprisentation(samples, bits, max, min)
    process(sample_bits, map_to_eqn(bits**2 / 2.0 , -(bits**2) / 2.0 , max, min))
end
quantize_bit_reprisentation(samples, bits, max, min) click to toggle source

Based off max and min (defined by samples.max and samples.min if not provided), the samples are mapped from -1 to 1. Then they are multiplied by the number of “levels” of quantization (ie: number_of_bits ^ 2 / 2). Then, the samples are rounded, thus quantizing the samples to the number of bits

# File lib/signals/analog_signal.rb, line 76
def quantize_bit_reprisentation(samples, bits, max, min)
    max = samples.max if max.nil? 
    min = samples.min if min.nil?
    levels = bits ** 2
    mapped = map_to_unit_centered(samples, max, min)
    process(mapped, ->(n){ (n * levels / 2.0).round})
end
quantize_positive_bits(samples, bits, max, min) click to toggle source
# File lib/signals/analog_signal.rb, line 67
def quantize_positive_bits(samples, bits, max, min)
    levels = bits ** 2
    quantize_bit_reprisentation(samples, bits, max, min) + (levels / 2.0)
end
range_of(max, min) click to toggle source

Return range [Float]

# File lib/signals/analog_signal.rb, line 139
def range_of(max, min)
    max.to_f - min
end
sample() click to toggle source

Sample the signal and return [Array] of sampled values

# File lib/signals/analog_signal.rb, line 98
def sample
    sample_times = process(0...size, ->(n){ n * self.sample_rate })
    process(sample_times, self.signal)
end