xi-lang 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db7114dfd1ad753386c1b126704628fd79bb3ed6bd22d46cbc3348591146a90f
4
- data.tar.gz: f59674febf3c4021a36ab1b5de41fca7e2336b622c184693956b0fde32041ea7
3
+ metadata.gz: f2354b1293961d34731653d4461341638cc38e733a0c762b29ad8a7cc7574a7b
4
+ data.tar.gz: 3a3f2e8f0ce893304cde881800c03c33fe14e9ef01e76ba9810c1a2c3f9d8348
5
5
  SHA512:
6
- metadata.gz: 798f94e61d6a6d80e4d766e1670f772109ca8e3ce5745d6ace5c8d7fc7763d0362cc18fe3884cf4162ecbb9be2646685eda66218cec933659d81d879a34e1b3e
7
- data.tar.gz: 34130095d8b4e99e53f13a309a7817bc1f01107b71e2c71b8099a3b872a38abe52d53c385999d1c11569df02773944713795f08b534c8307b7bce9085a2b0586
6
+ metadata.gz: b13a449ea692bfafc775fe0af0d05997655d15493e33bc2849d77e7237c58a4bdd5af89f9962c97eb265462fa353b5b468678cdf1ffe416e90b7c716b663151b
7
+ data.tar.gz: fdaaf2a270cf50cbe45de430b23269cd5e30d43ca145bd4d691a19801e3e355df1f3ba6c99f9b33d9eab3ec6e80ac257b3cb95e8c7fb066036ba0025d3ab1d02
data/README.md CHANGED
@@ -1,13 +1,12 @@
1
1
  # Xi [![Build Status](https://travis-ci.org/xi-livecode/xi.svg?branch=master)](https://travis-ci.org/xi-livecode/xi)
2
2
 
3
- Xi (pronounced /ˈzaɪ/) is a musical pattern language inspired in Tidal and
4
- SuperCollider for building higher-level musical constructs easily. It is
5
- implemented on the Ruby programming language.
3
+ Xi is a musical pattern language inspired in Tidal and SuperCollider for
4
+ building higher-level musical constructs easily. It is implemented on the Ruby
5
+ programming language and uses SuperCollider as a backend.
6
6
 
7
- Xi is only a patterns library, but can talk to different backends:
8
-
9
- - [SuperCollider](https://github.com/supercollider/supercollider)
10
- - MIDI devices
7
+ Xi is only a patterns library, but can talk to
8
+ [SuperCollider](https://github.com/supercollider/supercollider) synths or MIDI
9
+ devices.
11
10
 
12
11
  *NOTE*: Be advised that this project is in very early alpha stages. There are a
13
12
  multiple known bugs, missing features, documentation and tests.
@@ -40,17 +39,20 @@ clap.set n: s("..x. xyz. .x.. .xyx", 60, 61, 60).p.decelerate(2),
40
39
 
41
40
  ### Quickstart
42
41
 
43
- You will need Ruby 2.1+ installed on your system. Check by running `ruby
44
- -v`. To install Xi you must install the core libraries and REPL, and then one
45
- or more backends.
42
+ You will need Ruby 2.4+ installed on your system. Check by running `ruby -v`.
43
+ To install Xi you must install the core libraries and REPL, and then one or
44
+ more backends.
45
+
46
+ $ gem install xi-lang
46
47
 
47
- If you want to use Xi with SuperCollider:
48
+ Available backends:
48
49
 
49
- $ gem install xi-lang xi-supercollider
50
+ * xi-midi: MIDI devices support
51
+ * xi-superdirt: [SuperDirt](https://github.com/musikinformatik/SuperDirt) backend
50
52
 
51
- Or with MIDI:
53
+ For example:
52
54
 
53
- $ gem install xi-lang xi-midi
55
+ $ gem install xi-lang xi-superdirt
54
56
 
55
57
  Then run Xi REPL with:
56
58
 
@@ -100,4 +102,4 @@ the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
100
102
 
101
103
  ## License
102
104
 
103
- See [LICENSE](LICENSE)
105
+ See [LICENSE](LICENSE.txt)
@@ -0,0 +1,11 @@
1
+ module Xi::CoreExt
2
+ module Integer
3
+ def /(o)
4
+ super(o.to_r)
5
+ end
6
+ end
7
+ end
8
+
9
+ class Integer
10
+ prepend Xi::CoreExt::Integer
11
+ end
@@ -21,14 +21,8 @@ module Xi::CoreExt
21
21
  end
22
22
  end
23
23
 
24
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4')
25
- class Fixnum
26
- include Xi::CoreExt::Numeric
27
- end
28
- else
29
- class Integer
30
- include Xi::CoreExt::Numeric
31
- end
24
+ class Integer
25
+ include Xi::CoreExt::Numeric
32
26
  end
33
27
 
34
28
  class Float
@@ -8,12 +8,7 @@ module Xi::CoreExt
8
8
  end
9
9
  end
10
10
 
11
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4')
12
- class Fixnum; include Xi::CoreExt::Scalar; end
13
- else
14
- class Integer; include Xi::CoreExt::Scalar; end
15
- end
16
-
11
+ class Integer; include Xi::CoreExt::Scalar; end
17
12
  class Float; include Xi::CoreExt::Scalar; end
18
13
  class String; include Xi::CoreExt::Scalar; end
19
14
  class Symbol; include Xi::CoreExt::Scalar; end
data/lib/xi/core_ext.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'xi/core_ext/array'
2
2
  require 'xi/core_ext/enumerable'
3
3
  require 'xi/core_ext/enumerator'
4
- require 'xi/core_ext/fixnum'
4
+ require 'xi/core_ext/integer'
5
5
  require 'xi/core_ext/numeric'
6
6
  require 'xi/core_ext/object'
7
7
  require 'xi/core_ext/scalar'
@@ -0,0 +1,5 @@
1
+ module Xi
2
+ module OSC
3
+ VERSION = "0.1.5"
4
+ end
5
+ end
data/lib/xi/osc.rb ADDED
@@ -0,0 +1,36 @@
1
+ require "xi/osc/version"
2
+ require 'osc-ruby'
3
+
4
+ module Xi
5
+ module OSC
6
+ def initialize(name, clock, server: 'localhost', port:, **opts)
7
+ super
8
+ @osc = ::OSC::Client.new(server, port)
9
+ end
10
+
11
+ private
12
+
13
+ def send_msg(address, *args)
14
+ msg = message(address, *args)
15
+ debug(__method__, msg.address, *msg.to_a)
16
+ send_osc_msg(msg)
17
+ end
18
+
19
+ def send_bundle(address, *args, at: Time.now)
20
+ msg = message(address, *args)
21
+ bundle = ::OSC::Bundle.new(at, msg)
22
+ debug(__method__, msg.address, at.to_i, at.usec, *msg.to_a)
23
+ send_osc_msg(bundle)
24
+ end
25
+
26
+ def message(address, *args)
27
+ ::OSC::Message.new(address, *args)
28
+ end
29
+
30
+ def send_osc_msg(msg)
31
+ @osc.send(msg)
32
+ rescue StandardError => err
33
+ error(err)
34
+ end
35
+ end
36
+ end
@@ -61,7 +61,7 @@ module Xi
61
61
  # peek P.rand([1, 2, 3, 4], 6) #=> [1, 3, 2, 2, 4, 3]
62
62
  #
63
63
  # @param list [#each] list of values
64
- # @param repeats [Fixnum, Symbol] number or inf (default: 1)
64
+ # @param repeats [Integer, Symbol] number or inf (default: 1)
65
65
  # @return [Pattern]
66
66
  #
67
67
  def rand(list, repeats=1)
@@ -83,7 +83,7 @@ module Xi
83
83
  # peek P.xrand([1, 2, 3], 8) #=> [1, 3, 2, 3, 1, 2, 3, 2]
84
84
  #
85
85
  # @param list [#each] list of values
86
- # @param repeats [Fixnum, Symbol] number or inf (default: 1)
86
+ # @param repeats [Integer, Symbol] number or inf (default: 1)
87
87
  # @return [Pattern]
88
88
  #
89
89
  def xrand(list, repeats=1)
@@ -109,7 +109,7 @@ module Xi
109
109
  # peek P.shuf([1, 2, 3], 3) #=> [2, 3, 1, 2, 3, 1, 2, 3, 1]
110
110
  #
111
111
  # @param list [#each] list of values
112
- # @param repeats [Fixnum, Symbol] number or inf (default: 1)
112
+ # @param repeats [Integer, Symbol] number or inf (default: 1)
113
113
  # @return [Pattern]
114
114
  #
115
115
  def shuf(list, repeats=1)
@@ -137,8 +137,8 @@ module Xi
137
137
  # P.sin(22).duration #=> (1/1)
138
138
  # P.sin(19, 2).duration #=> (2/1)
139
139
  #
140
- # @param quant [Fixnum]
141
- # @param delta [Fixnum] (default: 1)
140
+ # @param quant [Integer]
141
+ # @param delta [Integer] (default: 1)
142
142
  # @return [Pattern]
143
143
  #
144
144
  def sin(quant, delta=1)
@@ -160,8 +160,8 @@ module Xi
160
160
  # peek P.sin1(8).map { |i| i.round(2) }
161
161
  # #=> [0.5, 0.85, 1.0, 0.85, 0.5, 0.15, 0.0, 0.15]
162
162
  #
163
- # @param quant [Fixnum]
164
- # @param delta [Fixnum] (default: 1)
163
+ # @param quant [Integer]
164
+ # @param delta [Integer] (default: 1)
165
165
  # @return [Pattern]
166
166
  #
167
167
  def sin1(quant, delta=1)
@@ -132,17 +132,17 @@ module Xi
132
132
  # peek [1, 2, 3].p.seq(1, 1) #=> [2, 3, 1]
133
133
  # peek [1, 2, 3].p.seq(2, 2) #=> [3, 2, 1, 3, 2, 1]
134
134
  #
135
- # @param repeats [Fixnum] number (defaut: 1)
136
- # @param offset [Fixnum] (default: 0)
135
+ # @param repeats [Integer] number (defaut: 1)
136
+ # @param offset [Integer] (default: 0)
137
137
  # @return [Pattern]
138
138
  #
139
139
  def seq(repeats=1, offset=0)
140
- unless repeats.is_a?(Fixnum) && repeats >= 0
141
- fail ArgumentError, "repeats must be a non-negative Fixnum"
140
+ unless repeats.is_a?(Integer) && repeats >= 0
141
+ fail ArgumentError, "repeats must be a non-negative Integer"
142
142
  end
143
143
 
144
- unless offset.is_a?(Fixnum) && offset >= 0
145
- fail ArgumentError, "offset must be a non-negative Fixnum"
144
+ unless offset.is_a?(Integer) && offset >= 0
145
+ fail ArgumentError, "offset must be a non-negative Integer"
146
146
  end
147
147
 
148
148
  Pattern.new(self, size: size * repeats) do |y|
@@ -362,7 +362,7 @@ module Xi
362
362
  # peek [1, 2, 3].p.rand #=> [2]
363
363
  # peek [1, 2, 3, 4].p.rand(6) #=> [1, 3, 2, 2, 4, 3]
364
364
  #
365
- # @param repeats [Fixnum, Symbol] number or inf (default: 1)
365
+ # @param repeats [Integer, Symbol] number or inf (default: 1)
366
366
  # @return [Pattern]
367
367
  #
368
368
  def rand(repeats=1)
@@ -378,7 +378,7 @@ module Xi
378
378
  # peek [1, 2, 3, 4, 5].p.xrand #=> [4]
379
379
  # peek [1, 2, 3].p.xrand(8) #=> [1, 3, 2, 3, 1, 2, 3, 2]
380
380
  #
381
- # @param repeats [Fixnum, Symbol] number or inf (default: 1)
381
+ # @param repeats [Integer, Symbol] number or inf (default: 1)
382
382
  # @return [Pattern]
383
383
  #
384
384
  def xrand(repeats=1)
@@ -394,7 +394,7 @@ module Xi
394
394
  # peek [1, 2, 3, 4, 5].p.xrand #=> [4]
395
395
  # peek [1, 2, 3].p.xrand(8) #=> [1, 3, 2, 3, 1, 2, 3, 2]
396
396
  #
397
- # @param repeats [Fixnum, Symbol] number or inf (default: 1)
397
+ # @param repeats [Integer, Symbol] number or inf (default: 1)
398
398
  # @return [Pattern]
399
399
  #
400
400
  def shuf(repeats=1)
data/lib/xi/pattern.rb CHANGED
@@ -59,7 +59,7 @@ module Xi
59
59
  # # [3, 4, 1, 0]]
60
60
  #
61
61
  # @param source [Array]
62
- # @param size [Fixnum] number of events per iteration
62
+ # @param size [Integer] number of events per iteration
63
63
  # @param delta [Numeric, Array<Numeric>, Pattern<Numeric>] event delta
64
64
  # @param metadata [Hash]
65
65
  # @yield [yielder, delta] yielder and event delta
@@ -345,7 +345,7 @@ module Xi
345
345
 
346
346
  # Returns the first +n+ events from the pattern, starting from +cycle+
347
347
  #
348
- # @param n [Fixnum]
348
+ # @param n [Integer]
349
349
  # @param cycle [Numeric]
350
350
  # @return [Array] values
351
351
  #
@@ -380,7 +380,7 @@ module Xi
380
380
  #
381
381
  # @see #take
382
382
  #
383
- # @param n [Fixnum]
383
+ # @param n [Integer]
384
384
  # @param args same arguments as {#take}
385
385
  # @return [Object, Array]
386
386
  #
@@ -420,7 +420,7 @@ module Xi
420
420
  # pattern size is assumed to be 1, so iteration size depends on delta
421
421
  # values.
422
422
  #
423
- # @return [Fixnum]
423
+ # @return [Integer]
424
424
  #
425
425
  def iteration_size
426
426
  finite? ? delta_size.lcm(@size) : delta_size
@@ -0,0 +1,128 @@
1
+ require 'xi/stream'
2
+ require 'xi/osc'
3
+ require 'set'
4
+
5
+ module Xi::Supercollider
6
+ class Stream < Xi::Stream
7
+ include Xi::OSC
8
+
9
+ MAX_NODE_ID = 10000
10
+ DEFAULT_PARAMS = {
11
+ out: 0,
12
+ amp: 1.0,
13
+ pan: 0.0,
14
+ vel: 127,
15
+ }
16
+
17
+ def initialize(name, clock, server: 'localhost', port: 57110, base_node_id: 2000, **opts)
18
+ super
19
+
20
+ @base_node_id = base_node_id
21
+ @playing_synths = [].to_set
22
+ at_exit { free_playing_synths }
23
+ end
24
+
25
+ def set(params)
26
+ super(gate: params[:gate] || :freq, **params)
27
+ end
28
+
29
+ def stop
30
+ @mutex.synchronize do
31
+ @playing_synths.each do |so_id|
32
+ n_set(node_id(so_id), gate: 0)
33
+ end
34
+ end
35
+ super
36
+ end
37
+
38
+ def free_playing_synths
39
+ n_free(*@playing_synths.map { |so_id| node_id(so_id) })
40
+ end
41
+
42
+ def node_id(so_id)
43
+ (@base_node_id + so_id) % MAX_NODE_ID
44
+ end
45
+
46
+ private
47
+
48
+ def transform_state
49
+ super
50
+
51
+ @state = DEFAULT_PARAMS.merge(@state)
52
+
53
+ if changed_param?(:db) && !changed_param?(:amp)
54
+ @state[:amp] = @state[:db].db_to_amp
55
+ @changed_params << :amp
56
+ end
57
+
58
+ if changed_param?(:midinote) && !changed_param?(:freq)
59
+ @state[:freq] = Array(@state[:midinote]).map(&:midi_to_cps)
60
+ @changed_params << :freq
61
+ end
62
+ end
63
+
64
+ def do_gate_on_change(changes)
65
+ debug "Gate on change: #{changes}"
66
+
67
+ name = @state[:s] || :default
68
+ state_params = @state.reject { |k, _| %i(s).include?(k) }
69
+
70
+ freq = Array(state_params[:freq])
71
+
72
+ changes.each do |change|
73
+ at = Time.at(change.fetch(:at))
74
+
75
+ change.fetch(:so_ids).each.with_index do |so_id, i|
76
+ freq_i = freq.size > 0 ? freq[i % freq.size] : nil
77
+
78
+ s_new(name, node_id(so_id), **state_params, freq: freq_i, at: at)
79
+ @playing_synths << so_id
80
+ end
81
+ end
82
+ end
83
+
84
+ def do_gate_off_change(changes)
85
+ debug "Gate off change: #{changes}"
86
+
87
+ changes.each do |change|
88
+ at = Time.at(change.fetch(:at))
89
+
90
+ change.fetch(:so_ids).each do |so_id|
91
+ n_set(node_id(so_id), gate: 0, at: at)
92
+ @playing_synths.delete(so_id)
93
+ end
94
+ end
95
+ end
96
+
97
+ def do_state_change
98
+ debug "State change: #{changed_state}"
99
+ @playing_synths.each do |so_id|
100
+ n_set(node_id(so_id), **changed_state)
101
+ end
102
+ end
103
+
104
+ def n_set(id, at: Time.now, **args)
105
+ send_bundle('/n_set', id, *osc_args(args), at: at)
106
+ end
107
+
108
+ def s_new(name, id, add_action: 0, target_id: 1, at: Time.now, **args)
109
+ send_bundle('/s_new', name.to_s, id.to_i, add_action.to_i,
110
+ target_id.to_i, *osc_args(args), at: at)
111
+ end
112
+
113
+ def n_free(*ids, at: Time.now)
114
+ send_bundle('/n_free', *ids, at: at)
115
+ end
116
+
117
+ def osc_args(**args)
118
+ args.map { |k, v| [k.to_s, coerce_osc_value(v)] }.flatten(1)
119
+ end
120
+
121
+ def coerce_osc_value(value)
122
+ v = Array(value).first
123
+ v = v.to_f if v.is_a?(Rational)
124
+ v = v.to_i if !v.is_a?(Float) && !v.is_a?(String) && !v.is_a?(Symbol)
125
+ v
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,5 @@
1
+ module Xi
2
+ module Supercollider
3
+ VERSION = "0.1.6"
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ require "xi/supercollider/version"
2
+ require "xi/supercollider/stream"
3
+
4
+ module Xi
5
+ module Supercollider
6
+ # Your code goes here...
7
+ end
8
+ end
data/lib/xi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Xi
2
- VERSION = "0.1.6"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,43 @@
1
+ (
2
+ SynthDef(\kick, { |out=0, amp=1, freq=70, attack=0.001, xstart=1, xend=0.25, xdur=0.05, release=0.15, pan=0.5|
3
+ var sig = SinOsc.ar(freq * XLine.ar(xstart, xend, xdur));
4
+ var env = EnvGen.ar(Env.perc(attack, release, 1, -4), doneAction: 2);
5
+ sig = Pan2.ar(sig, pan) * env;
6
+ sig = (sig * 5).tanh;
7
+ OffsetOut.ar(out, Pan2.ar(sig * env, pan, amp * 0.25));
8
+ }).add;
9
+
10
+ SynthDef(\snare, { |out=0, amp=1, freq=1000, freq2=180, attack=0.01, release=0.2, pan=0|
11
+ var snd1 = WhiteNoise.ar(amp);
12
+ var snd2 = SinOsc.ar(freq2, 0, amp);
13
+ var env = Env.perc(attack, release).kr(doneAction: 2);
14
+ var sum = RHPF.ar(snd1 * env, 2500) + LPF.ar(snd2 * env, 1500);
15
+ OffsetOut.ar(out, Pan2.ar(sum, pan, amp * 0.1))
16
+ }).add;
17
+
18
+ SynthDef(\bassy, { |out=0, amp=1, freq=440, hctf=1000, lctf=5000, rq=0.5, attack=0.001, release=1, mul=1, pan=0.5|
19
+ var sig = Saw.ar(freq);
20
+ var env = EnvGen.ar(Env.perc(attack, release), doneAction: 2);
21
+ sig = mul * BHiPass.ar(RLPF.ar(sig, lctf * env, rq), hctf, rq);
22
+ OffsetOut.ar(out, Pan2.ar(sig * env, pan, amp * 0.1))
23
+ }).add;
24
+
25
+ SynthDef(\sin, { |out, amp=1, attack=0.001, release=1, sustain=1, pan=0, accelerate=0, freq=440, detune=0.1|
26
+ var env = EnvGen.ar(Env.perc(attack, release, 1, -4), timeScale: sustain / 2, doneAction: 2);
27
+ var sound = SinOsc.ar([freq, freq+detune] * Line.kr(1,1+accelerate, sustain));
28
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan, amp));
29
+ }).add;
30
+
31
+ SynthDef(\fm, { |out, amp=1, attack=0.001, sustain=1, pan=0, accelerate=0, freq=440, carPartial=1, modPartial=1, index=3, mul=0.1, detune=0.1|
32
+ var env = EnvGen.ar(Env.perc(attack, 0.999, 1, -3), timeScale: sustain / 2, doneAction: 2);
33
+ var mod = SinOsc.ar(freq * modPartial * Line.kr(1,1+accelerate, sustain), 0, freq * index * LFNoise1.kr(5.reciprocal).abs);
34
+ var car = SinOsc.ar(([freq, freq+detune] * carPartial) + mod, 0, mul);
35
+ OffsetOut.ar(out, Pan2.ar(car * env, pan, amp));
36
+ }).add;
37
+
38
+ SynthDef(\saw, {|out, amp=1, attack=0.001, sustain=1, pan=0, accelerate=0, freq=440, detune=0.1|
39
+ var env = EnvGen.ar(Env.perc(attack, 0.999, 1, -4), timeScale: sustain / 2, doneAction: 2);
40
+ var sound = Saw.ar([freq, freq + detune] * Line.kr(1, 1 + accelerate, sustain));
41
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan, amp * 0.1));
42
+ }).add;
43
+ )
@@ -0,0 +1,322 @@
1
+ //
2
+ // The following synth definitions were taken from Superdirt almost as is.
3
+ // Original file at https://github.com/musikinformatik/SuperDirt/blob/bb1536329896e6e211c3bafee7befb06d06fc856/library/default-synths-extra.scd
4
+ //
5
+ // Source code included in this file is licensed under GPL v2.
6
+ // Please refer to LICENSE file at
7
+ // https://github.com/musikinformatik/SuperDirt/blob/master/LICENSE
8
+ //
9
+
10
+ // Physical modeling of a vibrating string, using a delay line (CombL) excited
11
+ // by an intial pulse (Impulse). To make it a bit richer, I've combined two
12
+ // slightly detuned delay lines.
13
+ //
14
+ // "accelerate" is used for a pitch glide
15
+ // "sustain" changes the envelope timescale
16
+ (
17
+ SynthDef(\smandolin, { |out, amp=1, sustain=1, pan=0, accelerate=0, freq=440, detune=0.2|
18
+ var env = EnvGen.ar(Env.linen(0.002, 0.996, 0.002, 1,-3), timeScale: sustain, doneAction: 2);
19
+ var sound = Decay.ar(Impulse.ar(0,0,0.1), 0.1 * (freq.cpsmidi) / 69) * WhiteNoise.ar;
20
+ var pitch = freq * Line.kr(1, 1 + accelerate, sustain);
21
+ sound = CombL.ar(sound, 0.05, pitch.reciprocal * (1 - (detune/100)), sustain)
22
+ + CombL.ar(sound, 0.05, pitch.reciprocal * (1 + (detune/100)), sustain);
23
+ OffsetOut.ar(out, Pan2.ar(sound * env * amp, pan))
24
+ }).add
25
+ );
26
+
27
+ // An example of additive synthesis, building up a gong-like noise from a sum
28
+ // of sine-wave harmonics. Notice how the envelope timescale and amplitude can
29
+ // be scaled as a function of the harmonic frequency.
30
+ // "voice" provides something like a tone knob
31
+ // "decay" adjusts how the harmonics decay as in the other SynthDefs
32
+ // "sustain" affects the overall envelope timescale
33
+ // "accelerate" for pitch glide
34
+ (
35
+ SynthDef(\sgong, { |out, amp=1, sustain=1, pan=0, accelerate=0, freq=440, voice=0, decay=1|
36
+ // lowest modes for clamped circular plate
37
+ var freqlist = [1.000, 2.081, 3.414, 3.893, 4.995, 5.954, 6.819, 8.280, 8.722, 8.882, 10.868, 11.180, 11.754, 13.710, 13.715, 15.057, 15.484, 16.469, 16.817, 18.628] ** 1.0;
38
+ var tscale = 100.0 / freq / (freqlist ** (2 - clip(decay, 0, 2)));
39
+ var ascale =freqlist ** clip(voice,0,4);
40
+ var sound = Mix.arFill(15, { arg i;
41
+ EnvGen.ar(Env.perc(0.01 * tscale[i], 0.5 * tscale[i], 0.2 * ascale[i]), timeScale: sustain * 5)
42
+ * SinOsc.ar(freq * freqlist[i] * Line.kr(1, 1 + accelerate, sustain))
43
+ });
44
+ OffsetOut.ar(out, Pan2.ar(sound * amp / 15, pan))
45
+ }).add
46
+ );
47
+
48
+ // Hooking into a nice synth piano already in Supercollider.
49
+ //
50
+ // Uses the "velocity" parameter to affect how hard the keys are pressed
51
+ // "sustain" controls envelope and decay time.
52
+ (
53
+ SynthDef(\spiano, { |out, amp=1, sustain=1, pan=0, velocity=1, detune=0.1, muffle=1, stereo=0.2, freq=440|
54
+ var env = EnvGen.ar(Env.linen(0.002, 0.996, 0.002, 1, -3), timeScale: sustain, doneAction: 2);
55
+ // the +0.01 to freq is because of edge case rounding internal to the MdaPiano synth
56
+ var sound = MdaPiano.ar(freq + 0.01, vel: velocity * 100, hard: 0.8 * velocity, decay: 0.1 * sustain,
57
+ tune: 0.5, random: 0.05, stretch: detune, muffle: 0.8 * muffle, stereo: stereo);
58
+ OffsetOut.ar(out, Pan2.ar(sound * env * amp * 0.35, pan))
59
+ }).add
60
+ );
61
+
62
+ // Waveguide mesh, hexagonal drum-like membrane
63
+ (
64
+ SynthDef(\shex, { |out, speed=1, sustain=1, pan=0, freq=440, accelerate=0|
65
+ var env = EnvGen.ar(Env.linen(0.02, 0.96, 0.02, 1,-3), timeScale: sustain, doneAction: 2);
66
+ var tension = 0.05 * freq / 400 * Line.kr(1, accelerate + 1, sustain);
67
+ var loss = 1.0 - (0.01 * speed / freq);
68
+ var sound = MembraneHexagon.ar(Decay.ar(Impulse.ar(0, 0, 1), 0.01), tension, loss);
69
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan))
70
+ }).add
71
+ );
72
+
73
+ // Kick Drum using Rumble-San's implementation as a starting point
74
+ // http://blog.rumblesan.com/post/53271713518/drum-sounds-in-supercollider-part-1
75
+ //
76
+ // "n" controls the kick frequency in a nonstandard way
77
+ // "sustain" affects overall envelope timescale
78
+ // "accelerate" sweeps the click filter freq
79
+ // "pitch1" affects the click frequency
80
+ // "decay" changes the click duration relative to the overall timescale
81
+ (
82
+ SynthDef(\skick, { |out, sustain=1, pan=0, accelerate=0, n=60, pitch1=1, decay=1, amp=1|
83
+ var env, sound, dur, clickdur;
84
+ env = EnvGen.ar(Env.linen(0.01, 0, 0.5, 1, -3), timeScale: sustain, doneAction: 2);
85
+ sound = SinOsc.ar((n - 25.5).midicps);
86
+ clickdur = 0.02 * sustain * decay;
87
+ sound = sound + (LPF.ar(WhiteNoise.ar(1), 1500 * pitch1 * Line.kr(1, 1 + accelerate, clickdur))
88
+ * Line.ar(1, 0, clickdur));
89
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan, amp))
90
+ }).add
91
+ );
92
+
93
+ // A vaguely 808-ish kick drum
94
+ //
95
+ // "n" controls the chirp frequency
96
+ // "sustain" the overall timescale
97
+ // "speed" the filter sweep speed
98
+ // "voice" the sinewave feedback
99
+ (
100
+ SynthDef(\s808, { |out, speed=1, sustain=1, pan=0, voice=0, n=60, amp=1|
101
+ var env, sound, freq;
102
+ n = ((n>0)*n) + ((n<1)*3);
103
+ freq = (n*10).midicps;
104
+ env = EnvGen.ar(Env.linen(0.01, 0, 1, 1, -3), timeScale:sustain, doneAction:2);
105
+ sound = LPF.ar(SinOscFB.ar(XLine.ar(freq.expexp(10, 2000, 1000, 8000), freq, 0.025/speed), voice), 9000);
106
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan, amp))
107
+ }).add
108
+ );
109
+
110
+ // Hi-hat using Rumble-San's implementation as a starting point
111
+ // http://blog.rumblesan.com/post/53271713518/drum-sounds-in-supercollider-part-1
112
+ //
113
+ // "n" using it in a weird way to provide some variation on the frequency
114
+ // "sustain" affects the overall envelope rate,
115
+ // "accelerate" sweeps the filter
116
+ (
117
+ SynthDef(\shat, {|out, sustain=1, pan=0, accelerate=0, n=60, amp=1, freq=2000|
118
+ var env, sound, accel, frq;
119
+ env = EnvGen.ar(Env.linen(0.01, 0, 0.3, 1, -3), timeScale: sustain, doneAction:2);
120
+ accel = Line.kr(1, 1+accelerate, 0.2*sustain);
121
+ frq = freq*accel*(n/5 + 1).wrap(0.5,2);
122
+ sound = HPF.ar(LPF.ar(WhiteNoise.ar(1), 3*frq), frq);
123
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan, amp))
124
+ }).add
125
+ );
126
+
127
+ // Snare drum using Rumble-San's implementation as a starting point
128
+ // http://blog.rumblesan.com/post/53271713909/drum-sounds-in-supercollider-part-2
129
+ //
130
+ // "n" for some variation on frequency
131
+ // "decay" for scaling noise duration relative to tonal part
132
+ // "sustain" for overall timescale
133
+ // "accelerate" for tonal glide
134
+ (
135
+ SynthDef(\ssnare, {|out, sustain=1, pan=0, accelerate=0, n=60, decay=1, amp=1|
136
+ var env, sound, accel;
137
+ env = EnvGen.ar(Env.linen(0.01, 0, 0.6, 1, -3), timeScale:sustain, doneAction:2);
138
+ accel = Line.kr(1, 1+accelerate, 0.2);
139
+ sound = LPF.ar(Pulse.ar(100*accel*(n/5+1).wrap(0.5,2)), Line.ar(1030, 30, 0.2*sustain));
140
+ sound = sound + (BPF.ar(HPF.ar(WhiteNoise.ar(1), 500), 1500) * Line.ar(1, 0, 0.2*decay));
141
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan, amp))
142
+ }).add
143
+ );
144
+
145
+ // Hand clap using Rumble-San's implementation as a starting point
146
+ // http://blog.rumblesan.com/post/53271713909/drum-sounds-in-supercollider-part-2
147
+ //
148
+ // "delay" controls the echo delay
149
+ // "speed" will affect the decay time
150
+ // "n" changes how spread is calculated
151
+ // "pitch1" will scale the bandpass frequency
152
+ // "sustain" the overall timescale
153
+ (
154
+ SynthDef(\sclap, {|out, speed=1, sustain=1, pan=0, n=60, delay=1, pitch1=1, amp=1|
155
+ var env, sound;
156
+ var spr = 0.005 * delay;
157
+ env = EnvGen.ar(Env.linen(0.01, 0, 0.6, 1, -3), timeScale:sustain, doneAction:2);
158
+ sound = BPF.ar(LPF.ar(WhiteNoise.ar(1), 7500*pitch1), 1500*pitch1);
159
+ sound = Mix.arFill(4, {arg i; sound * 0.5 * EnvGen.ar(Env.new([0,0,1,0],[spr*(i**(n.clip(0,5)+1)),0,0.04/speed]))});
160
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan, amp))
161
+ }).add
162
+ );
163
+
164
+ // A controllable synth siren, defaults to 1 second, draw it out with "sustain"
165
+ (
166
+ SynthDef(\ssiren, {|out, sustain=1, pan=0, freq=440, amp=1|
167
+ var env, sound;
168
+ env = EnvGen.ar(Env.linen(0.05, 0.9, 0.05, 1, -2), timeScale:sustain, doneAction:2);
169
+ sound = VarSaw.ar(freq * (1.0 + EnvGen.kr(Env.linen(0.25,0.5,0.25,3,0), timeScale:sustain, doneAction:2)),
170
+ 0, width:Line.kr(0.05,1,sustain));
171
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan, amp))
172
+ }).add
173
+ );
174
+
175
+ // The next four synths respond to the following parameters in addition to
176
+ // gain, pan, n, and all the "effect" parameters (including attack, hold, and
177
+ // release). Default values in parentheses.
178
+ //
179
+ // sustain - scales overall duration
180
+ // decay(0) - amount of decay after initial attack
181
+ // accelerate(0) - pitch glide
182
+ // semitone(12) - how far off in pitch the secondary oscillator is (need not be integer)
183
+ // pitch1(1) - filter frequency scaling multiplier, the frequency itself follows the pitch set by "n"
184
+ // speed(1)- LFO rate
185
+ // lfo(1) - how much the LFO affects the filter frequency
186
+ // resonance(0.2) - filter resonance
187
+ // voice(0.5) - depends on the individual synth
188
+
189
+ // A moog-inspired square-wave synth; variable-width pulses with filter
190
+ // frequency modulated by an LFO
191
+ //
192
+ // "voice" controls the pulse width (exactly zero or one will make no sound)
193
+ (
194
+ SynthDef(\ssquare, {|out, speed=1, decay=0, sustain=1, pan=0, accelerate=0, freq=440,
195
+ voice=0.5, semitone=12, resonance=0.2, lfo=1, pitch1=1, amp=1|
196
+ var env = EnvGen.ar(Env.pairs([[0,0],[0.05,1],[0.2,1-decay],[0.95,1-decay],[1,0]], -3), timeScale:sustain, doneAction:2);
197
+ var basefreq = freq* Line.kr(1, 1+accelerate, sustain);
198
+ var basefreq2 = basefreq / (2**(semitone/12));
199
+ var lfof1 = min(basefreq*10*pitch1, 22000);
200
+ var lfof2 = min(lfof1 * (lfo + 1), 22000);
201
+ var sound = (0.7 * Pulse.ar(basefreq, voice)) + (0.3 * Pulse.ar(basefreq2, voice));
202
+ sound = MoogFF.ar(
203
+ sound,
204
+ SinOsc.ar(basefreq/64*speed, 0).range(lfof1,lfof2),
205
+ resonance*4);
206
+ sound = sound.tanh * 2;
207
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan, amp));
208
+ }).add
209
+ );
210
+
211
+ // A moog-inspired sawtooth synth; slightly detuned saws with triangle
212
+ // harmonics, filter frequency modulated by LFO
213
+ //
214
+ // "voice" controls a relative phase and detune amount
215
+ (
216
+ SynthDef(\ssaw, {|out, speed=1, decay=0, sustain=1, pan=0, accelerate=0, freq=440,
217
+ voice=0.5, semitone=12, resonance=0.2, lfo=1, pitch1=1, amp=1|
218
+ var env = EnvGen.ar(Env.pairs([[0,0],[0.05,1],[0.2,1-decay],[0.95,1-decay],[1,0]], -3), timeScale:sustain, doneAction:2);
219
+ var basefreq = freq * Line.kr(1, 1+accelerate, sustain);
220
+ var basefreq2 = basefreq * (2**(semitone/12));
221
+ var lfof1 = min(basefreq*10*pitch1, 22000);
222
+ var lfof2 = min(lfof1 * (lfo + 1), 22000);
223
+ var sound = MoogFF.ar(
224
+ (0.5 * Mix.arFill(3, {|i| SawDPW.ar(basefreq * ((i-1)*voice/50+1), 0)})) + (0.5 * LFTri.ar(basefreq2, voice)),
225
+ LFTri.ar(basefreq/64*speed, 0.5).range(lfof1,lfof2),
226
+ resonance*4);
227
+ sound = sound.tanh*2;
228
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan, amp));
229
+ }).add
230
+ );
231
+
232
+ // A moog-inspired PWM synth; pulses multiplied by phase-shifted pulses, double
233
+ // filtering with an envelope on the second.
234
+ //
235
+ // "voice" controls the phase shift rate
236
+ (
237
+ SynthDef(\spwm, {|out, speed=1, decay=0, sustain=1, pan=0, accelerate=0, freq=440,
238
+ voice=0.5, semitone=12, resonance=0.2, lfo=1, pitch1=1, amp=1|
239
+ var env = EnvGen.ar(Env.pairs([[0,0],[0.05,1],[0.2,1-decay],[0.95,1-decay],[1,0]], -3), timeScale:sustain, doneAction:2);
240
+ var env2 = EnvGen.ar(Env.pairs([[0,0.1],[0.1,1],[0.4,0.5],[0.9,0.2],[1,0.2]], -3), timeScale:sustain/speed);
241
+ var basefreq = freq * Line.kr(1, 1+accelerate, sustain);
242
+ var basefreq2 = basefreq / (2**(semitone/12));
243
+ var lfof1 = min(basefreq*10*pitch1, 22000);
244
+ var lfof2 = min(lfof1 * (lfo + 1), 22000);
245
+ var sound = 0.7 * PulseDPW.ar(basefreq) * DelayC.ar(PulseDPW.ar(basefreq), 0.2, Line.kr(0,voice,sustain)/basefreq);
246
+ sound = 0.3 * PulseDPW.ar(basefreq2) * DelayC.ar(PulseDPW.ar(basefreq2), 0.2, Line.kr(0.1,0.1+voice,sustain)/basefreq) + sound;
247
+ sound = MoogFF.ar(sound, SinOsc.ar(basefreq/32*speed, 0).range(lfof1,lfof2), resonance*4);
248
+ sound = MoogFF.ar(sound, min(env2*lfof2*1.1, 22000), 3);
249
+ sound = sound.tanh*5;
250
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan, amp));
251
+ }).add
252
+ );
253
+
254
+ // This synth is inherently stereo, so handles the "pan" parameter itself and
255
+ // tells SuperDirt not to mix down to mono.
256
+ //
257
+ // "voice" scales the comparator frequencies, higher values will sound "breathier"
258
+ (
259
+ SynthDef(\scomparator, {|out, speed=1, decay=0, sustain=1, pan=0, accelerate=0, freq=440,
260
+ voice=0.5, resonance=0.5, lfo=1, pitch1=1, amp=1|
261
+ var env = EnvGen.ar(Env.pairs([[0,0],[0.05,1],[0.2,1-decay],[0.95,1-decay],[1,0]], -3), timeScale:sustain, doneAction:2);
262
+ var basefreq = freq * Line.kr(1, 1+accelerate, sustain);
263
+ var sound = VarSaw.ar(basefreq, 0, Line.ar(0,1,sustain));
264
+ var freqlist =[ 1.000, 2.188, 5.091, 8.529, 8.950, 9.305, 13.746, 14.653, 19.462, 22.003, 24.888, 25.991,
265
+ 26.085, 30.509, 33.608, 35.081, 40.125, 42.023, 46.527, 49.481]**(voice/5);
266
+ sound = Splay.arFill(16, {|i| sound > LFTri.ar(freqlist[i])}, 1);
267
+ sound = MoogFF.ar(
268
+ sound,
269
+ pitch1 * 4 * basefreq + SinOsc.ar(basefreq/64*speed, 0, lfo*basefreq/2) + LFNoise2.ar(1,lfo*basefreq),
270
+ LFNoise2.ar(0,0.1,4*resonance));
271
+ sound = 0.5 * Balance2.ar(sound[0], sound[1], pan*2-1);
272
+ OffsetOut.ar(out, Pan2.ar(sound * env, 0.5, amp));
273
+ }).add
274
+ );
275
+
276
+ // Uses the Atari ST emulation UGen with 3 oscillators
277
+ //
278
+ // "slide" is for a linear frequency glide that will repeat "speed" times (can
279
+ // be fractional or negative).
280
+ // "accelerate" is for an overall glide
281
+ // "pitch2" and "pitch3" control the ratio of harmonics
282
+ // "voice" causes variations in the levels of the 3 oscillators
283
+ (
284
+ SynthDef(\schip, {|out, sustain=1, pan=0, freq=440, speed=1, slide=0, pitch2=2, pitch3=3, accelerate=0, voice=0, amp=1|
285
+ var env, basefreq, sound, va, vb, vc;
286
+ env = EnvGen.ar(Env.linen(0.01, 0.98, 0.01,1,-1), timeScale:sustain, doneAction:2);
287
+ basefreq = freq + wrap2(slide * 100 * Line.kr(-1,1+(2*speed-2),sustain), slide * 100);
288
+ basefreq = basefreq * Line.kr(1, accelerate+1, sustain);
289
+ va = (voice < 0.5) * 15;
290
+ vb = ((2*voice) % 1 < 0.5) * 15;
291
+ vc = ((4*voice) % 1 < 0.5) * 15;
292
+ sound= AY.ar( AY.freqtotone(basefreq), AY.freqtotone(pitch2*basefreq), AY.freqtotone(pitch3*basefreq),
293
+ vola:va, volb:vb, volc:vc)/2;
294
+ sound = tanh(sound)*2;
295
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan, amp));
296
+ }).add
297
+ );
298
+
299
+ // Digital noise in several flavors with a bandpass filter
300
+ //
301
+ // "voice" at 0 is a digital noise for which "n" controls rate, at 1 is
302
+ // Brown+White noise for which "n" controls knee frequency.
303
+ // "accelerate" causes glide in n, "speed" will cause it to repeat
304
+ // "pitch1" scales the bandpass frequency (which tracks "n")
305
+ // "slide" works like accelerate on the bandpass
306
+ // "resonance" is the filter resonance
307
+ (
308
+ SynthDef(\snoise, {|out, sustain=1, pan=0, freq=440, accelerate=0, slide=0, pitch1=1, speed=1, resonance=0, voice=0, amp=1|
309
+ var env, basefreq, sound, ffreq, acc;
310
+ env = EnvGen.ar(Env.linen(0.01, 0.98, 0.01,1,-1), timeScale:sustain, doneAction:2);
311
+ acc = accelerate * freq * 4;
312
+ basefreq = freq * 8 + wrap2(acc* Line.kr(-1,1+(2*speed-2), sustain), acc);
313
+ ffreq = basefreq*5*pitch1* Line.kr(1,1+slide, sustain);
314
+ ffreq = clip(ffreq, 60,20000);
315
+ sound = XFade2.ar( LFDNoise0.ar(basefreq.min(22000), 0.5),
316
+ XFade2.ar(BrownNoise.ar(0.5), WhiteNoise.ar(0.5), basefreq.cpsmidi/127),
317
+ 2*voice-1);
318
+ sound = HPF.ar(BMoog.ar(sound, ffreq, resonance, 3), 20);
319
+ sound = clip(sound, -1,1) * 0.3;
320
+ OffsetOut.ar(out, Pan2.ar(sound * env, pan, amp));
321
+ }).add
322
+ );
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.6
4
+ version: 0.2.0
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: 2018-09-13 00:00:00.000000000 Z
11
+ date: 2019-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -134,13 +134,15 @@ files:
134
134
  - lib/xi/core_ext/array.rb
135
135
  - lib/xi/core_ext/enumerable.rb
136
136
  - lib/xi/core_ext/enumerator.rb
137
- - lib/xi/core_ext/fixnum.rb
137
+ - lib/xi/core_ext/integer.rb
138
138
  - lib/xi/core_ext/numeric.rb
139
139
  - lib/xi/core_ext/object.rb
140
140
  - lib/xi/core_ext/scalar.rb
141
141
  - lib/xi/core_ext/string.rb
142
142
  - lib/xi/error_log.rb
143
143
  - lib/xi/logger.rb
144
+ - lib/xi/osc.rb
145
+ - lib/xi/osc/version.rb
144
146
  - lib/xi/pattern.rb
145
147
  - lib/xi/pattern/generators.rb
146
148
  - lib/xi/pattern/transforms.rb
@@ -148,8 +150,13 @@ files:
148
150
  - lib/xi/scale.rb
149
151
  - lib/xi/step_sequencer.rb
150
152
  - lib/xi/stream.rb
153
+ - lib/xi/supercollider.rb
154
+ - lib/xi/supercollider/stream.rb
155
+ - lib/xi/supercollider/version.rb
151
156
  - lib/xi/tidal_clock.rb
152
157
  - lib/xi/version.rb
158
+ - synthdefs/other.scd
159
+ - synthdefs/superdirt.scd
153
160
  - xi.gemspec
154
161
  homepage: https://github.com/xi-livecode/xi
155
162
  licenses:
@@ -170,8 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
170
177
  - !ruby/object:Gem::Version
171
178
  version: '0'
172
179
  requirements: []
173
- rubyforge_project:
174
- rubygems_version: 2.7.6
180
+ rubygems_version: 3.0.3
175
181
  signing_key:
176
182
  specification_version: 4
177
183
  summary: Musical pattern language for livecoding
@@ -1,17 +0,0 @@
1
- module Xi::CoreExt
2
- module Fixnum
3
- def /(o)
4
- super(o.to_r)
5
- end
6
- end
7
- end
8
-
9
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4')
10
- class Fixnum
11
- prepend Xi::CoreExt::Fixnum
12
- end
13
- else
14
- class Integer
15
- prepend Xi::CoreExt::Fixnum
16
- end
17
- end