rsynth 0.0.1.alpha0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/Rakefile +2 -0
- data/lib/rsynth/combiner.rb +18 -0
- data/lib/rsynth/functions.rb +91 -0
- data/lib/rsynth/notes.rb +101 -0
- data/lib/rsynth/oscillator.rb +32 -0
- data/lib/rsynth/phase_shifter.rb +17 -0
- data/lib/rsynth/pipe/pipeline.rb +84 -0
- data/lib/rsynth/pipe/portaudio.rb +93 -0
- data/lib/rsynth/rsynth.rb +14 -0
- data/lib/rsynth/scales.rb +41 -0
- data/lib/rsynth/version.rb +3 -0
- data/rsynth.gemspec +24 -0
- metadata +107 -0
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module RSynth
|
2
|
+
class Combiner
|
3
|
+
include RSynth::Functions
|
4
|
+
attr_accessor :a, :b
|
5
|
+
|
6
|
+
def initialize(a, b, proc=nil)
|
7
|
+
proc = Proc.new{|a,b| yield a, b} if block_given?
|
8
|
+
raise 'No block or proc given' if proc.nil?
|
9
|
+
@proc = proc
|
10
|
+
@a = a
|
11
|
+
@b = b
|
12
|
+
end
|
13
|
+
|
14
|
+
def value_at(time)
|
15
|
+
@proc.call(@a.value_at(time), @b.value_at(time))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module RSynth
|
2
|
+
module Functions
|
3
|
+
def combine(type, other)
|
4
|
+
case type
|
5
|
+
when :add, :plus
|
6
|
+
RSynth::Combiner.new(self, other) { |a, b| a + b }
|
7
|
+
when :sub, :subtract, :minus
|
8
|
+
RSynth::Combiner.new(self, other) { |a, b| a - b }
|
9
|
+
when :div, :divide
|
10
|
+
RSynth::Combiner.new(self, other) { |a, b| a / b }
|
11
|
+
when :mul, :multiply, :times
|
12
|
+
RSynth::Combiner.new(self, other) { |a, b| a * b }
|
13
|
+
when :exp, :exponent, :pow, :power
|
14
|
+
RSynth::Combiner.new(self, other) { |a, b| a ** b }
|
15
|
+
when :mod, :modulo, :rem, :remainder
|
16
|
+
RSynth::Combiner.new(self, other) { |a, b| a % b }
|
17
|
+
when :mix
|
18
|
+
RSynth::Combiner.new(self, other) do |a, b|
|
19
|
+
# Normalise between 0 and 1
|
20
|
+
a = (a + 1) / 2
|
21
|
+
b = (b + 1) / 2
|
22
|
+
z = (a < 0.5 and b < 0.5)? 2*a*b : 2*(a+b) - (2*a*b) - 1
|
23
|
+
# Convert back
|
24
|
+
(z * 2) - 1
|
25
|
+
end
|
26
|
+
else
|
27
|
+
raise "Unknown combiner type: #{type}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def phase_shift(offset=nil)
|
32
|
+
offset = Proc.new{ |time| yield time } if block_given?
|
33
|
+
raise 'No offset or block given' if offset.nil?
|
34
|
+
RSynth::PhaseShifer.new(self, offset)
|
35
|
+
end
|
36
|
+
|
37
|
+
# won't work for numeric or proc (unless we override but there'd be a huge performance hit)
|
38
|
+
def +(other)
|
39
|
+
combine(:add, other)
|
40
|
+
end
|
41
|
+
|
42
|
+
def -(other)
|
43
|
+
combine(:sub, other)
|
44
|
+
end
|
45
|
+
|
46
|
+
def /(other)
|
47
|
+
combine(:div, other)
|
48
|
+
end
|
49
|
+
|
50
|
+
def *(other)
|
51
|
+
combine(:mul, other)
|
52
|
+
end
|
53
|
+
|
54
|
+
def **(other)
|
55
|
+
combine(:exp, other)
|
56
|
+
end
|
57
|
+
|
58
|
+
def %(other)
|
59
|
+
combine(:mod, other)
|
60
|
+
end
|
61
|
+
|
62
|
+
def &(other)
|
63
|
+
combine(:mix, other)
|
64
|
+
end
|
65
|
+
|
66
|
+
def <<(offset)
|
67
|
+
phase_shift(-offset)
|
68
|
+
end
|
69
|
+
|
70
|
+
def >>(offset)
|
71
|
+
phase_shift(offset)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Proc
|
77
|
+
include RSynth::Functions
|
78
|
+
|
79
|
+
def value_at(time)
|
80
|
+
self.call(time)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Add mixins to int and float :O
|
85
|
+
class Numeric
|
86
|
+
include RSynth::Functions
|
87
|
+
|
88
|
+
def value_at(time)
|
89
|
+
self
|
90
|
+
end
|
91
|
+
end
|
data/lib/rsynth/notes.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
module RSynth
|
2
|
+
class Note
|
3
|
+
ChromaticInterval = 2**(1.0/12)
|
4
|
+
ChromaticNotes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
|
5
|
+
IntervalMap = [
|
6
|
+
%w(perfect_unison unison P1 diminished_second d2),
|
7
|
+
%w(minor_second m2 augmented_unison A1 semitone S),
|
8
|
+
%w(major_second M2 diminished_third d3 tone whole_tone T),
|
9
|
+
%w(minor_third m3 augmented_second A2),
|
10
|
+
%w(major_third M3 diminished_fourth d4),
|
11
|
+
%w(perfect_fourth fourth P4 augmented_third A3),
|
12
|
+
%w(tritone diminished_fifth d5 augmented_fourth A4 TT),
|
13
|
+
%w(perfect_fifth fifth P5 diminished_sixth d6),
|
14
|
+
%w(minor_sixth m6 augmented_fifth A5),
|
15
|
+
%w(major_sixth M6 diminished_seventh d7),
|
16
|
+
%w(minor_seventh m7 augmented_sixth A6),
|
17
|
+
%w(major_seventh M7 diminished_octave d8),
|
18
|
+
%w(perfect_octave octave P8 augmented_seventh A7)
|
19
|
+
]
|
20
|
+
include RSynth::Functions
|
21
|
+
|
22
|
+
attr_reader :note, :octave, :freq
|
23
|
+
|
24
|
+
def initialize(note, octave)
|
25
|
+
@note = note.upcase
|
26
|
+
@octave = octave
|
27
|
+
raise "Invalid note value: #{note}" unless ChromaticNotes.include?(@note)
|
28
|
+
raise "Invalid octave: #{octave}, must be >= 0" if octave < 0
|
29
|
+
|
30
|
+
# Calculate frequency (notes are relative to A4 (440Hz))
|
31
|
+
rn = -(ChromaticNotes.index('A') - ChromaticNotes.index(@note))
|
32
|
+
ro = @octave - 4
|
33
|
+
steps = (ro * 12) + rn
|
34
|
+
@freq = 440*(ChromaticInterval**steps)
|
35
|
+
end
|
36
|
+
|
37
|
+
def value_at(time)
|
38
|
+
@freq
|
39
|
+
end
|
40
|
+
|
41
|
+
def next
|
42
|
+
transpose(1)
|
43
|
+
end
|
44
|
+
|
45
|
+
def previous
|
46
|
+
transpose(-1)
|
47
|
+
end
|
48
|
+
|
49
|
+
def previous_octave
|
50
|
+
transpose(-12)
|
51
|
+
end
|
52
|
+
|
53
|
+
def transpose(count=1)
|
54
|
+
oct = @octave
|
55
|
+
ni = ChromaticNotes.index(@note)
|
56
|
+
ni += count
|
57
|
+
while ni >= ChromaticNotes.length
|
58
|
+
oct += 1
|
59
|
+
ni -= ChromaticNotes.length
|
60
|
+
end
|
61
|
+
while ni < 0
|
62
|
+
oct -= 1
|
63
|
+
ni += ChromaticNotes.length
|
64
|
+
end
|
65
|
+
Note.retrieve(ChromaticNotes[ni], oct)
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
"#@note#@octave"
|
70
|
+
end
|
71
|
+
|
72
|
+
def <=>(note)
|
73
|
+
os = @octave <=> note.octave
|
74
|
+
return os unless os == 0
|
75
|
+
ChromaticNotes.index(@note) <=> ChromaticNotes.index(note.note)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.retrieve(note, octave)
|
79
|
+
note.upcase!
|
80
|
+
name = "#{note.sub('#', 'Sharp')}#{octave}"
|
81
|
+
RSynth.const_set(name, Note.new(note, octave)) unless RSynth.const_defined?(name)
|
82
|
+
RSynth.const_get(name)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Define the interval methods...
|
86
|
+
IntervalMap.each_with_index do |names, interval|
|
87
|
+
names.each do |name|
|
88
|
+
define_method(name.to_sym) do
|
89
|
+
self.transpose(interval)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Define the chromatic scale at octaves 0-9
|
96
|
+
9.times do |o|
|
97
|
+
Note::ChromaticNotes.each do |n|
|
98
|
+
Note.retrieve(n, o)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module RSynth
|
2
|
+
class Oscillator
|
3
|
+
PI2 = Math::PI*2
|
4
|
+
TableLength = 1024
|
5
|
+
SinTable = TableLength.times.map{|i| Math.sin((PI2 * i) / TableLength)}
|
6
|
+
CosTable = TableLength.times.map{|i| Math.cos((PI2 * i) / TableLength)}
|
7
|
+
|
8
|
+
include RSynth::Functions
|
9
|
+
|
10
|
+
attr_accessor :freq, :func
|
11
|
+
|
12
|
+
def initialize(func, freq)
|
13
|
+
@freq = freq
|
14
|
+
@func = func
|
15
|
+
@phase = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def value_at(time)
|
19
|
+
f = @freq.value_at(time)
|
20
|
+
i = @phase
|
21
|
+
@phase = (@phase + (f.to_f / RSynth::SampleRate)) % 1.0
|
22
|
+
case @func
|
23
|
+
when :sin, :sine then SinTable[(i*TableLength).to_i]
|
24
|
+
when :cos, :cosine then CosTable[(i*TableLength).to_i]
|
25
|
+
when :sq, :square then i <= 0.5? 1: -1
|
26
|
+
when :tri, :triangle then i <= 0.25? (i * 4): i <= 0.5? (1 - (i - 0.25) * 4): i <= 0.75? (0 - (i - 0.5) * 4): (i - 0.75) * 4 - 1
|
27
|
+
when :saw, :sawtooth then i * 2 - 1
|
28
|
+
else @func.value_at(@phase)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module RSynth
|
2
|
+
class PhaseShifter
|
3
|
+
include RSynth::Functions
|
4
|
+
attr_accessor :source, :offset
|
5
|
+
|
6
|
+
def initialize(source, offset=nil)
|
7
|
+
offset = Proc.new {|time| yield time} if block_given?
|
8
|
+
raise 'No offset or block defined' if offset.nil?
|
9
|
+
@source = source
|
10
|
+
@offset = offset
|
11
|
+
end
|
12
|
+
|
13
|
+
def value_at(time)
|
14
|
+
@source.value_at(time + @offset.value_at(time))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module RSynth
|
2
|
+
module Pipe
|
3
|
+
class Pipeline
|
4
|
+
NumBuffers = 2 # Number of buffers we keep in our ring buffer
|
5
|
+
FramesPerBuffer = 512 # How many bytes per buffer (higher is higher latency, obviously...)
|
6
|
+
|
7
|
+
attr_accessor :sequence, :source
|
8
|
+
attr_reader :time
|
9
|
+
|
10
|
+
def initialize(source, *sequence)
|
11
|
+
source = Proc.new { |time| yield time } if block_given?
|
12
|
+
raise 'No source or block given' if source.nil?
|
13
|
+
@sequence = sequence || []
|
14
|
+
@source = source
|
15
|
+
@stopped = true
|
16
|
+
end
|
17
|
+
|
18
|
+
def start(time=Float::INFINITY)
|
19
|
+
return unless @stopped
|
20
|
+
@stopped = false
|
21
|
+
@length = time
|
22
|
+
Thread.new {generate}
|
23
|
+
end
|
24
|
+
|
25
|
+
def stop
|
26
|
+
@stopped = true
|
27
|
+
end
|
28
|
+
|
29
|
+
def restart
|
30
|
+
stop
|
31
|
+
start
|
32
|
+
end
|
33
|
+
|
34
|
+
def consume(sequence)
|
35
|
+
sidx = @sequence.index(sequence) || @sequence.length
|
36
|
+
bidx = @seqpos[sidx] || 0
|
37
|
+
@generated[bidx]
|
38
|
+
end
|
39
|
+
|
40
|
+
def consumed(sequence)
|
41
|
+
sidx = @sequence.index(sequence) || @sequence.length
|
42
|
+
bidx = @seqpos[sidx] || 0
|
43
|
+
@seqpos[sidx] = bidx + 1 # increment this sequences buffer index entry...
|
44
|
+
|
45
|
+
# If all sequences have consumed the bottom buffer, release it
|
46
|
+
if @seqpos.all?{|s| s > 0}
|
47
|
+
@buffers.push(@generated.shift)
|
48
|
+
@seqpos.map!{|i| i - 1}
|
49
|
+
@time += FramesPerBuffer * RSynth::TimeStep
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate
|
54
|
+
@time = 0
|
55
|
+
@generate_time = 0
|
56
|
+
timePerLoop = FramesPerBuffer * RSynth::TimeStep
|
57
|
+
@buffers = 10.times.map{Array.new(FramesPerBuffer)}
|
58
|
+
@generated = []
|
59
|
+
@seqpos = @sequence.length.times.map{0}
|
60
|
+
@sequence.each { |s| s.start(self) }
|
61
|
+
while not @stopped
|
62
|
+
if @generate_time < @length
|
63
|
+
sleep(RSynth::TimeStep) while (@buffers.length == 0 or @generated.length >= NumBuffers) and not @stopped
|
64
|
+
buf = @buffers.shift
|
65
|
+
len = @generate_time + timePerLoop > @length ? (@length - @generate_time) / RSynth::TimeStep : FramesPerBuffer
|
66
|
+
len.times do |i|
|
67
|
+
buf[i] = @source.value_at(@generate_time)
|
68
|
+
@generate_time += RSynth::TimeStep
|
69
|
+
end
|
70
|
+
(FramesPerBuffer - len).times do |i|
|
71
|
+
buf[i] = 0
|
72
|
+
end
|
73
|
+
@generated << buf
|
74
|
+
end
|
75
|
+
end
|
76
|
+
@sequence.each{ |s| s.stop }
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_s
|
80
|
+
"RSynth::Pipe::Pipeline source=#{@source},seq=#{@sequence}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'ffi-portaudio'
|
2
|
+
|
3
|
+
module RSynth
|
4
|
+
module Pipe
|
5
|
+
class PortAudio
|
6
|
+
include ::FFI::PortAudio
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
wrap_call('initialise portaudio') { | | API.Pa_Initialize() }
|
10
|
+
@buf = FFI::Buffer.new(:float, RSynth::Pipe::Pipeline::FramesPerBuffer, true) # Create our native buffer
|
11
|
+
end
|
12
|
+
|
13
|
+
def start(pipeline)
|
14
|
+
@source = pipeline
|
15
|
+
open_stream
|
16
|
+
end
|
17
|
+
|
18
|
+
def stop
|
19
|
+
close_stream
|
20
|
+
@source = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def process input, output, frame_count, time_info, status_flags, user_data
|
25
|
+
n = @source.consume(self)
|
26
|
+
return :pcComplete if n.nil?
|
27
|
+
output.write_array_of_float(n)
|
28
|
+
@source.consumed(self)
|
29
|
+
:paContinue
|
30
|
+
end
|
31
|
+
|
32
|
+
def open_stream
|
33
|
+
close_stream unless @stream.nil?
|
34
|
+
# Get the audio host
|
35
|
+
info = wrap_call('get default audio host') do | |
|
36
|
+
return idx if error?(idx = API.Pa_GetDefaultHostApi())
|
37
|
+
API.Pa_GetHostApiInfo(idx)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get the default device
|
41
|
+
inIdx = info[:defaultInputDevice] # Todo: add sampling for luls
|
42
|
+
outIdx = info[:defaultOutputDevice]
|
43
|
+
outDev = wrap_call 'get device info' do | |
|
44
|
+
API.Pa_GetDeviceInfo(outIdx)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Create parameters for the output stream
|
48
|
+
outopts = API::PaStreamParameters.new
|
49
|
+
outopts[:device] = outIdx
|
50
|
+
outopts[:channelCount] = RSynth::Channels
|
51
|
+
outopts[:sampleFormat] = API::Float32
|
52
|
+
outopts[:suggestedLatency] = outDev[:defaultHighOutputLatency]
|
53
|
+
outopts[:hostApiSpecificStreamInfo] = nil
|
54
|
+
|
55
|
+
@stream = FFI::Buffer.new :pointer
|
56
|
+
@callback = method(:process)
|
57
|
+
wrap_call('open output stream') do | |
|
58
|
+
API.Pa_OpenStream(
|
59
|
+
@stream, # Stream
|
60
|
+
nil, # Input options
|
61
|
+
outopts, # Output options
|
62
|
+
RSynth::SampleRate, # the sample rate
|
63
|
+
RSynth::Pipe::Pipeline::FramesPerBuffer, # frames per buffer
|
64
|
+
API::PrimeOutputBuffersUsingStreamCallback, # flags
|
65
|
+
@callback, # Callback
|
66
|
+
nil # User data
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Start the stream playing
|
71
|
+
wrap_call('starting output stream') do | |
|
72
|
+
API.Pa_StartStream @stream.read_pointer
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def close_stream
|
77
|
+
return if @stream.nil?
|
78
|
+
API.Pa_CloseStream(@stream.read_pointer)
|
79
|
+
@stream = nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def error?(val)
|
83
|
+
(Symbol === val and val != :paNoError) or (Integer === val and val < 0)
|
84
|
+
end
|
85
|
+
|
86
|
+
def wrap_call(msg, &block)
|
87
|
+
ret = yield block
|
88
|
+
raise "Failed to #{msg}: #{API.Pa_GetErrorText(ret)}" if error?(ret)
|
89
|
+
return ret
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative 'pipe/pipeline'
|
2
|
+
require_relative 'pipe/portaudio'
|
3
|
+
require_relative 'functions'
|
4
|
+
require_relative 'combiner'
|
5
|
+
require_relative 'phase_shifter'
|
6
|
+
require_relative 'oscillator'
|
7
|
+
require_relative 'notes'
|
8
|
+
require_relative 'scales'
|
9
|
+
|
10
|
+
module RSynth
|
11
|
+
SampleRate = 22050
|
12
|
+
TimeStep = 1.0/SampleRate
|
13
|
+
Channels = 1
|
14
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module RSynth
|
2
|
+
class Note
|
3
|
+
# Add some methods to the note class :O
|
4
|
+
def major_scale
|
5
|
+
DiatonicScale.new(self, :ionian)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# A diatonic scale :O
|
10
|
+
class DiatonicScale < Array
|
11
|
+
Degrees = %w(tonic supertonic mediant subdominant dominant submediant leading_tone octave).map(&:to_sym)
|
12
|
+
Modes = %w(ionian dorian phyrgian lydian aeolian locrian).map(&:to_sym)
|
13
|
+
Pattern = %w(2 2 1 2 2 2 1).map(&:to_i)
|
14
|
+
|
15
|
+
def initialize(key, mode)
|
16
|
+
@key = key
|
17
|
+
@mode = mode
|
18
|
+
|
19
|
+
# Define degrees...
|
20
|
+
pat = Pattern.rotate(Modes.index(mode))
|
21
|
+
semitones = 0
|
22
|
+
Degrees.each_with_index do |name, i|
|
23
|
+
idx = Degrees.index(name)
|
24
|
+
self << @key.transpose(pat.take(idx).inject(:+) || 0)
|
25
|
+
semitones += pat[i] unless i >= pat.length
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
Modes.each do |name|
|
30
|
+
define_method name do
|
31
|
+
DiatonicScale.new(@key, name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Degrees.each_with_index do |name, i|
|
36
|
+
define_method name do
|
37
|
+
self[i]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/rsynth.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
require 'rsynth/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "rsynth"
|
8
|
+
s.version = RSynth::VERSION
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.authors = %w(James Lawrence)
|
11
|
+
s.email = %w(james@kukee.co.uk)
|
12
|
+
|
13
|
+
s.summary = "Rsynth aims to be a simple to use audio synthesis library for use in ruby"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } # Stolen from github gem, uses git to list files, very nice!
|
18
|
+
s.require_paths = %w(lib)
|
19
|
+
|
20
|
+
s.add_dependency "ffi-portaudio", "~>0.1"
|
21
|
+
|
22
|
+
s.add_development_dependency "rake"
|
23
|
+
s.add_development_dependency "rspec", "~>1.3.1"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rsynth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.alpha0
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- James
|
9
|
+
- Lawrence
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-01-19 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: ffi-portaudio
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0.1'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0.1'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: rake
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
type: :development
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.3.1
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 1.3.1
|
63
|
+
description:
|
64
|
+
email:
|
65
|
+
- james@kukee.co.uk
|
66
|
+
executables: []
|
67
|
+
extensions: []
|
68
|
+
extra_rdoc_files: []
|
69
|
+
files:
|
70
|
+
- Gemfile
|
71
|
+
- Rakefile
|
72
|
+
- lib/rsynth/combiner.rb
|
73
|
+
- lib/rsynth/functions.rb
|
74
|
+
- lib/rsynth/notes.rb
|
75
|
+
- lib/rsynth/oscillator.rb
|
76
|
+
- lib/rsynth/phase_shifter.rb
|
77
|
+
- lib/rsynth/pipe/pipeline.rb
|
78
|
+
- lib/rsynth/pipe/portaudio.rb
|
79
|
+
- lib/rsynth/rsynth.rb
|
80
|
+
- lib/rsynth/scales.rb
|
81
|
+
- lib/rsynth/version.rb
|
82
|
+
- rsynth.gemspec
|
83
|
+
homepage:
|
84
|
+
licenses: []
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>'
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: 1.3.1
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.8.24
|
104
|
+
signing_key:
|
105
|
+
specification_version: 3
|
106
|
+
summary: Rsynth aims to be a simple to use audio synthesis library for use in ruby
|
107
|
+
test_files: []
|