Class: Deftones::Instrument::Sampler

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

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!, #input, #mute?, #render, #render_block, #set, #triggerAttack, #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, #disposed?, #fan, #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, #set, #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(samples:, max_voices: 8, release: 0.0, one_shot: false, choke_group: nil, context: Deftones.context) ⇒ Sampler

Returns a new instance of Sampler.



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

def initialize(samples:, max_voices: 8, release: 0.0, one_shot: false, choke_group: nil,
               context: Deftones.context)
  super(context: context)
  @samples = samples.transform_keys(&:to_s)
  @max_voices = max_voices
  @release = release.to_f
  @one_shot = !!one_shot
  @choke_group = choke_group
  @voices = []
  rebuild_root_note_cache
end

Instance Attribute Details

#choke_groupObject (readonly)

Returns the value of attribute choke_group.



6
7
8
# File 'lib/deftones/instrument/sampler.rb', line 6

def choke_group
  @choke_group
end

#one_shotObject (readonly) Also known as: oneShot

Returns the value of attribute one_shot.



7
8
9
# File 'lib/deftones/instrument/sampler.rb', line 7

def one_shot
  @one_shot
end

#releaseObject (readonly)

Returns the value of attribute release.



6
7
8
# File 'lib/deftones/instrument/sampler.rb', line 6

def release
  @release
end

#samplesObject (readonly)

Returns the value of attribute samples.



6
7
8
# File 'lib/deftones/instrument/sampler.rb', line 6

def samples
  @samples
end

#voicesObject (readonly)

Returns the value of attribute voices.



6
7
8
# File 'lib/deftones/instrument/sampler.rb', line 6

def voices
  @voices
end

Instance Method Details

#add(note, buffer) ⇒ Object



58
59
60
61
62
# File 'lib/deftones/instrument/sampler.rb', line 58

def add(note, buffer)
  @samples[note.to_s] = buffer.is_a?(Deftones::IO::Buffer) ? buffer : Deftones::IO::Buffer.load(buffer)
  rebuild_root_note_cache
  self
end

#choke_matching_voices(time) ⇒ Object (private)



115
116
117
118
119
120
121
122
123
124
125
# File 'lib/deftones/instrument/sampler.rb', line 115

def choke_matching_voices(time)
  return unless @choke_group

  @voices.delete_if do |voice|
    next false unless voice[:choke_group] == @choke_group

    voice[:player].stop(time)
    voice[:player].dispose
    true
  end
end

#closest_sample(note) ⇒ Object (private)

Raises:

  • (ArgumentError)


97
98
99
100
101
102
103
104
# File 'lib/deftones/instrument/sampler.rb', line 97

def closest_sample(note)
  raise ArgumentError, "Sampler requires at least one sample" if @samples.empty?

  target_midi = Deftones::Music::Note.to_midi(note)
  @samples.min_by do |sample_note, _|
    (@root_note_cache.fetch(sample_note) - target_midi).abs
  end
end

#disposeObject



84
85
86
87
88
# File 'lib/deftones/instrument/sampler.rb', line 84

def dispose
  release_all(context.current_time, force: true)
  @voices.clear
  super
end

#get(note) ⇒ Object



64
65
66
# File 'lib/deftones/instrument/sampler.rb', line 64

def get(note)
  @samples[note.to_s]
end

#has?(note) ⇒ Boolean

Returns:

  • (Boolean)


68
69
70
# File 'lib/deftones/instrument/sampler.rb', line 68

def has?(note)
  @samples.key?(note.to_s)
end

#loaded?Boolean Also known as: loaded

Returns:

  • (Boolean)


80
81
82
# File 'lib/deftones/instrument/sampler.rb', line 80

def loaded?
  !disposed?
end

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



21
22
23
24
25
26
27
# File 'lib/deftones/instrument/sampler.rb', line 21

def play(notes, duration: "8n", at: nil, velocity: 1.0)
  Array(notes).each do |note|
    trigger_attack(note, at, velocity)
    trigger_release(note, resolve_time(at) + Deftones::Music::Time.parse(duration))
  end
  self
end

#rebuild_root_note_cacheObject (private)



127
128
129
130
131
# File 'lib/deftones/instrument/sampler.rb', line 127

def rebuild_root_note_cache
  @root_note_cache = @samples.each_key.to_h do |sample_note|
    [sample_note, Deftones::Music::Note.to_midi(sample_note)]
  end
end

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



72
73
74
75
76
77
78
# File 'lib/deftones/instrument/sampler.rb', line 72

def release_all(time = nil, force: false)
  scheduled_time = resolve_time(time)
  return self if @one_shot && !force

  @voices.each { |voice| voice[:player].stop(scheduled_time) }
  self
end

#resolve_time(time) ⇒ Object (private)



133
134
135
136
137
# File 'lib/deftones/instrument/sampler.rb', line 133

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

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

#steal_oldest_voice(time) ⇒ Object (private)



106
107
108
109
110
111
112
113
# File 'lib/deftones/instrument/sampler.rb', line 106

def steal_oldest_voice(time)
  stolen = @voices.shift
  return unless stolen

  player = stolen[:player]
  player.stop(time)
  player.dispose
end

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



29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/deftones/instrument/sampler.rb', line 29

def trigger_attack(note, time = nil, velocity = 1.0)
  buffer_note, buffer = closest_sample(note)
  scheduled_time = resolve_time(time)
  choke_matching_voices(scheduled_time)
  playback_rate = Deftones::Music::Note.to_frequency(note) / Deftones::Music::Note.to_frequency(buffer_note)
  player = Source::Player.new(buffer: buffer, playback_rate: playback_rate, fade_out: @release, context: context)
  gain = Core::Gain.new(gain: velocity, context: context)
  player >> gain >> @output
  player.start(scheduled_time)
  @voices << { note: note, player: player, choke_group: @choke_group }
  steal_oldest_voice(scheduled_time) if @voices.length > @max_voices
  self
end

#trigger_attack_release(note, duration, time = nil, velocity = 1.0) ⇒ Object Also known as: triggerAttackRelease



51
52
53
54
55
56
# File 'lib/deftones/instrument/sampler.rb', line 51

def trigger_attack_release(note, duration, time = nil, velocity = 1.0)
  scheduled_time = resolve_time(time)
  trigger_attack(note, scheduled_time, velocity)
  trigger_release(note, scheduled_time + Deftones::Music::Time.parse(duration))
  self
end

#trigger_release(note, time = nil) ⇒ Object



43
44
45
46
47
48
49
# File 'lib/deftones/instrument/sampler.rb', line 43

def trigger_release(note, time = nil)
  return self if @one_shot

  voice = @voices.find { |entry| entry[:note] == note }
  voice&.fetch(:player)&.stop(resolve_time(time))
  self
end