alda-rb 0.2.1 → 0.3.0

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.
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'alda-rb'
4
+
5
+ # Marriage D' Amour
6
+ #
7
+ # Richard Clayderman
8
+ # Paul de Senneville
9
+ #
10
+ # sheet music:
11
+ # https://musescore.com/user/153958/scores/154629
12
+
13
+ using Alda::Sequence::RefineFlatten
14
+
15
+ module Alda::EventList
16
+ def up8 &block
17
+ result = block.()
18
+ result.events = result.events.map do |event|
19
+ if event.is_event_of? Alda::Octave
20
+ event
21
+ else
22
+ if event.respond_to? :labels
23
+ labels = event.labels
24
+ event.labels = []
25
+ end
26
+ sequence = Alda::Sequence.new
27
+ sequence.events = [Alda::Chord.new(
28
+ event,
29
+ +Alda::Octave.new(''),
30
+ event,
31
+ -Alda::Octave.new('')
32
+ )]
33
+ container = Alda::EventContainer.new(sequence, result)
34
+ container.labels = labels || []
35
+ container
36
+ end
37
+ end
38
+ result
39
+ end
40
+ end
41
+
42
+ Alda::Score.new do
43
+ key_sig! 'b- e-'
44
+ tempo! 80
45
+ piano_ 'right'
46
+ piano_ 'left'
47
+
48
+ right_
49
+ r2_4_8_16 o5 g16
50
+ left_
51
+ o2 g8 o! d b d b d b d
52
+
53
+ motif1 = -> do
54
+ right_
55
+ g16 a a b b a a g g d d o? b b g g o! f f e e d e f e4_8
56
+ left_
57
+ o2 g o! d b d b d b d e g o! e o? g o! e o? g
58
+
59
+ right_
60
+ v1 r16 e e f f g g a a f f c c e e d d c d e _marker1 d8_4
61
+ v2 __marker1 r8 o6 d32 o! d8_16_32 v0
62
+ left_
63
+ v1 o? f o! c a c a c a o? b o! f o! d o? d8_4
64
+ v2 __marker1 r8 o4 g4? v0
65
+ end
66
+ motif1.()
67
+
68
+ motif2 = -> do
69
+ right_
70
+ o5 s{ (d8 o? g16 b o! d c)*2; d8 (o? g16 b o! e d e8)*2; e16 d e e_ f8 f16 g f g d4_8 o!}*2
71
+ left_
72
+ s{o2 g8 o! d b d b d o? g o! d b c g o! e o? g o! e o? g o? f o! c a o? b
73
+ (o! b o? up8{s{a}})%1; up8{b a}%2}*2
74
+ end
75
+ motif2.()
76
+
77
+ motif3 = -> do
78
+ right_
79
+ o5 b8_16 d16 d e e8_16 c16 a g a8_16 c16 c d d8 o? b16 b o! g f g8_16 o? b16 b o! c c8_16 o? a16 o! d c d4_8
80
+ left_
81
+ up8{s{g}}; o! d b c g o! e o? o? f o! c a o? b o! f o? a g o! d b o? a o! e o! c o? d o? up8{e_ g?}
82
+ end
83
+ motif3.()
84
+
85
+ tt2 { s{o2 g o! d b c g o! e o? o? f o! c a o? b o! b o? up8{s{a}}; g o! d b c g o! e o? o? f o! c a o? g o! d b}*2 }
86
+ motif4 = -> do
87
+ tt1 = -> { (o5 b8_16 b16 b o! c c8_16 o? b16 a g f8_16 f16 g f d4_8%1)*2 }
88
+ right_
89
+ tt1.(); g4_8; up8{tt1.()}
90
+ left_
91
+ tt2 o4 d g b
92
+ end
93
+ motif4.()
94
+ right_; up8{s{g2_4}}
95
+
96
+ motif2.()
97
+
98
+ motif4.()
99
+ right_
100
+ o5 up8{s{g2_8}}; r16 g16
101
+
102
+ motif1.()
103
+ motif2.()
104
+ motif3.()
105
+
106
+ tt1 = -> { (o5 b8_16 b16 b o! c c8_16 o? b16 a g f8_16 f16 g f d4_8%1)*2 }
107
+
108
+ right_
109
+ tt1.(); g4_8
110
+ -> { up8{tt1.()}; up8{s{g4_8}} }*2
111
+ up8{tt1.()}; up8{s{g1}}
112
+ left_
113
+ tt2*2; o4 d g o! d g r8_16 o! g4_8
114
+ end.play
@@ -3,8 +3,8 @@
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
10
  t{ e f g }; t{ a b o! c }
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'alda-rb'
4
+
5
+ Alda::Score.new do
6
+ tempo! 105
7
+
8
+ piano_
9
+ pmotif do
10
+ s do
11
+ o3
12
+ vol 100; a16
13
+ vol 90; -b
14
+ vol 80; a
15
+ vol 70; -b
16
+ vol 60; o! c d
17
+ vol 50; e f
18
+ end * 2
19
+ end
20
+
21
+ 70.step(10, -20) { track_vol _1; pmotif }
22
+
23
+ clarinet_
24
+ cmotif do
25
+ quant 100; o5
26
+ vol 60; d8
27
+ vol 70; c
28
+ vol 80; o? +f2_4
29
+ end
30
+
31
+ 10.step(70, 20) { track_vol _1; cmotif }
32
+ end.play
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'alda-rb'
4
+
5
+ Alda::Score.new do
6
+ quiet { vol 25 }
7
+ loud { vol 50 }
8
+ louder { vol 75 }
9
+
10
+ notes { c d e }
11
+
12
+ piano_
13
+ quiet notes
14
+ loud notes
15
+ louder notes
16
+ end.play
data/exe/alda-irb ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'alda-rb'
4
+ require 'optparse'
5
+
6
+ HELP = 'Print this help message and exit'.freeze
7
+ HOST = 'The hostname of the Alda REPL server; only useful in Alda 2; see `alda repl --help`'.freeze
8
+ PORT = 'The port of the Alda REPL server; only useful in Alda 2; see `alda repl --help`'.freeze
9
+ NO_COLOR = 'Whether the output should not be colored'.freeze
10
+ NO_PREVIEW = 'Whether a preview of what Alda code will not be played everytime you input ruby codes'.freeze
11
+
12
+ host = 'localhost'
13
+ port = -1
14
+ color = true
15
+ preview = true
16
+
17
+ OptionParser.new do |opts|
18
+ opts.banner = 'Usage: alda-irb [options]'
19
+ opts.on('-h', '--help', HELP) { exit unless puts opts }
20
+ opts.on('-H', '--host string', HOST) { host = _1 }
21
+ opts.on('-p', '--port int', PORT) { port = _1 }
22
+ opts.on('-c', '--no-color', NO_COLOR) { color = false }
23
+ opts.on('-P', '--no-preview', NO_PREVIEW) { preview = false }
24
+ end.parse!
25
+
26
+ Alda.deduce_generation
27
+ opts = {color: color, preview: preview}
28
+ opts.merge! host: host, port: port unless Alda.v1?
29
+ Alda::REPL.new(**opts).run
@@ -1,3 +1,5 @@
1
+ ##
2
+ # Adding functions that is accessible everywhere.
1
3
  module Kernel
2
4
  ##
3
5
  # :call-seq:
@@ -19,7 +21,24 @@ end
19
21
  module Alda
20
22
 
21
23
  ##
22
- # The array of available subcommands of alda executable.
24
+ # The Array of possible values of ::generation.
25
+ # It is just the array <tt>[:v1, :v2]</tt>
26
+ #
27
+ # You can use +:v1?+ and +:v2?+ to get whether the current generation is +:v1+ or +:v2+.
28
+ # For example, <tt>Alda.v1?</tt> is the same as <tt>Alda.generation == :v1</tt>.
29
+ # You can also use +:v1!+ and +:v2!+ to set the generation to +:v1+ or +:v2+.
30
+ # For example, <tt>Alda.v1!</tt> is the same as <tt>Alda.generation = :v1</tt>.
31
+ GENERATIONS = %i[v1 v2].freeze
32
+
33
+ GENERATIONS.each do |gen|
34
+ module_function define_method("#{gen}?") { @generation == gen }
35
+ module_function define_method("#{gen}!") { @generation = gen }
36
+ end
37
+
38
+ ##
39
+ # The available subcommands of alda executable.
40
+ # This is a Hash, with keys being possible values of ::generation,
41
+ # and values being an Array of symbols of the available commands of that generation.
23
42
  #
24
43
  # Alda is able to invoke +alda+ at the command line.
25
44
  # The subcommand is the name of the method invoked upon Alda.
@@ -36,37 +55,43 @@ module Alda
36
55
  # Alda.parse code: 'bassoon: o3 c'
37
56
  # # => "{\"chord-mode\":false,\"current-instruments\":...}\n"
38
57
  #
39
- # The available commands are: +help+, +update+, +repl+, +up+,
40
- # +start_server+, +init+, +down+, +stop_server+, +downup+, +restart_server+,
41
- # +list+, +status+, +version+, +play+, +stop+, +parse+, +instruments+, and
42
- # +export+.
43
- COMMANDS = %i[
44
- help update repl up start_server init down stop_server
45
- downup restart_server list status version play stop parse
46
- instruments export
47
- ].freeze
58
+ # The available commands are:
59
+ #
60
+ # * If ::generation is +:v1+:
61
+ # +help+, +update+, +repl+, +up+, +start_server+, +init+, +down+, +stop_server+,
62
+ # +downup+, +restart_server+, +list+, +status+, +version+, +play+, +stop+, +parse+,
63
+ # +instruments+, and +export+.
64
+ # * If ::generation is +:v2+:
65
+ # +doctor+, +export+, +help+, +import+, +instruments+, +parse+, +play+,
66
+ # +ps+, +repl+, +shutdown+, +stop+, +telemetry+, +update+, and +version+.
67
+ #
68
+ # Trying to run a command that is not support by the current generation set by ::generation
69
+ # will raise an Alda::GenerationError.
70
+ COMMANDS_FOR_VERSIONS = {
71
+ v1: %i[
72
+ help update repl up start_server init down stop_server
73
+ downup restart_server list status version play stop parse
74
+ instruments export
75
+ ].freeze,
76
+ v2: %i[
77
+ doctor export help import instruments parse play ps repl shutdown stop
78
+ telemetry update version
79
+ ].freeze
80
+ }.freeze
48
81
 
49
- COMMANDS.each do |command|
82
+ ##
83
+ # The Hash of available commands.
84
+ # The symbols of commands are keys
85
+ # and each value is an Array of generations where the command is available.
86
+ COMMANDS = COMMANDS_FOR_VERSIONS.each_with_object({}) do |(gen, commands), r|
87
+ commands.each { (r[_1] ||= []).push gen }
88
+ end.freeze
89
+
90
+ COMMANDS.each do |command, generations|
50
91
  define_method command do |*args, **opts|
51
- block = ->key, val do
52
- next unless val
53
- args.push "--#{key.to_s.tr ?_, ?-}"
54
- args.push val.to_s unless val == true
55
- end
56
- # executable
57
- args.unshift Alda.executable
58
- args.map! &:to_s
59
- # options
60
- Alda.options.each &block
61
- # subcommand
62
- args.push command.to_s
63
- # subcommand options
64
- opts.each &block
65
- # subprocess
66
- IO.popen(args, &:read).tap do
67
- raise CommandLineError.new $?, _1 if $?.exitstatus.nonzero?
68
- end
69
- end
92
+ Alda::GenerationError.assert_generation generations
93
+ Alda.pipe(command, *args, **opts, &:read).tap { raise CommandLineError.new $?, _1 if $?.exitstatus.nonzero? }
94
+ end.tap { module_function _1 }
70
95
  end
71
96
 
72
97
  class << self
@@ -84,6 +109,19 @@ module Alda
84
109
  # Clear it using ::clear_options.
85
110
  attr_reader :options
86
111
 
112
+ ##
113
+ # The major version of the +alda+ command used.
114
+ # Possible values: +:v1+ or +:v2+ (i.e. one of the values in Alda::GENERATIONS).
115
+ # If you try to specify it to values other than those, an ArgumentError will be raised.
116
+ # This affects several things due to some incompatible changes from \Alda 1 to \Alda 2.
117
+ # You may use ::deduce_generation to automatically set it,
118
+ # or use #v1! or #v2! to set it in a shorter way.
119
+ attr_accessor :generation
120
+ def generation= gen # :nodoc:
121
+ raise ArgumentError, "bad generation: #{gen}" unless GENERATIONS.include? gen
122
+ @generation = gen
123
+ end
124
+
87
125
  ##
88
126
  # :call-seq:
89
127
  # Alda[**opts] -> self
@@ -91,6 +129,7 @@ module Alda
91
129
  # Sets the options of alda command.
92
130
  # Not the subcommand options.
93
131
  #
132
+ # # This example only works for Alda 1.
94
133
  # Alda[port: 1108].up # => "[1108] ..."
95
134
  # Alda.status # => "[1108] ..."
96
135
  #
@@ -115,14 +154,69 @@ module Alda
115
154
 
116
155
  @executable = 'alda'
117
156
  @options = {}
157
+ v2!
158
+
159
+ ##
160
+ # :call-seq:
161
+ # pipe(command, *args, **opts) -> IO
162
+ # pipe(command, *args, **opts) { |io| ... } -> Object
163
+ #
164
+ # Runs +alda+ in command line as a child process and returns the pipe IO
165
+ # or pass the IO to the block.
166
+ # See COMMANDS_FOR_VERSIONS for an explanation of +args+ and +opts+.
167
+ def pipe command, *args, **opts, &block
168
+ add_option = ->key, val do
169
+ next unless val
170
+ args.push "--#{Alda::Utils.snake_to_slug key}"
171
+ args.push val.to_s unless val == true
172
+ end
173
+ # executable
174
+ args.unshift Alda.executable
175
+ args.map! &:to_s
176
+ # options
177
+ Alda.options.each &add_option
178
+ # subcommand
179
+ args.push command.to_s
180
+ # subcommand options
181
+ opts.each &add_option
182
+ # subprocess
183
+ spawn_options = Alda::Utils.win_platform? ? {new_pgroup: true} : {pgroup: true}
184
+ IO.popen args, **spawn_options, &block
185
+ end
186
+
187
+ ##
188
+ # :call-seq:
189
+ # processes() -> Array
190
+ #
191
+ # Returns a Array of details about running \Alda processes.
192
+ # Only available for \Alda 2.
193
+ # Each element in the Array is a Hash,
194
+ # and each Hash has the following keys:
195
+ # - +:id+: the player-id of the process, a three-letter String.
196
+ # - +:port+: the port number of the process, an Integer.
197
+ # - +:state+: the state of the process, a Symbol (may be +nil+, +:ready+, +:active+ etc.).
198
+ # - +:expiry+: a human-readable description of expiry time of the process, a String (may be +nil+).
199
+ # - +:type+: the type of the process, a Symbol (may be +:player+ or +:repl_server+).
200
+ def processes
201
+ raise GenerationError.new [:v2] if v1?
202
+ Alda.ps.lines(chomp: true)[1..].map do |line|
203
+ id, port, state, expiry, type = line.split ?\t
204
+ port = port.to_i
205
+ state = state == ?- ? nil : state.to_sym
206
+ expiry = nil if expiry == ?-
207
+ type = Alda::Utils.slug_to_snake type
208
+ {id: id, port: port, state: state, expiry: expiry, type: type}
209
+ end
210
+ end
118
211
 
119
212
  ##
120
213
  # :call-seq:
121
214
  # up?() -> true or false
122
215
  #
123
216
  # Whether the alda server is up.
217
+ # Always returns true if ::generation is +:v2+.
124
218
  def up?
125
- status.include? 'up'
219
+ Alda.v2? || Alda.status.include?('up')
126
220
  end
127
221
 
128
222
  ##
@@ -130,10 +224,22 @@ module Alda
130
224
  # down? -> true or false
131
225
  #
132
226
  # Whether the alda server is down.
227
+ # Always returns false if ::generation is +:v2+.
133
228
  def down?
134
- status.include? 'down'
229
+ !Alda.v2? && Alda.status.include?('down')
230
+ end
231
+
232
+ ##
233
+ # :call-seq:
234
+ # deduce_generation -> one of Alda::GENERATIONS
235
+ #
236
+ # Deduce the generation of \Alda being used by running <tt>alda version</tt> in command line,
237
+ # and then set ::generation accordingly.
238
+ def deduce_generation
239
+ /(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)/ =~ Alda.version
240
+ @generation = major == '1' ? :v1 : :v2
135
241
  end
136
242
 
137
- module_function :up?, :down?, *COMMANDS
243
+ module_function :pipe, :processes, :up?, :down?, :deduce_generation
138
244
 
139
245
  end
data/lib/alda-rb/error.rb CHANGED
@@ -9,33 +9,105 @@ class Alda::CommandLineError < StandardError
9
9
 
10
10
  ##
11
11
  # The port on which the problematic alda server runs.
12
+ # This is only available for \Alda 1.
12
13
  #
13
14
  # begin
14
15
  # Alda[port: 1108].play code: 'y'
15
16
  # rescue CommandLineError => e
16
17
  # e.port # => 1108
17
18
  # end
18
- attr_reader :port
19
+ def port
20
+ Alda::GenerationError.assert_generation [:v1]
21
+ @port
22
+ end
19
23
 
20
24
  ##
21
25
  # :call-seq:
22
26
  # new(status, msg=nil) -> Alda::CommandLineError
23
27
  #
24
28
  # Create a Alda::CommandLineError object.
25
- # +status+ is the status of the process running +alda+ command.
26
- # +msg+ is output of +alda+ command. port# info is extracted from +msg+.
29
+ # +status+ is the status of the process running +alda+ command (can be nil).
30
+ # +msg+ is the output of +alda+ command. #port info is extracted from +msg+ in \Alda 1.
27
31
  def initialize status, msg = nil
28
- if match = msg&.match(/^\[(?<port>\d+)\]\sERROR\s(?<message>.*)$/)
29
- super match[:message]
30
- @port = match[:port].to_i
32
+ if Alda.v1? && msg && /^\[(?<port>\d+)\]\sERROR\s(?<message>.*)$/ =~ msg
33
+ super message
34
+ @port = port.to_i
31
35
  else
32
36
  super msg
33
- @port = nil
34
37
  end
35
38
  @status = status
36
39
  end
37
40
  end
38
41
 
42
+ ##
43
+ # The error is raised when the \Alda nREPL server returns problems.
44
+ # This is only available for \Alda 2.
45
+ # See Alda::REPL#message.
46
+ class Alda::NREPLServerError < StandardError
47
+
48
+ ##
49
+ # The hostname of the nREPL server.
50
+ attr_reader :host
51
+
52
+ ##
53
+ # The port of the nREPL server.
54
+ attr_reader :port
55
+
56
+ ##
57
+ # The problems returned by the nREPL server.
58
+ # This is an Array of String.
59
+ attr_reader :problems
60
+
61
+ ##
62
+ # :call-seq:
63
+ # new(host, port, problems) -> Alda::NREPLServerError
64
+ #
65
+ # Creates a Alda::NREPLServerError object.
66
+ # Raises Alda::GenerationError if the current generation is not \Alda 2.
67
+ def initialize host, port, problems
68
+ Alda::GenerationError.assert_generation [:v2]
69
+ super problems.join ?\n
70
+ @host = host
71
+ @port = port
72
+ @problems = problems
73
+ end
74
+ end
75
+
76
+ ##
77
+ # This error is raised when one tries to run commands that are not available for the generation
78
+ # of \Alda specified by Alda::generation.
79
+ #
80
+ # Alda.v1!
81
+ # Alda.import # (GenerationError)
82
+ class Alda::GenerationError < StandardError
83
+
84
+ ##
85
+ # The actual generation that was set by Alda::generation when the error occurs.
86
+ attr_reader :generation
87
+
88
+ ##
89
+ # The generations that could have been set to avoid the error.
90
+ # An Array.
91
+ attr_reader :fine_generations
92
+
93
+ ##
94
+ # :call-seq:
95
+ # new(fine_generations) -> Alda::GenerationError
96
+ #
97
+ # Creates a Alda::GenerationError object.
98
+ def initialize fine_generations
99
+ super "bad Alda generation for this action; good ones are #{fine_generations}"
100
+ @generation = Alda.generation
101
+ @fine_generations = fine_generations
102
+ end
103
+
104
+ ##
105
+ # Raises an Alda::GenerationError if the current generation is not in +fine_generations+.
106
+ def self.assert_generation fine_generations
107
+ raise new fine_generations unless fine_generations.include? Alda.generation
108
+ end
109
+ end
110
+
39
111
  ##
40
112
  # This error is raised when one tries to
41
113
  # append events in an Alda::EventList in a wrong order.
@@ -49,8 +121,7 @@ class Alda::OrderError < StandardError
49
121
 
50
122
  ##
51
123
  # The expected element gotten if it is of the correct order.
52
- #
53
- # See #got
124
+ # See #got.
54
125
  #
55
126
  # Alda::Score.new do
56
127
  # motif = f4 f e e d d c2
@@ -72,6 +143,8 @@ class Alda::OrderError < StandardError
72
143
  ##
73
144
  # :call-seq:
74
145
  # new(expected, got) -> Alda::OrderError
146
+ #
147
+ # Creates a Alda::OrderError object.
75
148
  def initialize expected, got
76
149
  super 'events are out of order'
77
150
  @expected = expected