alda-rb 0.2.0 → 0.3.0

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,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