Class: Deftones::Instrument::PolySynth

Inherits:
Core::Instrument show all
Defined in:
lib/deftones/instrument/poly_synth.rb

Constant Summary collapse

VOICE_STEALING_POLICIES =
%i[oldest newest quietest released_first].freeze
RETRIGGER_POLICIES =
%i[restart ignore].freeze

Instance Attribute Summary collapse

Attributes inherited from Core::Instrument

#mute, #output, #volume

Attributes inherited from Core::AudioNode

#context, #input

Instance Method Summary collapse

Methods inherited from Core::Instrument

#apply_volume!, #dispose, #get, #input, #mute?, #render, #render_block, #triggerAttack, #triggerAttackRelease, #triggerRelease

Methods inherited from Core::AudioNode

#>>, #attach_destination, #attach_source, #block_time, #chain, #channel_count, #channel_count_mode, #channel_interpretation, #connect, #connected?, #default_input_channels, #default_output_channels, #destination_for_connection, #detach_all_destinations, #detach_destination, #detach_source, #disconnect, #dispose, #disposed?, #fan, #get, #immediate, #input_for_index, #inputs, #mix_source_blocks, #multichannel_process?, #name, #normalize_connection_index, #normalize_output_block, #now, #number_of_inputs, #number_of_outputs, #output, #output_for_connection, #output_for_index, #outputs, #process, #raise_connection_index_error!, #reaches_node?, #render, #render_block, #sample_time, #to_destination, #to_frequency, #to_master, #to_midi, #to_output, #to_s, #to_seconds, #to_ticks, #uses_legacy_render_for_block?, #validate_connectable!, #validate_connection_index!

Constructor Details

#initialize(voice_class = Synth, voices: 8, voice_stealing: :oldest, retrigger: :restart, context: Deftones.context, **voice_options) ⇒ PolySynth

Returns a new instance of PolySynth.



11
12
13
14
15
16
17
18
19
20
# File 'lib/deftones/instrument/poly_synth.rb', line 11

def initialize(voice_class = Synth, voices: 8, voice_stealing: :oldest, retrigger: :restart,
               context: Deftones.context, **voice_options)
  super(context: context)
  @voice_class = voice_class
  @voice_pool = Array.new(voices) { @voice_class.new(context: context, **voice_options) }
  @voice_pool.each { |voice| voice >> @output }
  @voice_stealing = normalize_voice_stealing(voice_stealing)
  @retrigger = normalize_retrigger(retrigger)
  @active_voices = {}
end

Instance Attribute Details

#retriggerObject (readonly)

Returns the value of attribute retrigger.



9
10
11
# File 'lib/deftones/instrument/poly_synth.rb', line 9

def retrigger
  @retrigger
end

#voice_poolObject (readonly)

Returns the value of attribute voice_pool.



9
10
11
# File 'lib/deftones/instrument/poly_synth.rb', line 9

def voice_pool
  @voice_pool
end

#voice_stealingObject (readonly)

Returns the value of attribute voice_stealing.



9
10
11
# File 'lib/deftones/instrument/poly_synth.rb', line 9

def voice_stealing
  @voice_stealing
end

Instance Method Details

#active?Boolean

Returns:

  • (Boolean)


94
95
96
# File 'lib/deftones/instrument/poly_synth.rb', line 94

def active?
  @voice_pool.any?(&:active?)
end

#active_notesObject



98
99
100
# File 'lib/deftones/instrument/poly_synth.rb', line 98

def active_notes
  @active_voices.keys
end

#active_voice_countObject



102
103
104
# File 'lib/deftones/instrument/poly_synth.rb', line 102

def active_voice_count
  @active_voices.length
end

#allocate_voice(note, time) ⇒ Object (private)



108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/deftones/instrument/poly_synth.rb', line 108

def allocate_voice(note, time)
  cleanup_inactive_voices!
  return @active_voices[note][:voice] if @active_voices.key?(note)

  active_voice_ids = @active_voices.values.map { |entry| entry[:voice].object_id }
  available_voice = @voice_pool.find { |voice| !active_voice_ids.include?(voice.object_id) }
  return available_voice if available_voice

  stolen_note, stolen_entry = steal_voice_entry(time)
  @active_voices.delete(stolen_note)
  stolen_entry[:voice]
end

#cleanup_inactive_voices!Object (private)



137
138
139
# File 'lib/deftones/instrument/poly_synth.rb', line 137

def cleanup_inactive_voices!
  @active_voices.delete_if { |_, entry| entry[:released_at] && !entry[:voice].active? }
end

#loaded?Boolean Also known as: loaded

Returns:

  • (Boolean)


87
88
89
# File 'lib/deftones/instrument/poly_synth.rb', line 87

def loaded?
  !disposed?
end

#max_polyphonyObject



83
84
85
# File 'lib/deftones/instrument/poly_synth.rb', line 83

def max_polyphony
  @voice_pool.length
end

#normalize_retrigger(policy) ⇒ Object (private)

Raises:

  • (ArgumentError)


148
149
150
151
152
153
# File 'lib/deftones/instrument/poly_synth.rb', line 148

def normalize_retrigger(policy)
  normalized = policy.to_sym
  return normalized if RETRIGGER_POLICIES.include?(normalized)

  raise ArgumentError, "Unsupported retrigger policy: #{policy}"
end

#normalize_voice_stealing(policy) ⇒ Object (private)

Raises:

  • (ArgumentError)


141
142
143
144
145
146
# File 'lib/deftones/instrument/poly_synth.rb', line 141

def normalize_voice_stealing(policy)
  normalized = policy.to_sym
  return normalized if VOICE_STEALING_POLICIES.include?(normalized)

  raise ArgumentError, "Unsupported voice stealing policy: #{policy}"
end

#play(notes, duration: "8n", at: nil, velocity: 1.0) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/deftones/instrument/poly_synth.rb', line 22

def play(notes, duration: "8n", at: nil, velocity: 1.0)
  scheduled_time = resolve_time(at)

  Array(notes).compact.each do |note|
    trigger_attack(note, scheduled_time, velocity)
  end

  release_time = scheduled_time + Deftones::Music::Time.parse(duration)
  Array(notes).compact.each do |note|
    trigger_release(note, release_time)
  end

  self
end

#release_all(time = nil) ⇒ Object Also known as: releaseAll



76
77
78
79
80
81
# File 'lib/deftones/instrument/poly_synth.rb', line 76

def release_all(time = nil)
  scheduled_time = resolve_time(time)
  @active_voices.each_value { |entry| entry[:voice].trigger_release(scheduled_time) }
  @active_voices.clear
  self
end

#resolve_time(time) ⇒ Object (private)



155
156
157
158
159
# File 'lib/deftones/instrument/poly_synth.rb', line 155

def resolve_time(time)
  return context.current_time if time.nil?

  Deftones::Music::Time.parse(time)
end

#set(strict: false, **params) ⇒ Object

Raises:

  • (ArgumentError)


63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/deftones/instrument/poly_synth.rb', line 63

def set(strict: false, **params)
  unknown = params.keys.reject { |key| @voice_pool.all? { |voice| voice.respond_to?(:"#{key}=") } }
  raise ArgumentError, "Unknown parameter(s): #{unknown.join(', ')}" if strict && unknown.any?

  @voice_pool.each do |voice|
    params.each do |key, value|
      writer = :"#{key}="
      voice.public_send(writer, value) if voice.respond_to?(writer)
    end
  end
  self
end

#steal_voice_entry(time) ⇒ Object (private)



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/deftones/instrument/poly_synth.rb', line 121

def steal_voice_entry(time)
  case @voice_stealing
  when :newest
    @active_voices.max_by { |_, entry| entry[:attacked_at] }
  when :quietest
    @active_voices.min_by { |_, entry| entry[:velocity] }
  when :released_first
    released = @active_voices.select { |_, entry| entry[:released_at] && entry[:released_at] <= time }
    return released.min_by { |_, entry| entry[:released_at] } if released.any?

    @active_voices.min_by { |_, entry| entry[:attacked_at] }
  else
    @active_voices.min_by { |_, entry| entry[:attacked_at] }
  end
end

#trigger_attack(note, time = nil, velocity = 1.0) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/deftones/instrument/poly_synth.rb', line 37

def trigger_attack(note, time = nil, velocity = 1.0)
  scheduled_time = resolve_time(time)
  return self if @active_voices.key?(note) && @retrigger == :ignore

  voice = allocate_voice(note, scheduled_time)
  @active_voices.delete(note)
  @active_voices[note] = {
    voice: voice,
    attacked_at: scheduled_time,
    released_at: nil,
    velocity: velocity.to_f
  }
  voice.trigger_attack(note, scheduled_time, velocity)
  self
end

#trigger_release(note, time = nil) ⇒ Object



53
54
55
56
57
58
59
60
61
# File 'lib/deftones/instrument/poly_synth.rb', line 53

def trigger_release(note, time = nil)
  entry = @active_voices[note]
  return self unless entry

  scheduled_time = resolve_time(time)
  entry[:released_at] = scheduled_time
  entry[:voice].trigger_release(scheduled_time)
  self
end