Last active
February 9, 2021 02:14
-
-
Save sujayvadlakonda/b93f3a99b0dff632425c2505c40e2ff9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# TODO: BPM | |
# CHECK FREQUENCY_FOR | |
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 | |
args.state.play_song_button = [50, 50, 100, 100] | |
args.state.song_queue = 0 | |
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) | |
args.outputs.borders << args.state.play_song_button | |
end | |
def input args | |
args.state.buttons.each do |b| | |
if args.inputs.mouse.click && (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 && (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 | |
if args.inputs.mouse.click && args.inputs.mouse.click.inside_rect?(args.state.play_song_button) | |
play_song args | |
end | |
end | |
def play_song args | |
queue_note args, :c, 6, 6.0 * 3.0 | |
queue_note args, :c, 6, 1.5 * 3.0 | |
queue_note args, :g, 5, 6.0 * 3.0 | |
queue_note args, :g, 5, 1.5 * 3.0 | |
queue_note args, :f, 5, 6.0 * 3.0 | |
queue_note args, :f, 5, 1.5 * 3.0 | |
queue_note args, :d, 6, 6.0 * 3.0 | |
queue_note args, :d, 6, 1.5 * 3.0 | |
queue_note args, :c, 6, 6.0 * 3.0 | |
queue_note args, :c, 6, 1.5 * 3.0 | |
queue_note args, :g, 5, 6.0 * 3.0 | |
queue_note args, :g, 5, 1.5 * 3.0 | |
queue_note args, :f, 5, 6.0 * 3.0 | |
queue_note args, :f, 5, 1.5 * 3.0 | |
queue_note args, :d, 6, 6.0 * 3.0 | |
queue_note args, :d, 6, 1.5 * 3.0 | |
queue_note args, :e, 6, 6.0 * 3.0 | |
queue_note args, :e, 6, 1.5 * 3.0 | |
queue_note args, :d, 6, 6.0 * 3.0 | |
queue_note args, :d, 6, 1.5 * 3.0 | |
queue_note args, :g, 5, 6.0 * 3.0 | |
queue_note args, :g, 5, 1.5 * 3.0 | |
queue_note args, :f, 5, 6.0 * 3.0 | |
queue_note args, :f, 5, 1.5 * 3.0 | |
queue_note args, :e, 6, 6.0 * 3.0 | |
queue_note args, :e, 6, 1.5 * 3.0 | |
queue_note args, :d, 6, 6.0 * 3.0 | |
queue_note args, :d, 6, 1.5 * 3.0 | |
queue_note args, :c, 6, 6.0 * 3.0 | |
queue_note args, :c, 6, 1.5 * 3.0 | |
queue_note args, :b, 5, 6.0 * 3.0 | |
queue_note args, :b, 5, 1.5 * 3.0 | |
end | |
def queue_note args, note, octave, duration, queue_in = args.state.song_queue | |
frequency = frequency_for note: note, octave: octave | |
args.state.song_queue += duration | |
queue_sine_wave(args, { type: :square, | |
frequency: frequency, | |
duration: duration, | |
queue_in: queue_in}) | |
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 } | |
to_queue.each { |a| puts 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment