analog 0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d406496d1f9d968519029f39c59cfd4de9d89959
4
+ data.tar.gz: 6c6594988cb9a366f6125f21a340d1782ee1930c
5
+ SHA512:
6
+ metadata.gz: fd71860f234567f1f0111c7b723a0b0760ccf02645a1ea6f85f678dbd1e863974af788b45429a93c2708873d128c8ddb72e29597b8b7a2c96266e865dce1ebe7
7
+ data.tar.gz: 92ef67f94ef2ff5049da82808d8046d4cae7e11a836ea3ee546914d1e0b3dbdfcfa6db991ce401f20bcfcf4fe8b29eec4f55548f63d3b4d5dd87158608ce45ef
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2014 Ari Russo
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # analog
2
+
3
+ A Ruby helper for scaling numbers.
4
+
5
+ #### Background
6
+
7
+ This helper provides a quick way to take a number and scale it given a source and destination range/set/etc.
8
+
9
+ It's useful for converting data from raw values to a range used for display or other kinds of output.
10
+
11
+ I use this all the time in music programming, converting OSC messages to musical notes and things like that and figure it might be useful to others.
12
+
13
+ A simple example is:
14
+
15
+ If you want to plot a point on a 500px graph using a data that lies in the 0..1 range, do
16
+
17
+ ```ruby
18
+ Scale.transform(0.5).using(0..1, 0..500)
19
+ => 250
20
+ ```
21
+
22
+ Please note: I am not a Math Person. If I've named any concepts here poorly, [please let me know](https://github.com/arirusso/analog/issues). Or [submit a pull request](https://github.com/arirusso/analog/pulls)
23
+
24
+ #### Usage
25
+
26
+ ```ruby
27
+ require "scale"
28
+ ```
29
+
30
+ This example will scale a number down by 1/10th`
31
+
32
+ ```ruby
33
+ Scale.transform(22).using(0..150, 0..15)
34
+ => 2
35
+
36
+ ```
37
+
38
+ You can use Arrays and Sets
39
+ ```ruby
40
+ Scale.transform(8).using(Set.new([0, 2, 4, 8, 16, 64]), 0..10)
41
+ => 6
42
+
43
+ Scale.transform(0.40).using(0..1, [0, 2, 4, 8, 12, 16, 32, 64, 128, 512])
44
+ => 8
45
+ ```
46
+
47
+ See the [examples](https://github.com/arirusso/analog/tree/master/examples) for more
48
+
49
+ ###### Core Extension
50
+
51
+ There is a Numeric extension that you can optionally include.
52
+
53
+ ```ruby
54
+ require "scale/core_ext"
55
+
56
+ 0.40.scaled_from(0..1).to([0, 2, 4, 8, 12, 16, 32, 64, 128, 512])
57
+ => 8
58
+ ```
59
+
60
+ See the [core_ext example](https://github.com/arirusso/analog/blob/master/examples/core_ext.rb) for more examples.
61
+
62
+ #### Installation
63
+
64
+ gem install analog
65
+
66
+ or with Bundler
67
+
68
+ gem "analog"
69
+
70
+ #### License
71
+
72
+ Licensed under Apache 2.0, See the file LICENSE
73
+ Copyright (c) 2014 [Ari Russo](http://arirusso.com)
74
+
data/lib/scale.rb ADDED
@@ -0,0 +1,11 @@
1
+ # classes
2
+ require "scale/destination"
3
+ require "scale/scheme"
4
+ require "scale/source"
5
+
6
+ # A Ruby helper for scaling numbers
7
+ module Scale
8
+
9
+ VERSION = "0.1"
10
+
11
+ end
@@ -0,0 +1,46 @@
1
+ require "scale"
2
+
3
+ # Extend the Numeric class to add a scaling helper.
4
+ # eg: 0.40.scaled_from(0..1).to([512, 128, 64, 32, 16, 12, 8, 4, 2, 0])
5
+ #
6
+ # This is not included by default with the rest of the library. use require "scale/core_ext"
7
+ #
8
+ class Numeric
9
+
10
+ # Build a Scheme for this numeric as input.
11
+ # @return [Scale::Scheme]
12
+ def scaled
13
+ Scale.transform(self)
14
+ end
15
+
16
+ # Scale using the given source and destination, with this numeric as input.
17
+ # @param [Scale::Source] source
18
+ # @param [Scale::Destination] destination
19
+ # @return [Numeric]
20
+ def scaled_using(source, destination)
21
+ Scale.transform(self).using(source, destination)
22
+ end
23
+
24
+ # Build a scheme with this numeric as input and add the given source to it. If
25
+ # on calling this method, the scheme has all of its needed properties, the scaled
26
+ # value will be returned. Otherwise # this method will return the updated Scheme
27
+ # object.
28
+ #
29
+ # @param [Scale::Source] source
30
+ # @return [Numeric, Scale::Scheme]
31
+ def scaled_from(source)
32
+ Scale.transform(self).from(source)
33
+ end
34
+
35
+ # Build a scheme with this numeric as input and add the given destination to it. If
36
+ # on calling this method, the scheme has all of its needed properties, the scaled
37
+ # value will be returned. Otherwise # this method will return the updated Scheme
38
+ # object.
39
+ #
40
+ # @param [Scale::Destination] destination
41
+ # @return [Numeric, Scale::Scheme]
42
+ def scaled_to(destination)
43
+ Scale.transform(self).to(destination)
44
+ end
45
+
46
+ end
@@ -0,0 +1,73 @@
1
+ module Scale
2
+
3
+ # These are the classes that describe what range the transformed number
4
+ # will wind up in. They're named after the core Ruby class that the input closest
5
+ # resembles.
6
+ module Destination
7
+
8
+ # Contains logic for dealing with Ruby's core ::Range as input
9
+ class Range
10
+
11
+ # @param [::Range] range A range to operate with
12
+ def initialize(range)
13
+ @range = range
14
+ end
15
+
16
+ # Scale the given input and source using this destination
17
+ # @param [Numeric] input A numeric value to scale
18
+ # @param [Scale::Source] source The source for the input value
19
+ # @return [Numeric]
20
+ def scale(input, source)
21
+ to_range_len = (@range.last - @range.first).abs
22
+
23
+ proportion = to_range_len.to_f / source.denominator
24
+ abs_output = proportion.to_f * source.numerator(input)
25
+ output = abs_output + @range.first
26
+
27
+ float_requested = [@range.first, @range.last].any? { |n| n.kind_of?(::Float) }
28
+ float_requested ? output : output.to_i
29
+ end
30
+
31
+ end
32
+
33
+ # Contains logic for dealing with input that includes Ruby's core ::Enumerable
34
+ class Enumerable
35
+
36
+ # @param [::Enumerable] enum An enumerable (eg Array, Set) to operate with
37
+ def initialize(enum)
38
+ @enum = enum
39
+ end
40
+
41
+ # Scale the given input and source using this destination
42
+ # @param [Numeric] input A numeric value to scale
43
+ # @param [Scale::Source] source The source for the input value
44
+ # @return [Numeric]
45
+ def scale(input, source)
46
+ proportion = source.numerator(input) / source.denominator
47
+ index = [((proportion * @enum.size).to_i - 1), 0].max
48
+ @enum.to_a.at(index)
49
+ end
50
+
51
+ end
52
+
53
+ # Map Ruby classes/modules to scaling destination classes/modules
54
+ MAP = {
55
+ ::Enumerable => Destination::Enumerable,
56
+ ::Range => Destination::Range
57
+ }
58
+
59
+ # Build the appropriate scaling destination class for the given Ruby object
60
+ # @param [::Enumerable] destination
61
+ # @return [Scale::Destination::Enumerable, Scale::Destination::Range]
62
+ def self.new(destination)
63
+ klass = MAP[destination.class]
64
+ if klass.nil?
65
+ klasses = MAP.select { |k,v| destination.kind_of?(k) }
66
+ klass = klasses.values.first
67
+ end
68
+ klass.new(destination) unless klass.nil?
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,65 @@
1
+ module Scale
2
+
3
+ # Build a scaling scheme for the given input
4
+ # @param [Numeric] input
5
+ # @return [Scale::Scheme]
6
+ def self.transform(input)
7
+ Scheme.new(:input => input)
8
+ end
9
+
10
+ # Describes a particular scaling scenario
11
+ class Scheme
12
+
13
+ attr_reader :destination, :input, :source
14
+
15
+ # @param [Hash] options
16
+ # @option options [Scale::Destination] :destination The destination for this scaling scenario
17
+ # @option options [Scale::Source] :source The source for this scaling scenario
18
+ def initialize(options = {})
19
+ @input = options[:input]
20
+ @source = Source.new(options[:source]) unless options[:source].nil?
21
+ @destination = Destination.new(options[:destination]) unless options[:destination].nil?
22
+ end
23
+
24
+ # Set the source for this scaling scenario. If on calling this method, the scenario
25
+ # has all of its needed properties, the scaled value will be returned. Otherwise
26
+ # this method will return the updated Scheme object.
27
+ #
28
+ # @param [Scale::Source] source
29
+ # @return [Fixnum, Scale::Scheme]
30
+ def from(source)
31
+ @source = Source.new(source)
32
+ if @input.nil? || @destination.nil?
33
+ self
34
+ else
35
+ @destination.scale(@input, @source)
36
+ end
37
+ end
38
+
39
+ # Set the destination for this scaling scenario. If on calling this method, the
40
+ # scenario has all of its needed properties, the scaled value will be returned.
41
+ # Otherwise this method will return the updated Scheme object.
42
+ #
43
+ # @param [Scale::Destination] destination
44
+ # @return [Fixnum, Scale::Scheme]
45
+ def to(destination)
46
+ @destination = Destination.new(destination)
47
+ if @input.nil? || @source.nil?
48
+ self
49
+ else
50
+ @destination.scale(@input, @source)
51
+ end
52
+ end
53
+
54
+ # Set both the source and destination on this scheme
55
+ # @param [Scale::Source] source
56
+ # @param [Scale::Destination] destination
57
+ def using(source, destination)
58
+ @source = Source.new(source)
59
+ @destination = Destination.new(destination)
60
+ @destination.scale(@input, @source)
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,70 @@
1
+ module Scale
2
+
3
+ # These are the classes that describe what range the transformed number
4
+ # starts in. They're named after the core Ruby class that the input closest
5
+ # resembles.
6
+ module Source
7
+
8
+ # Contains logic for dealing with Ruby's core ::Range as input
9
+ class Range
10
+
11
+ # @param [::Range] range A range to operate on
12
+ def initialize(range)
13
+ @range = range
14
+ end
15
+
16
+ # @param [Numeric] input
17
+ # @return [Float]
18
+ def numerator(input)
19
+ (input - @range.first).to_f
20
+ end
21
+
22
+ # @return [Float]
23
+ def denominator
24
+ (@range.last - @range.first).abs.to_f
25
+ end
26
+
27
+ end
28
+
29
+ # Contains logic for dealing with input that includes Ruby's core ::Enumerable
30
+ class Enumerable
31
+
32
+ # @param [::Enumerable] enum An enumerable (Array, Set, etc) to operate on
33
+ def initialize(enum)
34
+ @enum = enum
35
+ end
36
+
37
+ # @param [Numeric] input
38
+ # @return [Float]
39
+ def numerator(input)
40
+ @enum.to_a.index(input).to_f
41
+ end
42
+
43
+ # @return [Float]
44
+ def denominator
45
+ (@enum.size - 1).to_f
46
+ end
47
+
48
+ end
49
+
50
+ # Map Ruby classes/modules to scaling source classes/modules
51
+ MAP = {
52
+ ::Enumerable => Source::Enumerable,
53
+ ::Range => Source::Range
54
+ }
55
+
56
+ # Build the appropriate scaling source class for the given Ruby object
57
+ # @param [::Enumerable] source
58
+ # @return [Scale::Source::Enumerable, Scale::Source::Range]
59
+ def self.new(source)
60
+ klass = MAP[source.class]
61
+ if klass.nil?
62
+ klasses = MAP.select { |k,v| source.kind_of?(k) }
63
+ klass = klasses.values.first
64
+ end
65
+ klass.new(source) unless klass.nil?
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,84 @@
1
+ require "helper"
2
+
3
+ class CoreExtTest < Test::Unit::TestCase
4
+
5
+ context "Numeric" do
6
+
7
+ setup do
8
+ require "scale/core_ext"
9
+ end
10
+
11
+ context "#scaled" do
12
+
13
+ should "amplify" do
14
+ output = 0.10.scaled.using(0..1, 0..127)
15
+ assert_equal(12, output)
16
+ end
17
+
18
+ should "deamplify" do
19
+ output = 22.scaled.using(0..150, 0..15)
20
+ assert_equal(2, output)
21
+ end
22
+
23
+ should "scale neg/pos into pos/pos" do
24
+ output = 10.scaled.using(-24..24, 0..3.0)
25
+ assert_equal(2.125, output)
26
+ end
27
+
28
+ should "scale neg/pos into another neg/pos" do
29
+ output = 10.scaled.using(-24..24, -3..3.0)
30
+ assert_equal(1.25, output)
31
+ end
32
+
33
+ end
34
+
35
+ context "#scaled_using" do
36
+
37
+ should "scale neg/neg into neg/pos" do
38
+ output = -14.scaled_using(-24..-12, -3..3.0)
39
+ assert_equal(2, output)
40
+ end
41
+
42
+ should "scale neg/neg to another neg/neg" do
43
+ output = -16.scaled_using(-24..-12, -5..-3.0)
44
+ assert_equal(-3.666666666666667, output)
45
+ end
46
+
47
+ should "scale neg/neg to pos/pos" do
48
+ output = -18.scaled_using(-24..-12, 1..3.0)
49
+ assert_equal(2, output)
50
+ end
51
+
52
+ end
53
+
54
+ context "#scaled_to" do
55
+
56
+ should "take an array as the source" do
57
+ output = 8.scaled_to(0..10).from([0, 2, 4, 8, 16, 64])
58
+ assert_equal(6, output)
59
+ end
60
+
61
+ should "output a float when specified" do
62
+ output = 12.scaled_to(0..1.0).from(0..120)
63
+ assert_equal(0.10, output)
64
+ end
65
+
66
+ end
67
+
68
+ context "#scaled_from" do
69
+
70
+ should "take an ascending array as the destination" do
71
+ output = 0.40.scaled_from(0..1).to([0, 2, 4, 8, 12, 16, 32, 64, 128, 512])
72
+ assert_equal(8, output)
73
+ end
74
+
75
+ should "take a descending array as the destination" do
76
+ output = 0.40.scaled_from(0..1).to([512, 128, 64, 32, 16, 12, 8, 4, 2, 0])
77
+ assert_equal(32, output)
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+
84
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,8 @@
1
+ dir = File.dirname(File.expand_path(__FILE__))
2
+ $LOAD_PATH.unshift dir + '/../lib'
3
+
4
+ require 'test/unit'
5
+ require "mocha/test_unit"
6
+ require "shoulda-context"
7
+
8
+ require "scale"
@@ -0,0 +1,92 @@
1
+ require "helper"
2
+
3
+ class ScaleTest < Test::Unit::TestCase
4
+
5
+ context "Scale" do
6
+
7
+ context "#initialize" do
8
+
9
+ should "recognize a Range" do
10
+ scheme = Scale::Scheme.new(:source => 0..1, :destination => 0..100)
11
+ assert_equal(Scale::Destination::Range, scheme.destination.class)
12
+ end
13
+
14
+ should "recognize an Array" do
15
+ scheme = Scale::Scheme.new(:source => 0..1, :destination => [0, 10, 20, 50])
16
+ assert_equal(Scale::Destination::Enumerable, scheme.destination.class)
17
+ end
18
+
19
+ end
20
+
21
+ context "#scale" do
22
+
23
+ should "amplify" do
24
+ output = Scale.transform(0.10).using(0..1, 0..127)
25
+ assert_equal(12, output)
26
+ end
27
+
28
+ should "deamplify" do
29
+ output = Scale.transform(22).using(0..150, 0..15)
30
+ assert_equal(2, output)
31
+ end
32
+
33
+ should "scale neg/pos into pos/pos" do
34
+ output = Scale.transform(10).using(-24..24, 0..3.0)
35
+ assert_equal(2.125, output)
36
+ end
37
+
38
+ should "scale neg/pos into another neg/pos" do
39
+ output = Scale.transform(10).using(-24..24, -3..3.0)
40
+ assert_equal(1.25, output)
41
+ end
42
+
43
+ should "scale neg/neg into neg/pos" do
44
+ output = Scale.transform(-14).using(-24..-12, -3..3.0)
45
+ assert_equal(2, output)
46
+ end
47
+
48
+ should "scale neg/neg to another neg/neg" do
49
+ output = Scale.transform(-16).using(-24..-12, -5..-3.0)
50
+ assert_equal(-3.666666666666667, output)
51
+ end
52
+
53
+ should "scale neg/neg to pos/pos" do
54
+ output = Scale.transform(-18).using(-24..-12, 1..3.0)
55
+ assert_equal(2, output)
56
+ end
57
+
58
+ should "take an array as the source" do
59
+ output = Scale.transform(8).using([0, 2, 4, 8, 16, 64], 0..10)
60
+ assert_equal(6, output)
61
+ end
62
+
63
+ should "take an set as the source" do
64
+ output = Scale.transform(8).using(Set.new([0, 2, 4, 8, 16, 64]), 0..10)
65
+ assert_equal(6, output)
66
+ end
67
+
68
+ should "output a float when specified" do
69
+ output = Scale.transform(12).using(0..120, 0..1.0)
70
+ assert_equal(0.10, output)
71
+ end
72
+
73
+ should "take an ascending array as the destination" do
74
+ output = Scale.transform(0.40).using(0..1, [0, 2, 4, 8, 12, 16, 32, 64, 128, 512])
75
+ assert_equal(8, output)
76
+ end
77
+
78
+ should "take a descending array as the destination" do
79
+ output = Scale.transform(0.40).using(0..1, [512, 128, 64, 32, 16, 12, 8, 4, 2, 0])
80
+ assert_equal(32, output)
81
+ end
82
+
83
+ should "take a set as the destination" do
84
+ output = Scale.transform(0.40).using(0..1, Set.new([0, 2, 4, 8, 12, 16, 32, 64, 128, 512]))
85
+ assert_equal(8, output)
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+
92
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: analog
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Ari Russo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mocha
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: shoulda-context
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Provides a quick way to take a number and scale it given a source and
56
+ destination Array, Range, Set, etc
57
+ email:
58
+ - ari.russo@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - LICENSE
64
+ - README.md
65
+ - lib/scale.rb
66
+ - lib/scale/core_ext.rb
67
+ - lib/scale/destination.rb
68
+ - lib/scale/scheme.rb
69
+ - lib/scale/source.rb
70
+ - test/core_ext_test.rb
71
+ - test/helper.rb
72
+ - test/scale_test.rb
73
+ homepage: http://github.com/arirusso/analog
74
+ licenses:
75
+ - Apache License (2.0)
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 1.3.6
91
+ requirements: []
92
+ rubyforge_project: analog
93
+ rubygems_version: 2.2.2
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: A Ruby helper for scaling numbers
97
+ test_files:
98
+ - test/core_ext_test.rb
99
+ - test/scale_test.rb