Class: Multisample

Inherits:
Object
  • Object
show all
Defined in:
lib/Multisample.rb

Overview

A Multisample is a collection of related Samples that can form an intrument.

Constant Summary collapse

XFADE =

auto-xfade (usually best)

0
STRICTKEYS =

1:1 key map (usually worst)

1
KEYCURVE_GAIN =

Gain keycurve

3
KEYCURVE_POWER =

Power keycurve

4
LINE_LIMIT =

Maximum number of columns in SFZ comments before a line wrap

80

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, name = false) ⇒ Multisample

instantiate the Multisample



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/Multisample.rb', line 44

def initialize( path, name=false )
  #TODO: Raise Exception if path not valid

  @path=path
  @samples=Array.new
  @name = "Unnamed Multisample"
  if name 
    @name=name.chomp.gsub(/^\s*/, '').gsub(/\s*$/, '')
  end
  @mapping  = XFADE  
  @message  = false    # SFZ header message

  @padding  = 12       # padding in semitones

  
  # include a default path?

  @default_path = false 
  
  # Number of semitones to shift the note on the keyboard

  @transpose = 0
end

Instance Attribute Details

#default_pathObject

Returns the value of attribute default_path.



39
40
41
# File 'lib/Multisample.rb', line 39

def default_path
  @default_path
end

#mappingObject

Returns the value of attribute mapping.



39
40
41
# File 'lib/Multisample.rb', line 39

def mapping
  @mapping
end

#messageObject

Returns the value of attribute message.



39
40
41
# File 'lib/Multisample.rb', line 39

def message
  @message
end

#paddingObject

Returns the value of attribute padding.



39
40
41
# File 'lib/Multisample.rb', line 39

def padding
  @padding
end

#transposeObject

Returns the value of attribute transpose.



39
40
41
# File 'lib/Multisample.rb', line 39

def transpose
  @transpose
end

Class Method Details

.dir_contains_multisamples?(dir) ⇒ Boolean

returns true if the directory contains files that might be multisamples

Returns:

  • (Boolean)


240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/Multisample.rb', line 240

def Multisample.dir_contains_multisamples?( dir )
  result = false
  values = {}
  @min_samples_per_multisample = 3
  
  Dir.foreach( dir ) do |file|
    if v = Multisample.multisample_filename?( file )
      values[v] = 1
      if( values.size >= @min_samples_per_multisample  )
        result = true
        break
      end
    end
  end

  result
end

.multisample_filename?(name) ⇒ Boolean

returns true if a filename can be understood as a multisample element

Returns:

  • (Boolean)


224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/Multisample.rb', line 224

def Multisample.multisample_filename?( name )
  #types = Sample.suffix_regexp


  # test for A(#)1.wav type

  return NamedSample.value( name ) if NamedSample.sample?( name )

  # test for Sample (0)00.wav type

  #if name =~ /^.*((0|1)?\d\d)\.#{types}$/i

  #  return true if (0..127).include?( $1.to_i )

  #end

  false
end

Instance Method Details

#_transpose(note) ⇒ Object



215
216
217
218
219
220
# File 'lib/Multisample.rb', line 215

def _transpose( note )
  if !note.nil? and note
    note = note + @transpose
  end
  note
end

#add(sample) ⇒ Object

add a Sample to the Multisample



66
67
68
69
# File 'lib/Multisample.rb', line 66

def add( sample )
  @samples.push( sample )
  @samples.sort!
end

#get_xfade(sample, prev_sample, next_sample, keycurve = false) ⇒ Object

Returns SFZ opcodes to specify pitch_keycenter and xfade parameters for a region.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/Multisample.rb', line 137

def get_xfade( sample, prev_sample, next_sample, keycurve=false )
  
  xfin_lo = false
  xfin_hi = false
  xfout_lo = false
  xfout_hi = false
  lokey = false
  hikey = false

  # calculate xfade ins from previous sample

  if prev_sample and ( prev_sample.value < (sample.value-1) )
    mid     = ( sample.value + prev_sample.value ) / 2
    diff    = sample.value - mid
    xfin_lo = mid - (diff/2)
    xfin_hi = mid + (diff/2)
    lokey   = xfin_lo
  elsif prev_sample and ( sample.value - prev_sample.value == 1 )
    lokey = sample.value
  else
    lokey = sample.value - @padding
  end

  #calculate xfade outs into next sample

  if next_sample and ( next_sample.value > (sample.value+1) )
    mid      = ( sample.value + next_sample.value ) / 2
    diff     = sample.value - mid
    xfout_hi = mid - (diff/2)
    xfout_lo = mid + (diff/2)
    hikey    = xfout_hi
  elsif next_sample and ( next_sample.value - sample.value == 1 )
    hikey = sample.value 
  else
    hikey = sample.value + @padding
  end

   # depending on the distance between lokey and hikey, apply xfades

   
   # Apply tranpose

   value = _transpose( sample.value )
   hikey = _transpose( hikey )
   lokey = _transpose( lokey )
   xfin_lokey = _transpose( xfin_lokey )  
  xfin_hikey = _transpose( xfin_hikey )      
   xfout_lokey = _transpose( xfout_lokey )  
  xfout_hikey = _transpose( xfout_hikey )          
   
   result = "pitch_keycenter=#{value} "
   if lokey == hikey  and lokey == sample.value
    result = "key=#{value}"
  else
     
    if lokey 
      result = result + "lokey=#{lokey} "
      if xfin_lo and xfin_hi
        result = result + "xfin_lokey=#{xfin_lo} xfin_hikey=#{xfin_hi} "
      end
    end
    
    if hikey 
      if xfout_lo and xfout_hi
        result = result + "xfout_lokey=#{xfout_lo} xfout_hikey=#{xfout_hi} "
      end
      result = result + "hikey=#{hikey} "
    end

  end

  # add xf_keycurve, if applicable

  if keycurve == KEYCURVE_GAIN
    result = result + "xf_keycurve=gain"
  elsif keycurve == KEYCURVE_POWER
    result = result + "xf_keycurve=power"
  end

  result
end

#sanitize_file_path(f) ⇒ Object

Sanitizes file path separators.

Whilst every path separator within Ruby must be "/" (Unix), evey path separator within SFZ must be "" (DOS).



129
130
131
# File 'lib/Multisample.rb', line 129

def sanitize_file_path( f )
  f.gsub( File::SEPARATOR, "\\" )
end

#sfz_bannerObject

Returns the Multisample's SFZ header as a string.



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/Multisample.rb', line 301

def sfz_banner
  separator = "//----------------------------------------------------------------------------"
  result = ""


  if @name
    result = result + "//\n"
    result = result + "// "+ "*** SFZ Intrument: #{@name} ***".center(76) + "\n"
    result = result + "//\n"
  end

  # add optional message to SFZ header

  if @message and @message.chomp.size > 0
    result = result + separator + "\n"
    result = result + sfz_banner_message( @message )

  end

  # add generator message to SFZ header

  result = result + separator + "\n"
  result = result + "// Generated on #{Time.now.strftime("%Y/%m/%d %I:%M%p") } "
  result = result + "by #{SFZer::NAME} v#{SFZer::VERSION} (#{SFZer::URL})\n"
  result = result + "#{separator}\n"
result  
end

#sfz_banner_message(fulltext) ⇒ Object

Returns a formatted message formatted to the SFZ



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/Multisample.rb', line 328

def sfz_banner_message( fulltext )
  limit = 76
  result = ""
  result = result + "//\n"
  
  fulltext.each_line{ |text|
    while text.length > limit
      subtext = text.slice( 0 .. ( limit - 1 ) )
      text = text.slice( limit .. text.length )

      # don't cut off words; break at spaces

      i = subtext.rindex( ' ' )
      if i > ( limit / 2 )
        nub = subtext.slice( i+1, subtext.length )
        subtext = subtext.slice( 0..i )
        text = nub + text
      end

      result = result + "// " + subtext.center( limit ) + "\n"
    end

    result = result + "// " + text.center( limit ) + "\n" 
  }
  result = result + "//\n" 
  
  result
end

#to_sObject

Sreturn a String representation of the Multisample



74
75
76
# File 'lib/Multisample.rb', line 74

def to_s
  "Multisample \"#{@name}\" [#{@samples.size}]: " + @samples.sort.to_s
end

#to_sfzObject

Returns a string of SFZ elements



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
# File 'lib/Multisample.rb', line 81

def to_sfz
  result = sfz_banner

  if @default_path
    sanitized_path = sanitize_file_path( @path )
    result = result + "\n<control>\ndefault_path=#{sanitized_path}\\\n\n"
  end

  maxfilenamesize = max_sample_filename_size

  #result = result + "// Keyboard/Sample mappings:\n"

  @samples.each_index{ |i|
    sample=@samples[i]
    prev_sample = get_previous_sample( i )
    next_sample = get_next_sample( i )
    result = result + "<region> sample=" +  sanitize_file_path( sample.sample.ljust( maxfilenamesize + 1 ) )
     
    if @mapping == XFADE
      result = result + get_xfade( sample, prev_sample, next_sample )
      elsif @mapping == STRICTKEYS
       result = result + "key=#{ _transpose( sample.value )} "
    end

    # *****************************************

    # TODO: PLACE FUTURE  PER-SAMPLE LOGIC HERE

    # *****************************************


     result = result + "\n"
  }

  result 
end

#valid?Boolean

Do the samples within the Multisample justify its existence?

Returns:

  • (Boolean)


116
117
118
119
120
121
122
# File 'lib/Multisample.rb', line 116

def valid?
  values = {}
  @samples.each{ |s|
    values[s.value] = s
  }
  values.size >= 3
end