sfzer 0.4 → 0.5.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.
- data/History.txt +26 -1
- data/Manifest.txt +2 -0
- data/Notes.txt +35 -0
- data/README.txt +9 -6
- data/bin/sfzer +1 -0
- data/lib/DirectoryProcessor.rb +132 -64
- data/lib/Multisample.rb +50 -15
- data/lib/NamedSample.rb +6 -4
- data/lib/Sample.rb +2 -1
- data/lib/sfzer.rb +157 -128
- data/test/DirectoryProcessorTest.rb +27 -0
- data/test/MultisampleTest.rb +22 -21
- data/test/NamedSampleTest.rb +13 -3
- data/test/test_sfzer.rb +1 -0
- metadata +5 -2
data/History.txt
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
|
+
=== 0.5 / 2009-02-17
|
|
2
|
+
* 3 Features:
|
|
3
|
+
|
|
4
|
+
* Added --padding flag to pad the ends of multisamples (default: 12).
|
|
5
|
+
* Added --transpose N flag to transpose sample values by N semitones.
|
|
6
|
+
(for fog: -t-12)
|
|
7
|
+
* Added rudimentary unique filename support.
|
|
8
|
+
|
|
9
|
+
* 1 Improvement:
|
|
10
|
+
|
|
11
|
+
* Smarter detection: It takes at least three regions to make a Multisample.
|
|
12
|
+
|
|
13
|
+
* 5 Fixes:
|
|
14
|
+
|
|
15
|
+
* Fixed a bug that prevented recursion below any directory with Multisamples.
|
|
16
|
+
* Corrected bug that would cause a crash on Multisamples with no names (test
|
|
17
|
+
added).
|
|
18
|
+
* Corrected bug that allowed "-" and "_" at the end of Multisample names
|
|
19
|
+
(test added).
|
|
20
|
+
* Corrected bug that did not recognize lowercase note names (test added).
|
|
21
|
+
* Removed premature numeric note detection, as numeric notes are currently not
|
|
22
|
+
supported.
|
|
23
|
+
|
|
24
|
+
|
|
1
25
|
=== 0.4.0 / 2009-02-08
|
|
2
26
|
|
|
3
27
|
* 4 Improvements:
|
|
4
28
|
|
|
5
|
-
* Smarter Multisample.get_xfade method: xfade in/outs are only calculated when
|
|
29
|
+
* Smarter Multisample.get_xfade method: xfade in/outs are only calculated when
|
|
30
|
+
needed (i.e., for gaps between Samples that are over a semitone).
|
|
6
31
|
* Introduced a better system for unique filenames when using the --top-dir option.
|
|
7
32
|
* Text entered via --message now wraps every 80 lines.
|
|
8
33
|
* Upgraded license to GPLv3.
|
data/Manifest.txt
CHANGED
data/Notes.txt
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
= Notes
|
|
2
|
+
|
|
3
|
+
== SFZ Client Errata
|
|
4
|
+
=== Dimension Pro
|
|
5
|
+
When reading SFZ, Dimension Pro in Windows cannot load samples referenced by
|
|
6
|
+
absolute path:
|
|
7
|
+
|
|
8
|
+
==== SFZ snippet:
|
|
9
|
+
<control>
|
|
10
|
+
default_path=C:\snd\samples\prog_bass
|
|
11
|
+
<region> sample=ProgK1Bass E0.wav pitch_keycenter=16
|
|
12
|
+
<region> sample=ProgK1Bass G0.wav pitch_keycenter=19
|
|
13
|
+
|
|
14
|
+
==== Dimension Pro 1.2 sfzlog.txt error snippet:
|
|
15
|
+
c:\dev\sfzer\trunk\c:\snd\samples\prog_bass\progk1bass e0.wav not found or couldn't be loaded.
|
|
16
|
+
c:\dev\sfzer\trunk\c:\snd\samples\prog_bass\progk1bass g0.wav not found or couldn't be loaded.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
=== Rapture
|
|
22
|
+
Rapture in Windows has the opposite problem from Dimension. It can load via
|
|
23
|
+
absolute paths (the Dimension Pro snippet above will pass without
|
|
24
|
+
a murmur), but it wont't load samples referenced by relative paths:
|
|
25
|
+
|
|
26
|
+
==== SFZ snippet:
|
|
27
|
+
<control>
|
|
28
|
+
default_path=..\..\..\snd\samples\prog_bass
|
|
29
|
+
<region> sample=ProgK1Bass E0.wav pitch_keycenter=16
|
|
30
|
+
<region> sample=ProgK1Bass G0.wav pitch_keycenter=19
|
|
31
|
+
|
|
32
|
+
==== Rapture 1.1 sfzlog.txt error snippet:
|
|
33
|
+
File c:\dev\sfzer\trunk\\snd\samples\prog_bass\progk1bass e0.wav not found or couldn't be loaded.
|
|
34
|
+
File c:\dev\sfzer\trunk\\snd\samples\prog_bass\progk1bass g0.wav not found or couldn't be loaded.
|
|
35
|
+
|
data/README.txt
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
= SFZer
|
|
2
2
|
|
|
3
3
|
* http://sfzer.rubyforge.org
|
|
4
|
+
* http://rubyforge.org/projects/sfzer/
|
|
4
5
|
* http://christessmer.com
|
|
5
6
|
|
|
7
|
+
|
|
6
8
|
== DESCRIPTION:
|
|
7
9
|
|
|
8
10
|
SFZer recursively scans through directories of Multisamples (.wav, .aiff, .ogg,
|
|
@@ -16,13 +18,15 @@ etc) and automagically generates SFZ soundfonts from what it finds.
|
|
|
16
18
|
|
|
17
19
|
== FEATURES/PROBLEMS:
|
|
18
20
|
|
|
19
|
-
* Quickly converts entire
|
|
21
|
+
* Quickly converts entire directories of named samples into SFZ instruments.
|
|
20
22
|
* Creates quick keyboard mapping from sample file names.
|
|
21
23
|
* Automatically computes xfades between adjacent samples over a semitone apart.
|
|
22
24
|
|
|
23
25
|
=== Future Plans:
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
+
* Numeric Notes (some filenames use the numeric instead of chromatic key value)
|
|
27
|
+
* Future Music sample name format (sample order number + chromatic value)
|
|
28
|
+
* Specify multisamples to include or exclude by name or pattern.
|
|
29
|
+
* Specify minimum number of regions/samples in a multisample.
|
|
26
30
|
* loop_mode=one-shot option for percussion
|
|
27
31
|
|
|
28
32
|
=== Ideas without much substance yet:
|
|
@@ -31,7 +35,6 @@ etc) and automagically generates SFZ soundfonts from what it finds.
|
|
|
31
35
|
* (possibly) 2-n pass (as opposed to 1-pass) processing?
|
|
32
36
|
|
|
33
37
|
|
|
34
|
-
|
|
35
38
|
== SYNOPSIS:
|
|
36
39
|
|
|
37
40
|
% sfzer [options] directorie(s)
|
|
@@ -44,14 +47,14 @@ etc) and automagically generates SFZ soundfonts from what it finds.
|
|
|
44
47
|
|
|
45
48
|
== INSTALL:
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
% sudo gem install sfzer
|
|
48
51
|
|
|
49
52
|
|
|
50
53
|
== LICENSE:
|
|
51
54
|
|
|
52
55
|
(The GPL License, version 3)
|
|
53
56
|
|
|
54
|
-
Copyright (c) 2008 Chris Tessmer
|
|
57
|
+
Copyright (c) 2008, 2009 Chris Tessmer
|
|
55
58
|
|
|
56
59
|
This program is free software: you can redistribute it and/or modify
|
|
57
60
|
it under the terms of the GNU General Public License as published by
|
data/bin/sfzer
CHANGED
data/lib/DirectoryProcessor.rb
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# along with SFZer. If not, see <http://www.gnu.org/licenses/>.
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
# Processes one or more directories
|
|
18
19
|
#
|
|
19
20
|
# * Scan for Multisamples
|
|
@@ -22,7 +23,8 @@
|
|
|
22
23
|
# TODO: Refactor this into an application class
|
|
23
24
|
class DirectoryProcessor
|
|
24
25
|
|
|
25
|
-
attr_accessor :dirs, :message, :mapping, :generate_in_top_dir
|
|
26
|
+
attr_accessor :dirs, :message, :mapping, :generate_in_top_dir,
|
|
27
|
+
:transpose, :padding
|
|
26
28
|
|
|
27
29
|
# Instantiate the DirectoryProcessor
|
|
28
30
|
def initialize
|
|
@@ -41,38 +43,50 @@ class DirectoryProcessor
|
|
|
41
43
|
# If true (which is the default), all SFZ files are generated in the
|
|
42
44
|
# directory the script was called from.
|
|
43
45
|
@generate_in_top_dir = true
|
|
46
|
+
|
|
47
|
+
@transpose = 0
|
|
48
|
+
@padding = 12
|
|
44
49
|
end
|
|
45
|
-
|
|
46
50
|
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Scan each directory for multisamples
|
|
54
|
+
def scan_dirs
|
|
55
|
+
multidirs = get_multisample_directories
|
|
56
|
+
|
|
57
|
+
# convert each multisample directory
|
|
58
|
+
pwd = Dir.getwd
|
|
59
|
+
if multidirs.size > 0
|
|
60
|
+
|
|
61
|
+
puts "converting..."
|
|
62
|
+
for dir in multidirs
|
|
63
|
+
Dir.chdir( pwd )
|
|
64
|
+
process_multisample_dir( dir, pwd )
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
else
|
|
68
|
+
raise NoMultisamplesInDirectoryException
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
47
74
|
# starting from directory,
|
|
48
75
|
def process_multisample_dir( path, top_path )
|
|
49
76
|
named_hash = {}
|
|
50
|
-
numbered_hash = Hash.new
|
|
77
|
+
#numbered_hash = Hash.new
|
|
51
78
|
|
|
52
|
-
puts
|
|
79
|
+
puts
|
|
53
80
|
|
|
54
81
|
Dir.chdir( path )
|
|
55
82
|
Dir.glob( "*#{Sample.suffix_glob}" ).each{ |file|
|
|
56
83
|
|
|
57
84
|
# Start tracking potential instrument names
|
|
58
85
|
if NamedSample.sample?( file )
|
|
86
|
+
|
|
59
87
|
# Sanitize hashkeys
|
|
60
88
|
file_basename = File.basename( file )
|
|
61
|
-
|
|
62
|
-
# FIXME: Potential bug: this assumes the first portion of the string is the most
|
|
63
|
-
# significant indicator
|
|
64
|
-
(file_hashkey, ) = file_basename.gsub( /\.#{Sample.suffix_regexp}$/, '' ).sub( NamedSample.regexp, '?' ).split('?')
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# if there is no label for the multisample, name it after the directory it's in.
|
|
68
|
-
if file_hashkey.nil?
|
|
69
|
-
file_hashkey = File.basename( path )
|
|
70
|
-
else
|
|
71
|
-
# otherwise, strip it of common paired symbols
|
|
72
|
-
file_hashkey.sub!( /_\d\d(_)$/, '' )
|
|
73
|
-
file_hashkey.sub!( /[(\[{_]$/, '' )
|
|
74
|
-
file_hashkey.sub!( /\s*$/, '' )
|
|
75
|
-
end
|
|
89
|
+
file_hashkey = sanitize_multisample_name( file_basename, path )
|
|
76
90
|
|
|
77
91
|
#add Sample to a hashed Multisample
|
|
78
92
|
multi = get_multisample( file_hashkey, path, named_hash )
|
|
@@ -80,82 +94,123 @@ class DirectoryProcessor
|
|
|
80
94
|
named_hash[ file_hashkey ] = multi
|
|
81
95
|
end
|
|
82
96
|
}
|
|
83
|
-
puts
|
|
84
97
|
|
|
85
98
|
# for each hash entry, make an SFZ
|
|
86
99
|
for key in named_hash.keys.sort
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
|
|
101
|
+
filename = key
|
|
90
102
|
# generate the SFZ file from the Multisample
|
|
103
|
+
|
|
91
104
|
extra_name = ""
|
|
92
105
|
if @generate_in_top_dir
|
|
93
106
|
Dir.chdir( top_path )
|
|
94
107
|
relative_path = path.gsub( /^\\/, '' ).gsub( /^\//, '' )
|
|
95
108
|
named_hash[ key ].default_path = true
|
|
96
|
-
|
|
109
|
+
|
|
110
|
+
#filename = "#{top_path}/#{key}.sfz"
|
|
97
111
|
# create a name based on subfolders to ensure uniqueness
|
|
98
112
|
extra_name = relative_path.gsub( /\\/, "_" ).gsub( /\//, "_" ) + "_"
|
|
99
|
-
extra_name.gsub!(/^(
|
|
113
|
+
extra_name.gsub!(/^(\.|-|_)+/, '')
|
|
114
|
+
|
|
115
|
+
filename = "#{extra_name}#{filename}"
|
|
116
|
+
|
|
117
|
+
end
|
|
118
|
+
filename = filename.gsub( ' ', '_')
|
|
119
|
+
filename = get_unique_filename( filename, path )
|
|
120
|
+
|
|
121
|
+
puts "#{key}: #{filename}"
|
|
122
|
+
multi = named_hash[ key ]
|
|
123
|
+
#puts multi.to_sfz
|
|
124
|
+
if( multi.valid? )
|
|
125
|
+
f = File.new( filename, "w" )
|
|
126
|
+
f.puts multi.to_sfz
|
|
127
|
+
f.close
|
|
128
|
+
else
|
|
129
|
+
puts "NOT VALID\n\n\n"
|
|
100
130
|
end
|
|
101
131
|
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def get_unique_filename( name, path )
|
|
137
|
+
result = "#{name}.sfz"
|
|
138
|
+
count = 0
|
|
139
|
+
while( FileTest.exists?( result ) )
|
|
140
|
+
result = "#{name}-#{count}.sfz"
|
|
141
|
+
count = count + 1
|
|
142
|
+
end
|
|
143
|
+
result
|
|
144
|
+
end
|
|
145
|
+
|
|
102
146
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
147
|
+
# Santizes a multisample
|
|
148
|
+
#
|
|
149
|
+
#
|
|
150
|
+
#
|
|
151
|
+
def sanitize_multisample_name( string, path )
|
|
152
|
+
# puts "sanitizing '#{string}':"
|
|
106
153
|
|
|
154
|
+
# FIXME: Potential bug: this assumes the first portion of the string
|
|
155
|
+
# is the most significant indicator
|
|
156
|
+
result = string.gsub( /\.#{Sample.suffix_regexp}$/, '' )
|
|
157
|
+
result = result.sub( NamedSample.regexp, '?' )
|
|
158
|
+
(result,) = result.split('?')
|
|
107
159
|
|
|
160
|
+
# sometimes, sample producers A
|
|
161
|
+
if result.nil?
|
|
162
|
+
result = File.basename( path )
|
|
108
163
|
end
|
|
164
|
+
|
|
165
|
+
# otherwise, strip it of common paired symbols
|
|
166
|
+
result.sub!( /_\d\d(_)$/, '' ) # _01 + .wav and _01_.wav
|
|
167
|
+
result.sub!( /[(\[{_]$/, '' ) # blahblha( + A#1
|
|
168
|
+
result.sub!( /[-_]+$/, '' )
|
|
169
|
+
#result.sub!( /^[-_]+/, '' )
|
|
170
|
+
result.sub!( /\s*$/, '' )
|
|
171
|
+
result
|
|
109
172
|
end
|
|
110
173
|
|
|
111
174
|
|
|
112
|
-
# Scan each directory for multisamples
|
|
113
|
-
def scan_dirs
|
|
114
|
-
# tidy this junk up
|
|
115
|
-
multidirs = []
|
|
116
175
|
|
|
117
|
-
for dir in @dirs
|
|
118
|
-
if File.exists?( dir ) and File.stat( dir ).directory? \
|
|
119
|
-
and Multisample.dir_contains_multisamples?( dir )
|
|
120
176
|
|
|
121
|
-
puts "\"#{ dir }\" has multisamples!"
|
|
122
|
-
multidirs.push( dir )
|
|
123
177
|
|
|
124
|
-
|
|
178
|
+
protected
|
|
179
|
+
|
|
180
|
+
def get_multisample_directories
|
|
181
|
+
# tidy this junk up
|
|
182
|
+
multidirs = []
|
|
125
183
|
|
|
126
|
-
|
|
127
|
-
|
|
184
|
+
# push every directory w/multisamples onto the stack
|
|
185
|
+
for dir in @dirs
|
|
186
|
+
puts "scanning \"#{dir}\""
|
|
187
|
+
|
|
188
|
+
if File.exists?( dir ) and File.stat( dir ).directory?
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
#if File.exists?( dir ) and File.stat( dir ).directory?
|
|
192
|
+
|
|
193
|
+
Dir.foreach( dir ){ |file|
|
|
194
|
+
#puts "... #{file}"
|
|
128
195
|
current_dir = dir + File::SEPARATOR + file
|
|
129
196
|
if File.stat( current_dir ).directory? and file !~ /^\./
|
|
130
|
-
puts "
|
|
197
|
+
#puts "...scanning \"#{current_dir}\""
|
|
131
198
|
@dirs.push( current_dir )
|
|
132
|
-
end
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
199
|
+
end
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if Multisample.dir_contains_multisamples?( dir )
|
|
203
|
+
puts "\"#{ dir }\" has multisamples!"
|
|
204
|
+
multidirs.push( dir )
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# => puts "pushed \"#{ dir }\""
|
|
136
208
|
else
|
|
137
209
|
puts "\"#{ dir }\" is unusable."
|
|
138
210
|
end
|
|
139
211
|
end
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
pwd = Dir.getwd
|
|
143
|
-
if multidirs.size > 0
|
|
144
|
-
puts "converting..."
|
|
145
|
-
for dir in multidirs
|
|
146
|
-
Dir.chdir( pwd )
|
|
147
|
-
process_multisample_dir( dir, pwd )
|
|
148
|
-
end
|
|
149
|
-
else
|
|
150
|
-
STDERR.print "ERROR: No multisample directories were found in the given "
|
|
151
|
-
STDERR.puts "arguments."
|
|
152
|
-
STDERR.puts "Quitting."
|
|
153
|
-
exit SFZer::EXITSTATUS_NO_MULTISAMPLES_FOUND
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
protected
|
|
212
|
+
multidirs
|
|
213
|
+
end
|
|
159
214
|
|
|
160
215
|
|
|
161
216
|
# Returns the Multisample for the given hashkey.
|
|
@@ -168,7 +223,20 @@ class DirectoryProcessor
|
|
|
168
223
|
multi = Multisample.new( path, file_hashkey )
|
|
169
224
|
multi.mapping = @mapping
|
|
170
225
|
multi.message = @message if( @message )
|
|
226
|
+
multi.transpose = @transpose
|
|
227
|
+
multi.padding = @padding
|
|
171
228
|
end
|
|
172
229
|
multi
|
|
173
230
|
end
|
|
174
231
|
end
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# Exception to be raised in the case that no Multisample directories
|
|
237
|
+
# Have been found by DirectoryProcessor.
|
|
238
|
+
class NoMultisamplesInDirectoryException < Exception
|
|
239
|
+
def message
|
|
240
|
+
"No multisample directories were found in the given directories."
|
|
241
|
+
end
|
|
242
|
+
end
|
data/lib/Multisample.rb
CHANGED
|
@@ -36,7 +36,7 @@ class Multisample
|
|
|
36
36
|
# Maximum number of columns in SFZ comments before a line wrap
|
|
37
37
|
LINE_LIMIT = 80
|
|
38
38
|
|
|
39
|
-
attr_accessor :message, :mapping, :default_path
|
|
39
|
+
attr_accessor :message, :mapping, :default_path, :padding, :transpose
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
|
|
@@ -55,6 +55,9 @@ class Multisample
|
|
|
55
55
|
|
|
56
56
|
# include a default path?
|
|
57
57
|
@default_path = false
|
|
58
|
+
|
|
59
|
+
# Number of semitones to shift the note on the keyboard
|
|
60
|
+
@transpose = 0
|
|
58
61
|
end
|
|
59
62
|
|
|
60
63
|
|
|
@@ -94,8 +97,8 @@ class Multisample
|
|
|
94
97
|
|
|
95
98
|
if @mapping == XFADE
|
|
96
99
|
result = result + get_xfade( sample, prev_sample, next_sample )
|
|
97
|
-
|
|
98
|
-
result = result + "key=#{sample.value} "
|
|
100
|
+
elsif @mapping == STRICTKEYS
|
|
101
|
+
result = result + "key=#{ _transpose( sample.value )} "
|
|
99
102
|
end
|
|
100
103
|
|
|
101
104
|
# *****************************************
|
|
@@ -109,6 +112,15 @@ class Multisample
|
|
|
109
112
|
end
|
|
110
113
|
|
|
111
114
|
|
|
115
|
+
# Do the samples within the Multisample justify its existence?
|
|
116
|
+
def valid?
|
|
117
|
+
values = {}
|
|
118
|
+
@samples.each{ |s|
|
|
119
|
+
values[s.value] = s
|
|
120
|
+
}
|
|
121
|
+
values.size >= 3
|
|
122
|
+
end
|
|
123
|
+
|
|
112
124
|
|
|
113
125
|
# Sanitizes file path separators.
|
|
114
126
|
#
|
|
@@ -123,7 +135,7 @@ class Multisample
|
|
|
123
135
|
# Returns SFZ opcodes to specify pitch_keycenter and xfade parameters for a
|
|
124
136
|
# region.
|
|
125
137
|
def get_xfade( sample, prev_sample, next_sample, keycurve=false )
|
|
126
|
-
|
|
138
|
+
|
|
127
139
|
xfin_lo = false
|
|
128
140
|
xfin_hi = false
|
|
129
141
|
xfout_lo = false
|
|
@@ -152,16 +164,25 @@ class Multisample
|
|
|
152
164
|
xfout_lo = mid + (diff/2)
|
|
153
165
|
hikey = xfout_hi
|
|
154
166
|
elsif next_sample and ( next_sample.value - sample.value == 1 )
|
|
155
|
-
hikey = sample.value
|
|
167
|
+
hikey = sample.value
|
|
156
168
|
else
|
|
157
169
|
hikey = sample.value + @padding
|
|
158
170
|
end
|
|
159
171
|
|
|
160
172
|
# depending on the distance between lokey and hikey, apply xfades
|
|
161
173
|
|
|
174
|
+
# Apply tranpose
|
|
175
|
+
value = _transpose( sample.value )
|
|
176
|
+
hikey = _transpose( hikey )
|
|
177
|
+
lokey = _transpose( lokey )
|
|
178
|
+
xfin_lokey = _transpose( xfin_lokey )
|
|
179
|
+
xfin_hikey = _transpose( xfin_hikey )
|
|
180
|
+
xfout_lokey = _transpose( xfout_lokey )
|
|
181
|
+
xfout_hikey = _transpose( xfout_hikey )
|
|
162
182
|
|
|
183
|
+
result = "pitch_keycenter=#{value} "
|
|
163
184
|
if lokey == hikey and lokey == sample.value
|
|
164
|
-
result = "key=#{
|
|
185
|
+
result = "key=#{value}"
|
|
165
186
|
else
|
|
166
187
|
|
|
167
188
|
if lokey
|
|
@@ -177,6 +198,7 @@ class Multisample
|
|
|
177
198
|
end
|
|
178
199
|
result = result + "hikey=#{hikey} "
|
|
179
200
|
end
|
|
201
|
+
|
|
180
202
|
end
|
|
181
203
|
|
|
182
204
|
# add xf_keycurve, if applicable
|
|
@@ -190,19 +212,25 @@ class Multisample
|
|
|
190
212
|
end
|
|
191
213
|
|
|
192
214
|
|
|
215
|
+
def _transpose( note )
|
|
216
|
+
if !note.nil? and note
|
|
217
|
+
note = note + @transpose
|
|
218
|
+
end
|
|
219
|
+
note
|
|
220
|
+
end
|
|
221
|
+
|
|
193
222
|
|
|
194
223
|
# returns true if a filename can be understood as a multisample element
|
|
195
224
|
def Multisample.multisample_filename?( name )
|
|
196
|
-
types = Sample.suffix_regexp
|
|
225
|
+
#types = Sample.suffix_regexp
|
|
197
226
|
|
|
198
227
|
# test for A(#)1.wav type
|
|
199
|
-
|
|
200
|
-
return true if NamedSample.sample?( name )
|
|
228
|
+
return NamedSample.value( name ) if NamedSample.sample?( name )
|
|
201
229
|
|
|
202
230
|
# test for Sample (0)00.wav type
|
|
203
|
-
if name =~ /^.*((0|1)?\d\d)\.#{types}$/i
|
|
204
|
-
|
|
205
|
-
end
|
|
231
|
+
#if name =~ /^.*((0|1)?\d\d)\.#{types}$/i
|
|
232
|
+
# return true if (0..127).include?( $1.to_i )
|
|
233
|
+
#end
|
|
206
234
|
false
|
|
207
235
|
end
|
|
208
236
|
|
|
@@ -211,12 +239,19 @@ class Multisample
|
|
|
211
239
|
# returns true if the directory contains files that might be multisamples
|
|
212
240
|
def Multisample.dir_contains_multisamples?( dir )
|
|
213
241
|
result = false
|
|
242
|
+
values = {}
|
|
243
|
+
@min_samples_per_multisample = 3
|
|
244
|
+
|
|
214
245
|
Dir.foreach( dir ) do |file|
|
|
215
|
-
if Multisample.multisample_filename?( file )
|
|
216
|
-
|
|
217
|
-
|
|
246
|
+
if v = Multisample.multisample_filename?( file )
|
|
247
|
+
values[v] = 1
|
|
248
|
+
if( values.size >= @min_samples_per_multisample )
|
|
249
|
+
result = true
|
|
250
|
+
break
|
|
251
|
+
end
|
|
218
252
|
end
|
|
219
253
|
end
|
|
254
|
+
|
|
220
255
|
result
|
|
221
256
|
end
|
|
222
257
|
|
data/lib/NamedSample.rb
CHANGED
|
@@ -28,13 +28,14 @@ class NamedSample < Sample
|
|
|
28
28
|
|
|
29
29
|
# returns true if the String name is a NamedSample.
|
|
30
30
|
def NamedSample.sample?( name )
|
|
31
|
-
name =~ /^.*(#{regexp})
|
|
31
|
+
name =~ /^.*(#{NamedSample.regexp})/ &&
|
|
32
|
+
name =~ /.*\.#{Sample.suffix_regexp}$/i
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
# returns the RegExp used to test for a NamedSample.
|
|
36
37
|
def NamedSample.regexp
|
|
37
|
-
/[
|
|
38
|
+
/([A-Ga-g])(#)?(\d)/
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
|
|
@@ -42,8 +43,9 @@ class NamedSample < Sample
|
|
|
42
43
|
def NamedSample.value( string )
|
|
43
44
|
result = false
|
|
44
45
|
|
|
45
|
-
if( string =~
|
|
46
|
-
notes = { 'C'=>0, 'D'=>2, 'E'=>4, 'F'=>5, 'G'=>7, 'A'=>9, 'B'=>11
|
|
46
|
+
if( string =~ regexp )
|
|
47
|
+
notes = { 'C'=>0, 'D'=>2, 'E'=>4, 'F'=>5, 'G'=>7, 'A'=>9, 'B'=>11,
|
|
48
|
+
'c'=>0, 'd'=>2, 'e'=>4, 'f'=>5, 'g'=>7, 'a'=>9, 'b'=>11}
|
|
47
49
|
note = $1
|
|
48
50
|
sharp = 0
|
|
49
51
|
if $2 == "#" then
|
data/lib/Sample.rb
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# You should have received a copy of the GNU General Public License
|
|
14
14
|
# along with SFZer. If not, see <http://www.gnu.org/licenses/>.
|
|
15
15
|
|
|
16
|
+
|
|
16
17
|
# An SFZ Sample is a file that represents a single note.
|
|
17
18
|
class Sample
|
|
18
19
|
attr_reader :path, :sample
|
|
@@ -79,5 +80,5 @@ class Sample
|
|
|
79
80
|
"#{@sample}"
|
|
80
81
|
end
|
|
81
82
|
end
|
|
82
|
-
|
|
83
|
+
|
|
83
84
|
end
|
data/lib/sfzer.rb
CHANGED
|
@@ -1,128 +1,157 @@
|
|
|
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 '
|
|
21
|
-
require '
|
|
22
|
-
require '
|
|
23
|
-
require '
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
opt.
|
|
71
|
-
opt.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
opt.on( "
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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 'DirectoryProcessor'
|
|
21
|
+
require 'Sample'
|
|
22
|
+
require 'NamedSample'
|
|
23
|
+
require 'Multisample'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Parses the command line options and sets the program into motion.
|
|
27
|
+
class SFZer
|
|
28
|
+
|
|
29
|
+
# Version information
|
|
30
|
+
VERSION = '0.5.0'
|
|
31
|
+
VERSION_DATE = "17 Feb 2009"
|
|
32
|
+
URL = "http://sfzer.rubyforge.org"
|
|
33
|
+
NAME = "SFZer"
|
|
34
|
+
|
|
35
|
+
# File separator for SFZ samples
|
|
36
|
+
SEPARATOR = "\\"
|
|
37
|
+
|
|
38
|
+
#Exit codes
|
|
39
|
+
EXITSTATUS_COMPLETE = 0
|
|
40
|
+
EXITSTATUS_NO_MULTISAMPLES_FOUND = 1
|
|
41
|
+
EXITSTATUS_ARG_ERROR = 2
|
|
42
|
+
# EXITSTATUS_USER_ABORT = 3
|
|
43
|
+
MAX_NOTE_SHIFT = 9 * 12
|
|
44
|
+
|
|
45
|
+
# Execute the SFZer program
|
|
46
|
+
def do( argv )
|
|
47
|
+
begin
|
|
48
|
+
sfzer = process_options( argv )
|
|
49
|
+
sfzer.scan_dirs
|
|
50
|
+
rescue NoMultisamplesInDirectoryException => e
|
|
51
|
+
STDERR.puts "ERROR: #{e.message}"
|
|
52
|
+
STDERR.puts " Quitting."
|
|
53
|
+
exit SFZer::EXITSTATUS_NO_MULTISAMPLES_FOUND
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
protected
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# Processes each command line option and returns a configured Directory Processor.
|
|
62
|
+
def process_options( argv )
|
|
63
|
+
sfzer = DirectoryProcessor.new
|
|
64
|
+
exit_now = false
|
|
65
|
+
|
|
66
|
+
begin
|
|
67
|
+
argv.options{ |opt|
|
|
68
|
+
|
|
69
|
+
# help message banner and usage instructions
|
|
70
|
+
opt.banner = "A script by Chris Tessmer that creates SFZ files from multisamples.\n"
|
|
71
|
+
opt.banner += "version #{SFZer::VERSION}, #{SFZer::VERSION_DATE}.\n"
|
|
72
|
+
opt.banner += "\n"
|
|
73
|
+
opt.banner += "Usage: sfzer.rb [options] directorie(s)\n"
|
|
74
|
+
opt.banner += "\n"
|
|
75
|
+
|
|
76
|
+
# defining options
|
|
77
|
+
opt.on( "Options:" )
|
|
78
|
+
opt.on( "\n" )
|
|
79
|
+
opt.on( "Mapping:" )
|
|
80
|
+
opt.on( "--xfade", "-x", "Map crossfades between region keycenters *" ){
|
|
81
|
+
sfzer.mapping = Multisample::XFADE
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
opt.on( "--strictkeys", "-s", "Map regions on a 1:1 key:sample basis" ){
|
|
85
|
+
sfzer.mapping = Multisample::STRICTKEYS
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
opt.on( "--transpose NOTES", "-t", Integer,
|
|
89
|
+
"Remaps regions on the keyboard by NOTES"){ |n|
|
|
90
|
+
|
|
91
|
+
if n > MAX_NOTE_SHIFT or n < -MAX_NOTE_SHIFT
|
|
92
|
+
raise ArgumentError,
|
|
93
|
+
"Cannot shift octaves by greater than #{MAX_NOTE_SHIFT}."
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
sfzer.transpose = n
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
opt.on( "--padding NOTES", "-p", Integer,
|
|
100
|
+
"Pads the ends of each multisample by NOTES "){ |n|
|
|
101
|
+
if n < 0
|
|
102
|
+
raise ArgumentError,
|
|
103
|
+
"Cannot pad by a negative number}."
|
|
104
|
+
end
|
|
105
|
+
sfzer.padding = n
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
opt.on( "" )
|
|
109
|
+
opt.on( "File placement:" )
|
|
110
|
+
opt.on( "--top-dir", "-d", "Create SFZ files in current directory *" ){
|
|
111
|
+
sfzer.generate_in_top_dir = true
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
opt.on( "--each-dir", "-e", "Create each SFZ file with its own Samples"){
|
|
115
|
+
sfzer.generate_in_top_dir = false
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
opt.on( "\n" )
|
|
119
|
+
opt.on( "Decoration:" )
|
|
120
|
+
opt.on( "--message 'TEXT'", "-m",
|
|
121
|
+
"Add TEXT to each generated SFZ file header" ) {
|
|
122
|
+
|text|
|
|
123
|
+
sfzer.message = text
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
opt.on( "--help", "-h", "This text" ) {
|
|
127
|
+
puts opt
|
|
128
|
+
exit_now = true
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
opt.parse!
|
|
132
|
+
|
|
133
|
+
# Exit with help message if no arguments are given
|
|
134
|
+
if( argv.length == 0 )
|
|
135
|
+
puts opt
|
|
136
|
+
exit_now = true
|
|
137
|
+
end
|
|
138
|
+
}
|
|
139
|
+
rescue Exception => e
|
|
140
|
+
STDERR.puts "\n#{e.class}"
|
|
141
|
+
STDERR.puts "\t#{e}\n"
|
|
142
|
+
STDERR.puts "Since this messed up the arguments, I am ABORTING THE PROGRAM. Sorry.\n\n"
|
|
143
|
+
exit EXITSTATUS_ARG_ERROR
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Handle graceful exit (couldn't put in opt block because of rescue clause)
|
|
147
|
+
if exit_now
|
|
148
|
+
exit EXITSTATUS_COMPLETE
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
argv.each{ |arg|
|
|
152
|
+
sfzer.dirs.push( arg )
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
sfzer
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class DirectoryProcessorTest < Test::Unit::TestCase
|
|
2
|
+
def name
|
|
3
|
+
"DirectoryProcessorTest tests"
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def setup
|
|
8
|
+
@d = DirectoryProcessor.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_for_hashkey_sanitization
|
|
12
|
+
names = [ ['MS_Cavern-C5.wav', 'path'],
|
|
13
|
+
['A#4.ogg', 'C:\snd\Analog_Pad' ],
|
|
14
|
+
['D#4.ogg', '/snd/Juno_Lead' ],
|
|
15
|
+
['d#4.ogg', '/snd/Juno_Lead' ],
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
names.each{ |name|
|
|
19
|
+
path = name[1]
|
|
20
|
+
name = name[0]
|
|
21
|
+
key = @d.sanitize_multisample_name( name, path )
|
|
22
|
+
assert( key !~ /[-_]$/ ,
|
|
23
|
+
"\"#{key}\" has a minus or underscore at the end.")
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
data/test/MultisampleTest.rb
CHANGED
|
@@ -52,25 +52,25 @@ class MultisampleTest < Test::Unit::TestCase
|
|
|
52
52
|
|
|
53
53
|
|
|
54
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
|
|
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
74
|
|
|
75
75
|
|
|
76
76
|
|
|
@@ -78,8 +78,9 @@ class MultisampleTest < Test::Unit::TestCase
|
|
|
78
78
|
# Names of real files that should succeed
|
|
79
79
|
names = [ 'NRGK4ObBass F#1.wav',
|
|
80
80
|
'OBX-SoundTrk_03B2.wav',
|
|
81
|
-
'Bass06_C2.wav',
|
|
81
|
+
'Bass06_C2.wav',
|
|
82
82
|
]
|
|
83
|
+
|
|
83
84
|
for name in names
|
|
84
85
|
assert( Multisample.multisample_filename?( name ),
|
|
85
86
|
"\"#{name}\"is NOT a valid multisample filename" )
|
|
@@ -89,7 +90,7 @@ class MultisampleTest < Test::Unit::TestCase
|
|
|
89
90
|
#badnames = ['119F#001.wav']
|
|
90
91
|
#for name in badnames
|
|
91
92
|
# assert( Multisample.multisample_filename?( name ) == false ,
|
|
92
|
-
#
|
|
93
|
+
# "\"#{name}\" IS a valid multisample filename, but shouldn't be!" )
|
|
93
94
|
#end
|
|
94
95
|
end
|
|
95
96
|
|
data/test/NamedSampleTest.rb
CHANGED
|
@@ -6,7 +6,7 @@ class NamedSampleTest < Test::Unit::TestCase
|
|
|
6
6
|
def test_for_named_samples_files
|
|
7
7
|
names = [
|
|
8
8
|
'Bassy01(C3).wav',
|
|
9
|
-
'
|
|
9
|
+
'd#2.wav',
|
|
10
10
|
'Bass10_C1.wav'
|
|
11
11
|
]
|
|
12
12
|
|
|
@@ -14,6 +14,16 @@ class NamedSampleTest < Test::Unit::TestCase
|
|
|
14
14
|
assert( NamedSample.sample?( name ) ,
|
|
15
15
|
"\"#{name}\" is NOT a valid note name.")
|
|
16
16
|
end
|
|
17
|
+
|
|
18
|
+
badnames = [
|
|
19
|
+
'090C001.wav',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
#badnames.each do |name|
|
|
23
|
+
#assert( !NamedSample.sample?( name ) ,
|
|
24
|
+
# "\"#{name}\" IS a valid note name (value = #{NamedSample.value( name )}).")
|
|
25
|
+
#end
|
|
26
|
+
|
|
17
27
|
end
|
|
18
28
|
|
|
19
29
|
|
|
@@ -27,7 +37,7 @@ class NamedSampleTest < Test::Unit::TestCase
|
|
|
27
37
|
|
|
28
38
|
sample_values.each do |sample, value|
|
|
29
39
|
assert_equal( value, NamedSample.value( sample ),
|
|
30
|
-
"The value of \"#{sample}\"
|
|
40
|
+
"The value of \"#{sample}\" did NOT evaluate to \"#{value}\".")
|
|
31
41
|
end
|
|
32
42
|
end
|
|
33
43
|
|
|
@@ -52,4 +62,4 @@ class NamedSampleTest < Test::Unit::TestCase
|
|
|
52
62
|
assert_operator c3, :==, c3o, "#{c3} = #{c3o}"
|
|
53
63
|
end
|
|
54
64
|
|
|
55
|
-
end
|
|
65
|
+
end
|
data/test/test_sfzer.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sfzer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Tessmer
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2009-02-
|
|
12
|
+
date: 2009-02-17 00:00:00 +00:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
@@ -32,10 +32,12 @@ extensions: []
|
|
|
32
32
|
extra_rdoc_files:
|
|
33
33
|
- History.txt
|
|
34
34
|
- Manifest.txt
|
|
35
|
+
- Notes.txt
|
|
35
36
|
- README.txt
|
|
36
37
|
files:
|
|
37
38
|
- History.txt
|
|
38
39
|
- Manifest.txt
|
|
40
|
+
- Notes.txt
|
|
39
41
|
- README.txt
|
|
40
42
|
- COPYING
|
|
41
43
|
- Rakefile
|
|
@@ -48,6 +50,7 @@ files:
|
|
|
48
50
|
- test/test_sfzer.rb
|
|
49
51
|
- test/NamedSampleTest.rb
|
|
50
52
|
- test/MultisampleTest.rb
|
|
53
|
+
- test/DirectoryProcessorTest.rb
|
|
51
54
|
has_rdoc: true
|
|
52
55
|
homepage: http://sfzer.rubyforge.org
|
|
53
56
|
post_install_message:
|