Improve piano, add harmonica

This commit is contained in:
Alex Clink 2022-02-19 23:01:22 -05:00
parent 55f9ce35f4
commit 2c22abf2ea
4 changed files with 49 additions and 9 deletions

View file

@ -18,7 +18,7 @@ module PF
@white_keys = [] of Tuple(Vector2(Int32), Vector2(Int32), String) @white_keys = [] of Tuple(Vector2(Int32), Vector2(Int32), String)
@black_keys = [] of Tuple(Vector2(Int32), Vector2(Int32), String) @black_keys = [] of Tuple(Vector2(Int32), Vector2(Int32), String)
@instruments : Array(Instrument) = [RetroVoice.new, PianoVoice.new, Flute.new, KickDrum.new, SnareDrum.new] @instruments : Array(Instrument) = [RetroVoice.new, PianoVoice.new, Flute.new, KickDrum.new, SnareDrum.new, Harmonica.new]
def initialize(*args, **kwargs) def initialize(*args, **kwargs)
super super

View file

@ -6,7 +6,7 @@ module PF
@device_id : LibSDL::AudioDeviceID @device_id : LibSDL::AudioDeviceID
property volume = 0.5 property volume = 0.5
# https://dsp.stackexchange.com/questions/3581/algorithms-to-mix-audio-signals-without-clipping # https://dsp.stackexchange.com/questions/3581/algorithms-to-mix-audio-signals-without-clipping
property headroom = 0.4 property headroom = 0.3
delegate :freq, to: @spec delegate :freq, to: @spec
@playing : Bool = false @playing : Bool = false
getter time : Float64 = 0.0 getter time : Float64 = 0.0

View file

@ -3,6 +3,7 @@ module PF
property name : String = "Unnamed Instrument" property name : String = "Unnamed Instrument"
property envelope : Envelope property envelope : Envelope
property wave : Sound::Wave property wave : Sound::Wave
property volume : Float64 = 1.0
getter sounds : Array(Sound) = [] of Sound getter sounds : Array(Sound) = [] of Sound
@notes : Hash(UInt32, Sound) = {} of UInt32 => Sound @notes : Hash(UInt32, Sound) = {} of UInt32 => Sound
@ -13,7 +14,7 @@ module PF
def on(hertz : Float64, time : Float64) def on(hertz : Float64, time : Float64)
@note_id += 1_u32 @note_id += 1_u32
sound = Sound.new(hertz, @envelope, time, @wave) sound = Sound.new(hertz, @envelope, time, @volume, @wave)
@notes[@note_id] = sound @notes[@note_id] = sound
@sounds << sound @sounds << sound
@note_id @note_id
@ -47,13 +48,32 @@ module PF
class PianoVoice < Instrument class PianoVoice < Instrument
def initialize def initialize
@name = "Piano" @name = "Piano"
exp_interpolation = ->(time : Float64, duration : Float64, initial : Float64, level : Float64) do
# https://www.desmos.com/calculator/r2jn9wurwv
curve = 1000
(initial - level) * ((curve ** -(time / duration)) * (1 + (1 / curve)) - (1 / curve)) + level
end
@envelope = Envelope.new( @envelope = Envelope.new(
attack: Envelope::Stage.new(0.001, 0.0, 1.0), attack: Envelope::Stage.new(0.001, 0.0, 1.0, exp_interpolation),
decay: Envelope::Stage.new(0.7, 1.0, 0.0), decay: Envelope::Stage.new(3.0, 1.0, 0.0, exp_interpolation),
sustain: Envelope::Stage.new(0.0, 0.0, 0.0), sustain: Envelope::Stage.new(0.0, 0.0, 0.0),
release: Envelope::Stage.new(0.5, 1.0, 0.0) release: Envelope::Stage.new(0.3, 1.0, 0.0)
) )
@wave = Sound.triangle_wave(6.0, 0.0005)
@wave = ->(time : Float64, hertz : Float64) do
# https://www.desmos.com/calculator/mnxargxllk
av = 2 * Math::PI * hertz * time
y = (Math.sin(Math::PI * (av / Math::PI)) ** 3) + Math.sin(Math::PI * ((av / Math::PI) + (2 / 3)))
y = (Math.sin(av) ** 3) + Math.sin(av + 0.6666)
y += y / 2
y += y / 4
y += y / 8
y += y / 16
y += y / 32
y /= 5
end
end end
end end
@ -106,4 +126,23 @@ module PF
end end
end end
end end
class Harmonica < Instrument
def initialize
@name = "Harmonica"
@envelope = Envelope.new(
attack: Envelope::Stage.new(0.1, 0.0, 1.0),
decay: Envelope::Stage.new(0.3, 1.0, 0.8),
sustain: Envelope::Stage.new(Float64::INFINITY, 0.8, 0.8),
release: Envelope::Stage.new(0.3, 1.0, 0.0)
)
@volume = 0.5
wave = Sound.square_wave
@wave = ->(time : Float64, hertz : Float64) do
0.5 * wave.call(time + 0.1, hertz * 2) +
wave.call(time, hertz) +
rand(-0.05..0.05)
end
end
end
end end

View file

@ -1,7 +1,8 @@
module PF module PF
struct Note struct Note
NAMES = %w[C C#/Db D D#/Eb E F F#/Gb G G#/Ab A A#/Bb B] TWELFTH_ROOT = 2 ** (1 / 12)
ACCIDENTALS = StaticArray[1u8, 3u8, 6u8, 8u8, 10u8] NAMES = %w[C C#/Db D D#/Eb E F F#/Gb G G#/Ab A A#/Bb B]
ACCIDENTALS = StaticArray[1u8, 3u8, 6u8, 8u8, 10u8]
getter tuning : Float64 = 440.0 getter tuning : Float64 = 440.0
getter number : Float64 getter number : Float64