alda-rb 0.2.1 → 0.3.1

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,32 @@
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
+ NO_RELINE = 'Whether to use Reline to read input'.freeze
12
+
13
+ host = 'localhost'
14
+ port = -1
15
+ color = true
16
+ preview = true
17
+ reline = true
18
+
19
+ OptionParser.new do |opts|
20
+ opts.banner = 'Usage: alda-irb [options]'
21
+ opts.on('-h', '--help', HELP) { exit unless puts opts }
22
+ opts.on('-H', '--host string', HOST) { host = _1 }
23
+ opts.on('-p', '--port int', PORT) { port = _1 }
24
+ opts.on('-c', '--no-color', NO_COLOR) { color = false }
25
+ opts.on('-P', '--no-preview', NO_PREVIEW) { preview = false }
26
+ opts.on('-r', '--no-reline', NO_RELINE) { reline = false }
27
+ end.parse!
28
+
29
+ Alda.deduce_generation
30
+ opts = { color: color, preview: preview, reline: reline }
31
+ opts.merge! host: host, port: port unless Alda.v1?
32
+ 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,45 @@ 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
+ result = Alda.pipe command, *args, **opts, &:read
94
+ raise CommandLineError.new $?, result if $?.exitstatus.nonzero?
95
+ result
96
+ end.tap { module_function _1 }
70
97
  end
71
98
 
72
99
  class << self
@@ -84,6 +111,19 @@ module Alda
84
111
  # Clear it using ::clear_options.
85
112
  attr_reader :options
86
113
 
114
+ ##
115
+ # The major version of the +alda+ command used.
116
+ # Possible values: +:v1+ or +:v2+ (i.e. one of the values in Alda::GENERATIONS).
117
+ # If you try to specify it to values other than those, an ArgumentError will be raised.
118
+ # This affects several things due to some incompatible changes from \Alda 1 to \Alda 2.
119
+ # You may use ::deduce_generation to automatically set it,
120
+ # or use #v1! or #v2! to set it in a shorter way.
121
+ attr_accessor :generation
122
+ def generation= gen # :nodoc:
123
+ raise ArgumentError, "bad generation: #{gen}" unless GENERATIONS.include? gen
124
+ @generation = gen
125
+ end
126
+
87
127
  ##
88
128
  # :call-seq:
89
129
  # Alda[**opts] -> self
@@ -91,6 +131,7 @@ module Alda
91
131
  # Sets the options of alda command.
92
132
  # Not the subcommand options.
93
133
  #
134
+ # # This example only works for Alda 1.
94
135
  # Alda[port: 1108].up # => "[1108] ..."
95
136
  # Alda.status # => "[1108] ..."
96
137
  #
@@ -115,14 +156,104 @@ module Alda
115
156
 
116
157
  @executable = 'alda'
117
158
  @options = {}
159
+ @env = {
160
+ 'ALDA_DISABLE_SPAWNING' => 'yes',
161
+ 'ALDA_DISABLE_TELEMETRY' => 'yes'
162
+ }
163
+ v2!
164
+
165
+ ##
166
+ # :call-seq:
167
+ # env() -> Hash
168
+ # env(hash) -> Hash
169
+ # env(hash) { ... } -> Object
170
+ #
171
+ # When called with no arguments,
172
+ # returns the commandline environment variables (a Hash)
173
+ # used when running +alda+ on command line.
174
+ # It is <tt>{"ALDA_DISABLE_SPAWNING"=>"yes","ALDA_DISABLE_TELEMETRY"=>"yes"}</tt> by default
175
+ # (for speeding up the command line responses:
176
+ # {alda-lang/alda#368}[https://github.com/alda-lang/alda/issues/368]).
177
+ #
178
+ # When called with an argument +hash+,
179
+ # merge the old environment variables with +hash+ and set
180
+ # the merged Hash as the new environment variables.
181
+ # Returns the new environment variables (a Hash).
182
+ #
183
+ # When called with an argument +hash+ and a block,
184
+ # execute the block with the environment being set to the merge of the old environment
185
+ # and +hash+, and then restore the old environment.
186
+ # Returns the returned value of the block.
187
+ def env hash = nil, &block
188
+ if hash
189
+ @env = (old_env = @env).merge hash.map { |k, v| [k.to_s, v.to_s] }.to_h
190
+ block ? block.().tap { @env = old_env } : @env
191
+ else
192
+ @env
193
+ end
194
+ end
195
+
196
+ ##
197
+ # :call-seq:
198
+ # pipe(command, *args, **opts) -> IO
199
+ # pipe(command, *args, **opts) { |io| ... } -> Object
200
+ #
201
+ # Runs +alda+ in command line as a child process and returns the pipe IO
202
+ # or pass the IO to the block.
203
+ # See COMMANDS_FOR_VERSIONS for an explanation of +args+ and +opts+.
204
+ def pipe command, *args, **opts, &block
205
+ add_option = ->((key, val)) do
206
+ next unless val
207
+ args.push "--#{Alda::Utils.snake_to_slug key}"
208
+ args.push val.to_s unless val == true
209
+ end
210
+ # executable
211
+ args.unshift Alda.executable
212
+ args.map! &:to_s
213
+ # options
214
+ Alda.options.each &add_option
215
+ # subcommand
216
+ args.push command.to_s
217
+ # subcommand options
218
+ opts.each &add_option
219
+ # subprocess
220
+ spawn_options = Alda::Utils.win_platform? ? { new_pgroup: true } : { pgroup: true }
221
+ IO.popen Alda.env, args, **spawn_options, &block
222
+ end
223
+
224
+ ##
225
+ # :call-seq:
226
+ # processes() -> Array
227
+ #
228
+ # Returns a Array of details about running \Alda processes.
229
+ # Only available for \Alda 2.
230
+ # Each element in the Array is a Hash,
231
+ # and each Hash has the following keys:
232
+ # - +:id+: the player-id of the process, a three-letter String.
233
+ # - +:port+: the port number of the process, an Integer.
234
+ # - +:state+: the state of the process, a Symbol (may be +nil+, +:ready+, +:active+, or +:starting+).
235
+ # - +:expiry+: a human-readable description of expiry time of the process, a String (may be +nil+).
236
+ # - +:type+: the type of the process, a Symbol (may be +:player+ or +:repl_server+).
237
+ def processes
238
+ raise GenerationError.new [:v2] if v1?
239
+ Alda.ps.lines(chomp: true)[1..].map do |line|
240
+ id, port, state, expiry, type = line.split ?\t
241
+ port = port.to_i
242
+ state = state == ?- ? nil : state.to_sym
243
+ expiry = nil if expiry == ?-
244
+ type = Alda::Utils.slug_to_snake type
245
+ { id: id, port: port, state: state, expiry: expiry, type: type }
246
+ end
247
+ end
118
248
 
119
249
  ##
120
250
  # :call-seq:
121
251
  # up?() -> true or false
122
252
  #
123
253
  # Whether the alda server is up.
254
+ # Checks whether there are any play processes in ::processes in \Alda 2.
124
255
  def up?
125
- status.include? 'up'
256
+ Alda.v1? ? Alda.status.include?('up') : Alda.processes.any? { _1[:type] == :player }
126
257
  end
127
258
 
128
259
  ##
@@ -130,10 +261,22 @@ module Alda
130
261
  # down? -> true or false
131
262
  #
132
263
  # Whether the alda server is down.
264
+ # Checks whether there are no play processes in ::processes in \Alda 2.
133
265
  def down?
134
- status.include? 'down'
266
+ Alda.v1? ? Alda.status.include?('down') : Alda.processes.none? { _1[:type] == :player }
267
+ end
268
+
269
+ ##
270
+ # :call-seq:
271
+ # deduce_generation -> one of Alda::GENERATIONS
272
+ #
273
+ # Deduce the generation of \Alda being used by running <tt>alda version</tt> in command line,
274
+ # and then set ::generation accordingly.
275
+ def deduce_generation
276
+ /(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)/ =~ Alda.version
277
+ @generation = major == '1' ? :v1 : :v2
135
278
  end
136
279
 
137
- module_function :up?, :down?, *COMMANDS
280
+ module_function :env, :pipe, :processes, :up?, :down?, :deduce_generation
138
281
 
139
282
  end
data/lib/alda-rb/error.rb CHANGED
@@ -9,33 +9,116 @@ 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
+ # The status returned by the nREPL server.
63
+ # It is an Array of Symbol.
64
+ # Symbols must appear are +:done+, +:error+, and there may be +:unknown_op+.
65
+ attr_reader :status
66
+
67
+ ##
68
+ # :call-seq:
69
+ # new(host, port, problems, status) -> Alda::NREPLServerError
70
+ #
71
+ # Creates a Alda::NREPLServerError object.
72
+ # Raises Alda::GenerationError if the current generation is not \Alda 2.
73
+ def initialize host, port, problems, status
74
+ Alda::GenerationError.assert_generation [:v2]
75
+ @status = status.map { Alda::Utils.slug_to_snake _1 }
76
+ if @status.include? :unknown_op
77
+ super 'unknown operation'
78
+ else
79
+ super problems.join ?\n
80
+ end
81
+ @host = host
82
+ @port = port
83
+ @problems = problems
84
+ end
85
+ end
86
+
87
+ ##
88
+ # This error is raised when one tries to run commands that are not available for the generation
89
+ # of \Alda specified by Alda::generation.
90
+ #
91
+ # Alda.v1!
92
+ # Alda.import # (GenerationError)
93
+ class Alda::GenerationError < StandardError
94
+
95
+ ##
96
+ # The actual generation that was set by Alda::generation when the error occurs.
97
+ attr_reader :generation
98
+
99
+ ##
100
+ # The generations that could have been set to avoid the error.
101
+ # An Array.
102
+ attr_reader :fine_generations
103
+
104
+ ##
105
+ # :call-seq:
106
+ # new(fine_generations) -> Alda::GenerationError
107
+ #
108
+ # Creates a Alda::GenerationError object.
109
+ def initialize fine_generations
110
+ super "bad Alda generation for this action; good ones are #{fine_generations}"
111
+ @generation = Alda.generation
112
+ @fine_generations = fine_generations
113
+ end
114
+
115
+ ##
116
+ # Raises an Alda::GenerationError if the current generation is not in +fine_generations+.
117
+ def self.assert_generation fine_generations
118
+ raise new fine_generations unless fine_generations.include? Alda.generation
119
+ end
120
+ end
121
+
39
122
  ##
40
123
  # This error is raised when one tries to
41
124
  # append events in an Alda::EventList in a wrong order.
@@ -49,8 +132,7 @@ class Alda::OrderError < StandardError
49
132
 
50
133
  ##
51
134
  # The expected element gotten if it is of the correct order.
52
- #
53
- # See #got
135
+ # See #got.
54
136
  #
55
137
  # Alda::Score.new do
56
138
  # motif = f4 f e e d d c2
@@ -72,6 +154,8 @@ class Alda::OrderError < StandardError
72
154
  ##
73
155
  # :call-seq:
74
156
  # new(expected, got) -> Alda::OrderError
157
+ #
158
+ # Creates a Alda::OrderError object.
75
159
  def initialize expected, got
76
160
  super 'events are out of order'
77
161
  @expected = expected