alda-rb 0.2.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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