Module: Cyclotone::Controls

Defined in:
lib/cyclotone/controls.rb

Constant Summary collapse

CONTROL_DEFS =
{
  s: { type: :string, aliases: [:sound] },
  n: { type: :integer },
  speed: { type: :float, default: 1.0 },
  begin: { type: :float, aliases: [:sample_begin], range: 0..1 },
  end: { type: :float, aliases: [:sample_end], range: 0..1 },
  pan: { type: :float, default: 0.5, range: 0..1 },
  gain: { type: :float, default: 1.0, minimum: 0 },
  amp: { type: :float, default: 1.0, minimum: 0 },
  cut: { type: :integer },
  unit: { type: :string },
  accelerate: { type: :float },
  legato: { type: :float },
  attack: { type: :float, aliases: [:att] },
  hold: { type: :float },
  release: { type: :float, aliases: [:rel] },
  cutoff: { type: :float, aliases: [:lpf] },
  resonance: { type: :float, aliases: [:lpq] },
  hcutoff: { type: :float, aliases: [:hpf] },
  hresonance: { type: :float, aliases: [:hpq] },
  bandf: { type: :float, aliases: [:bpf] },
  bandq: { type: :float, aliases: [:bpq] },
  djf: { type: :float },
  vowel: { type: :string },
  delay: { type: :float },
  delaytime: { type: :float, aliases: [:delayt] },
  delayfeedback: { type: :float, aliases: [:delayfb] },
  lock: { type: :integer },
  dry: { type: :float },
  room: { type: :float },
  size: { type: :float, aliases: [:sz] },
  distort: { type: :float },
  triode: { type: :float },
  shape: { type: :float },
  squiz: { type: :float },
  crush: { type: :float },
  coarse: { type: :float },
  tremolorate: { type: :float, aliases: [:tremr] },
  tremolodepth: { type: :float, aliases: [:tremdp] },
  phaserrate: { type: :float, aliases: [:phasr] },
  phaserdepth: { type: :float, aliases: [:phasdp] },
  leslie: { type: :float },
  lrate: { type: :float },
  lsize: { type: :float },
  octer: { type: :float },
  octersub: { type: :float },
  octersubsub: { type: :float },
  fshift: { type: :float },
  fshiftnote: { type: :float },
  fshiftphase: { type: :float },
  ring: { type: :float },
  ringf: { type: :float },
  ringdf: { type: :float },
  note: { type: :numeric_or_array },
  velocity: { type: :integer, default: 100, range: 0..127 },
  sustain: { type: :float, default: 1.0 },
  channel: { type: :integer, default: 0, range: 0..15 },
  cc: { type: :hash }
}.freeze
ALIASES =
CONTROL_DEFS.each_with_object({}) do |(name, options), mapping|
  mapping[name] = name
  Array(options[:aliases]).each { |alias_name| mapping[alias_name] = name }
end.freeze

Class Method Summary collapse

Class Method Details

.canonical(name) ⇒ Object



123
124
125
# File 'lib/cyclotone/controls.rb', line 123

def canonical(name)
  ALIASES.fetch(name.to_sym) { raise InvalidControlError, "unknown control #{name}" }
end

.coerce_pattern(pattern_or_value) ⇒ Object



97
98
99
# File 'lib/cyclotone/controls.rb', line 97

def coerce_pattern(pattern_or_value)
  Pattern.ensure_pattern(pattern_or_value, strings: :mini_notation)
end

.control(name, pattern_or_value) ⇒ Object



84
85
86
# File 'lib/cyclotone/controls.rb', line 84

def control(name, pattern_or_value)
  factory(name, pattern_or_value)
end

.factory(name, pattern_or_value) ⇒ Object



88
89
90
91
92
93
94
95
# File 'lib/cyclotone/controls.rb', line 88

def factory(name, pattern_or_value)
  canonical_name = canonical(name)
  pattern = coerce_pattern(pattern_or_value)

  pattern.fmap do |value|
    wrap_value(canonical_name, value)
  end
end

.validate_cc(value) ⇒ Object



160
161
162
163
164
165
166
167
168
# File 'lib/cyclotone/controls.rb', line 160

def validate_cc(value)
  raise InvalidControlError, "cc must be a Hash" unless value.is_a?(Hash)

  value.each do |controller, amount|
    raise InvalidControlError, "cc controllers must be within 0..127" unless controller.is_a?(Numeric) && controller.between?(0, 127)

    raise InvalidControlError, "cc values must be within 0..127" unless amount.is_a?(Numeric) && amount.between?(0, 127)
  end
end

.validate_range(control_name, value, definition) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/cyclotone/controls.rb', line 146

def validate_range(control_name, value, definition)
  values = value.is_a?(Array) ? value : [value]

  if definition[:range]
    range = definition[:range]
    raise InvalidControlError, "#{control_name} must be within #{range}" unless values.all? { |entry| range.cover?(entry) }
  end

  return unless definition.key?(:minimum)

  minimum = definition.fetch(:minimum)
  raise InvalidControlError, "#{control_name} must be >= #{minimum}" unless values.all? { |entry| entry >= minimum }
end

.validate_type(control_name, value, type) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
# File 'lib/cyclotone/controls.rb', line 134

def validate_type(control_name, value, type)
  case type
  when :integer, :float
    raise InvalidControlError, "#{control_name} must be numeric" unless value.is_a?(Numeric)
  when :numeric_or_array
    values = value.is_a?(Array) ? value : [value]
    raise InvalidControlError, "#{control_name} must be numeric" unless values.all?(Numeric)
  when :hash
    validate_cc(value) if control_name == :cc
  end
end

.validate_value(control_name, value) ⇒ Object



127
128
129
130
131
132
# File 'lib/cyclotone/controls.rb', line 127

def validate_value(control_name, value)
  definition = CONTROL_DEFS.fetch(control_name)
  validate_type(control_name, value, definition.fetch(:type))
  validate_range(control_name, value, definition)
  value
end

.wrap_value(control_name, value) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/cyclotone/controls.rb', line 101

def wrap_value(control_name, value)
  if value.is_a?(Hash)
    if control_name == :s && (value.key?(:s) || value.key?(:n))
      sample = value.key?(:s) ? value[:s] : value[:value]
      value.merge(s: validate_value(control_name, sample))
    elsif control_name == :note && value.key?(:note)
      value.merge(note: validate_value(control_name, value[:note]))
    else
      raw_value = if value.key?(control_name)
                    value[control_name]
                  elsif value.key?(:value)
                    value[:value]
                  else
                    value
                  end
      value.merge(control_name => validate_value(control_name, raw_value))
    end
  else
    { control_name => validate_value(control_name, value) }
  end
end