xi-lang 0.1.6 → 0.2.0

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