summaryrefslogtreecommitdiffhomepage
path: root/samples/01_rendering_basics/07_sound_synthesis/app/main.rb
diff options
context:
space:
mode:
Diffstat (limited to 'samples/01_rendering_basics/07_sound_synthesis/app/main.rb')
-rw-r--r--samples/01_rendering_basics/07_sound_synthesis/app/main.rb593
1 files changed, 0 insertions, 593 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
deleted file mode 100644
index 391ce68..0000000
--- a/samples/01_rendering_basics/07_sound_synthesis/app/main.rb
+++ /dev/null
@@ -1,593 +0,0 @@
-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.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 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 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
-
- 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 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 }
-
- args.audio.find_all { |k, v| v[:decay_rate] }
- .each { |k, v| v[:gain] -= v[:decay_rate] }
-
- sounds_to_stop = args.audio
- .find_all { |k, v| v[:stop_at] && args.state.tick_count >= v[:stop_at] }
- .map { |k, v| k }
-
- sounds_to_stop.each { |k| args.audio.delete k }
- 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
-
- button_def
- end
-
- def play_sine_wave args, sender
- queue_sine_wave args,
- frequency: sender[:frequency],
- duration: 1.seconds,
- fade_out: true
- end
-
- 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
-
- 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 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
-
- 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
-
- 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
-
- 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
-
-begin # region: wave generation
- begin # sine wave
- def defaults_sine_wave_for
- { frequency: 440, sample_rate: 48000 }
- end
-
- 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
-
- 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
-
- begin # region: saw tooth wave
- def defaults_saw_tooth_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 defaults_queue_saw_tooth_wave
- { frequency: 440, duration: 60, gain: 0.3, fade_out: false, queue_in: 0 }
- end
-
- def queue_saw_tooth_wave args, opts = {}
- opts = defaults_queue_saw_tooth_wave.merge opts
- frequency = opts[:frequency]
- sample_rate = 48000
-
- 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
-
- 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
-
- begin # region: triangle wave
- def defaults_triangle_wave_for
- { frequency: 440, sample_rate: 48000 }
- 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 defaults_queue_triangle_wave
- { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 }
- end
-
- def queue_triangle_wave args, opts = {}
- opts = defaults_queue_triangle_wave.merge opts
- frequency = opts[:frequency]
- sample_rate = 48000
-
- 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
-
- begin # region: bell
- def defaults_queue_bell
- { frequency: 440, duration: 1.seconds, queue_in: 0 }
- end
-
- 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
-
- 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 defaults_new_audio_state
- { frequency: 440, duration: 60, gain: 1.0, fade_out: false, queue_in: 0 }
- end
-
- 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
-
- {
- 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
-
- def queue_audio args, opts = {}
- graph_wave args, opts[:wave], opts[:audio_state][:frequency]
- args.state.audio_queue << opts[:audio_state]
- end
-
- 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
-
- 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