Skip to content

Instantly share code, notes, and snippets.

@xord
Created December 23, 2024 18:00
Show Gist options
  • Select an option

  • Save xord/6d0400193cdafe0600f1765880414729 to your computer and use it in GitHub Desktop.

Select an option

Save xord/6d0400193cdafe0600f1765880414729 to your computer and use it in GitHub Desktop.
MML から Sequencer オブジェクト生成サンプル
require 'strscan'
require 'rubysketch'
using RubySketch
B = Beeps
class MMLCompiler
def compile(str)
scanner = StringScanner.new str.gsub(/;.*\n/, '')
seq = B::Sequencer.new
bpm = 120
time = 0
tone = 0
octave = 4
length = 4
velocity = 127
scanner.skip /\s*/
until scanner.empty?
case
when scanner.scan(/T\s*(\d+)/i)
bpm = scanner[1].to_i
when scanner.scan(/O\s*(\d+)/i)
octave = scanner[1].to_i
when scanner.scan(/L\s*(\d+)/i)
length = scanner[1].to_i
when scanner.scan(/V\s*(\d+)/i)
velocity = scanner[1].to_i
when scanner.scan(/R\s*(\d+)?/i)
time += seconds scanner[1]&.to_i || length, bpm
when scanner.scan(/@\s*(\d+)/)
tone = scanner[1].to_i
when scanner.scan(/([<>])/)
case scanner[1]
when '<' then octave -= 1
when '>' then octave += 1
end
when scanner.scan(/([CDEFGAB])\s*([#+-]+)?\s*(\d+)?\s*(\.+)?/i)&.chomp
char, offset, len, dots = [1, 2, 3, 4].map {scanner[_1]}
freq = frequency(char, offset, octave) or next
sec = seconds len&.to_i || length, bpm
sec *= 1 + dots.size.times.map {0.5 / (_1 + 1)}.sum if dots
osc = B::Oscillator.new TONES[tone], freq: freq
env = B::Envelope.new {note_on; note_off (sec - 0.01).clamp(0.01..)}
gain = B::Gain.new gain: velocity.clamp(0, 127) / 127.0 * 0.5
seq.add osc >> env >> gain, time, sec
time += sec
else
raise "Unknown input: #{scanner.rest[..10]}"
end
scanner.skip /\s*/
end
seq
end
private
TONES = [:sine, :triangle, :square, :sawtooth]
DISTANCES = -> {
notes = 'c_d_ef_g_a_b'.each_char.with_index.reject {|c,| c == '_'}.to_a
octaves = (0..11).to_a
octaves.product(notes)
.map.with_object({}) {|(octave, (note, index)), hash|
hash[[note, octave]] = octave * 12 + index - 57
}
}.call
def frequency(note, offset, octave)
raise "Bad note: '#{note}'" unless note =~ /[cdefgab]/i
distance = DISTANCES[[note.downcase, octave]]
distance += (offset || '').each_char.reduce(0) {|value, char|
case char
when '+', '#' then value + 1
when '-' then value - 1
else value
end
}
440 * (2 ** (distance.to_f / 12))
end
def seconds(length, bpm)
60.0 / bpm / length
end
end# MMLCompiler
setup do
ellipseMode CENTER
background 0
textSize 30
text "Click!", 10, 100
end
mousePressed do
circle mouseX, mouseY, 50
seq1 = MMLCompiler.new.compile '@1 ffffre-rgrf1'
seq2 = MMLCompiler.new.compile '@1 c<bb-argrb-ra1'
B::Sound.new(seq1, 3).play
B::Sound.new(seq2, 3).play
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment