Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add support for seeking of module files
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: e636bcc8d0fe50097fb8970b3f1158d53732a32617d0bdc8b56d794ffbf85fb6
User & Date: alexa 2024-06-02 07:17:23.858
Context
2024-06-02
07:30
Change the order of some function calls check-in: bd266f6c80 user: alexa tags: trunk
07:17
Add support for seeking of module files check-in: e636bcc8d0 user: alexa tags: trunk
05:58
Remove a debug statement check-in: 4bf9f31327 user: alexa tags: trunk
Changes
Side-by-Side Diff Ignore Whitespace Patch
Changes to NEWS.
17
18
19
20
21
22
23



24
25
26
27
28
29
30
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33







+
+
+







    player's output rate.
  * New: Song-specific configs can be reloaded at run time with the 'C' key.
  * New: theme support.  If truecolor ANSI support isn't working, and you know
    your terminal supports 24-bit colors, try setting the COLORTERM=truecolor
    environment variable before using Benben.
  * New: Added the --long-version argument.
  * New: More startup animations added.
  * New: Seeking support for MP3, MP2, MP1, RIFF WAV, module files, and Au
    files added.  Support for seeking in other formats will come in the
    future.
  * Change: The maximum sample rate is now 48KHz.

changes in Benben 0.4.1 relative to Benben 0.4.0:
  * Updated to YunoSynth v0.4.1.
  * Fixed: Building using Crystal v1.5.0 now works again.
  * Fixed: Building on Dragonfly BSD now works again.
  * Fixed: Disabling certain drivers with the Rakefile now works properly and
Changes to TRUNKSTATUS.
30
31
32
33
34
35
36
37

38
39
40
41
42
43
44
30
31
32
33
34
35
36

37
38
39
40
41
42
43
44







-
+







* Modules (libxmp): working
* MIDI (Haematite): working
* MP1/2/3 (libmpg123): working, not well tested with all layers
* FLAC (RemiAudio): working
* Opus (libopus): working
* Vorbis (libvorbis): working
* WAV/Au (RemiAudio): working
* Seeking: not implemented
* Seeking: working for WAV/Au, modules, and MPEG-1 only
* Resampling: working

======
Config
======

* Overall: working, FORMAT HAS CHANGED SINCE v0.4.1
Changes to shard.lock.
30
31
32
33
34
35
36
37

38
39
40
41
42
43
44
30
31
32
33
34
35
36

37
38
39
40
41
42
43
44







-
+








  remisound:
    fossil: https://nanako.mooo.com/fossil/remisound
    version: 0.1.0

  remixmp:
    fossil: https://nanako.mooo.com/fossil/remixmp
    version: 0.1.0
    version: 0.1.1

  remixspf:
    fossil: https://chiselapp.com/user/MistressRemilia/repository/remixspf
    version: 0.1.2

  yunosynth:
    fossil: https://chiselapp.com/user/MistressRemilia/repository/yunosynth
Changes to shard.yml.
33
34
35
36
37
38
39
40

41
42
43
44
33
34
35
36
37
38
39

40
41
42
43
44







-
+





  remislang:
    fossil: https://chiselapp.com/user/MistressRemilia/repository/remislang
    version: 0.1.1

  remixmp:
    fossil: https://nanako.mooo.com/fossil/remixmp
    version: 0.1.0
    version: 0.1.1

  haematite:
    fossil: https://chiselapp.com/user/MistressRemilia/repository/Haematite
    version: 0.5.0
Changes to src/audio-formats/modulefile.cr.
24
25
26
27
28
29
30

31
32
33
34
35

36
37
38
39
40
41
42
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44







+





+







  # reading it from a file.  The file can optionally be compressed with Gzip,
  # Bzip2, or ZStandard.
  class ModuleFile < PlayableFile
    alias Xmp = RemiXmp

    @ctx : Xmp::Context = Xmp::Context.new
    @loaded : Bool = false
    @dtinfo : RemiXmp::ModuleInfo?

    def initialize(@filename : Path|String)
    end

    def finalize
      @dtinfo = nil
      @ctx.unload if @loaded
    end

    def self.test(filename : Path|String) : Bool
      info = RemiXmp.test(filename)
      !info[0].starts_with?("ID3")
    end
132
133
134
135
136
137
138
























139
140
141
142




143
144
145







146


147
148
149
150
151
152
153
154
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




+
+
+
+



+
+
+
+
+
+
+
-
+
+








      @ctx.unload if @loaded
      @loaded = false
    end

    def totalSamples : UInt64
      @ctx.info.lengthSamples
    end

    def pattern
      @ctx.position
    end

    def nextPattern
      @ctx.nextPosition
    end

    def prevPattern
      @ctx.prevPosition
    end

    def numPatterns : Int32
      self.info(true).detailed.try(&.length) || 0
    end

    def loopCount
      @ctx.loopCount
    end

    def pos : Int64
      ((@ctx.time / 1000) * self.sampleRate).to_i64!
    end

    def stop : Nil
      @ctx.stop unless @ctx.state.unloaded?
    end

    def playing? : Bool
      @ctx.state.playing?
    end

    def info(full : Bool = false)
      ensureFile
      if full
        if ret = @dtinfo
          ret
        else
          ret = @ctx.info(true)
        end
      else
      @ctx.info(full)
        @ctx.info(false)
      end
    end

    @[AlwaysInline]
    def decode(dest : Array(Int16)) : Nil
      @ctx.renderFrame(dest)
    end
  end
end
Changes to src/players/moduleplayer.cr.
35
36
37
38
39
40
41

42
43
44
45
46
47
48
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49







+







    @songHasNoLoop : Bool = false

    @bufI16 : Array(Int16)
    @neededFadeLoops : Int32
    @fadeCoeff : Float64
    @loopSamples : UInt64 = 0u64
    @fadeoutVolAdjust : Float64 = 1.0 # Initial starting multiplier
    @lastPattern : Int32 = -1

    def initialize
      Benben.dlog!("ModulePlayer starting up")

      # Setup buffers and variables
      @bufSize = Benben.config.bufferSize.to_u32
      @bufRealSize = @bufSize * 2 # multiplied by 2 for stereo
78
79
80
81
82
83
84














85
86
87
88
89
90
91
92

93
94
95
96
97
98
99
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+







-
+







    end

    protected def resetInternalVars : Nil
      super
      @skipFadeout = false
      @fadeoutVolAdjust = 1.0 # Initial starting multiplier
    end

    @[AlwaysInline]
    private def updateInfoLine(force : Bool = false) : Nil
      if self.ctx.playing?
        if force || @lastPattern != self.ctx.pattern
          self.infoLine = sprintf("Pattern $%02X / $%02X - %s interpolation",
                                  self.ctx.pattern, Math.max(0, self.ctx.numPatterns - 1),
                                  self.ctx.interpolation.to_s.capitalize)
          @lastPattern = self.ctx.pattern
        end
      else
        self.infoLine = "#{Benben.config.modules.interpolation.to_s.capitalize} interpolation"
      end
    end

    def play(file : PlayableFile) : Nil
      Benben.dlog!("ModulePlayer is going to play a file")
      raise "ModulePlayer received something that wasn't a ModuleFile" unless file.is_a?(ModuleFile)
      @ctx = file

      self.ctx.defaultPan = Benben.config.modules.defaultPanning # Must be done before a module is loaded
      self.infoLine = "#{Benben.config.modules.interpolation.to_s.capitalize} interpolation"
      updateInfoLine
      self.state = PlayerState::Frame

      # Reset some variables
      resetInternalVars

      # Set the total samples and the samples per loop.
      @totalSamples.set(self.ctx.info.lengthSamples)
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
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







-
-
-
-
+
+
-





-
+










+
+







    def playFrame : Bool
      case @state
      in .frame?
        # Render the buffer
        renderBuffers

        # Update position info
        @samplesPlayed.add(@bufSize)
        if @samplesPlayed.get > @loopSamples
          @samplesPlayed.set(0)
          @currentLoop.add(1)
        @samplesPlayed.set(self.ctx.pos.to_u64)
        @currentLoop.set(self.ctx.loopCount.to_u32!)
        end

        # Send the data to the audio device.
        Benben.driver.writeBuffer(@buf)

        # Are we looping still?
        if @maxLoops.get >= 0 && @currentLoop.get > @maxLoops.get
        if  @maxLoops.get >= 0 && @currentLoop.get > @maxLoops.get
          if !@skipFadeout
            if Benben.config.modules.fadeOutSongs?
              self.state = PlayerState::Fadeout
            else
              self.state = PlayerState::Tails
            end
          else
            self.state = PlayerState::Tails
          end
        end

        updateInfoLine
        true

      in .fadeout?
        # Continue rendering, fading out as we go.
        if@neededFadeLoops > 0
          @neededFadeLoops -= 1
          renderBuffers
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







+







          self.state = PlayerState::Done
          false
        end

      in .tails?
        # Track is not looping, play just a bit longer to add some silence
        # or let some instrument tails play.
        @samplesPlayed.set(self.ctx.totalSamples)
        @buf.fill(0.0f32)
        @effects.channelVolL.set(SILENCE)
        @effects.channelVolR.set(SILENCE)
        if @nonLoopingExtraLoops > 0
          @nonLoopingExtraLoops -= 1
          Benben.driver.writeBuffer(@buf)
          true
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+




















-
+



    def loopUp : Nil
      Benben.ui.queueError("Cannot change loop count at runtime for this format")
    end

    def loopDown : Nil
      Benben.ui.queueError("Cannot change loop count at runtime for this format")
    end

    def seekForward : Nil
      unless self.ctx.pattern + 1 >= self.ctx.numPatterns
        self.ctx.nextPattern
      end
      @samplesPlayed.set(self.ctx.pos.to_u64)
    end

    def seekBackward : Nil
      unless self.ctx.pattern == 0
        self.ctx.prevPattern
      end
      @samplesPlayed.set(self.ctx.pos.to_u64)
    end

    def stop : Nil
      Benben.dlog!("Stopping ModulePlayer")
      @skipFadeout = true
      self.state = PlayerState::Done
      self.ctx.stop
    end

    def curFile : PlayableFile?
      @ctx
    end

    def toggleInterpolation : Nil
      interp = self.ctx.interpolation
      newInterp = case interp
                  in .nearest? then RemiXmp::Interpolation::Linear
                  in .linear? then RemiXmp::Interpolation::Spline
                  in .spline? then RemiXmp::Interpolation::Nearest
                  end
      self.ctx.interpolation = newInterp
      self.infoLine = "#{newInterp.to_s.capitalize} interpolation"
      updateInfoLine(true)
    end
  end
end
Changes to src/uis/orig/orig.cr.
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
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







-
+






+







+







    MESSAGE_TIME = 5.seconds

    # 30 fps, should be more than plenty for a TUI.
    FPS_CAP = 1000.0 / 30.0

    ##
    ## These are messages that are displayed when `h` is pressed.  These must be
    ## 3-4 lines each after the call to String#strip.
    ## 4-5 lines each after the call to String#strip.

    HELP_MESSAGE = %|
n - Next  p - Prev  t - Tag lang          a - Vol up    ] - Loop up
q - Quit  e - EQ    c - Toggle soft clip  z - Vol down  [ - Loop down
s - Toggle stereo enhancer                Space - Pause/Unpause
R - Redraw screen   S - Stop after song   C - Reload song configs
> - Seek forward    < - Seek backward
    |.strip.lines

    HELP_MESSAGE_MODULES = %|
n - Next  p - Prev  i - Toggle Interp.    a - Vol up
q - Quit  e - EQ    c - Toggle soft clip  z - Vol down
s - Toggle stereo enhancer                Space - Pause/Unpause
R - Redraw screen   S - Stop after song   C - Reload song configs
> - Next pattern    < - Prev pattern
    |.strip.lines

    HELP_MESSAGE_MIDI = %|
n - Next  p - Prev  x - Toggle Chorus     a - Vol up
q - Quit  e - EQ    c - Toggle soft clip  z - Vol down
s - Toggle stereo enhancer                Space - Pause/Unpause
R - Redraw screen   S - Stop after song   C - Reload song configs