summaryrefslogtreecommitdiff
path: root/src/Sound/RubberBand/Option.hs
blob: 2bef7dc5d396e6410c27b0e64d688b0007113a9f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
71
72
73
74
75
76
77
78
79
80
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
129
130
131
132
133
134
135
136
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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
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
326
327
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
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
module Sound.RubberBand.Option
( Options(..)

, Process(..)
, Stretch(..)
, Transients(..)
, Detector(..)
, Phase(..)
, Threading(..)
, Window(..)
, Smoothing(..)
, Formant(..)
, Pitch(..)
, Channels(..)

, Option(..)

, setOption
, getOption

, toOptions
, fromOptions

, defaultOptions
, percussiveOptions
) where

import Data.Bits ((.|.), (.&.), complement)
import Data.Maybe (fromMaybe)

{- |
Processing options for the timestretcher. The preferred
options should normally be set when calling 'Sound.RubberBand.Nice.new'.
-}
data Options = Options
  { oProcess    :: Process
  , oStretch    :: Stretch
  , oTransients :: Transients
  , oDetector   :: Detector
  , oPhase      :: Phase
  , oThreading  :: Threading
  , oWindow     :: Window
  , oSmoothing  :: Smoothing
  , oFormant    :: Formant
  , oPitch      :: Pitch
  , oChannels   :: Channels
  } deriving (Eq, Ord, Show, Read)

{- |
'Process' flags determine how the timestretcher
will be invoked. These options may not be changed after
construction.

The 'Process' setting is likely to depend on your architecture:
non-real-time operation on seekable files: 'Offline'; real-time
or streaming operation: 'RealTime'.
-}
data Process
  = Offline
  {- ^
  Run the stretcher in offline mode. In this mode the input data needs to
  be provided twice, once to 'Sound.RubberBand.Nice.study', which calculates
  a stretch profile for the audio, and once to
  'Sound.RubberBand.Nice.process', which stretches it.
  -}
  | RealTime
  {- ^
  Run the stretcher in real-time mode. In this mode only
  'Sound.RubberBand.Nice.process' should be called, and the
  stretcher adjusts dynamically in response to the input audio.
  -}
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

{- |
'Stretch' flags control the profile used for
variable timestretching.  Rubber Band always adjusts the
stretch profile to minimise stretching of busy broadband
transient sounds, but the degree to which it does so is
adjustable.  These options may not be changed after
construction.
-}
data Stretch
  = Elastic
  {- ^
  Only meaningful in offline
  mode, and the default in that mode.  The audio will be
  stretched at a variable rate, aimed at preserving the quality
  of transient sounds as much as possible.  The timings of low
  activity regions between transients may be less exact than
  when the precise flag is set.
  -}
  | Precise
  {- ^
  Although still using a variable
  stretch rate, the audio will be stretched so as to maintain
  as close as possible to a linear stretch ratio throughout.
  Timing may be better than when using 'Elastic', at
  slight cost to the sound quality of transients.  This setting
  is always used when running in real-time mode.
  -}
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

{- |
'Transients' flags control the component
frequency phase-reset mechanism that may be used at transient
points to provide clarity and realism to percussion and other
significant transient sounds.  These options may be changed
after construction when running in real-time mode, but not when
running in offline mode.
-}
data Transients
  = Crisp
  {- ^
  Reset component phases at the
  peak of each transient (the start of a significant note or
  percussive event).  This, the default setting, usually
  results in a clear-sounding output; but it is not always
  consistent, and may cause interruptions in stable sounds
  present at the same time as transient events.  The
  'Detector' flags can be used to tune this to some
  extent.
  -}
  | Mixed
  {- ^
  Reset component phases at the
  peak of each transient, outside a frequency range typical of
  musical fundamental frequencies.  The results may be more
  regular for mixed stable and percussive notes than
  'Crisp', but with a "phasier" sound.  The
  balance may sound very good for certain types of music and
  fairly bad for others.
  -}
  | Smooth
  {- ^
  Do not reset component phases
  at any point.  The results will be smoother and more regular
  but may be less clear than with either of the other
  'Transients' flags.
  -}
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

{- |
'Detector' flags control the type of
transient detector used.  These options may be changed
after construction when running in real-time mode, but not when
running in offline mode.
-}
data Detector
  = Compound
  {- ^
  Use a general-purpose
  transient detector which is likely to be good for most
  situations.  This is the default.
  -}
  | Percussive
  {- ^
  Detect percussive
  transients.  Note that this was the default and only option
  in Rubber Band versions prior to 1.5.
  -}
  | Soft
  {- ^
  Use an onset detector with less
  of a bias toward percussive transients.  This may give better
  results with certain material (e.g. relatively monophonic
  piano music).
  -}
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

{- |
'Phase' flags control the adjustment of
component frequency phases from one analysis window to the next
during non-transient segments.  These options may be changed at
any time.
-}
data Phase
  = Laminar
  {- ^
  Adjust phases when stretching in
  such a way as to try to retain the continuity of phase
  relationships between adjacent frequency bins whose phases
  are behaving in similar ways.  This, the default setting,
  should give good results in most situations.
  -}
  | Independent
  {- ^
  Adjust the phase in each
  frequency bin independently from its neighbours.  This
  usually results in a slightly softer, phasier sound.
  -}
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

{- |
'Threading' flags control the threading
model of the stretcher.  These options may not be changed after
construction.
-}
data Threading
  = Auto
  {- ^
  Permit the stretcher to
  determine its own threading model.  Usually this means using
  one processing thread per audio channel in offline mode if
  the stretcher is able to determine that more than one CPU is
  available, and one thread only in realtime mode.  This is the
  defafult.
  -}
  | Never
  {- ^
  Never use more than one thread.
  -}
  | Always
  {- ^
  Use multiple threads in any
  situation where 'Auto' would do so, except omit
  the check for multiple CPUs and instead assume it to be true.
  -}
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

{- |
'Window' flags control the window size for
FFT processing.  The window size actually used will depend on
many factors, but it can be influenced.  These options may not
be changed after construction.
-}
data Window
  = Standard
  {- ^
  Use the default window size.
  The actual size will vary depending on other parameters.
  This option is expected to produce better results than the
  other window options in most situations.
  -}
  | Short
  {- ^
  Use a shorter window.  This may
  result in crisper sound for audio that depends strongly on
  its timing qualities.
  -}
  | Long
  {- ^
  Use a longer window.  This is
  likely to result in a smoother sound at the expense of
  clarity and timing.
  -}
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

{- |
'Smoothing' flags control the use of
window-presum FFT and time-domain smoothing.  These options may
not be changed after construction.
-}
data Smoothing
  = SmoothingOff
  {- ^
  Do not use time-domain smoothing.
  This is the default.
  -}
  | SmoothingOn
  {- ^
  Use time-domain smoothing.  This
  will result in a softer sound with some audible artifacts
  around sharp transients, but it may be appropriate for longer
  stretches of some instruments and can mix well with a 'Window' setting of
  'Short'.
  -}
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

{- |
'Formant' flags control the handling of
formant shape (spectral envelope) when pitch-shifting.  These
options may be changed at any time.
-}
data Formant
  = Shifted
  {- ^
  Apply no special formant
  processing.  The spectral envelope will be pitch shifted as
  normal.  This is the default.
  -}
  | Preserved
  {- ^
  Preserve the spectral
  envelope of the unshifted signal.  This permits shifting the
  note frequency without so substantially affecting the
  perceived pitch profile of the voice or instrument.
  -}
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

{- |
'Pitch' flags control the method used for
pitch shifting.  These options may be changed at any time.
They are only effective in realtime mode; in offline mode, the
pitch-shift method is fixed.
-}
data Pitch
  = HighSpeed
  {- ^
  Use a method with a CPU cost
  that is relatively moderate and predictable.  This may
  sound less clear than 'HighQuality', especially
  for large pitch shifts.  This is the default.
  -}
  | HighQuality
  {- ^
  Use the highest quality
  method for pitch shifting.  This method has a CPU cost
  approximately proportional to the required frequency shift.
  -}
  | HighConsistency
  {- ^
  Use the method that gives
  greatest consistency when used to create small variations in
  pitch around the 1.0-ratio level.  Unlike the previous two
  options, this avoids discontinuities when moving across the
  1.0 pitch scale in real-time; it also consumes more CPU than
  the others in the case where the pitch scale is exactly 1.0.
  -}
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

{- |
'Channels' flags control the method used for
processing two-channel audio.  These options may not be changed
after construction.
-}
data Channels
  = Apart
  {- ^
  Each channel is processed
  individually, though timing is synchronised and phases are
  synchronised at transients (depending on the 'Transients'
  setting).  This gives the highest quality for the individual
  channels but a relative lack of stereo focus and unrealistic
  increase in "width".  This is the default.
  -}
  | Together
  {- ^
  The first two channels (where
  two or more are present) are considered to be a stereo pair
  and are processed in mid-side format; mid and side are
  processed individually, with timing synchronised and phases
  synchronised at transients (depending on the 'Transients'
  setting).  This usually leads to better focus in the centre
  but a loss of stereo space and width.  Any channels beyond
  the first two are processed individually.
  -}
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

-- | A more limited enumeration class, without the extra functionality of
-- Haskell's 'Enum'.
class (Enum o, Bounded o) => Option o where
  -- | Note that 'optionEnum' returns different values from 'toEnum'.
  optionEnum :: o -> Int

setOption :: (Option o) => o -> Int -> Int
setOption o i = let
  allOptions = [minBound..maxBound] `asTypeOf` [o]
  mask = foldr (.|.) 0 $ map optionEnum allOptions
  in (i .&. complement mask) .|. optionEnum o
  -- Each option type uses some bits of an 'Int' for its value.
  -- Conveniently only the low 29 bits are used, fitting nicely
  -- into the Haskell Report's 30-bit guaranteed 'Int' size.

getOption :: (Option o) => Int -> o
getOption i = let
  allOptions = [minBound..maxBound] `asTypeOf` [o]
  lookupList = zip (map optionEnum allOptions) allOptions
  mask = foldr (.|.) 0 $ map fst lookupList
  masked = i .&. mask
  o = fromMaybe
    (error $ "getOption: no result for masked value " ++ show masked)
    (lookup masked lookupList)
  in o

toOptions :: Int -> Options
toOptions i = Options
  (getOption i)
  (getOption i)
  (getOption i)
  (getOption i)
  (getOption i)
  (getOption i)
  (getOption i)
  (getOption i)
  (getOption i)
  (getOption i)
  (getOption i)

fromOptions :: Options -> Int
fromOptions (Options a b c d e f g h i j k) = foldr (.|.) 0
  [ optionEnum a
  , optionEnum b
  , optionEnum c
  , optionEnum d
  , optionEnum e
  , optionEnum f
  , optionEnum g
  , optionEnum h
  , optionEnum i
  , optionEnum j
  , optionEnum k
  ]

instance Option Process where
  optionEnum Offline  = 0x00000000
  optionEnum RealTime = 0x00000001

instance Option Stretch where
  optionEnum Elastic = 0x00000000
  optionEnum Precise = 0x00000010

instance Option Transients where
  optionEnum Crisp  = 0x00000000
  optionEnum Mixed  = 0x00000100
  optionEnum Smooth = 0x00000200

instance Option Detector where
  optionEnum Compound   = 0x00000000
  optionEnum Percussive = 0x00000400
  optionEnum Soft       = 0x00000800

instance Option Phase where
  optionEnum Laminar     = 0x00000000
  optionEnum Independent = 0x00002000

instance Option Threading where
  optionEnum Auto   = 0x00000000
  optionEnum Never  = 0x00010000
  optionEnum Always = 0x00020000

instance Option Window where
  optionEnum Standard = 0x00000000
  optionEnum Short    = 0x00100000
  optionEnum Long     = 0x00200000

instance Option Smoothing where
  optionEnum SmoothingOff = 0x00000000
  optionEnum SmoothingOn  = 0x00800000

instance Option Formant where
  optionEnum Shifted   = 0x00000000
  optionEnum Preserved = 0x01000000

instance Option Pitch where
  optionEnum HighSpeed       = 0x00000000
  optionEnum HighQuality     = 0x02000000
  optionEnum HighConsistency = 0x04000000

instance Option Channels where
  optionEnum Apart    = 0x00000000
  optionEnum Together = 0x10000000

-- | Intended to give good results in most situations.
defaultOptions :: Options
defaultOptions = toOptions 0x00000000

percussiveOptions :: Options
percussiveOptions = toOptions 0x00102000