Class: Reight::Sound::Note

Inherits:
Object
  • Object
show all
Defined in:
lib/reight/sound.rb

Constant Summary collapse

MAX =
127
TONES =
i[
  sine triangle square sawtooth pulse12_5 pulse25 noise
]
INDEX2NOTE =
-> {
  notes   = %w[ c c+ d d+ e f f+ g g+ a a+ b ].map {_1.sub '+', '#'}
  octaves = (-1..9).to_a
  octaves.product(notes)
    .each_with_object({}).with_index do |((octave, note), hash), index|
      hash[index] = "#{note}#{octave}"
    end
}.call

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(index, tone = TONES.first) ⇒ Note

Returns a new instance of Note.



153
154
155
156
157
# File 'lib/reight/sound.rb', line 153

def initialize(index, tone = TONES.first)
  raise "Invalid note index: #{index}" unless (0..MAX).include? index
  raise "Invalid tone: #{tone}"        unless    TONES.include? tone
  @index, @tone = index, tone
end

Instance Attribute Details

#indexObject (readonly)

Returns the value of attribute index.



159
160
161
# File 'lib/reight/sound.rb', line 159

def index
  @index
end

#toneObject (readonly)

Returns the value of attribute tone.



159
160
161
# File 'lib/reight/sound.rb', line 159

def tone
  @tone
end

Class Method Details

.create_samples(type, size) ⇒ Object



204
205
206
207
208
209
210
211
212
213
# File 'lib/reight/sound.rb', line 204

def self.create_samples(type, size)
  input = size.times.map {_1.to_f / size}
  duty  = {pulse12_5: 0.125, pulse25: 0.25, pulse75: 0.75}[type] || 0.5
  case type
  when :sine     then input.map {Math.sin _1 * Math::PI * 2}
  when :triangle then input.map {_1 < 0.5 ? _1 * 4 - 1 : 3 - _1 * 4}
  when :sawtooth then input.map {_1 * 2 - 1}
  else                input.map {_1 < duty ? 1 : -1}
  end
end

.envelope(seconds) ⇒ Object



215
216
217
218
219
220
# File 'lib/reight/sound.rb', line 215

def self.envelope(seconds)
  Beeps::Envelope.new release: seconds * 0.05 do
    note_on
    note_off seconds * 0.95
  end
end

.gain(gain = 0.2) ⇒ Object



222
223
224
# File 'lib/reight/sound.rb', line 222

def self.gain(gain = 0.2)
  Beeps::Gain.new gain
end

.oscillator(type, size, **kwargs) ⇒ Object



195
196
197
198
199
200
201
202
# File 'lib/reight/sound.rb', line 195

def self.oscillator(type, size, **kwargs)
  case type
  when :noise then Beeps::Oscillator.new type, **kwargs
  else
    samples = (@samples ||= {})[type] ||= create_samples type, size
    Beeps::Oscillator.new samples: samples, **kwargs
  end
end

.restore(hash) ⇒ Object



232
233
234
235
236
# File 'lib/reight/sound.rb', line 232

def self.restore(hash)
  index, tone = hash.values_at :index, :tone
  #hash => {index:, tone:}
  new index, TONES[tone]
end

.seconds(length, bpm) ⇒ Object

Raises:

  • (ArgumentError)


226
227
228
229
230
# File 'lib/reight/sound.rb', line 226

def self.seconds(length, bpm)
  raise ArgumentError, "Invalid length: #{length}" if length <= 0
  raise ArgumentError, "Invalid bpm: #{bpm}"       if bpm    <= 0
  60.0 / bpm / length
end

Instance Method Details

#frequencyObject



165
166
167
# File 'lib/reight/sound.rb', line 165

def frequency()
  440 * (2 ** ((@index - 69).to_f / 12))
end

#play(bpm) ⇒ Object



161
162
163
# File 'lib/reight/sound.rb', line 161

def play(bpm)
  to_sound(bpm).play
end

#to_hashObject



182
183
184
# File 'lib/reight/sound.rb', line 182

def to_hash()
  {index: @index, tone: TONES.index(@tone)}
end

#to_sObject



178
179
180
# File 'lib/reight/sound.rb', line 178

def to_s()
  "#{INDEX2NOTE[@index]}:#{@tone}"
end

#to_sound(bpm) ⇒ Object



186
187
188
189
190
191
192
193
# File 'lib/reight/sound.rb', line 186

def to_sound(bpm)
  osc  = self.class.oscillator tone, 32, freq: frequency
  sec  = self.class.seconds 4, bpm
  seq  = Beeps::Sequencer.new.tap {_1.add osc, 0, sec}
  env  = self.class.envelope sec
  gain = self.class.gain
  RubySketch::Sound.new Beeps::Sound.new(seq >> env >> gain, sec)
end