sfzer 0.4

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.
@@ -0,0 +1,70 @@
1
+ # This file is part of SFZer.
2
+ #
3
+ # SFZer is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # SFZer is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with SFZer. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+
17
+
18
+ # A Sample with a note value (such as A1, C#4, F5, etc) in the filename.
19
+ class NamedSample < Sample
20
+ include Comparable # instant sortability; I love Mixins!
21
+
22
+ # Instantiate the NamedSample.
23
+ def initialize( sample, path=false )
24
+ super( sample, path )
25
+ @value = NamedSample.value( sample )
26
+ end
27
+
28
+
29
+ # returns true if the String name is a NamedSample.
30
+ def NamedSample.sample?( name )
31
+ name =~ /^.*(#{regexp}).*\.#{Sample.suffix_regexp}$/i
32
+ end
33
+
34
+
35
+ # returns the RegExp used to test for a NamedSample.
36
+ def NamedSample.regexp
37
+ /[ABCDEFG](#)?\d/
38
+ end
39
+
40
+
41
+ # returns the numeric value of a NamedSample
42
+ def NamedSample.value( string )
43
+ result = false
44
+
45
+ if( string =~ /([ABCDEFG])(#)?(\d)/i )
46
+ notes = { 'C'=>0, 'D'=>2, 'E'=>4, 'F'=>5, 'G'=>7, 'A'=>9, 'B'=>11}
47
+ note = $1
48
+ sharp = 0
49
+ if $2 == "#" then
50
+ sharp = 1
51
+ end
52
+ octave = $3.to_i
53
+ result = ((octave * 12) + 12) + notes[note] + sharp
54
+ end
55
+
56
+ result
57
+ end
58
+
59
+
60
+ # returns the numeric value of this instance of NamedSample
61
+ def value
62
+ @value
63
+ end
64
+
65
+
66
+ # required by the Comparable mixin; allows sorting
67
+ def <=>(anOther)
68
+ value <=> anOther.value
69
+ end
70
+ end
data/lib/Sample.rb ADDED
@@ -0,0 +1,83 @@
1
+ # This file is part of SFZer.
2
+ #
3
+ # SFZer is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # SFZer is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with SFZer. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ # An SFZ Sample is a file that represents a single note.
17
+ class Sample
18
+ attr_reader :path, :sample
19
+ # sample = full path to sample
20
+ def initialize( sample, path=false )
21
+ @sample = sample
22
+ @path = path
23
+ end
24
+
25
+ # ----------------------------------------------
26
+ # static methods
27
+ # ----------------------------------------------
28
+
29
+ # yields all the sample suffixes that SFZ supports
30
+ def Sample.suffixes
31
+ ['wav', 'ogg', 'aif', 'aiff'].each do |type|
32
+ yield type
33
+ end
34
+ end
35
+
36
+
37
+ # returns a regexp string for all possible sample suffixes
38
+ def Sample.suffix_regexp
39
+ # build filename suffix types
40
+ types = "("
41
+ Sample.suffixes do |s|
42
+ types = types + "#{s}|"
43
+ end
44
+ types.chop!
45
+ types = types + ")"
46
+ end
47
+
48
+
49
+ # returns a Dir glob string for all possible sample suffixes
50
+ def Sample.suffix_glob
51
+ # build filename suffix types
52
+ types = "{"
53
+ Sample.suffixes do |s|
54
+ types = types + "#{s},"
55
+ end
56
+ types.chop!
57
+ types = types + "}"
58
+ end
59
+
60
+ def Sample.sample?( name )
61
+ name =~ /*.#{Sample.suffix_regexp}$/i
62
+ end
63
+
64
+
65
+
66
+ def note
67
+ false
68
+ end
69
+
70
+
71
+ def value
72
+ false
73
+ end
74
+
75
+ def to_s
76
+ if @path
77
+ "#{@path}#{SFZer::SEPARATOR}#{@sample}"
78
+ else
79
+ "#{@sample}"
80
+ end
81
+ end
82
+
83
+ end
data/lib/sfzer.rb ADDED
@@ -0,0 +1,128 @@
1
+ # This file is part of SFZer.
2
+ #
3
+ # SFZer is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # SFZer is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with SFZer. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+
17
+ # Program Requirements
18
+ require 'optparse'
19
+
20
+ require 'Sample'
21
+ require 'NamedSample'
22
+ require 'Multisample'
23
+ require 'DirectoryProcessor'
24
+
25
+ # Parses the command line options and sets the program into motion.
26
+ class SFZer
27
+
28
+ # Version information
29
+ VERSION = '0.4'
30
+ VERSION_DATE = "08 Feb 2009"
31
+ URL = "http://sfzer.rubyforge.org"
32
+ NAME = "SFZer"
33
+ SEPARATOR = "\\"
34
+
35
+ #Exit codes
36
+ EXITSTATUS_COMPLETE = 0
37
+ EXITSTATUS_NO_MULTISAMPLES_FOUND = 1
38
+ EXITSTATUS_ARG_ERROR = 2
39
+ EXITSTATUS_USER_ABORT = 3
40
+
41
+
42
+ # Execute the SFZer program
43
+ def do( argv )
44
+ sfzer = process_options( argv )
45
+ sfzer.scan_dirs
46
+ end
47
+
48
+
49
+ protected
50
+
51
+
52
+ # Processes each command line option and returns a configured Directory Processor.
53
+ def process_options( argv )
54
+ sfzer = DirectoryProcessor.new
55
+ exit_now = false
56
+
57
+ begin
58
+ argv.options{ |opt|
59
+
60
+ # help message banner and usage instructions
61
+ opt.banner = "A script by Chris Tessmer that creates SFZ files from multisamples.\n"
62
+ opt.banner += "version #{SFZer::VERSION}, #{SFZer::VERSION_DATE}.\n"
63
+ opt.banner += "\n"
64
+ opt.banner += "Usage: sfzer.rb [options] directorie(s)\n"
65
+ opt.banner += "\n"
66
+
67
+ # defining options
68
+ opt.on( "Options:" )
69
+ opt.on( "\n" )
70
+ opt.on( "Mapping:" )
71
+ opt.on( "--xfade", "-x", "Map crossfades between region keycenters *" ) {
72
+ sfzer.mapping = Multisample::XFADE
73
+ }
74
+
75
+ opt.on( "--strictkeys", "-s", "Map regions on a 1:1 key:sample basis" ) {
76
+ sfzer.mapping = Multisample::STRICTKEYS
77
+ }
78
+
79
+
80
+ opt.on( "\n" )
81
+ opt.on( "File placement:" )
82
+ opt.on( "--top-dir", "-t", "Create SFZ files in topmost directory *" ){
83
+ sfzer.generate_in_top_dir = true
84
+ }
85
+
86
+ opt.on( "--each-dir", "-e", "Create each SFZ file with its own Samples"){
87
+ sfzer.generate_in_top_dir = false
88
+ }
89
+
90
+ opt.on( "\n" )
91
+ opt.on( "Decoration:" )
92
+ opt.on( "--message 'TEXT'", "-m", "Add TEXT to each generated SFZ file header" ) {
93
+ |text|
94
+ sfzer.message = text
95
+ }
96
+
97
+ opt.on( "--help", "-h", "This text" ) {
98
+ puts opt
99
+ exit_now = true
100
+ }
101
+
102
+ opt.parse!
103
+
104
+ # Exit with help message if no arguments are given
105
+ if( argv.length == 0 )
106
+ puts opt
107
+ exit_now = true
108
+ end
109
+ }
110
+ rescue Exception => e
111
+ STDERR.puts "\n#{e.class}"
112
+ STDERR.puts "\t#{e}\n"
113
+ STDERR.puts "Since this messed up the arguments, I am ABORTING THE PROGRAM. Sorry.\n\n"
114
+ exit EXITSTATUS_ARG_ERROR
115
+ end
116
+
117
+ # Handle graceful exit (couldn't put in opt block because of rescue clause)
118
+ if exit_now
119
+ exit EXITSTATUS_COMPLETE
120
+ end
121
+
122
+ argv.each{ |arg|
123
+ sfzer.dirs.push( arg )
124
+ }
125
+
126
+ sfzer
127
+ end
128
+ end
@@ -0,0 +1,115 @@
1
+ class MultisampleTest < Test::Unit::TestCase
2
+ # def setup
3
+ # end
4
+
5
+ # def teardown
6
+ # end
7
+
8
+ def name
9
+ "Multisample tests"
10
+ end
11
+
12
+
13
+ # SFZ *REQUIRES* that path separators follow the DOS convention (i.e., lean forward)
14
+ def test_default_path_file_seperators_are_dos
15
+ test_paths = [
16
+ 'test/path',
17
+ './test/path/with/dot',
18
+ '../test/path/with/twodots',
19
+ '../test/path/with\\twodots'
20
+ ]
21
+
22
+ test_paths.each{ |path|
23
+ m = Multisample.new( path, "Default Path Test Multisample" )
24
+ m.default_path = path
25
+ sfz = m.to_sfz
26
+ line = /^(default_path=.*)$/.match( sfz )[1]
27
+ assert_no_match( /\//, line, "\"#{line}\" does NOT contain only DOS path separators." )
28
+ }
29
+ end
30
+
31
+
32
+
33
+
34
+ # Tests that A1.wav through G#8.wav are valid filenames
35
+ def test_simple_named_note_filenames
36
+ # construct notes
37
+ notes = Array.new
38
+ ['A', 'B', 'C', 'D', 'E', 'F', 'G'].each do |a|
39
+ ['1','2','3','4','5','6','7','8'].each do |n|
40
+ Sample.suffixes do |s|
41
+ notes.push "#{a}#{n}.#{s}" # natural
42
+ notes.push "#{a}##{n}.#{s}" # sharp
43
+ end
44
+ end
45
+ end
46
+
47
+ notes.each do |note|
48
+ assert( Multisample.multisample_filename?( note ) ,
49
+ "\"#{note}\" is NOT a valid named note name.")
50
+ end
51
+ end
52
+
53
+
54
+
55
+ def test_simple_numeric_note_filenames
56
+ (0..127).each do |i|
57
+ # Note 001.wav
58
+ n = sprintf "%03d", i
59
+ Sample.suffixes do |s|
60
+ note = "#{n}.#{s}"
61
+ assert( Multisample.multisample_filename?( note ) ,
62
+ "\"#{note}\" is NOT a valid 3-digit numeric note name.")
63
+ end
64
+
65
+ # Note 01.wav
66
+ n = sprintf "%02d", i
67
+ Sample.suffixes do |s|
68
+ note = "#{n}.#{s}"
69
+ assert( Multisample.multisample_filename?( note ) ,
70
+ "\"#{note}\" is NOT a valid 2-to-3-digit numeric note name.")
71
+ end
72
+ end
73
+ end
74
+
75
+
76
+
77
+ def test_real_filenames
78
+ # Names of real files that should succeed
79
+ names = [ 'NRGK4ObBass F#1.wav',
80
+ 'OBX-SoundTrk_03B2.wav',
81
+ 'Bass06_C2.wav',
82
+ ]
83
+ for name in names
84
+ assert( Multisample.multisample_filename?( name ),
85
+ "\"#{name}\"is NOT a valid multisample filename" )
86
+ end
87
+
88
+ #Names of real files that should fail
89
+ #badnames = ['119F#001.wav']
90
+ #for name in badnames
91
+ # assert( Multisample.multisample_filename?( name ) == false ,
92
+ # "\"#{name}\" IS a valid multisample filename, but shouldn't be!" )
93
+ #end
94
+ end
95
+
96
+
97
+ def test_banner_line_lengths
98
+ message = "(The GPL License, version 3) Copyright (c) 2008 Chris Tessmer This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>."
99
+ m = Multisample.new( "" )
100
+ m.message = message
101
+ max_line_length = 80
102
+ biggest_line_length_yet = 0
103
+
104
+ s = m.sfz_banner
105
+ s.each_line{ |x|
106
+ if x.length > biggest_line_length_yet
107
+ biggest_line_length_yet = x.length
108
+ end
109
+ }
110
+
111
+ assert_operator biggest_line_length_yet, :<=, max_line_length , "SFZ banner lines exceed #{max_line_length} characters per line:\n\n#{s}"
112
+ end
113
+ end
114
+
115
+
@@ -0,0 +1,55 @@
1
+ class NamedSampleTest < Test::Unit::TestCase
2
+ def name
3
+ "NamedSample tests"
4
+ end
5
+
6
+ def test_for_named_samples_files
7
+ names = [
8
+ 'Bassy01(C3).wav',
9
+ 'D#2.wav',
10
+ 'Bass10_C1.wav'
11
+ ]
12
+
13
+ names.each do |name|
14
+ assert( NamedSample.sample?( name ) ,
15
+ "\"#{name}\" is NOT a valid note name.")
16
+ end
17
+ end
18
+
19
+
20
+ def test_for_named_sample_values
21
+ sample_values = {
22
+ 'C4.ogg' => 60,
23
+ 'Bassy01(C3).wav' => 48,
24
+ 'D#2.wav' => 39,
25
+ 'Bass10_C1.wav' => 24
26
+ }
27
+
28
+ sample_values.each do |sample, value|
29
+ assert_equal( value, NamedSample.value( sample ),
30
+ "The value of \"#{sample}\" is NOT \"#{value}\".")
31
+ end
32
+ end
33
+
34
+
35
+ def test_named_sample_comparables
36
+ c3 = NamedSample.new( "Bassy01(C3).wav" )
37
+ assert_equal( 48, c3.value )
38
+
39
+ d2 = NamedSample.new( 'D#2.aif' )
40
+ assert_equal( 39, d2.value )
41
+
42
+ c3o = NamedSample.new( "C3.ogg" )
43
+ assert_equal( 48, c3o.value )
44
+
45
+ # Since
46
+ c1 = NamedSample.new( "C1.wav" ) #24
47
+
48
+ b1 = NamedSample.new( "B1.wav" ) #35
49
+
50
+ assert_operator b1, :>, c1, "#{b1} > #{c1}"
51
+ assert_operator d2, :<, c3, "#{d2} < #{c3}"
52
+ assert_operator c3, :==, c3o, "#{c3} = #{c3o}"
53
+ end
54
+
55
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ lib = File.dirname(__FILE__) + '/../lib/'
4
+ test = File.dirname(__FILE__) + '/../test/'
5
+ $: << lib
6
+ $: << test
7
+
8
+ require 'test/unit'
9
+ require 'sfzer'
10
+
11
+ require 'NamedSampleTest'
12
+ require 'MultisampleTest'
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sfzer
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.4"
5
+ platform: ruby
6
+ authors:
7
+ - Chris Tessmer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-11 00:00:00 +00:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.8.3
24
+ version:
25
+ description: SFZer recursively scans through directories of Multisamples (.wav, .aiff, .ogg, etc) and automagically generates SFZ soundfonts from what it finds.
26
+ email:
27
+ - http://christessmer.com
28
+ executables:
29
+ - sfzer
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - History.txt
34
+ - Manifest.txt
35
+ - README.txt
36
+ files:
37
+ - History.txt
38
+ - Manifest.txt
39
+ - README.txt
40
+ - COPYING
41
+ - Rakefile
42
+ - bin/sfzer
43
+ - lib/sfzer.rb
44
+ - lib/DirectoryProcessor.rb
45
+ - lib/Sample.rb
46
+ - lib/NamedSample.rb
47
+ - lib/Multisample.rb
48
+ - test/test_sfzer.rb
49
+ - test/NamedSampleTest.rb
50
+ - test/MultisampleTest.rb
51
+ has_rdoc: true
52
+ homepage: http://sfzer.rubyforge.org
53
+ post_install_message:
54
+ rdoc_options:
55
+ - --main
56
+ - README.txt
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ requirements: []
72
+
73
+ rubyforge_project: sfzer
74
+ rubygems_version: 1.3.1
75
+ signing_key:
76
+ specification_version: 2
77
+ summary: SFZer recursively scans through directories of Multisamples (.wav, .aiff, .ogg, etc) and automagically generates SFZ soundfonts from what it finds.
78
+ test_files:
79
+ - test/test_sfzer.rb