alda-rb 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1a40fb3e4fa0d3ae035cbaf901b0fa209676743cd36b31168b9e04af9bf6aaf
4
- data.tar.gz: df7faaf786abc7e90260864d5abaa275c3702f81b7ad9b114b287ca940d07dee
3
+ metadata.gz: fa068d3caf9d89e4c0bfb90b2bc0800f5fe2824cbd9d3674be7a4853a55e5b25
4
+ data.tar.gz: ec0c8aa26fd9f789d2bbcaef2502890a21223036779e29704eb8c30ebf08b27f
5
5
  SHA512:
6
- metadata.gz: 7378bb4040c96cf509ea800412000f5f1d676a5e2d3e62016906c928c9991fc6f2fd0358989e5ea0d0e8526767fcbc8d8942ccbb9f7742fe2106f7ab44d0596f
7
- data.tar.gz: bfb477c6af6d034c3b4b05ddf8a5e73fd2887584eae8b959487c72dddcd7deda77617e2c6b3392b2cc71574ac81a05df9825cd686a7de9f3eb79e6d8731ddb09
6
+ metadata.gz: 98860e1ec9598d9935bb59371d8621be062dcab44f524ada4ae1a1a5460f4c85bd440429b38bbdc74032fc19f18cfc0787f575f60593f1e1abceedc5d2ad06c1
7
+ data.tar.gz: 83a071c40c8e82a5d57a609c668d5f5886d02be45b1656f2a89946208eb590af940e292b550cefbcf4ed930000c1a46b518ec2a204d7f0bca8cb07293911727c
data/Gemfile CHANGED
@@ -1,7 +1,7 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in alda-rb-rb.gemspec
4
3
  gemspec
5
4
 
6
5
  gem "rake", "~> 12.0"
7
6
  gem "minitest", "~> 5.0"
7
+ gem "colorize"
data/alda-rb.gemspec CHANGED
@@ -6,19 +6,19 @@ Gem::Specification.new do |spec|
6
6
  spec.name = "alda-rb"
7
7
  spec.version = Alda::VERSION
8
8
  spec.authors = ["Ulysses Zhan"]
9
- spec.email = ["2938747508@qq.com"]
9
+ spec.email = ["UlyssesZhan@gmail.com"]
10
10
 
11
11
  spec.summary = %q{A Ruby library for live-coding music with Alda.}
12
12
  # spec.description = %q{TODO: Write a longer description or delete this line.}
13
13
  spec.homepage = "https://github.com/UlyssesZh/alda-rb"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
16
16
 
17
17
  # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
18
18
 
19
19
  spec.metadata["homepage_uri"] = spec.homepage
20
20
  spec.metadata["source_code_uri"] = spec.homepage
21
- # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
21
+ spec.metadata["changelog_uri"] = "https://github.com/UlyssesZh/alda-rb/releases"
22
22
 
23
23
  # Specify which files should be added to the gem when it is released.
24
24
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -9,18 +9,18 @@ Alda::Score.new do
9
9
  vibraphone_ 'vibes-1'
10
10
  panning 10
11
11
  s do
12
- a; b8; +o; d; -o; b; g; b; +o; c
13
- e4; -o; a; +o; c; -o; g
14
- s{ g; +o; g8; f; e; c; -o; a4 }%1..2
15
- s{ b8; +o; d; g2_4 }%3
12
+ a b8 o! d o? b g b o! c
13
+ e4 o? a o! c o? g
14
+ (g o! g8 f e c o? a4) % (1..2)
15
+ (b8 o! d g2_4) % 3
16
16
  end * 3
17
17
 
18
18
  vibraphone_ 'vibes-2'
19
19
  panning 90
20
20
  s do
21
- a; +o; e; -o; a; r
22
- b; r; b; r
23
- s{ g; r; +o; g; -o; g }%1..2
24
- s{ +o; d; r; a; g }%3
21
+ a o! e o? a r
22
+ b r b r
23
+ (g r o! g o? g) % (1..2)
24
+ (o! d r a g) % 3
25
25
  end * 3
26
26
  end.play
@@ -10,16 +10,18 @@ require 'alda-rb'
10
10
  # sheet music:
11
11
  # http://www.freesheetpianomusic.com/bach/content/Well-Tempered%20Clavier_Book_1/Prelude%20and%20Fugue%20No.1%20C%20major%20BWV%20846.pdf
12
12
 
13
- include Alda
14
-
15
- Score.new do
16
- def Note.absolute event_list, pitch, duration
13
+ class Alda::Sequence
14
+ def absolute pitch, duration
17
15
  /(?<letter>[a-g][-+_]*)(?<octave>\d*)/ =~ pitch
18
- octave = @last_octave ||= '4' if octave.empty?
19
- event_list.events.push new "o#{@last_octave = octave} #{letter}", duration
16
+ octave = @@last_octave ||= '4' if octave.empty?
17
+ result = Alda::Note.new "o#{@@last_octave = octave} #{letter}", duration
18
+ @events.push result
19
+ result
20
20
  end
21
-
22
- piano_; tempo 60
21
+ end
22
+
23
+ Alda::Score.new do
24
+ piano_ tempo 60
23
25
  %w[
24
26
  c e g c5 e
25
27
  c4 d a d5 f
@@ -55,9 +57,9 @@ Score.new do
55
57
  c2 c3 g b- e4
56
58
  ].each_slice 5 do |n1, n2, *notes|
57
59
  s do
58
- v1; Note.absolute self, n1, '2'
59
- v2; r16; Note.absolute self, n2, '4..'
60
- v3; r8; s{ notes.each { Note.absolute self, _1, '16' } }*2
60
+ v1 absolute n1, '2'
61
+ v2 r16 absolute n2, '4..'
62
+ v3 r8 s{ notes.each { absolute _1, '16' } }*2
61
63
  end * 2
62
64
  end
63
65
  alda_code <<~ENDING
@@ -12,14 +12,13 @@ require 'alda-rb'
12
12
 
13
13
  Alda::Score.new do
14
14
  pattern = %i[clap clap clap rest clap clap rest clap rest clap clap rest]
15
- pattern.define_singleton_method(:round_shift) { push shift }
16
15
  Alda::Sequence.class_exec do
17
16
  define_method(:clap) { +d }; define_method(:rest) { r }
18
17
  define_method(:play) { pattern.each { __send__ _1 } }
19
18
  end
20
19
 
21
20
  tempo! 172
22
- midi_percussion_; o2; set_note_length 8
23
- v1; s{ play }*(12*13)
24
- v2; 13.times { s{ play; pattern.round_shift }*12 }
21
+ midi_percussion_ o2 set_note_length 8
22
+ v1; s{ play }*12*13
23
+ v2; 13.times { s{ play; pattern.rotate! }*12 }
25
24
  end.play
@@ -4,5 +4,5 @@ require 'alda-rb'
4
4
 
5
5
  Alda::Score.new do
6
6
  violin_/viola_/cello_('strings'); g1_1_1
7
- strings_.cello_; -o; c1_1_1
7
+ strings_.cello_ o? c1_1_1
8
8
  end.play
data/examples/entropy.rb CHANGED
@@ -13,8 +13,8 @@ Alda::Score.new do
13
13
  if rand < REST_RATE
14
14
  pause ms.()
15
15
  octave rand OCTAVE_UPPER
16
- note pitch("abcdefg"[rand 7].to_sym,
17
- %i[sharp flat natural][rand 3]), ms.()
16
+ note pitch('abcdefg'[rand 7].to_sym,
17
+ %i[sharp flat natural].sample), ms.()
18
18
  end
19
19
  end
20
20
 
data/examples/hanon.rb ADDED
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'alda-rb'
4
+
5
+ SYMBOLS = %i[c d e f g a b]
6
+ class Solfege
7
+ attr_accessor :i, :octave
8
+ def initialize i, octave
9
+ @i = i
10
+ @octave = octave
11
+ update
12
+ end
13
+ def update
14
+ old_octave = @octave
15
+ @octave, @i = @i.divmod SYMBOLS.size
16
+ @octave += old_octave
17
+ end
18
+ def letter
19
+ SYMBOLS[@i]
20
+ end
21
+ end
22
+
23
+ class Alda::Sequence
24
+ def play_solfege solfege
25
+ v1; octave solfege.octave; note pitch solfege.letter
26
+ v2; octave solfege.octave-1; note pitch solfege.letter
27
+ end
28
+ def play_hanon ary, octave, delta
29
+ solfeges = ary.map { Solfege.new _1, octave }
30
+ 14.times do
31
+ solfeges.each do |solfege|
32
+ play_solfege solfege
33
+ solfege.i += delta
34
+ solfege.update
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ Alda::Score.new do
41
+ piano_; set_note_length 16
42
+ def play_hanon ary1, ary2
43
+ s do
44
+ play_hanon ary1, 3, 1
45
+ play_hanon ary2, 5, -1
46
+ end * 2
47
+ end
48
+ play_hanon [0, 2, 3, 4, 5, 4, 3, 2], [4, 2, 1, 0, -1, 0, 1, 2]
49
+ play_hanon [0, 2, 5, 4, 3, 4, 3, 2], [4, 1, -1, 0, 1, 0, 1, 2]
50
+ v1; o3; c2; v2; o2; c2
51
+ end.play
@@ -3,6 +3,5 @@
3
3
  require 'alda-rb'
4
4
 
5
5
  Alda::Score.new do
6
- piano_
7
- c; d; e; f; g2; g4; f; e; d; c2
6
+ piano_ c d e f g2 g4 f e d c2
8
7
  end.play
@@ -7,11 +7,11 @@ Alda::Score.new do
7
7
  quant 200
8
8
 
9
9
  key_signature 'f+ c+ g+'
10
- a8; b; +o; c; d; e; f; g; a; -o
11
- a; b; +o; ~c; d; e; ~f; ~g; a; -o
10
+ a8 b o! c d e f g a o?
11
+ a b o! c_ d e f_ g_ a o?
12
12
 
13
13
  key_signature [:g, :minor]
14
- g; a; b; +o; +c; d; e; +f; g; -o
14
+ g a b o! c! d e f! g o?
15
15
 
16
16
  key_signature e: [:flat], b: [:flat]
17
17
  g1_1/b/d
@@ -5,5 +5,5 @@ require 'alda-rb'
5
5
  Alda::Score.new do
6
6
  midi_calliope_lead_
7
7
  set_note_length 8
8
- (10...100).step(10) { |n| note midi_note n }
8
+ (10...100).step(10) { note midi_note _1 }
9
9
  end.play
data/examples/modes2.rb CHANGED
@@ -7,6 +7,6 @@ Alda::Score.new do
7
7
  piano_
8
8
  %i[ionian dorian phrygian lydian mixolydian aeolian locrian].each do |mode|
9
9
  key_sig [:c, mode]
10
- o4; c4; d8; e; f; g; a; b; +o; c1
10
+ o4 c4 d8 e f g a b o! c1
11
11
  end
12
12
  end.play
@@ -3,12 +3,12 @@
3
3
  require 'alda-rb'
4
4
 
5
5
  Alda::Score.new do
6
- piano_; set_duration 4
7
- harp_; set_duration 2; octave 3
6
+ piano_ set_duration 4
7
+ harp_ set_duration 2; octave 3
8
8
 
9
9
  piano_/harp_
10
- t { e; f; g }; t { a; b; +o; c }
10
+ t{ e f g }; t{ a b o! c }
11
11
 
12
12
  harp_
13
- t { d; e; f }; t { g; a; b }
13
+ t{ d e f }; t { g a b }
14
14
  end.play
data/examples/nesting.rb CHANGED
@@ -6,21 +6,21 @@ Alda::Score.new do
6
6
  piano_
7
7
 
8
8
  v1
9
- c8; d; e; f; g2_
9
+ c8 d e f g2_
10
10
 
11
11
  v2
12
- s{ c8; d }*2; e
13
- f; g*3
12
+ (c8 d)*2; e
13
+ f g*3
14
14
  t do
15
- t{ c; c }
15
+ t{ c c }
16
16
  s{ g/e/c; s{ d }*2 }
17
- s{ c; d }*5
17
+ (c d)*5
18
18
  end * 5
19
- a/b/c; b; c/e
19
+ a/b/c; b c/e
20
20
 
21
21
  v3
22
- a8; b; +o; c2_
22
+ a8 b o! c2_
23
23
 
24
24
  clarinet_
25
- a2; e
25
+ a2 e
26
26
  end.play
data/examples/phase.rb CHANGED
@@ -10,5 +10,5 @@ Alda::Score.new do
10
10
  tempo tempo
11
11
  end
12
12
  tempos.keys.lazy.map { __send__ _1 }.inject :/
13
- s{ e8; f; g }*99
13
+ (e8 f g)*99
14
14
  end.play
@@ -5,6 +5,6 @@ require 'alda-rb'
5
5
  Alda::Score.new do
6
6
  accordion_
7
7
  c500ms/e/g
8
- c1s/f/a
8
+ x{ c1s; f; a }
9
9
  c2s/e/g
10
10
  end.play
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'alda-rb'
4
+
5
+ Alda::Score.new do
6
+ tempo! 160
7
+
8
+ riffA__ f8 f g! a o! c c d c o?
9
+ riffB__ b8? b? o! c! d f f g f o?
10
+ riffC__ o! c8 c d! e g g a g o?
11
+ riffD do
12
+ f8 f g! a o! c c d o? b o!
13
+ c c o? b? b? a a g g
14
+ end
15
+
16
+ rockinRiff do
17
+ riffA*4
18
+ riffB*2; riffA*2
19
+ riffC riffB riffD
20
+ end
21
+
22
+ electric_guitar_distorted_ 'guitar'; o2
23
+ tenor_saxophone_ 'sax'; o3
24
+
25
+ guitar_/sax_
26
+ rockinRiff*8
27
+ end.play
data/lib/alda-rb.rb CHANGED
@@ -1,35 +1,22 @@
1
+ require 'readline'
2
+ require 'set'
3
+ require 'stringio'
4
+ require 'irb/ruby-lex'
1
5
  require 'alda-rb/version'
6
+ require 'colorize'
7
+
8
+ {
9
+ Array => -> { "[#{map(&:to_alda_code).join ' '}]" },
10
+ Hash => -> { "{#{to_a.reduce(:+).map(&:to_alda_code).join ' '}}" },
11
+ String => -> { dump },
12
+ Symbol => -> { ?: + to_s },
13
+ Numeric => -> { inspect },
14
+ Range => -> { "#{first}-#{last}" },
15
+ TrueClass => -> { 'true' },
16
+ FalseClass => -> { 'false' },
17
+ NilClass => -> { 'nil' }
18
+ }.each { |klass, block| klass.define_method :to_alda_code, &block }
2
19
 
3
- class Array
4
- def to_alda_code
5
- "[#{map(&:to_alda_code).join ' '}]"
6
- end
7
- end
8
- class Hash
9
- def to_alda_code
10
- "{#{to_a.reduce(:+).map(&:to_alda_code).join ' '}}"
11
- end
12
- end
13
- class String
14
- def to_alda_code
15
- inspect
16
- end
17
- end
18
- class Symbol
19
- def to_alda_code
20
- ?: + to_s
21
- end
22
- end
23
- class Numeric
24
- def to_alda_code
25
- inspect
26
- end
27
- end
28
- class Range
29
- def to_alda_code
30
- "#{first}-#{last}"
31
- end
32
- end
33
20
  class Proc
34
21
  # Runs +self+ for +n+ times.
35
22
  def * n
@@ -41,19 +28,35 @@ class Proc
41
28
  end
42
29
  end
43
30
 
31
+ class StringIO
32
+ # Equivalent to #string.
33
+ def to_s
34
+ string
35
+ end
36
+ end
37
+
38
+ module Kernel
39
+ # Runs the alda command.
40
+ # Does not capture output.
41
+ # @example
42
+ # alda 'version'
43
+ # alda 'play', '-c', 'piano: a'
44
+ # alda 'repl'
45
+ def alda *args
46
+ system Alda.executable, *args
47
+ end
48
+ end
49
+
44
50
  # The module serving as a namespace.
45
51
  module Alda
46
52
 
47
- # The path to the +alda+ executable.
53
+ # The array of available subcommands of alda executable.
48
54
  #
49
- # The default value is <tt>"alda"</tt>,
50
- # which will depend on your PATH.
51
- singleton_class.attr_accessor :executable
52
- @executable = 'alda'
53
-
54
- # The method give Alda# ability to invoke +alda+ at the command line,
55
- # using +name+ as subcommand and +args+ as arguments.
56
- # +opts+ are converted to command line options.
55
+ # Alda# is able to invoke +alda+ at the command line.
56
+ # The subcommand is the name of the method invoked upon Alda#.
57
+ #
58
+ # The first argument (a hash) is interpreted as the options.
59
+ # The keyword arguments are interpreted as the subcommand options.
57
60
  #
58
61
  # The return value is the string output by the command in STDOUT.
59
62
  #
@@ -63,43 +66,72 @@ module Alda
63
66
  # # => "Client version: 1.4.0\nServer version: [27713] 1.4.0\n"
64
67
  # Alda.parse code: 'bassoon: o3 c'
65
68
  # # => "{\"chord-mode\":false,\"current-instruments\":...}\n"
66
- # Alda.sandwich
67
- # # => Alda::CommandLineError (Expected a command, got sandwich)
68
- def self.method_missing name, *args, **opts
69
- name = name.to_s.gsub ?_, ?-
70
- args.concat opts.map { |key, val|
71
- ["--#{key.to_s.gsub ?_, ?-}", val.to_s]
72
- }.flatten
73
- output = IO.popen [executable, name, *args], &:read
74
- raise CommandLineError.new $?, output if $?.exitstatus.nonzero?
75
- output
69
+ COMMANDS = %i[
70
+ help update repl up start_server init down stop_server
71
+ downup restart_server list status version play stop parse
72
+ instruments export
73
+ ].freeze
74
+
75
+ COMMANDS.each do |command|
76
+ define_method command do |*args, **opts|
77
+ block = ->key, val do
78
+ next unless val
79
+ args.push "--#{key.to_s.tr ?_, ?-}"
80
+ args.push val.to_s unless val == true
81
+ end
82
+ # executable
83
+ args.unshift Alda.executable
84
+ args.map! &:to_s
85
+ # options
86
+ Alda.options.each &block
87
+ # subcommand
88
+ args.push command.to_s
89
+ # subcommand options
90
+ opts.each &block
91
+ # subprocess
92
+ IO.popen(args, &:read).tap do
93
+ raise CommandLineError.new $?, _1 if $?.exitstatus.nonzero?
94
+ end
95
+ end
76
96
  end
77
97
 
98
+ # The path to the +alda+ executable.
99
+ #
100
+ # The default value is <tt>"alda"</tt>,
101
+ # which will depend on your PATH.
102
+ singleton_class.attr_accessor :executable
103
+ @executable = 'alda'
104
+
105
+ singleton_class.attr_reader :options
106
+ @options = {}
107
+
78
108
  # @return Whether the alda server is up.
79
- def self.up?
109
+ def up?
80
110
  status.include? 'up'
81
111
  end
82
112
 
83
113
  # @return Whether the alda server is down.
84
- def self.down?
114
+ def down?
85
115
  status.include? 'down'
86
116
  end
87
117
 
88
- # The error raised when one tries to run a non-existing subcommand
89
- # of +alda+.
90
- class CommandLineError < Exception
91
-
92
- # The <tt>Process::Status</tt> object representing the status of
93
- # the process that runs +alda+ command.
94
- attr_accessor :status
95
-
96
- # Create a CommandLineError# object.
97
- # @param status The status of the process running +alda+ command.
98
- # @param msg The exception message.
99
- def initialize status, msg = nil
100
- super msg
101
- @status = status
102
- end
118
+ module_function :up?, :down?, *COMMANDS
119
+
120
+ # Start a REPL session.
121
+ def self.repl
122
+ REPL.new.run
123
+ end
124
+
125
+ # Sets the options of alda command.
126
+ # Not the subcommand options.
127
+ def self.[] **opts
128
+ @options.merge! opts
129
+ self
130
+ end
131
+
132
+ # Clears the command line options.
133
+ def self.clear_options
134
+ @options.clear
103
135
  end
104
136
 
105
137
  # Including this module can make your class have the ability
@@ -111,35 +143,46 @@ module Alda
111
143
  # most of which are EventContainer# objects.
112
144
  attr_accessor :events
113
145
 
146
+ # The set containing the available variable names.
147
+ attr_accessor :variables
148
+
149
+ def on_contained
150
+ instance_eval &@block if @block
151
+ end
152
+
114
153
  # Make the object have the ability to appending its +events+
115
154
  # conveniently.
116
155
  #
117
156
  # Here is a list of sugar. When the name of a method meets certain
118
157
  # condition, the method is regarded as an event appended to +events+.
119
158
  #
120
- # 1. Starting with 2 lowercase letters and
159
+ # 1. Ending with 2 underlines: set variable. See SetVariable#.
160
+ #
161
+ # 2. Starting with 2 lowercase letters and
121
162
  # ending with underline character: instrument. See Part#.
122
163
  #
123
- # 2. Starting with 2 lowercase letters: inline lisp code.
124
- # See InlineLisp#.
164
+ # 3. Starting with 2 lowercase letters: inline lisp code,
165
+ # set variable, or get variable.
166
+ # One of the above three is chosen intelligently.
167
+ # See InlineLisp#, SetVariable#, GetVariable#.
125
168
  #
126
- # 3. Starting with "t": CRAM. See Cram#.
169
+ # 4. Starting with "t": CRAM. See Cram#.
127
170
  #
128
- # 4. Starting with one of "a", "b", ..., "g": note. See Note#.
171
+ # 5. Starting with one of "a", "b", ..., "g": note. See Note#.
129
172
  #
130
- # 5. Starting with "r": rest. See Rest#.
173
+ # 6. Starting with "r": rest. See Rest#.
131
174
  #
132
- # 6. "x": chord. See Chord#.
175
+ # 7. "x": chord. See Chord#.
133
176
  #
134
- # 7. "s": sequence. See Sequence#.
177
+ # 8. "s": sequence. See Sequence#.
135
178
  #
136
- # 8. Starting with "o": octave. See Octave#.
179
+ # 9. Starting with "o": octave. See Octave#.
137
180
  #
138
- # 9. Starting with "v": voice. See Voice#.
181
+ # 10. Starting with "v": voice. See Voice#.
139
182
  #
140
- # 10. Starting with "__" (2 underlines): at marker. See AtMarker#.
183
+ # 11. Starting with "__" (2 underlines): at marker. See AtMarker#.
141
184
  #
142
- # 11. Starting with "_" (underline): marker. See Marker#.
185
+ # 12. Starting with "_" (underline): marker. See Marker#.
143
186
  #
144
187
  # Notes cannot have dots.
145
188
  # To tie multiple durations, +_+ is used instead of +~+.
@@ -151,29 +194,59 @@ module Alda
151
194
  # @see #initialize.
152
195
  # @return an EventContainer# object.
153
196
  def method_missing name, *args, &block
197
+ if @parent&.respond_to? name, true
198
+ return @parent.__send__ name, *args, &block
199
+ end
200
+ sequence_sugar = ->event do
201
+ if args.size == 1
202
+ joined = args.first
203
+ unless (got = @events.pop) == (expected = joined)
204
+ raise OrderError.new expected, got
205
+ end
206
+ Sequence.join event, joined
207
+ else
208
+ event
209
+ end
210
+ end
154
211
  case
155
- when /^(?<part>[a-z][a-z].*)_$/ =~ name
156
- Part.new [part], args.first
157
- when /^[a-z][a-z].*$/ =~ name
158
- InlineLisp.new name, *args
159
- when /^t(?<duration>.*)$/ =~ name
212
+ when /\A(?<head>[a-z][a-z].*)__\z/ =~ name
213
+ SetVariable.new head, *args, &block
214
+ when /\A(?<part>[a-z][a-z].*)_\z/ =~ name
215
+ if args.first.is_a? String
216
+ Part.new [part], args.first
217
+ else
218
+ sequence_sugar.(Part.new [part])
219
+ end
220
+ when /\A[a-z][a-z].*\z/ =~ name
221
+ if block
222
+ SetVariable.new name, *args, &block
223
+ elsif has_variable?(name) && (args.empty? || args.size == 1 && args.first.is_a?(Event))
224
+ sequence_sugar.(GetVariable.new name)
225
+ else
226
+ InlineLisp.new name, *args
227
+ end
228
+ when /\At(?<duration>.*)\z/ =~ name
160
229
  Cram.new duration, &block
161
- when /^(?<pitch>[a-g])(?<duration>.*)$/ =~ name
162
- Note.new pitch, duration
163
- when /^r(?<duration>.*)$/ =~ name
164
- Rest.new duration
165
- when /^x$/ =~ name
230
+ when /\A(?<pitch>[a-g])(?<duration>.*)\z/ =~ name
231
+ sequence_sugar.(Note.new pitch, duration)
232
+ when /\Ar(?<duration>.*)\z/ =~ name
233
+ sequence_sugar.(Rest.new duration)
234
+ when /\Ax\z/ =~ name
166
235
  Chord.new &block
167
- when /^s$/ =~ name
236
+ when /\As\z/ =~ name
168
237
  Sequence.new *args, &block
169
- when /^o(?<num>\d*)$/ =~ name
170
- Octave.new num
171
- when /^v(?<num>\d+)$/ =~ name
172
- Voice.new num
173
- when /^__(?<head>.+)$/ =~ name
174
- AtMarker.new head
175
- when /^_(?<head>.+)$/ =~ name
176
- Marker.new head
238
+ when /\Ao!\z/ =~ name
239
+ sequence_sugar.(Octave.new('').tap { _1.up_or_down = 1})
240
+ when /\Ao\?\z/ =~ name
241
+ sequence_sugar.(Octave.new('').tap { _1.up_or_down = -1})
242
+ when /\Ao(?<num>\d*)\z/ =~ name
243
+ sequence_sugar.(Octave.new num)
244
+ when /\Av(?<num>\d+)\z/ =~ name
245
+ sequence_sugar.(Voice.new num)
246
+ when /\A__(?<head>.+)\z/ =~ name
247
+ sequence_sugar.(AtMarker.new head)
248
+ when /\A_(?<head>.+)\z/ =~ name
249
+ sequence_sugar.(Marker.new head)
177
250
  else
178
251
  super
179
252
  end.then do |event|
@@ -181,6 +254,10 @@ module Alda
181
254
  end.tap &@events.method(:push)
182
255
  end
183
256
 
257
+ def has_variable? name
258
+ @variables.include?(name) || !!@parent&.has_variable?(name)
259
+ end
260
+
184
261
  # Append the events of another EventList# object here.
185
262
  # This method covers the disadvantage of alda's being unable to
186
263
  # import scores from other files.
@@ -192,18 +269,19 @@ module Alda
192
269
  # @param block to be passed with the EventList# object as +self+.
193
270
  # @example
194
271
  # Alda::Score.new do
195
- # tempo! 108 # inline lisp
196
- # piano_ # piano part
197
- # o4 # octave 4
198
- # c8; d; e; f # notes
199
- # g4; g; a; f; g; e; f; d; e; c
200
- # d4_8 # cannot have '~', use '_' instead
201
- # o3 b8 o4 c2
272
+ # tempo! 108 # inline lisp
273
+ # piano_ # piano part
274
+ # o4 # octave 4
275
+ # c8; d; e; f # notes
276
+ # g4 g a f g e f d e c # a sequence
277
+ # d4_8 # cannot have '~', use '_' instead
278
+ # o3 b8 o4 c2 # a sequence
202
279
  # end
203
280
  # # => #<Alda::Score:0x... @events=[...]>
204
281
  def initialize &block
205
282
  @events ||= []
206
- instance_eval &block if block
283
+ @variables ||= Set.new
284
+ @block ||= block
207
285
  end
208
286
 
209
287
  # Same as #events
@@ -231,7 +309,6 @@ module Alda
231
309
  # Alda::Score.new { piano_; c; d; e }.play from: 1
232
310
  # # (plays only an E note)
233
311
  def play **opts
234
- Alda.stop
235
312
  Alda.play code: self, **opts
236
313
  end
237
314
 
@@ -253,9 +330,231 @@ module Alda
253
330
  Alda.export code: self, **opts
254
331
  end
255
332
 
333
+ # Saves the alda codes into a file.
334
+ def save filename
335
+ File.open(filename, 'w') { _1.puts to_s }
336
+ end
337
+
338
+ # Loads alda codes from a file.
339
+ def load filename
340
+ event = InlineLisp.new :alda_code, File.read(filename)
341
+ @events.push event
342
+ event
343
+ end
344
+
345
+ # @return Alda codes.
256
346
  def to_s
257
347
  events_alda_codes
258
348
  end
349
+
350
+ # The initialization.
351
+ def initialize(...)
352
+ super
353
+ on_contained
354
+ end
355
+
356
+ # Clears all the events and variables.
357
+ def clear
358
+ @events.clear
359
+ @variables.clear
360
+ self
361
+ end
362
+ end
363
+
364
+ # An encapsulation for the REPL session for alda-rb.
365
+ class REPL
366
+
367
+ # The score object used in REPL.
368
+ # Includes Alda#, so it can refer to alda commandline.
369
+ class TempScore < Score
370
+ include Alda
371
+
372
+ Score.instance_methods(false).each do |meth|
373
+ define_method meth, Score.instance_method(meth)
374
+ end
375
+
376
+ def initialize session
377
+ super()
378
+ @session = session
379
+ end
380
+
381
+ def to_s
382
+ history
383
+ end
384
+
385
+ def history
386
+ @session.history.to_s
387
+ end
388
+
389
+ def clear_history
390
+ @session.clear_history
391
+ end
392
+
393
+ def get_binding
394
+ binding
395
+ end
396
+
397
+ alias quit exit
398
+ end
399
+
400
+ # The history.
401
+ attr_reader :history
402
+
403
+ # Initialization.
404
+ def initialize
405
+ @score = TempScore.new self
406
+ @binding = @score.get_binding
407
+ @lex = RubyLex.new
408
+ @history = StringIO.new
409
+ end
410
+
411
+ # Runs the session. Includes the start, the main loop, and the termination.
412
+ def run
413
+ start
414
+ while code = rb_code
415
+ break unless process_rb_code code
416
+ end
417
+ terminate
418
+ end
419
+
420
+ # Starts the session.
421
+ def start
422
+ end
423
+
424
+ # Reads the next Ruby codes input in the REPL session.
425
+ # It can intelligently continue reading if the code is not complete yet.
426
+ def rb_code
427
+ result = ''
428
+ begin
429
+ buf = Readline.readline '> '.green, true
430
+ return unless buf
431
+ result.concat buf, ?\n
432
+ ltype, indent, continue, block_open = @lex.check_state result
433
+ rescue Interrupt
434
+ $stdout.puts
435
+ retry
436
+ end while ltype || indent.nonzero? || continue || block_open
437
+ result
438
+ end
439
+
440
+ # Processes the Ruby codes read.
441
+ # Sending it to a score and sending the result to alda.
442
+ # @return +true+ for continue looping, +false+ for breaking the loop.
443
+ def process_rb_code code
444
+ @score.clear
445
+ begin
446
+ @binding.eval code
447
+ rescue StandardError, ScriptError => e
448
+ $stderr.print e.full_message
449
+ return true
450
+ rescue Interrupt
451
+ return true
452
+ rescue SystemExit
453
+ return false
454
+ end
455
+ code = @score.events_alda_codes
456
+ unless code.empty?
457
+ $stdout.puts code.yellow
458
+ play_score code
459
+ end
460
+ true
461
+ end
462
+
463
+ # Tries to run the block and rescue CommandLineError#.
464
+ def try_command # :block:
465
+ begin
466
+ yield
467
+ rescue CommandLineError => e
468
+ puts e.message.red
469
+ end
470
+ end
471
+
472
+ # Plays the score.
473
+ def play_score code
474
+ try_command do
475
+ Alda.play code: code, history: @history
476
+ @history.puts code
477
+ end
478
+ end
479
+
480
+ # Terminates the REPL session.
481
+ def terminate
482
+ clear_history
483
+ end
484
+
485
+ # Clears the history.
486
+ def clear_history
487
+ @history = StringIO.new
488
+ end
489
+ end
490
+
491
+ # The error is raised when one tries to
492
+ # run a non-existing subcommand of +alda+.
493
+ class CommandLineError < StandardError
494
+
495
+ # The <tt>Process::Status</tt> object representing the status of
496
+ # the process that runs +alda+ command.
497
+ attr_reader :status
498
+
499
+ # The port on which the problematic Alda server runs.
500
+ # @example
501
+ # begin
502
+ # Alda.play({port: 1108}, code: "y")
503
+ # rescue CommandLineError => e
504
+ # e.port # => 1108
505
+ # end
506
+ attr_reader :port
507
+
508
+ # Create a CommandLineError# object.
509
+ # @param status The status of the process running +alda+ command.
510
+ # @param msg The exception message.
511
+ def initialize status, msg = nil
512
+ if match = msg&.match(/^\[(?<port>\d+)\]\sERROR\s(?<message>.*)$/)
513
+ super match[:message]
514
+ @port = match[:port].to_i
515
+ else
516
+ super msg
517
+ @port = nil
518
+ end
519
+ @status = status
520
+ end
521
+ end
522
+
523
+ # This error is raised when one tries to
524
+ # append events in an EventList# in a wrong order.
525
+ # @example
526
+ # Alda::Score.new do
527
+ # motif = f4 f e e d d c2
528
+ # g4 f e d c2 # It commented out, error will not occur
529
+ # c4 c g g a a g2 motif # (OrderError)
530
+ # end
531
+ class OrderError < StandardError
532
+
533
+ # The expected element gotten if it is of the correct order.
534
+ # @see #got
535
+ # @example
536
+ # Alda::Score.new do
537
+ # motif = f4 f e e d d c2
538
+ # g4 f e d c2
539
+ # p @events.size # => 2
540
+ # c4 c g g a a g2 motif
541
+ # rescue OrderError => e
542
+ # p @events.size # => 1
543
+ # p e.expected # => #<Alda::EventContainer:...>
544
+ # p e.got # => #<Alda::EventContainer:...>
545
+ # end
546
+ attr_reader :expected
547
+
548
+ # The actually gotten element.
549
+ # For an example, see #expected.
550
+ # @see #expected
551
+ attr_reader :got
552
+
553
+ def initialize expected, got
554
+ super 'events are out of order'
555
+ @expected = expected
556
+ @got = got
557
+ end
259
558
  end
260
559
 
261
560
  # The class of elements of EventList#events.
@@ -266,9 +565,13 @@ module Alda
266
565
  # object in the middle.
267
566
  attr_accessor :parent
268
567
 
568
+ # The EventContainer# object that contains it.
569
+ # It may be +nil+, especially probably when
570
+ # it itself is an EventContainer#.
571
+ attr_accessor :container
572
+
269
573
  # The callback invoked when it is contained in an EventContainer#.
270
- # It is overridden in InlineLisp#, so be aware if you want to
271
- # override InlineLisp#on_contained.
574
+ # It is overridden in InlineLisp# and EventList#.
272
575
  # @example
273
576
  # class Alda::Note
274
577
  # def on_contained
@@ -302,9 +605,8 @@ module Alda
302
605
  def initialize event, parent
303
606
  @event = event
304
607
  @parent = parent
305
- @event.parent = @parent
306
608
  @labels = []
307
- @event.on_contained
609
+ on_containing
308
610
  end
309
611
 
310
612
  # Make #event a Chord# object.
@@ -318,7 +620,9 @@ module Alda
318
620
  # Alda::Score.new { violin_/viola_/cello_; e; f; g}.play
319
621
  # # (plays notes E, F, G with three instruments simultaneously)
320
622
  def / other
321
- raise unless other == @parent.events.pop
623
+ unless (expected = other) == (got = @parent.events.pop)
624
+ raise OrderError.new expected, got
625
+ end
322
626
  @event =
323
627
  if @event.is_a? Part
324
628
  Part.new @event.names + other.event.names, other.event.arg
@@ -339,13 +643,29 @@ module Alda
339
643
 
340
644
  # Marks repetition.
341
645
  def * num
342
- @count = num
646
+ @count = (@count || 1) * num
647
+ self
343
648
  end
344
649
 
345
650
  # Marks alternative repetition.
346
651
  def % labels
347
- labels = [labels] unless labels.respond_to? :to_a
652
+ labels = [labels] unless labels.is_a? Array
348
653
  @labels.replace labels.to_a
654
+ self
655
+ end
656
+
657
+ def event= event
658
+ @event = event
659
+ on_containing
660
+ @event
661
+ end
662
+
663
+ def on_containing
664
+ if @event
665
+ @event.container = self
666
+ @event.parent = @parent
667
+ @event.on_contained
668
+ end
349
669
  end
350
670
 
351
671
  def method_missing name, *args
@@ -363,7 +683,7 @@ module Alda
363
683
 
364
684
  # The arguments passed to the lisp function.
365
685
  # Its elements can be
366
- # Array#, Hash#, Numeric#, String#, Symbol#, or InlineLisp#.
686
+ # Array#, Hash#, Numeric#, String#, Symbol#, or Event#.
367
687
  attr_accessor :args
368
688
 
369
689
  # The underlines in +head+ will be converted to hyphens.
@@ -377,8 +697,11 @@ module Alda
377
697
  end
378
698
 
379
699
  def on_contained
700
+ super
380
701
  @args.reverse_each do |event|
381
- raise if event.is_a?(Event) && event != @parent.events.pop
702
+ if event.is_a?(Event) && (expected = event) != (got = @parent.events.pop)
703
+ raise OrderError.new expected, got
704
+ end
382
705
  end
383
706
  end
384
707
  end
@@ -394,9 +717,32 @@ module Alda
394
717
  attr_accessor :duration
395
718
 
396
719
  # The underlines in +duration+ will be converted to +~+.
720
+ # Exclamation mark and question mark in +duration+
721
+ # will be interpreted as accidentals in #pitch.
722
+ #
723
+ # The number of underlines at the end of +duration+ means:
724
+ # neither natural nor slur if 0,
725
+ # natural if 1,
726
+ # slur if 2,
727
+ # both natural and slur if 3.
397
728
  def initialize pitch, duration
398
729
  @pitch = pitch.to_s
399
- @duration = duration.to_s.gsub ?_, ?~
730
+ @duration = duration.to_s.tr ?_, ?~
731
+ case @duration[-1]
732
+ when ?! # sharp
733
+ @pitch.concat ?+
734
+ @duration[-1] = ''
735
+ when ?? # flat
736
+ @pitch.concat ?-
737
+ @duration[-1] = ''
738
+ end
739
+ waves = /(?<str>~+)\z/ =~ @duration ? str.size : return
740
+ @duration[@duration.length - waves..] = ''
741
+ if waves >= 2
742
+ waves -= 2
743
+ @duration.concat ?~
744
+ end
745
+ @pitch.concat ?_ * waves
400
746
  end
401
747
 
402
748
  # Append a sharp sign after #pitch.
@@ -441,7 +787,7 @@ module Alda
441
787
 
442
788
  # Underlines in +duration+ will be converted to +~+.
443
789
  def initialize duration
444
- @duration = duration.to_s.gsub ?_, ?~
790
+ @duration = duration.to_s.tr ?_, ?~
445
791
  end
446
792
 
447
793
  def to_alda_code
@@ -525,7 +871,7 @@ module Alda
525
871
  attr_accessor :arg
526
872
 
527
873
  def initialize names, arg = nil
528
- @names = names.map { |name| name.to_s.gsub ?_, ?- }
874
+ @names = names.map { |name| name.to_s.tr ?_, ?- }
529
875
  @arg = arg
530
876
  end
531
877
 
@@ -535,16 +881,29 @@ module Alda
535
881
  result.concat ?:
536
882
  end
537
883
 
884
+ # Enables dot accessor.
538
885
  # @example
539
886
  # Alda::Score.new do
540
887
  # violin_/viola_/cello_('strings'); g1_1_1
541
888
  # strings_.cello_; -o; c1_1_1
542
889
  # end.play
543
890
  def method_missing name, *args
544
- name = name.to_s
545
- return super unless name[-1] == ?_
546
- name[-1] = ''
547
- @names.last.concat ?., name
891
+ str = name.to_s
892
+ return super unless str[-1] == ?_
893
+ str[-1] = ''
894
+ @names.last.concat ?., str
895
+ if args.size == 1
896
+ joined = args.first
897
+ unless (got = @parent.events.pop) == (expected = joined)
898
+ raise OrderError.new expected, got
899
+ end
900
+ unless @container
901
+ @container = EventContainer.new nil, @parent
902
+ @parent.events.delete self
903
+ @parent.push @container
904
+ end
905
+ @container.event = Sequence.join self, joined
906
+ end
548
907
  end
549
908
  end
550
909
 
@@ -594,7 +953,7 @@ module Alda
594
953
 
595
954
  # Underlines in +name+ is converted to hyphens.
596
955
  def initialize name
597
- @name = name.to_s.gsub ?_, ?-
956
+ @name = name.to_s.tr ?_, ?-
598
957
  end
599
958
 
600
959
  def to_alda_code
@@ -611,7 +970,7 @@ module Alda
611
970
 
612
971
  # Underlines in +name+ is converted to hyphens.
613
972
  def initialize name
614
- @name = name.to_s.gsub ?_, ?-
973
+ @name = name.to_s.tr ?_, ?-
615
974
  end
616
975
 
617
976
  def to_alda_code
@@ -623,8 +982,94 @@ module Alda
623
982
  class Sequence < Event
624
983
  include EventList
625
984
 
985
+ # Using this module can fix a bug of Array#flatten.
986
+ # @example
987
+ # def (a = Object.new).method_missing(...)
988
+ # Object.new
989
+ # end
990
+ # [a].flatten rescue $! # => #<TypeError:...>
991
+ # using Alda::Sequence::RefineFlatten
992
+ # [a].flatten # => [#<Object:...>]
993
+ module RefineFlatten
994
+ refine Array do
995
+ def flatten
996
+ each_with_object [] do |element, result|
997
+ if element.is_a? Array
998
+ result.push *element.flatten
999
+ else
1000
+ result.push element
1001
+ end
1002
+ end
1003
+ end
1004
+ end
1005
+ end
1006
+ using RefineFlatten
1007
+
626
1008
  def to_alda_code
627
1009
  @events.to_alda_code
628
1010
  end
1011
+
1012
+ # Creates a Sequence# object by joining +events+.
1013
+ # The EventContainer# objects are extracted,
1014
+ # and the Sequence# objects are flattened.
1015
+ def self.join *events
1016
+ new do
1017
+ @events = events.map do |event|
1018
+ while event.is_a?(EventContainer) && !event.count && event.labels.empty?
1019
+ event = event.event
1020
+ end
1021
+ event.is_a?(Sequence) ? event.events : event
1022
+ end.flatten
1023
+ end
1024
+ end
1025
+ end
1026
+
1027
+ # A set-variable event.
1028
+ # Includes EventList#.
1029
+ class SetVariable < Event
1030
+ include EventList
1031
+
1032
+ # The name of the variable.
1033
+ attr_accessor :name
1034
+
1035
+ # The events passed to it using arguments instead of a block.
1036
+ attr_reader :original_events
1037
+
1038
+ def initialize name, *events, &block
1039
+ @name = name.to_sym
1040
+ @original_events = events
1041
+ @events = events.clone
1042
+ super &block
1043
+ end
1044
+
1045
+ # Specially, the result ends with a newline.
1046
+ def to_alda_code
1047
+ "#@name = #{events_alda_codes}\n"
1048
+ end
1049
+
1050
+ def on_contained
1051
+ super
1052
+ @parent.variables.add @name
1053
+ @original_events.reverse_each do |event|
1054
+ unless (expected = event) == (got = @parent.events.pop)
1055
+ raise OrderError.new expected, got
1056
+ end
1057
+ end
1058
+ end
1059
+ end
1060
+
1061
+ # A get-variable event
1062
+ class GetVariable < Event
1063
+
1064
+ # The name of the variable
1065
+ attr_accessor :name
1066
+
1067
+ def initialize name
1068
+ @name = name
1069
+ end
1070
+
1071
+ def to_alda_code
1072
+ @name.to_s
1073
+ end
629
1074
  end
630
1075
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alda
4
- VERSION = '0.1.2'
4
+ VERSION = '0.1.4'
5
5
  end
metadata CHANGED
@@ -1,18 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alda-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ulysses Zhan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-16 00:00:00.000000000 Z
11
+ date: 2020-04-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
15
- - 2938747508@qq.com
15
+ - UlyssesZhan@gmail.com
16
16
  executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
@@ -32,6 +32,7 @@ files:
32
32
  - examples/clapping_music.rb
33
33
  - examples/dot_accessor.rb
34
34
  - examples/entropy.rb
35
+ - examples/hanon.rb
35
36
  - examples/hello_world.rb
36
37
  - examples/key_signature.rb
37
38
  - examples/midi_note_numbers.rb
@@ -41,6 +42,7 @@ files:
41
42
  - examples/phase.rb
42
43
  - examples/seconds_and_milliseconds.rb
43
44
  - examples/showcase.rb
45
+ - examples/variables.rb
44
46
  - lib/alda-rb.rb
45
47
  - lib/alda-rb/version.rb
46
48
  homepage: https://github.com/UlyssesZh/alda-rb
@@ -49,6 +51,7 @@ licenses:
49
51
  metadata:
50
52
  homepage_uri: https://github.com/UlyssesZh/alda-rb
51
53
  source_code_uri: https://github.com/UlyssesZh/alda-rb
54
+ changelog_uri: https://github.com/UlyssesZh/alda-rb/releases
52
55
  post_install_message:
53
56
  rdoc_options: []
54
57
  require_paths:
@@ -57,7 +60,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
60
  requirements:
58
61
  - - ">="
59
62
  - !ruby/object:Gem::Version
60
- version: 2.3.0
63
+ version: 2.7.0
61
64
  required_rubygems_version: !ruby/object:Gem::Requirement
62
65
  requirements:
63
66
  - - ">="