Created
December 23, 2024 18:00
-
-
Save xord/6d0400193cdafe0600f1765880414729 to your computer and use it in GitHub Desktop.
MML から Sequencer オブジェクト生成サンプル
This file contains hidden or 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
| 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