summaryrefslogtreecommitdiffhomepage
path: root/samples/01_rendering_basics/07_sound_synthesis
diff options
context:
space:
mode:
authorAmir Rajan <[email protected]>2021-01-18 12:08:34 -0600
committerAmir Rajan <[email protected]>2021-01-18 12:08:34 -0600
commita4b9c048a1d751f5226833bb0c527ba1a8ac5d09 (patch)
tree3f2535e7a6272e796d50e7f07c906d4c9eb1b14a /samples/01_rendering_basics/07_sound_synthesis
parenta24a71805b1924ae7f80776c736f94575c171d2c (diff)
downloaddragonruby-game-toolkit-contrib-a4b9c048a1d751f5226833bb0c527ba1a8ac5d09.tar.gz
dragonruby-game-toolkit-contrib-a4b9c048a1d751f5226833bb0c527ba1a8ac5d09.zip
Synced with 2.3.
Diffstat (limited to 'samples/01_rendering_basics/07_sound_synthesis')
-rw-r--r--samples/01_rendering_basics/07_sound_synthesis/app/main.rb833
-rw-r--r--samples/01_rendering_basics/07_sound_synthesis/sprites/square-white.pngbin0 -> 279 bytes
2 files changed, 535 insertions, 298 deletions
diff --git a/samples/01_rendering_basics/07_sound_synthesis/app/main.rb b/samples/01_rendering_basics/07_sound_synthesis/app/main.rb
index fc58126..391ce68 100644
--- a/samples/01_rendering_basics/07_sound_synthesis/app/main.rb
+++ b/samples/01_rendering_basics/07_sound_synthesis/app/main.rb
@@ -1,356 +1,593 @@
-def tick args
- defaults args
- render args
- input args
- process_audio_queue args
-end
+begin # region: top level tick methods
+ def tick args
+ defaults args
+ render args
+ input args
+ process_audio_queue args
+ end
-def defaults args
- args.state.sine_waves ||= {}
- args.state.audio_queue ||= []
- args.state.buttons ||= [
- (frequency_buttons args),
- (note_buttons args),
- (bell_buttons args)
- ].flatten
-end
+ def defaults args
+ args.state.sine_waves ||= {}
+ args.state.square_waves ||= {}
+ args.state.saw_tooth_waves ||= {}
+ args.state.triangle_waves ||= {}
+ args.state.audio_queue ||= []
+ args.state.buttons ||= [
+ (frequency_buttons args),
+ (sine_wave_note_buttons args),
+ (bell_buttons args),
+ (square_wave_note_buttons args),
+ (saw_tooth_wave_note_buttons args),
+ (triangle_wave_note_buttons args),
+ ].flatten
+ end
-def frequency_buttons args
- [
- (button args,
- row: 4.0, col: 0, text: "300hz",
- frequency: 300,
- method_to_call: :play_sine_wave),
- (button args,
- row: 5.0, col: 0, text: "400hz",
- frequency: 400,
- method_to_call: :play_sine_wave),
- (button args,
- row: 6.0, col: 0, text: "500hz",
- frequency: 500,
- method_to_call: :play_sine_wave),
- ]
-end
+ def render args
+ args.outputs.borders << args.state.buttons.map { |b| b[:border] }
+ args.outputs.labels << args.state.buttons.map { |b| b[:label] }
+ args.outputs.labels << args.layout
+ .rect(row: 0, col: 11.5)
+ .yield_self { |r| r.merge y: r.y + r.h }
+ .merge(text: "This is a Pro only feature. Click here to watch the YouTube video if you are on the Standard License.",
+ alignment_enum: 1)
+ end
-def play_sine_wave args, sender
- queue_sine_wave args,
- frequency: sender[:frequency],
- duration: 1.seconds,
- fade_out: true
-end
+ def input args
+ args.state.buttons.each do |b|
+ if args.inputs.mouse.click.inside_rect? b[:rect]
+ parameter_string = (b.slice :frequency, :note, :octave).map { |k, v| "#{k}: #{v}" }.join ", "
+ args.gtk.notify! "#{b[:method_to_call]} #{parameter_string}"
+ send b[:method_to_call], args, b
+ end
+ end
-def note_buttons args
- [
- (button args,
- row: 1.5, col: 3, text: "C4",
- note: :c, octave: 4, method_to_call: :play_note),
- (button args,
- row: 2.5, col: 3, text: "D4",
- note: :d, octave: 4, method_to_call: :play_note),
- (button args,
- row: 3.5, col: 3, text: "E4",
- note: :e, octave: 4, method_to_call: :play_note),
- (button args,
- row: 4.5, col: 3, text: "F4",
- note: :f, octave: 4, method_to_call: :play_note),
- (button args,
- row: 5.5, col: 3, text: "G4",
- note: :g, octave: 4, method_to_call: :play_note),
- (button args,
- row: 6.5, col: 3, text: "A5",
- note: :a, octave: 5, method_to_call: :play_note),
- (button args,
- row: 7.5, col: 3, text: "B5",
- note: :b, octave: 5, method_to_call: :play_note),
- (button args,
- row: 8.5, col: 3, text: "C5",
- note: :c, octave: 5, method_to_call: :play_note),
- ]
-end
+ if args.inputs.mouse.click.inside_rect? (args.layout.rect(row: 0).yield_self { |r| r.merge y: r.y + r.h.half, h: r.h.half })
+ args.gtk.openurl 'https://www.youtube.com/watch?v=zEzovM5jT-k&ab_channel=AmirRajan'
+ end
+ end
-def play_note args, sender
- queue_sine_wave args,
- frequency: (frequency_for note: sender[:note],
- octave: sender[:octave]),
- duration: 1.seconds,
- fade_out: true
-end
+ def process_audio_queue args
+ to_queue = args.state.audio_queue.find_all { |v| v[:queue_at] <= args.tick_count }
+ args.state.audio_queue -= to_queue
+ to_queue.each { |a| args.audio[a[:id]] = a }
-def bell_buttons args
- [
- (button args,
- row: 1.5, col: 6, text: "Bell C4",
- note: :c, octave: 4, method_to_call: :play_bell),
- (button args,
- row: 2.5, col: 6, text: "Bell D4",
- note: :d, octave: 4, method_to_call: :play_bell),
- (button args,
- row: 3.5, col: 6, text: "Bell E4",
- note: :e, octave: 4, method_to_call: :play_bell),
- (button args,
- row: 4.5, col: 6, text: "Bell F4",
- note: :f, octave: 4, method_to_call: :play_bell),
- (button args,
- row: 5.5, col: 6, text: "Bell G4",
- note: :g, octave: 4, method_to_call: :play_bell),
- (button args,
- row: 6.5, col: 6, text: "Bell A5",
- note: :a, octave: 5, method_to_call: :play_bell),
- (button args,
- row: 7.5, col: 6, text: "Bell B5",
- note: :b, octave: 5, method_to_call: :play_bell),
- (button args,
- row: 8.5, col: 6, text: "Bell C5",
- note: :c, octave: 5, method_to_call: :play_bell),
- ]
-end
+ args.audio.find_all { |k, v| v[:decay_rate] }
+ .each { |k, v| v[:gain] -= v[:decay_rate] }
-def play_bell args, sender
- queue_bell args,
- frequency: (frequency_for note: sender[:note],
- octave: sender[:octave]),
- duration: 2.seconds,
- fade_out: true
-end
+ sounds_to_stop = args.audio
+ .find_all { |k, v| v[:stop_at] && args.state.tick_count >= v[:stop_at] }
+ .map { |k, v| k }
-def render args
- args.outputs.borders << args.state.buttons.map { |b| b[:border] }
- args.outputs.labels << args.state.buttons.map { |b| b[:label] }
- args.outputs.labels << args.layout
- .rect(row: 0,
- col: 11.5)
- .yield_self { |r| r.merge y: r.y + r.h }
- .merge(text: "This is a Pro only feature. Click here to watch the YouTube video if you are on the Standard License.",
- alignment_enum: 1)
+ sounds_to_stop.each { |k| args.audio.delete k }
+ end
end
-def input args
- args.state.buttons.each do |b|
- if args.inputs.mouse.click.inside_rect? b[:rect]
- parameter_string = (b.slice :frequency, :note, :octave).map { |k, v| "#{k}: #{v}" }.join ", "
- args.gtk.notify! "#{b[:method_to_call]} #{parameter_string}"
- send b[:method_to_call], args, b
- end
- end
+begin # region: button definitions, ui layout, callback functions
+ def button args, opts
+ button_def = opts.merge rect: (args.layout.rect (opts.merge w: 2, h: 1))
+
+ button_def[:border] = button_def[:rect].merge r: 0, g: 0, b: 0
+
+ label_offset_x = 5
+ label_offset_y = 30
+
+ button_def[:label] = button_def[:rect].merge text: opts[:text],
+ size_enum: -2.5,
+ x: button_def[:rect].x + label_offset_x,
+ y: button_def[:rect].y + label_offset_y
- if args.inputs.mouse.click.inside_rect? (args.layout.rect(row: 0).yield_self { |r| r.merge y: r.y + r.h.half, h: r.h.half })
- args.gtk.openurl 'https://www.youtube.com/watch?v=zEzovM5jT-k&ab_channel=AmirRajan'
+ button_def
end
-end
-def process_audio_queue args
- to_queue = args.state.audio_queue.find_all { |v| v[:queue_at] <= args.tick_count }
- args.state.audio_queue -= to_queue
+ def play_sine_wave args, sender
+ queue_sine_wave args,
+ frequency: sender[:frequency],
+ duration: 1.seconds,
+ fade_out: true
+ end
- to_queue.each do |a|
- args.audio[a[:id]] = a
+ def play_note args, sender
+ method_to_call = :queue_sine_wave
+ method_to_call = :queue_square_wave if sender[:type] == :square
+ method_to_call = :queue_saw_tooth_wave if sender[:type] == :saw_tooth
+ method_to_call = :queue_triangle_wave if sender[:type] == :triangle
+ method_to_call = :queue_bell if sender[:type] == :bell
+
+ send method_to_call, args,
+ frequency: (frequency_for note: sender[:note], octave: sender[:octave]),
+ duration: 1.seconds,
+ fade_out: true
end
- args.audio.each do |k, v|
- if v[:decay_rate]
- v[:gain] -= v[:decay_rate]
- end
+ def frequency_buttons args
+ [
+ (button args,
+ row: 4.0, col: 0, text: "300hz",
+ frequency: 300,
+ method_to_call: :play_sine_wave),
+ (button args,
+ row: 5.0, col: 0, text: "400hz",
+ frequency: 400,
+ method_to_call: :play_sine_wave),
+ (button args,
+ row: 6.0, col: 0, text: "500hz",
+ frequency: 500,
+ method_to_call: :play_sine_wave),
+ ]
end
- sounds_to_stop = args.audio.find_all do |k, v|
- v[:stop_at] && args.state.tick_count >= v[:stop_at]
+ def sine_wave_note_buttons args
+ [
+ (button args,
+ row: 1.5, col: 2, text: "Sine C4",
+ note: :c, octave: 4, type: :sine, method_to_call: :play_note),
+ (button args,
+ row: 2.5, col: 2, text: "Sine D4",
+ note: :d, octave: 4, type: :sine, method_to_call: :play_note),
+ (button args,
+ row: 3.5, col: 2, text: "Sine E4",
+ note: :e, octave: 4, type: :sine, method_to_call: :play_note),
+ (button args,
+ row: 4.5, col: 2, text: "Sine F4",
+ note: :f, octave: 4, type: :sine, method_to_call: :play_note),
+ (button args,
+ row: 5.5, col: 2, text: "Sine G4",
+ note: :g, octave: 4, type: :sine, method_to_call: :play_note),
+ (button args,
+ row: 6.5, col: 2, text: "Sine A5",
+ note: :a, octave: 5, type: :sine, method_to_call: :play_note),
+ (button args,
+ row: 7.5, col: 2, text: "Sine B5",
+ note: :b, octave: 5, type: :sine, method_to_call: :play_note),
+ (button args,
+ row: 8.5, col: 2, text: "Sine C5",
+ note: :c, octave: 5, type: :sine, method_to_call: :play_note),
+ ]
end
- sounds_to_stop.each do |(k, v)|
- args.audio.delete k
+ def square_wave_note_buttons args
+ [
+ (button args,
+ row: 1.5, col: 6, text: "Square C4",
+ note: :c, octave: 4, type: :square, method_to_call: :play_note),
+ (button args,
+ row: 2.5, col: 6, text: "Square D4",
+ note: :d, octave: 4, type: :square, method_to_call: :play_note),
+ (button args,
+ row: 3.5, col: 6, text: "Square E4",
+ note: :e, octave: 4, type: :square, method_to_call: :play_note),
+ (button args,
+ row: 4.5, col: 6, text: "Square F4",
+ note: :f, octave: 4, type: :square, method_to_call: :play_note),
+ (button args,
+ row: 5.5, col: 6, text: "Square G4",
+ note: :g, octave: 4, type: :square, method_to_call: :play_note),
+ (button args,
+ row: 6.5, col: 6, text: "Square A5",
+ note: :a, octave: 5, type: :square, method_to_call: :play_note),
+ (button args,
+ row: 7.5, col: 6, text: "Square B5",
+ note: :b, octave: 5, type: :square, method_to_call: :play_note),
+ (button args,
+ row: 8.5, col: 6, text: "Square C5",
+ note: :c, octave: 5, type: :square, method_to_call: :play_note),
+ ]
+ end
+ def saw_tooth_wave_note_buttons args
+ [
+ (button args,
+ row: 1.5, col: 8, text: "Saw C4",
+ note: :c, octave: 4, type: :saw_tooth, method_to_call: :play_note),
+ (button args,
+ row: 2.5, col: 8, text: "Saw D4",
+ note: :d, octave: 4, type: :saw_tooth, method_to_call: :play_note),
+ (button args,
+ row: 3.5, col: 8, text: "Saw E4",
+ note: :e, octave: 4, type: :saw_tooth, method_to_call: :play_note),
+ (button args,
+ row: 4.5, col: 8, text: "Saw F4",
+ note: :f, octave: 4, type: :saw_tooth, method_to_call: :play_note),
+ (button args,
+ row: 5.5, col: 8, text: "Saw G4",
+ note: :g, octave: 4, type: :saw_tooth, method_to_call: :play_note),
+ (button args,
+ row: 6.5, col: 8, text: "Saw A5",
+ note: :a, octave: 5, type: :saw_tooth, method_to_call: :play_note),
+ (button args,
+ row: 7.5, col: 8, text: "Saw B5",
+ note: :b, octave: 5, type: :saw_tooth, method_to_call: :play_note),
+ (button args,
+ row: 8.5, col: 8, text: "Saw C5",
+ note: :c, octave: 5, type: :saw_tooth, method_to_call: :play_note),
+ ]
end
-end
-def graph_sine_wave args, sine_wave, frequency
- if args.state.tick_count != args.state.graphed_at
- args.outputs.static_lines.clear
- args.outputs.static_sprites.clear
+ def triangle_wave_note_buttons args
+ [
+ (button args,
+ row: 1.5, col: 10, text: "Triangle C4",
+ note: :c, octave: 4, type: :triangle, method_to_call: :play_note),
+ (button args,
+ row: 2.5, col: 10, text: "Triangle D4",
+ note: :d, octave: 4, type: :triangle, method_to_call: :play_note),
+ (button args,
+ row: 3.5, col: 10, text: "Triangle E4",
+ note: :e, octave: 4, type: :triangle, method_to_call: :play_note),
+ (button args,
+ row: 4.5, col: 10, text: "Triangle F4",
+ note: :f, octave: 4, type: :triangle, method_to_call: :play_note),
+ (button args,
+ row: 5.5, col: 10, text: "Triangle G4",
+ note: :g, octave: 4, type: :triangle, method_to_call: :play_note),
+ (button args,
+ row: 6.5, col: 10, text: "Triangle A5",
+ note: :a, octave: 5, type: :triangle, method_to_call: :play_note),
+ (button args,
+ row: 7.5, col: 10, text: "Triangle B5",
+ note: :b, octave: 5, type: :triangle, method_to_call: :play_note),
+ (button args,
+ row: 8.5, col: 10, text: "Triangle C5",
+ note: :c, octave: 5, type: :triangle, method_to_call: :play_note),
+ ]
end
- r, g, b = frequency.to_i % 80, frequency.to_i % 128, frequency.to_i % 255
- center_row = args.layout.rect(row: 5, col: 9)
- x_scale = 20
- y_scale = 100
- max_points = 20
-
- points = sine_wave
- if sine_wave.length > max_points
- resolution = sine_wave.length.idiv max_points
- points = sine_wave.find_all
- .with_index { |y, i| i % resolution == 0 }
+ def bell_buttons args
+ [
+ (button args,
+ row: 1.5, col: 4, text: "Bell C4",
+ note: :c, octave: 4, type: :bell, method_to_call: :play_note),
+ (button args,
+ row: 2.5, col: 4, text: "Bell D4",
+ note: :d, octave: 4, type: :bell, method_to_call: :play_note),
+ (button args,
+ row: 3.5, col: 4, text: "Bell E4",
+ note: :e, octave: 4, type: :bell, method_to_call: :play_note),
+ (button args,
+ row: 4.5, col: 4, text: "Bell F4",
+ note: :f, octave: 4, type: :bell, method_to_call: :play_note),
+ (button args,
+ row: 5.5, col: 4, text: "Bell G4",
+ note: :g, octave: 4, type: :bell, method_to_call: :play_note),
+ (button args,
+ row: 6.5, col: 4, text: "Bell A5",
+ note: :a, octave: 5, type: :bell, method_to_call: :play_note),
+ (button args,
+ row: 7.5, col: 4, text: "Bell B5",
+ note: :b, octave: 5, type: :bell, method_to_call: :play_note),
+ (button args,
+ row: 8.5, col: 4, text: "Bell C5",
+ note: :c, octave: 5, type: :bell, method_to_call: :play_note),
+ ]
end
+end
- args.outputs.static_lines << points.map_with_index do |y, x|
- next_y = points[x + 1]
+begin # region: wave generation
+ begin # sine wave
+ def defaults_sine_wave_for
+ { frequency: 440, sample_rate: 48000 }
+ end
- if next_y
- {
- x: center_row.x + (x * x_scale),
- y: center_row.y + center_row.h.half + y_scale * y,
- x2: center_row.x + ((x + 1) * x_scale),
- y2: center_row.y + center_row.h.half + y_scale * next_y,
- r: r,
- g: g,
- b: b
- }
+ def sine_wave_for opts = {}
+ opts = defaults_sine_wave_for.merge opts
+ frequency = opts[:frequency]
+ sample_rate = opts[:sample_rate]
+ period_size = (sample_rate.fdiv frequency).ceil
+ period_size.map_with_index do |i|
+ Math::sin((2.0 * Math::PI) / (sample_rate.to_f / frequency.to_f) * i)
+ end.to_a
+ end
+
+ def defaults_queue_sine_wave
+ { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 }
+ end
+
+ def queue_sine_wave args, opts = {}
+ opts = defaults_queue_sine_wave.merge opts
+ frequency = opts[:frequency]
+ sample_rate = 48000
+
+ sine_wave = sine_wave_for frequency: frequency, sample_rate: sample_rate
+ args.state.sine_waves[frequency] ||= sine_wave_for frequency: frequency, sample_rate: sample_rate
+
+ proc = lambda do
+ generate_audio_data args.state.sine_waves[frequency], sample_rate
+ end
+
+ audio_state = new_audio_state args, opts
+ audio_state[:input] = [1, sample_rate, proc]
+ queue_audio args, audio_state: audio_state, wave: sine_wave
end
end
- args.outputs.static_sprites << points.map_with_index do |y, x|
- {
- x: (center_row.x + (x * x_scale)) - 1,
- y: (center_row.y + center_row.h.half + y_scale * y) - 1,
- w: 2,
- h: 2,
- path: 'sprites/square-black.png'
- }
+ begin # region: square wave
+ def defaults_square_wave_for
+ { frequency: 440, sample_rate: 48000 }
+ end
+
+ def square_wave_for opts = {}
+ opts = defaults_square_wave_for.merge opts
+ sine_wave = sine_wave_for opts
+ sine_wave.map do |v|
+ if v >= 0
+ 1.0
+ else
+ -1.0
+ end
+ end.to_a
+ end
+
+ def defaults_queue_square_wave
+ { frequency: 440, duration: 60, gain: 0.3, fade_out: false, queue_in: 0 }
+ end
+
+ def queue_square_wave args, opts = {}
+ opts = defaults_queue_square_wave.merge opts
+ frequency = opts[:frequency]
+ sample_rate = 48000
+
+ square_wave = square_wave_for frequency: frequency, sample_rate: sample_rate
+ args.state.square_waves[frequency] ||= square_wave_for frequency: frequency, sample_rate: sample_rate
+
+ proc = lambda do
+ generate_audio_data args.state.square_waves[frequency], sample_rate
+ end
+
+ audio_state = new_audio_state args, opts
+ audio_state[:input] = [1, sample_rate, proc]
+ queue_audio args, audio_state: audio_state, wave: square_wave
+ end
end
- args.state.graphed_at = args.state.tick_count
-end
+ begin # region: saw tooth wave
+ def defaults_saw_tooth_wave_for
+ { frequency: 440, sample_rate: 48000 }
+ end
-def defaults_period_sine_wave_for
- { frequency: 440, sample_rate: 48000 }
-end
+ def saw_tooth_wave_for opts = {}
+ opts = defaults_saw_tooth_wave_for.merge opts
+ sine_wave = sine_wave_for opts
+ period_size = sine_wave.length
+ sine_wave.map_with_index do |v, i|
+ (((i % period_size).fdiv period_size) * 2) - 1
+ end
+ end
-def sine_wave_for opts = { }
- opts = defaults_period_sine_wave_for.merge opts
- frequency = opts[:frequency]
- sample_rate = opts[:sample_rate]
- period_size = (sample_rate.fdiv frequency).ceil
- period_size.map_with_index do |i|
- Math::sin((2.0 * Math::PI) / (sample_rate.to_f / frequency.to_f) * i)
- end.to_a
-end
+ def defaults_queue_saw_tooth_wave
+ { frequency: 440, duration: 60, gain: 0.3, fade_out: false, queue_in: 0 }
+ end
-def generate_audio_data sine_wave, sample_rate
- sample_size = (sample_rate.fdiv (1000.fdiv 60)).ceil
- copy_count = (sample_size.fdiv sine_wave.length).ceil
- sine_wave * copy_count
-end
+ def queue_saw_tooth_wave args, opts = {}
+ opts = defaults_queue_saw_tooth_wave.merge opts
+ frequency = opts[:frequency]
+ sample_rate = 48000
-def defaults_queue_sine_wave
- { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 }
-end
+ saw_tooth_wave = saw_tooth_wave_for frequency: frequency, sample_rate: sample_rate
+ args.state.saw_tooth_waves[frequency] ||= saw_tooth_wave_for frequency: frequency, sample_rate: sample_rate
+
+ proc = lambda do
+ generate_audio_data args.state.saw_tooth_waves[frequency], sample_rate
+ end
-def queue_sine_wave args, opts = { }
- opts = defaults_queue_sine_wave.merge opts
- decay_rate = 0
- decay_rate = 1.fdiv(opts[:duration]) * opts[:gain] if opts[:fade_out]
- frequency = opts[:frequency]
- sample_rate = 48000
-
- audio_state = {
- id: (new_id! args),
- frequency: frequency,
- sample_rate: 48000,
- stop_at: args.tick_count + opts[:queue_in] + opts[:duration],
- gain: opts[:gain].to_f,
- queue_at: args.state.tick_count + opts[:queue_in],
- decay_rate: decay_rate,
- pitch: 1.0,
- looping: true,
- paused: false
- }
-
- sine_wave = sine_wave_for frequency: frequency, sample_rate: sample_rate
- args.state.sine_waves[frequency] ||= sine_wave_for frequency: frequency, sample_rate: sample_rate
-
- proc = lambda do
- generate_audio_data args.state.sine_waves[frequency], sample_rate
+ audio_state = new_audio_state args, opts
+ audio_state[:input] = [1, sample_rate, proc]
+ queue_audio args, audio_state: audio_state, wave: saw_tooth_wave
+ end
end
- audio_state[:input] = [1, sample_rate, proc]
- graph_sine_wave args, sine_wave, frequency
- args.state.audio_queue << audio_state
-end
+ begin # region: triangle wave
+ def defaults_triangle_wave_for
+ { frequency: 440, sample_rate: 48000 }
+ end
-def defaults_queue_bell
- { frequency: 440, duration: 1.seconds, queue_in: 0 }
-end
+ def triangle_wave_for opts = {}
+ opts = defaults_saw_tooth_wave_for.merge opts
+ sine_wave = sine_wave_for opts
+ period_size = sine_wave.length
+ sine_wave.map_with_index do |v, i|
+ ratio = (i.fdiv period_size)
+ if ratio <= 0.5
+ (ratio * 4) - 1
+ else
+ ratio -= 0.5
+ 1 - (ratio * 4)
+ end
+ end
+ end
-def queue_bell args, opts = {}
- (bell_to_sine_waves (defaults_queue_bell.merge opts)).each { |b| queue_sine_wave args, b }
-end
+ def defaults_queue_triangle_wave
+ { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 }
+ end
-def bell_harmonics
- [
- { frequency_ratio: 0.5, duration_ratio: 1.00 },
- { frequency_ratio: 1.0, duration_ratio: 0.80 },
- { frequency_ratio: 2.0, duration_ratio: 0.60 },
- { frequency_ratio: 3.0, duration_ratio: 0.40 },
- { frequency_ratio: 4.2, duration_ratio: 0.25 },
- { frequency_ratio: 5.4, duration_ratio: 0.20 },
- { frequency_ratio: 6.8, duration_ratio: 0.15 }
- ]
-end
+ def queue_triangle_wave args, opts = {}
+ opts = defaults_queue_triangle_wave.merge opts
+ frequency = opts[:frequency]
+ sample_rate = 48000
-def bell_to_sine_waves opts
- bell_harmonics.map do |b|
- {
- frequency: opts[:frequency] * b[:frequency_ratio],
- duration: opts[:duration] * b[:duration_ratio],
- queue_in: opts[:queue_in],
- gain: (1.fdiv bell_harmonics.length),
- fade_out: true
- }
+ triangle_wave = triangle_wave_for frequency: frequency, sample_rate: sample_rate
+ args.state.triangle_waves[frequency] ||= triangle_wave_for frequency: frequency, sample_rate: sample_rate
+
+ proc = lambda do
+ generate_audio_data args.state.triangle_waves[frequency], sample_rate
+ end
+
+ audio_state = new_audio_state args, opts
+ audio_state[:input] = [1, sample_rate, proc]
+ queue_audio args, audio_state: audio_state, wave: triangle_wave
+ end
end
-end
-def defaults_frequency_for
- { note: :a, octave: 5, sharp: false, flat: false }
-end
+ begin # region: bell
+ def defaults_queue_bell
+ { frequency: 440, duration: 1.seconds, queue_in: 0 }
+ end
-def frequency_for opts = {}
- opts = defaults_frequency_for.merge opts
- octave_offset_multiplier = opts[:octave] - 5
- note = note_frequencies_octave_5[opts[:note]]
- if octave_offset_multiplier < 0
- note = note * 1 / (octave_offset_multiplier.abs + 1)
- elsif octave_offset_multiplier > 0
- note = note * (octave_offset_multiplier.abs + 1) / 1
+ def queue_bell args, opts = {}
+ (bell_to_sine_waves (defaults_queue_bell.merge opts)).each { |b| queue_sine_wave args, b }
+ end
+
+ def bell_harmonics
+ [
+ { frequency_ratio: 0.5, duration_ratio: 1.00 },
+ { frequency_ratio: 1.0, duration_ratio: 0.80 },
+ { frequency_ratio: 2.0, duration_ratio: 0.60 },
+ { frequency_ratio: 3.0, duration_ratio: 0.40 },
+ { frequency_ratio: 4.2, duration_ratio: 0.25 },
+ { frequency_ratio: 5.4, duration_ratio: 0.20 },
+ { frequency_ratio: 6.8, duration_ratio: 0.15 }
+ ]
+ end
+
+ def defaults_bell_to_sine_waves
+ { frequency: 440, duration: 1.seconds, queue_in: 0 }
+ end
+
+ def bell_to_sine_waves opts = {}
+ opts = defaults_bell_to_sine_waves.merge opts
+ bell_harmonics.map do |b|
+ {
+ frequency: opts[:frequency] * b[:frequency_ratio],
+ duration: opts[:duration] * b[:duration_ratio],
+ queue_in: opts[:queue_in],
+ gain: (1.fdiv bell_harmonics.length),
+ fade_out: true
+ }
+ end
+ end
end
- note
-end
-def note_frequencies_octave_5
- {
- a: 440.0,
- a_sharp: 466.16, b_flat: 466.16,
- b: 493.88,
- c: 523.25,
- c_sharp: 554.37, d_flat: 587.33,
- d: 587.33,
- d_sharp: 622.25, e_flat: 659.25,
- e: 659.25,
- f: 698.25,
- f_sharp: 739.99, g_flat: 739.99,
- g: 783.99,
- g_sharp: 830.61, a_flat: 830.61
- }
-end
+ begin # audio entity construction
+ def generate_audio_data sine_wave, sample_rate
+ sample_size = (sample_rate.fdiv (1000.fdiv 60)).ceil
+ copy_count = (sample_size.fdiv sine_wave.length).ceil
+ sine_wave * copy_count
+ end
-def new_id! args
- args.state.audio_id ||= 0
- args.state.audio_id += 1
-end
+ def defaults_new_audio_state
+ { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 }
+ end
-def button args, opts
- button_def = opts.merge rect: (args.layout.rect (opts.merge w: 2, h: 1))
+ def new_audio_state args, opts = {}
+ opts = defaults_new_audio_state.merge opts
+ decay_rate = 0
+ decay_rate = 1.fdiv(opts[:duration]) * opts[:gain] if opts[:fade_out]
+ frequency = opts[:frequency]
+ sample_rate = 48000
- button_def[:border] = button_def[:rect].merge r: 0, g: 0, b: 0
+ {
+ id: (new_id! args),
+ frequency: frequency,
+ sample_rate: 48000,
+ stop_at: args.tick_count + opts[:queue_in] + opts[:duration],
+ gain: opts[:gain].to_f,
+ queue_at: args.state.tick_count + opts[:queue_in],
+ decay_rate: decay_rate,
+ pitch: 1.0,
+ looping: true,
+ paused: false
+ }
+ end
- font_size_enum = args.layout.font_relative_size_enum 0
- label_offset_x = 4
- label_offset_y = button_def[:rect].h.half + button_def[:rect].h.idiv(4)
+ def queue_audio args, opts = {}
+ graph_wave args, opts[:wave], opts[:audio_state][:frequency]
+ args.state.audio_queue << opts[:audio_state]
+ end
- button_def[:label] = button_def[:rect].merge text: opts[:text],
- size_enum: font_size_enum,
- x: button_def[:rect].x + label_offset_x,
- y: button_def[:rect].y + label_offset_y
+ def new_id! args
+ args.state.audio_id ||= 0
+ args.state.audio_id += 1
+ end
+
+ def graph_wave args, wave, frequency
+ if args.state.tick_count != args.state.graphed_at
+ args.outputs.static_lines.clear
+ args.outputs.static_sprites.clear
+ end
+
+ wave = wave
+
+ r, g, b = frequency.to_i % 85,
+ frequency.to_i % 170,
+ frequency.to_i % 255
+
+ starting_rect = args.layout.rect(row: 5, col: 13)
+ x_scale = 10
+ y_scale = 100
+ max_points = 25
+
+ points = wave
+ if wave.length > max_points
+ resolution = wave.length.idiv max_points
+ points = wave.find_all.with_index { |y, i| (i % resolution == 0) }
+ end
+
+ args.outputs.static_lines << points.map_with_index do |y, x|
+ next_y = points[x + 1]
+
+ if next_y
+ {
+ x: starting_rect.x + (x * x_scale),
+ y: starting_rect.y + starting_rect.h.half + y_scale * y,
+ x2: starting_rect.x + ((x + 1) * x_scale),
+ y2: starting_rect.y + starting_rect.h.half + y_scale * next_y,
+ r: r,
+ g: g,
+ b: b
+ }
+ end
+ end
+
+ args.outputs.static_sprites << points.map_with_index do |y, x|
+ {
+ x: (starting_rect.x + (x * x_scale)) - 2,
+ y: (starting_rect.y + starting_rect.h.half + y_scale * y) - 2,
+ w: 4,
+ h: 4,
+ path: 'sprites/square-white.png',
+ r: r,
+ g: g,
+ b: b
+ }
+ end
+
+ args.state.graphed_at = args.state.tick_count
+ end
+ end
+
+ begin # region: musical note mapping
+ def defaults_frequency_for
+ { note: :a, octave: 5, sharp: false, flat: false }
+ end
- button_def
+ def frequency_for opts = {}
+ opts = defaults_frequency_for.merge opts
+ octave_offset_multiplier = opts[:octave] - 5
+ note = note_frequencies_octave_5[opts[:note]]
+ if octave_offset_multiplier < 0
+ note = note * 1 / (octave_offset_multiplier.abs + 1)
+ elsif octave_offset_multiplier > 0
+ note = note * (octave_offset_multiplier.abs + 1) / 1
+ end
+ note
+ end
+
+ def note_frequencies_octave_5
+ {
+ a: 440.0,
+ a_sharp: 466.16, b_flat: 466.16,
+ b: 493.88,
+ c: 523.25,
+ c_sharp: 554.37, d_flat: 587.33,
+ d: 587.33,
+ d_sharp: 622.25, e_flat: 659.25,
+ e: 659.25,
+ f: 698.25,
+ f_sharp: 739.99, g_flat: 739.99,
+ g: 783.99,
+ g_sharp: 830.61, a_flat: 830.61
+ }
+ end
+ end
end
$gtk.reset
diff --git a/samples/01_rendering_basics/07_sound_synthesis/sprites/square-white.png b/samples/01_rendering_basics/07_sound_synthesis/sprites/square-white.png
new file mode 100644
index 0000000..378c565
--- /dev/null
+++ b/samples/01_rendering_basics/07_sound_synthesis/sprites/square-white.png
Binary files differ