xi-lang 0.1.4 → 0.1.5
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 +4 -4
- data/README.md +24 -11
- data/bin/xi +0 -1
- data/lib/xi/bjorklund.rb +60 -0
- data/lib/xi/clock.rb +9 -1
- data/lib/xi/core_ext/array.rb +13 -0
- data/lib/xi/core_ext/enumerable.rb +6 -14
- data/lib/xi/core_ext/enumerator.rb +14 -0
- data/lib/xi/core_ext/fixnum.rb +3 -3
- data/lib/xi/core_ext/numeric.rb +4 -4
- data/lib/xi/core_ext/scalar.rb +16 -0
- data/lib/xi/core_ext/string.rb +46 -44
- data/lib/xi/core_ext.rb +3 -1
- data/lib/xi/pattern/generators.rb +157 -162
- data/lib/xi/pattern/transforms.rb +96 -79
- data/lib/xi/pattern.rb +424 -95
- data/lib/xi/step_sequencer.rb +40 -0
- data/lib/xi/stream.rb +56 -64
- data/lib/xi/tidal_clock.rb +2 -1
- data/lib/xi/version.rb +1 -1
- data/lib/xi.rb +12 -3
- data/xi.gemspec +0 -1
- metadata +7 -18
- data/lib/xi/core_ext/simple.rb +0 -15
- data/lib/xi/event.rb +0 -82
data/lib/xi/stream.rb
CHANGED
@@ -3,7 +3,7 @@ require 'set'
|
|
3
3
|
|
4
4
|
module Xi
|
5
5
|
class Stream
|
6
|
-
attr_reader :clock, :opts, :source, :state, :
|
6
|
+
attr_reader :clock, :opts, :source, :state, :delta, :gate
|
7
7
|
|
8
8
|
DEFAULT_PARAMS = {
|
9
9
|
degree: 0,
|
@@ -25,16 +25,18 @@ module Xi
|
|
25
25
|
@state = {}
|
26
26
|
@changed_params = [].to_set
|
27
27
|
@playing_sound_objects = {}
|
28
|
-
@
|
28
|
+
@prev_ts = {}
|
29
|
+
@prev_delta = {}
|
29
30
|
|
30
31
|
self.clock = clock
|
31
32
|
end
|
32
33
|
|
33
|
-
def set(
|
34
|
+
def set(delta: nil, gate: nil, **source)
|
34
35
|
@mutex.synchronize do
|
35
36
|
@source = source
|
36
37
|
@gate = gate if gate
|
37
|
-
@
|
38
|
+
@delta = delta if delta
|
39
|
+
@reset = true unless @playing
|
38
40
|
update_internal_structures
|
39
41
|
end
|
40
42
|
play
|
@@ -42,9 +44,9 @@ module Xi
|
|
42
44
|
end
|
43
45
|
alias_method :call, :set
|
44
46
|
|
45
|
-
def
|
47
|
+
def delta=(new_value)
|
46
48
|
@mutex.synchronize do
|
47
|
-
@
|
49
|
+
@delta = new_value
|
48
50
|
update_internal_structures
|
49
51
|
end
|
50
52
|
end
|
@@ -83,6 +85,8 @@ module Xi
|
|
83
85
|
@mutex.synchronize do
|
84
86
|
@playing = false
|
85
87
|
@state.clear
|
88
|
+
@prev_ts.clear
|
89
|
+
@prev_delta.clear
|
86
90
|
@clock.unsubscribe(self)
|
87
91
|
end
|
88
92
|
self
|
@@ -103,7 +107,8 @@ module Xi
|
|
103
107
|
@mutex.synchronize do
|
104
108
|
@changed_params.clear
|
105
109
|
|
106
|
-
|
110
|
+
update_all_state if @reset
|
111
|
+
|
107
112
|
gate_off = gate_off_old_sound_objects(now)
|
108
113
|
gate_on = play_enums(now, cps)
|
109
114
|
|
@@ -131,36 +136,15 @@ module Xi
|
|
131
136
|
@state.select { |k, _| @changed_params.include?(k) }
|
132
137
|
end
|
133
138
|
|
134
|
-
def forward_enums(now, cps)
|
135
|
-
@enums.each do |p, (enum, total_dur)|
|
136
|
-
next if total_dur == 0
|
137
|
-
|
138
|
-
cur_pos = (now * cps) % total_dur
|
139
|
-
start_ts = now - (cur_pos / cps)
|
140
|
-
|
141
|
-
loop do
|
142
|
-
next_ev = enum.peek
|
143
|
-
distance = (cur_pos - next_ev.start) % total_dur
|
144
|
-
|
145
|
-
@prev_end[p] = @clock.init_ts + start_ts + (next_ev.end / cps)
|
146
|
-
enum.next
|
147
|
-
|
148
|
-
break if distance <= next_ev.duration
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
@must_forward = false
|
153
|
-
end
|
154
|
-
|
155
139
|
def gate_off_old_sound_objects(now)
|
156
140
|
gate_off = []
|
157
141
|
|
158
142
|
# Check if there are any currently playing sound objects that
|
159
143
|
# must be gated off
|
160
|
-
@playing_sound_objects.dup.each do |
|
144
|
+
@playing_sound_objects.dup.each do |start_pos, h|
|
161
145
|
if now + @clock.init_ts >= h[:at] - latency_sec
|
162
146
|
gate_off << h
|
163
|
-
@playing_sound_objects.delete(
|
147
|
+
@playing_sound_objects.delete(start_pos)
|
164
148
|
end
|
165
149
|
end
|
166
150
|
|
@@ -170,43 +154,50 @@ module Xi
|
|
170
154
|
def play_enums(now, cps)
|
171
155
|
gate_on = []
|
172
156
|
|
173
|
-
@enums.each do |p,
|
174
|
-
next
|
157
|
+
@enums.each do |p, enum|
|
158
|
+
next unless enum.next?
|
159
|
+
|
160
|
+
n_value, n_start, n_dur = enum.peek
|
175
161
|
|
176
|
-
|
177
|
-
|
162
|
+
@prev_ts[p] ||= n_start / cps
|
163
|
+
@prev_delta[p] ||= n_dur
|
178
164
|
|
179
|
-
|
165
|
+
next_start = @prev_ts[p] + (@prev_delta[p] / cps)
|
180
166
|
|
181
|
-
# Do we need to play next event
|
182
|
-
if
|
183
|
-
|
167
|
+
# Do we need to play next event? If not, skip this parameter value
|
168
|
+
if now >= next_start - latency_sec
|
169
|
+
# If it is too late to play this event, skip it
|
170
|
+
if now < next_start
|
171
|
+
starts_at = @clock.init_ts + next_start
|
184
172
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
173
|
+
# Update state based on pattern value
|
174
|
+
# TODO: Pass as parameter exact time: starts_at
|
175
|
+
update_state(p, n_value)
|
176
|
+
transform_state
|
189
177
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
178
|
+
# If a gate parameter changed, create a new sound object
|
179
|
+
if p == @gate
|
180
|
+
# If these sounds objects are new,
|
181
|
+
# consider them as new "gate on" events.
|
182
|
+
unless @playing_sound_objects.key?(n_start)
|
183
|
+
new_so_ids = Array(n_value)
|
184
|
+
.size.times.map { new_sound_object_id }
|
194
185
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
}
|
186
|
+
gate_on << {so_ids: new_so_ids, at: starts_at}
|
187
|
+
@playing_sound_objects[n_start] = {so_ids: new_so_ids}
|
188
|
+
end
|
199
189
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
}
|
190
|
+
# Set (or update) ends_at timestamp
|
191
|
+
ends_at = @clock.init_ts + next_start + (n_dur / cps)
|
192
|
+
@playing_sound_objects[n_start][:at] = ends_at
|
193
|
+
end
|
205
194
|
end
|
206
195
|
|
196
|
+
@prev_ts[p] = next_start
|
197
|
+
@prev_delta[p] = n_dur
|
198
|
+
|
207
199
|
# Because we already processed event, advance enumerator
|
208
|
-
|
209
|
-
@prev_end[p] = @clock.init_ts + start_ts + (next_ev.end / cps)
|
200
|
+
enum.next
|
210
201
|
end
|
211
202
|
end
|
212
203
|
|
@@ -250,11 +241,8 @@ module Xi
|
|
250
241
|
end
|
251
242
|
|
252
243
|
def update_internal_structures
|
253
|
-
|
254
|
-
@enums = @source.map { |k, v|
|
255
|
-
pat = v.p(@event_duration)
|
256
|
-
[k, [enum_for(:loop_events, pat), pat.total_duration]]
|
257
|
-
}.to_h
|
244
|
+
cycle = @clock.current_cycle
|
245
|
+
@enums = @source.map { |k, v| [k, v.p(@delta).each_event(cycle)] }.to_h
|
258
246
|
end
|
259
247
|
|
260
248
|
def do_gate_on_change(ss)
|
@@ -285,8 +273,12 @@ module Xi
|
|
285
273
|
!@changed_params.empty?
|
286
274
|
end
|
287
275
|
|
288
|
-
def
|
289
|
-
|
276
|
+
def update_all_state
|
277
|
+
@enums.each do |p, enum|
|
278
|
+
n_value, _ = enum.peek
|
279
|
+
update_state(p, n_value)
|
280
|
+
end
|
281
|
+
@reset = false
|
290
282
|
end
|
291
283
|
|
292
284
|
def latency_sec
|
data/lib/xi/tidal_clock.rb
CHANGED
data/lib/xi/version.rb
CHANGED
data/lib/xi.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
require "xi/version"
|
2
2
|
require 'xi/core_ext'
|
3
3
|
require 'xi/pattern'
|
4
|
-
require 'xi/event'
|
5
4
|
require 'xi/stream'
|
6
5
|
require 'xi/clock'
|
6
|
+
require 'xi/bjorklund'
|
7
|
+
require 'xi/step_sequencer'
|
7
8
|
|
8
9
|
def inf
|
9
10
|
Float::INFINITY
|
@@ -37,11 +38,19 @@ module Xi
|
|
37
38
|
end
|
38
39
|
|
39
40
|
def peek(pattern, *args)
|
41
|
+
pattern.peek_values(*args)
|
42
|
+
end
|
43
|
+
|
44
|
+
def peek_events(pattern, limit=10, *args)
|
40
45
|
pattern.peek(*args)
|
41
46
|
end
|
42
47
|
|
43
|
-
def
|
44
|
-
|
48
|
+
def e(n, m, value=nil)
|
49
|
+
Bjorklund.new([n, m].min, [n, m].max, value)
|
50
|
+
end
|
51
|
+
|
52
|
+
def s(str, *values)
|
53
|
+
StepSequencer.new(str, *values)
|
45
54
|
end
|
46
55
|
|
47
56
|
def method_missing(method, backend=nil, **opts)
|
data/xi.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xi-lang
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Damián Silvani
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-03-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,20 +108,6 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: websocket
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - ">="
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '0'
|
118
|
-
type: :runtime
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - ">="
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
125
111
|
description: |-
|
126
112
|
A musical pattern language inspired in Tidal and SuperCollider
|
127
113
|
for building higher-level musical constructs easily.
|
@@ -142,22 +128,25 @@ files:
|
|
142
128
|
- Rakefile
|
143
129
|
- bin/xi
|
144
130
|
- lib/xi.rb
|
131
|
+
- lib/xi/bjorklund.rb
|
145
132
|
- lib/xi/clock.rb
|
146
133
|
- lib/xi/core_ext.rb
|
134
|
+
- lib/xi/core_ext/array.rb
|
147
135
|
- lib/xi/core_ext/enumerable.rb
|
136
|
+
- lib/xi/core_ext/enumerator.rb
|
148
137
|
- lib/xi/core_ext/fixnum.rb
|
149
138
|
- lib/xi/core_ext/numeric.rb
|
150
139
|
- lib/xi/core_ext/object.rb
|
151
|
-
- lib/xi/core_ext/
|
140
|
+
- lib/xi/core_ext/scalar.rb
|
152
141
|
- lib/xi/core_ext/string.rb
|
153
142
|
- lib/xi/error_log.rb
|
154
|
-
- lib/xi/event.rb
|
155
143
|
- lib/xi/logger.rb
|
156
144
|
- lib/xi/pattern.rb
|
157
145
|
- lib/xi/pattern/generators.rb
|
158
146
|
- lib/xi/pattern/transforms.rb
|
159
147
|
- lib/xi/repl.rb
|
160
148
|
- lib/xi/scale.rb
|
149
|
+
- lib/xi/step_sequencer.rb
|
161
150
|
- lib/xi/stream.rb
|
162
151
|
- lib/xi/tidal_clock.rb
|
163
152
|
- lib/xi/version.rb
|
data/lib/xi/core_ext/simple.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
require 'xi/pattern'
|
2
|
-
|
3
|
-
module Xi
|
4
|
-
module Pattern::Simple
|
5
|
-
def p(dur=nil, **metadata)
|
6
|
-
[self].p(dur, metadata)
|
7
|
-
end
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
class Fixnum; include Xi::Pattern::Simple; end
|
12
|
-
class Float; include Xi::Pattern::Simple; end
|
13
|
-
class String; include Xi::Pattern::Simple; end
|
14
|
-
class Symbol; include Xi::Pattern::Simple; end
|
15
|
-
class Rational; include Xi::Pattern::Simple; end
|
data/lib/xi/event.rb
DELETED
@@ -1,82 +0,0 @@
|
|
1
|
-
module Xi
|
2
|
-
# An Event is an object that represents a scalar +value+ of some type, and
|
3
|
-
# has a +start+ position or onset, and +duration+ in time.
|
4
|
-
#
|
5
|
-
# Both +start+ and +duration+ are in terms of cycles.
|
6
|
-
#
|
7
|
-
# Usually you don't create events, they are created by a Pattern when
|
8
|
-
# assigned to a Stream, or by some transformation methods on Pattern, so you
|
9
|
-
# don't need to worry about them. Most of the time, you will manually build
|
10
|
-
# Patterns from values and let the Pattern handle when values are applied in
|
11
|
-
# time, based on its default event duration, for example.
|
12
|
-
#
|
13
|
-
# You can instantiate an Event using {.[]}, like this
|
14
|
-
#
|
15
|
-
# Event.new(42, 0, 2) #=> E[42,0,2]
|
16
|
-
# Event[:a, 1, 1/2] #=> E[:a,1,1/2]
|
17
|
-
#
|
18
|
-
# E is an alias of Event, so you can build them using E instead. Note that
|
19
|
-
# the string representation of the object can be used to build the same event
|
20
|
-
# again (almost the same ignoring whitespace between constructor arguments).
|
21
|
-
#
|
22
|
-
# E[:a, 1, 1/4] #=> E[:a,1,1/4]
|
23
|
-
#
|
24
|
-
class Event
|
25
|
-
attr_reader :value, :start, :duration
|
26
|
-
|
27
|
-
# Creates a new Event with +value+, with both +start+ position and
|
28
|
-
# +duration+ in cycles
|
29
|
-
#
|
30
|
-
# @param value [Object]
|
31
|
-
# @param start [Numeric] default: 0
|
32
|
-
# @param duration [Numeric] default: 1
|
33
|
-
# @return [Event]
|
34
|
-
#
|
35
|
-
def initialize(value, start=0, duration=1)
|
36
|
-
@value = value
|
37
|
-
@start = start
|
38
|
-
@duration = duration
|
39
|
-
end
|
40
|
-
|
41
|
-
# @see #initialize
|
42
|
-
def self.[](*args)
|
43
|
-
new(*args)
|
44
|
-
end
|
45
|
-
|
46
|
-
def ==(o)
|
47
|
-
self.class == o.class &&
|
48
|
-
value == o.value &&
|
49
|
-
start == o.start &&
|
50
|
-
duration == o.duration
|
51
|
-
end
|
52
|
-
|
53
|
-
# Return the end position in cycles
|
54
|
-
#
|
55
|
-
# @return [Numeric]
|
56
|
-
#
|
57
|
-
def end
|
58
|
-
@start + @duration
|
59
|
-
end
|
60
|
-
|
61
|
-
# Creates a Pattern that only yields this event
|
62
|
-
#
|
63
|
-
# @param dur [Numeric, #each] event duration
|
64
|
-
# @param metadata [Hash]
|
65
|
-
# @return [Pattern]
|
66
|
-
#
|
67
|
-
def p(dur=nil, **metadata)
|
68
|
-
[self].p(dur, metadata)
|
69
|
-
end
|
70
|
-
|
71
|
-
def inspect
|
72
|
-
"E[#{@value.inspect},#{@start}" \
|
73
|
-
"#{",#{@duration}" if @duration != 1}]"
|
74
|
-
end
|
75
|
-
|
76
|
-
def to_s
|
77
|
-
inspect
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
E = Xi::Event
|