Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: |
| Downloads: |
Tarball
| ZIP archive
|
|---|
| Timelines: |
family
| ancestors
| descendants
| both
| trunk
|
| Files: |
files
| file ages
| folders
|
| SHA3-256: |
04ad13987180da288841ce02a511c001b67742453b128cdb223b6688a9d05c9e |
| User & Date: |
alexa
2024-09-05 10:07:20.186 |
Context
|
2024-09-05
| | |
| 10:08 |
|
check-in: 9e227e475c user: alexa tags: trunk
|
| 10:07 |
|
check-in: 04ad139871 user: alexa tags: trunk
|
| 10:06 |
|
check-in: 64d9455ed9 user: alexa tags: library-consolidation
|
|
2024-09-03
| | |
| 11:05 |
|
check-in: cbf56c05e8 user: alexa tags: trunk
|
| | |
Changes
Changes to shard.lock.
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
|
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
|
-
-
-
-
+
-
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
|
version: 2.0
shards:
haematite:
fossil: https://chiselapp.com/user/MistressRemilia/repository/Haematite
version: 0.5.3
libremiliacr:
fossil: https://chiselapp.com/user/MistressRemilia/repository/libremiliacr
version: 0.91.0
remiaudio:
fossil: https://chiselapp.com/user/MistressRemilia/repository/remiaudio
version: 0.6.3
racodecs:
remimpg123:
fossil: https://nanako.mooo.com/fossil/remimpg123
version: 0.1.4
fossil: https://nanako.mooo.com/fossil/racodecs
version: 0.1.0+fossil.commit.b164f0f81df9bbc39488de99cce2cab13d8aa3a6465802be4612ffc2e542b732
remiportaudio:
fossil: https://chiselapp.com/user/MistressRemilia/repository/remiportaudio
version: 0.1.2
remiaudio:
fossil: https://chiselapp.com/user/MistressRemilia/repository/remiaudio
version: 0.7.0
remislang:
fossil: https://chiselapp.com/user/MistressRemilia/repository/remislang
version: 0.1.2
remisound:
fossil: https://nanako.mooo.com/fossil/remisound
version: 0.1.2
remixmp:
fossil: https://nanako.mooo.com/fossil/remixmp
version: 0.90.1
remixspf:
fossil: https://chiselapp.com/user/MistressRemilia/repository/remixspf
version: 0.90.1
yunosynth:
fossil: https://chiselapp.com/user/MistressRemilia/repository/yunosynth
version: 0.4.4+fossil.commit.90e380c31064e2202314916cdc549b10f413cc757f5fcc16d548f7a86e5b7bef
zstd:
git: https://github.com/didactic-drunk/zstd.cr.git
version: 1.2.0
|
| ︙ | | |
Changes to shard.yml.
| ︙ | | |
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
|
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
|
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
main: src/main.cr
dependencies:
libremiliacr:
fossil: https://chiselapp.com/user/MistressRemilia/repository/libremiliacr
version: 0.91.0
remiaudio:
fossil: https://chiselapp.com/user/MistressRemilia/repository/remiaudio
version: 0.7.0
racodecs:
fossil: https://nanako.mooo.com/fossil/racodecs
yunosynth:
fossil: https://chiselapp.com/user/MistressRemilia/repository/yunosynth
#version: 0.4.4
commit: 90e380c31064e2202314916cdc549b10f413cc757f5fcc16d548f7a86e5b7bef
haematite:
fossil: https://chiselapp.com/user/MistressRemilia/repository/Haematite
version: 0.5.3
remiaudio: # We use a slightly newer version of RemiAudio to get QOA support.
fossil: https://chiselapp.com/user/MistressRemilia/repository/remiaudio
version: 0.6.3
remixspf:
fossil: https://chiselapp.com/user/MistressRemilia/repository/remixspf
version: 0.90.1
remisound:
fossil: https://nanako.mooo.com/fossil/remisound
version: 0.1.2
remislang:
fossil: https://chiselapp.com/user/MistressRemilia/repository/remislang
version: 0.1.2
remixmp:
fossil: https://nanako.mooo.com/fossil/remixmp
version: 0.90.1
remimpg123:
fossil: https://nanako.mooo.com/fossil/remimpg123
version: 0.1.4
#remiwavpack:
# fossil: https://nanako.mooo.com/fossil/remiwavpack
# tag: tip
|
Changes to spec/config_spec.cr.
1
2
3
4
5
6
7
8
9
10
11
12
|
1
2
3
4
5
6
7
8
9
10
11
12
|
-
+
|
require "./spec_helper"
require "../src/config/*"
require "../src/reverb-monkeys"
@[RemiConf::ConfigOpts(filename: "benben.yaml", format: :yaml)]
@[RemiLib::Config::ConfigOpts(filename: "benben.yaml", format: :yaml)]
class Benben::Config
def checkForDefaults : Nil
self.stderrLogging.should eq DEF_STDERR_LOGGING
self.audioDriver.should eq DEF_DRIVER
self.bufferSize.should eq DEF_BUFFER_SIZE
self.sampleRate.should eq DEF_SAMPLE_RATE
self.repeatList?.should eq DEF_REPEAT_LIST
|
| ︙ | | |
Changes to spec/theme_spec.cr.
1
2
3
4
5
6
7
8
9
10
11
|
1
2
3
4
5
6
7
8
9
10
11
|
-
+
|
require "./spec_helper"
require "../src/config/theme"
@[RemiConf::ConfigOpts(filename: "benben.yaml", format: :yaml)]
@[RemiLib::Config::ConfigOpts(filename: "benben.yaml", format: :yaml)]
class Benben::Theme
def checkForDefaults : Nil
self.version.should eq THEME_FORMAT_VERSION
self.bgColor.should eq DEF_BG_COLOR
self.fgColor.should eq DEF_FG_COLOR
self.bannerColor.should eq DEF_BANNER_COLOR
|
| ︙ | | |
Changes to src/audio-driver.cr.
| ︙ | | |
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
-
+
-
|
#### 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
#### Affero General Public License for more details.
####
#### You should have received a copy of the GNU Affero General Public License
#### along with this program. If not, see <https://www.gnu.org/licenses/>.
require "remisound"
require "remiaudio"
require "./common"
####
#### Abstract Audio Driver Interface
####
#### Audio drivers can be disabled by sending the appropriate -D option to the
#### compiler. At least one audio driver must be enabled, however.
####
|
| ︙ | | |
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
-
+
|
# Use PortAudio for output.
PortAudio
{% end %}
end
# Base interface for all audio drivers.
class AudioDriver
@driver = uninitialized RemiSound::AudioDevice
@driver = uninitialized RemiAudio::Drivers::AudioDevice
# Creates an `AudioDriver` instance based on the value of `drv`. If this is
# `Any`, then Benben will select an audio driver automatically.
def initialize(drv : Driver)
{% begin %}
# Adjust drv in case it's set to Any
drv = if drv.any?
|
| ︙ | | |
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
|
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
|
-
+
-
+
-
+
-
+
-
+
-
+
|
# Create the AudioDriver instance.
@driver = AudioDriver.makeDevice(drv)
@driver.bufferSize = Benben.config.bufferSize.to_u32!
@driver.start
{% end %}
end
protected def self.makeDevice(drv : Driver) : RemiSound::AudioDevice
protected def self.makeDevice(drv : Driver) : RemiAudio::Drivers::AudioDevice
{% begin %}
ret = case drv
{% unless flag?(:benben_no_pulse_audio) %}
when .pulse_audio? then RemiSound::PulseDevice.new(Benben.config.sampleRate, 32, 2)
when .pulse_audio? then RemiAudio::Drivers::PulseAudio::PulseDevice.new(Benben.config.sampleRate, 32, 2)
{% end %}
{% unless flag?(:benben_no_port_audio) %}
when .port_audio? then RemiSound::PortDevice.new(Benben.config.sampleRate, 32, 2)
when .port_audio? then RemiAudio::Drivers::PortAudio::PortDevice.new(Benben.config.sampleRate, 32, 2)
{% end %}
{% unless flag?(:benben_no_ao) %}
# Output must be 8-, 16-, or 24-bit for Libao. We'll use 16-bit.
when .ao? then RemiSound::AoDevice.new(Benben.config.sampleRate, 16, 2)
when .ao? then RemiAudio::Drivers::Ao::AoDevice.new(Benben.config.sampleRate, 16, 2)
{% end %}
else raise "Unexpected audio driver: #{drv}"
end
{%unless flag?(:benben_no_pulse_audio) %}
# We can set some additional info for PulseAudio.
if ret.is_a?(RemiSound::PulseDevice)
if ret.is_a?(RemiAudio::Drivers::PulseAudio::PulseDevice)
ret.programName = "Benben"
ret.streamName = "Benben Audio Playback"
end
{% end %}
ret
{% end %}
end
def deinit : Nil
@driver.stop
end
@[AlwaysInline]
def writeBuffer(buf : Array(Float32)) : Nil
{% if flag?(:benben_no_port_audio) %}
@driver.writeBuffer(buf)
{% else %}
begin
@driver.writeBuffer(buf)
rescue RemiPA::PaError
rescue RemiAudio::RemiAudioError
end
{% end %}
end
end
end
{% end %}
#require "./audio-drivers/*"
|
Changes to src/audio-formats/bindings/vorbis.cr.
| ︙ | | |
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
-
|
#### 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
#### Affero General Public License for more details.
####
#### You should have received a copy of the GNU Affero General Public License
#### along with this program. If not, see <https://www.gnu.org/licenses/>.
require "../ogg"
####
#### Basic libvorbis Bindings
####
@[Link(ldflags: "`pkg-config vorbis --libs`")]
lib LibVorbis
|
| ︙ | | |
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
|
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
|
-
+
|
#
# This only checks up to the first 8192 bytes of the file.
def self.test(filename : Path|String) : Bool
Benben.dlog!("Testing #{filename} to see if it's a Vorbis file")
ret = false
File.open(filename, "rb") do |file|
demux = Benben::OggDemuxer.new(file, 8192)
demux = RemiAudio::Demuxers::Ogg.new(file, 8192)
packet1 = demux.nextPacket(8192)
return false if packet1.empty?
Decoder.withPacket(packet1, true, false, 0, 0) do |packet1Ptr|
if LibVorbis.vorbis_synthesis_idheader(packet1Ptr)
info : LibVorbis::VorbisInfo = LibVorbis::VorbisInfo.new
comment : LibVorbis::VorbisComment = LibVorbis::VorbisComment.new
|
| ︙ | | |
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
|
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
|
-
+
|
end # demux.nextPacket(8192)
end
end # if LibVorbis.vorbis_synthesis_idheader(packet1Ptr)
end # Decoder.withPacket(packet1, true, false, 0, 0) do |packet1Ptr|
end # File.open(filename, "rb") do |file|
ret
rescue ::Benben::OggDemuxer::MissingMarkerError
rescue RemiAudio::Demuxers::Ogg::MissingMarkerError
false
rescue err : Exception
Benben.dlog!("Error in Vorbis::Decoder.test: #{err} (#{err.backtrace})")
false
end
def sampleRate : Int64
|
| ︙ | | |
Changes to src/audio-formats/flacfile.cr.
| ︙ | | |
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
|
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
|
-
-
+
+
-
+
-
+
-
+
-
+
-
+
|
module Benben
# A simple wrapper for module files. This stores raw module data in RAM by
# reading it from a file. The file can optionally be compressed with Gzip,
# Bzip2, or ZStandard.
class FlacFile < PlayableFile
@io : IO?
@flac : RAFlac::StreamedFlac?
@flacDec : RAFlac::StreamedDecoder?
@flac : RAFlac::Flac?
@flacDec : RAFlac::Decoder?
protected def initialize(@filename : String)
ensureFile || raise PlayableFileError.new("Cannot load file: #{@filename}")
@taginfo = TagInfo.create(self)
_ = self.timeLength
end
def self.test(filename : Path|String) : Bool
File.open(filename, "rb") do |io|
validFlac?, flacChan, _, _ = RAFlac::AbstractFlac.testInfo(io)
validFlac?, flacChan, _, _ = RAFlac::Flac.testInfo(io)
validFlac? && flacChan == 2 # We only support stereo FLAC files
end
rescue Exception
false
end
def ensureFile : Bool
if @flac.nil?
if @io.nil?
@io = File.open(@filename, "rb")
else
@io.not_nil!.close
end
@flac = RAFlac::StreamedFlac.new(@io.not_nil!)
@flac = RAFlac::Flac.new(@io.not_nil!)
end
true
rescue err : Exception
RemiLib.log.error("Cannot load FLAC #{@filename}: #{err}")
false
end
def unload : Nil
Benben.dlog!("Unloading FLAC file: #{@filename}")
@flac = nil
@io = nil
@flacDec = nil
end
def flac : RAFlac::StreamedFlac
def flac : RAFlac::Flac
ensureFile
@flac.not_nil!
end
def decoder : RAFlac::StreamedDecoder
def decoder : RAFlac::Decoder
ensureFile
if @flacDec.nil?
@flacDec = RAFlac::StreamedDecoder.new(@flac.not_nil!, Benben.config.bufferSize)
@flacDec = RAFlac::Decoder.new(@flac.not_nil!, Benben.config.bufferSize)
end
@flacDec.not_nil!
end
def sampleFormat
ensureFile
@flac.not_nil!.sampleFormat
|
| ︙ | | |
94
95
96
97
98
99
100
101
102
103
104
105
106
|
94
95
96
97
98
99
100
101
102
103
104
105
106
|
-
-
-
+
+
+
|
end
def rewind : Nil
@flac.try(&.rewind)
@flacDec.try(&.rewind)
end
@[AlwaysInline]
def decode(dest : Array(Int32)) : Int32
self.decoder.renderStereo(dest)
def decode(dest : Array(Float32)) : Int32
ensureFile
self.decoder.decode(dest)
end
end
end
|
Changes to src/audio-formats/midifile.cr.
| ︙ | | |
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
|
-
-
+
-
+
-
-
+
-
+
|
@seq.not_nil!.play(@file.not_nil!, loop?)
end
def finished? : Bool
@seq.not_nil!.finished?
end
@[AlwaysInline]
def decode(dest : Array(Float32)) : Int64
def decode(dest : Array(Float64)) : Int32
ensureFileInRAM
@seq.not_nil!.render(dest, @arraypool)
dest.size.to_i64!
dest.size.to_i32!
end
@[AlwaysInline]
def decode(dest : Array(Float64)) : Int64
def decode(dest : Array(Float32)) : Int32
ensureFileInRAM
@seq.not_nil!.render(dest, @arraypool)
dest.size.to_i64!
dest.size.to_i32!
end
end
end
|
Changes to src/audio-formats/modulefile.cr.
| ︙ | | |
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
+
|
# Bzip2, or ZStandard.
class ModuleFile < PlayableFile
alias Xmp = RemiXmp
@ctx : Xmp::Context = Xmp::Context.new
@loaded : Bool = false
@dtinfo : RemiXmp::ModuleInfo?
@bufI16 : Array(Int16) = [] of Int16
protected def initialize(@filename : String)
ensureFile || raise PlayableFileError.new("Cannot load file: #{@filename}")
@taginfo = TagInfo.create(self)
_ = self.timeLength
end
|
| ︙ | | |
180
181
182
183
184
185
186
187
188
189
190
191
192
|
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
|
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
|
ret = @ctx.info(true)
end
else
@ctx.info(false)
end
end
def decode(dest : Array(Float32)) : Int32
{% begin %}
ensureFile
unless dest.size == @bufI16.size
@bufI16 = Array(Int16).new(dest.size, 0i16)
end
@[AlwaysInline]
def decode(dest : Array(Int16)) : Nil
@ctx.renderFrame(dest)
end
end
end
@ctx.renderFrame(@bufI16)
@bufI16.size.times do |i|
dest.put!(i, @bufI16.get!(i) * {{ 1.0f32 / 32768.0f32 }})
end
dest.size
{% end %}
end
def decode(dest : Array(Float64)) : Int32
{% begin %}
ensureFile
unless dest.size == @bufI16.size
@bufI16 = Array(Int16).new(dest.size, 0i16)
end
@ctx.renderFrame(@bufI16)
@bufI16.size.times do |i|
dest.put!(i, @bufI16.get!(i) * {{ 1.0 / 32768.0 }})
end
dest.size
{% end %}
end
end
end
|
Changes to src/audio-formats/mpeg1file.cr.
| ︙ | | |
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
|
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
|
-
-
-
-
-
-
+
|
#### 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
#### Affero General Public License for more details.
####
#### You should have received a copy of the GNU Affero General Public License
#### along with this program. If not, see <https://www.gnu.org/licenses/>.
require "remimpg123"
require "./playablefile"
####
#### Simple wrapper for MPEG-1 files.
####
module Benben
class Mpeg1File < PlayableFile
@decoder : RemiMpg123::Decoder?
protected def initialize(@filename : String)
ensureFile || raise PlayableFileError.new("Cannot load file: #{@filename}")
@taginfo = TagInfo.create(self)
_ = self.timeLength
end
def finalize
@decoder = nil
end
def self.test(filename : Path|String) : Bool
if mpegInfo = RemiMpg123::Decoder.test(filename)
mpegInfo.mode != Mpg123::MODE_MONO
mpegInfo.mode != RACodecs::MPEG1::Low::MODE_MONO
else
false
end
end
def ensureFile : Bool
if @decoder.nil?
|
| ︙ | | |
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
-
-
-
+
+
+
|
Benben.dlog!("Unloading MPEG-1 file: #{@filename}")
@decoder = nil
end
def replayGain=(mode : ReplayGain) : Nil
ensureFile
@decoder.not_nil!.rva = case mode
in .disabled? then Mpg123::RvaMode::Off
in .mix? then Mpg123::RvaMode::Mix
in .album? then Mpg123::RvaMode::Album
in .disabled? then RACodecs::MPEG1::Low::RvaMode::Off
in .mix? then RACodecs::MPEG1::Low::RvaMode::Mix
in .album? then RACodecs::MPEG1::Low::RvaMode::Album
end
Benben.dlog!("Set RVA mode for MPEG-1 decoder: #{@decoder.not_nil!.rva}")
end
def sampleRate
ensureFile
@decoder.not_nil!.sampleRate
|
| ︙ | | |
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
-
+
|
def layer
ensureFile
@decoder.not_nil!.layer
end
def totalSamples : UInt64
ensureFile
@decoder.not_nil!.totalSamples
@decoder.not_nil!.totalSamples.to_u64
end
def id3
ensureFile
@decoder.not_nil!.id3
end
|
| ︙ | | |
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
110
111
112
113
114
115
116
117
118
119
120
121
122
|
-
-
+
-
+
|
@[AlwaysInline]
def pos=(value) : Int64
ensureFile
@decoder.not_nil!.pos = value
end
@[AlwaysInline]
def decode(dest : Array(Float32)) : Int64
def decode(dest : Array(Float32)) : Int32
ensureFile
@decoder.not_nil!.decode(dest)
@decoder.not_nil!.decode(dest).to_i32!
end
end
end
|
Deleted src/audio-formats/ogg.cr.
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
|
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
#### Benben
#### Copyright (C) 2023-2024 Remilia Scarlet <remilia@posteo.jp>
####
#### This program is free software: you can redistribute it and/or
#### modify it under the terms of the GNU Affero 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
#### Affero General Public License for more details.
####
#### You should have received a copy of the GNU Affero General Public License
#### along with this program. If not, see <https://www.gnu.org/licenses/>.
####
#### Basic Ogg Demuxer
####
module Benben
class OggDemuxer
MAGIC = "OggS"
MAGIC_BYTES = MAGIC.to_bytes
PAGE_VERSION = 0 # Mandated to always be zero by the specifications
@[Flags]
enum PageType : UInt8
Continuation = 0x01u8
StreamStart = 0x02u8
StreamEnd = 0x04u8
end
class Error < Exception
end
class BadVersion < Error
end
class MissingMarkerError < Error
end
@io : IO
@packetsDecoded : Int64 = 0i64
getter? atPage : Bool = false
getter startPos : Int64 = 0i64
getter type : PageType = PageType::None
getter granulePos : UInt64 = 0u64
getter serial : UInt32 = 0u32
getter seqNumber : UInt32 = 0u32
getter crc : UInt32 = 0u32
@lastSegment : UInt32 = 0u32
@segmentLengths : Array(UInt8) = Array(UInt8).new(257, 0u8) # One extra for safety
@curSeg : Int32 = 0
getter pageNumber : UInt64 = 0u64
def packetsDecoded : Int64
@packetsDecoded - 1
end
def packetsDecoded=(@packetsDecoded : Int64)
end
def initialize(@io : IO, upto : Int? = nil)
# Possibly read the first segment
advancePage(upto)
end
def rewind : Nil
@io.pos = 0
@packetsDecoded = 0
advancePage(nil)
end
@[AlwaysInline]
private def findNextMarker(upto : Int?) : Nil
start = @io.pos
loop do
return if @io.read_string(4) == MAGIC
@io.read_byte
if upto && (@io.pos - start >= upto)
raise MissingMarkerError.new("Could not find an Ogg page marker before #{upto} bytes")
end
end
end
private def advancePage(upto : Int?) : Nil
findNextMarker(upto) unless @io.read_string(4) == MAGIC
if byt = @io.read_byte
if byt != PAGE_VERSION
raise BadVersion.new("Invalid Ogg version (byte position: #{@io.pos - 1})")
end
else
raise IO::EOFError.new("Unexpected end of stream (could not find Ogg version)")
end
@startPos = @io.pos.to_i64! - 5
@type = PageType.from_value(@io.read_byte || raise IO::EOFError.new("Could not read Ogg page type"))
@granulePos = @io.readUInt64
@serial = @io.readUInt32
@seqNumber = @io.readUInt32
@crc = @io.readUInt32
@curSeg = 0
@lastSegment = 0
if numSegs = @io.read_byte
numSegs.times do |_|
if len = @io.read_byte
@segmentLengths[@lastSegment] = len
@lastSegment += 1
else
raise IO::EOFError.new("Could not read segment length: unexpected end of stream")
end
end
else
raise IO::EOFError.new("Could not read num segments: unexpected end of stream")
end
@pageNumber += 1
@atPage = true
rescue IO::EOFError
@atPage = false
end
@[AlwaysInline]
protected def advanceSegment(upto : Int?) : Nil
if @atPage
@curSeg += 1
# Advance the page's current segment by one, and check if we've reached
# the end of this page's segments.
advancePage(upto) if @curSeg == @lastSegment
else
# No page yet, read the first one
advancePage(upto)
end
rescue IO::EOFError
@atPage = false
end
def nextPacket(upto : Int? = nil) : Bytes
ret = IO::Memory.new
lastLen : UInt8 = 0
len : UInt8 = 0
pos : Int32 = 0
buf : Bytes = Bytes.new(256)
while @atPage
lastLen = len
len = @segmentLengths[@curSeg]
if len == 0
break if lastLen == 255 # Packet was a multiple of 255?
end
pos = @io.read_fully(buf[...len])
if pos == len
ret.write(buf[...pos])
else
raise Error.new("Could not read enough data for segment #{@curSeg} " \
"(wanted #{len} bytes, got #{pos}")
end
# If the segment is less than 255 bytes, end of packet
break if len < 255
# Goto the next segment
advanceSegment(upto)
end # while @atPage
# Goto the next segment, then return
advanceSegment(upto)
@packetsDecoded += 1
ret.to_slice
end
end
end
|
Changes to src/audio-formats/opusfile.cr.
| ︙ | | |
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
|
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
|
-
-
+
|
#### 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
#### Affero General Public License for more details.
####
#### You should have received a copy of the GNU Affero General Public License
#### along with this program. If not, see <https://www.gnu.org/licenses/>.
require "./ogg"
require "./bindings/opus"
require "./playablefile"
####
#### Simple wrapper for Opus files.
####
module Benben
# A simple wrapper for Opus files.
class OpusFile < PlayableFile
OPUS_ID_HEADER = "OpusHead".to_slice
OPUS_COMMENT_HEADER = "OpusTags".to_slice
OPUS_VERSION = 1
@io : IO?
@demuxer : OggDemuxer?
@demuxer : RemiAudio::Demuxers::Ogg?
@decoder : Opus::Decoder?
@preSkip : UInt16 = 0u16
@inputSampleRate : UInt32 = 0u32
@outputGain : Float32 = 0.0f32
@channels : UInt8 = 0u8
@tagPacket : Bytes = Bytes.new(0)
getter? needResampling : Bool = false
|
| ︙ | | |
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
|
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
|
-
+
-
+
|
# `true` if it is, or `false` otherwise.
#
# This only checks up to the first 8192 bytes of the file.
def self.test(filename : Path|String) : Bool
Benben.dlog!("Testing #{filename} to see if it's an Opus file")
ret = false
File.open(filename, "rb") do |file|
demux = OggDemuxer.new(file, 8192)
demux = RemiAudio::Demuxers::Ogg.new(file, 8192)
rawPacket = demux.nextPacket
ret = (rawPacket[0..7] == OPUS_ID_HEADER &&
rawPacket[8] == OPUS_VERSION &&
rawPacket[9] == 2) # We only support 2 channels
end
ret
rescue Exception
false
end
private def loadOgg : Nil
RemiLib.assert(@demuxer.nil?)
if @io.nil?
@io = File.open(@filename, "rb")
else
@io.not_nil!.close
end
@demuxer = OggDemuxer.new(@io.not_nil!, UInt16::MAX)
@demuxer = RemiAudio::Demuxers::Ogg.new(@io.not_nil!, UInt16::MAX)
end
private def loadOpus : Nil
loadOgg
# This should have already been checked by the FileHandler, but we check
# here as well for consistency.
|
| ︙ | | |
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
|
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
-
+
|
end
true
rescue err : Exception
RemiLib.log.error("Cannot load Opus #{@filename}: #{err}")
false
end
def demuxer : OggDemuxer
def demuxer : RemiAudio::Demuxers::Ogg
ensureFile
@demuxer.not_nil!
end
def decoder : Opus::Decoder
ensureFile
@decoder.not_nil!
|
| ︙ | | |
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
|
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
|
+
+
+
+
+
|
def unload : Nil
Benben.dlog!("Unloading Opus file: #{@filename}")
@decoder = nil
@demuxer = nil
@io.try(&.close)
@io = nil
end
def decode(dest : Array(Float32)) : Int32
# TODO
raise NotImplementedError.new("Use other decode method")
end
@[AlwaysInline]
def decode(packet : Bytes, dest : Array(Float32)) : Int32
ret = self.decoder.decode(packet, dest)
dest.map!(&.*(@outputGain)) unless @outputGain == 1.0f32
ret
end
end
end
|
Changes to src/audio-formats/pcmfile.cr.
| ︙ | | |
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
|
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
+
-
-
-
-
|
####
#### Wrapper for PCM files (e.g. RIFF WAVE and Au files)
####
module Benben
class PcmFile < PlayableFile
@file : RemiAudio::Formats::AudioFile?
@buf64 : Array(Float64) = [] of Float64
protected def initialize(@filename : String)
ensureFile || raise PlayableFileError.new("Cannot load file: #{@filename}")
@taginfo = TagInfo.create(self)
_ = self.timeLength
end
def finalize
@file = nil
end
def self.test(filename : Path|String) : Bool
file = RemiAudio::Formats::AudioFile.open(filename)
file.channels == 2
rescue Exception
false
end
|
| ︙ | | |
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
|
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
|
-
-
-
-
+
-
+
|
@[AlwaysInline]
def pos=(value) : Int64
ensureFile
@file.not_nil!.pos = value * self.channels
@file.not_nil!.pos.to_i64.tdiv(self.channels)
end
@buf64 : Array(Float64) = [] of Float64
@[AlwaysInline]
def decode(dest : Array(Float32)) : Int64
def decode(dest : Array(Float32)) : Int32
if @buf64.size != dest.size
@buf64 = Array(Float64).new(dest.size, 0.0)
end
ensureFile
numRead = @file.not_nil!.read(@buf64)
if numRead < @buf64.size
@buf64.fill(0.0, numRead..)
end
dest.size.times do |idx|
dest.unsafe_put(idx, @buf64.unsafe_fetch(idx).to_f32!)
end
@buf64.size.to_i64!
@buf64.size.to_i32!
end
end
end
|
Changes to src/audio-formats/playablefile.cr.
| ︙ | | |
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
-
|
#### 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
#### Affero General Public License for more details.
####
#### You should have received a copy of the GNU Affero General Public License
#### along with this program. If not, see <https://www.gnu.org/licenses/>.
require "remixspf"
require "../taginfo"
####
#### Simple wrapper for MIDI files.
####
module Benben
|
| ︙ | | |
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
+
+
|
FileType::Vgm if VgmFile.test(filename)
when ".ogg", ".oga"
FileType::Vorbis if VorbisFile.test(filename)
when ".opus"
FileType::Opus if OpusFile.test(filename)
when ".flac"
FileType::Flac if FlacFile.test(filename)
when ".wv"
FileType::WavPack if WavPackFile.test(filename)
when ".mp3", ".mp2", ".mp1"
FileType::Mpeg1 if Mpeg1File.test(filename)
when ".wav", ".wave", ".au"
FileType::Pcm if PcmFile.test(filename)
when ".midi", ".mid", ".mus", ".rmi"
FileType::Midi if MidiFile.test(filename)
when ".qoa"
|
| ︙ | | |
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
+
+
|
ret = FileType::Module
when VorbisFile.test(filename)
ret = FileType::Vorbis
when OpusFile.test(filename)
ret = FileType::Opus
when FlacFile.test(filename)
ret = FileType::Flac
when WavPackFile.test(filename)
ret = FileType::WavPack
when PcmFile.test(filename)
ret = FileType::Pcm
# This one can sometimes give false positives for module files, so do it
# *AFTER* module testing
when Mpeg1File.test(filename)
ret = FileType::Mpeg1
|
| ︙ | | |
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
+
|
in .flac? then return FlacFile.new(filename)
in .opus? then return OpusFile.new(filename)
in .vorbis? then return VorbisFile.new(filename)
in .mpeg1? then return Mpeg1File.new(filename)
in .midi? then return MidiFile.new(filename)
in .pcm? then return PcmFile.new(filename)
in .qoa? then return QoaFile.new(filename)
in .wav_pack? then return WavPackFile.new(filename)
in .unknown?
RemiLib.log.error("Cannot play file: #{filename}")
nil
end
rescue err : Yuno::YunoError
Benben.dlog!("YunoSynth Error loading file: #{err} (#{err.backtrace}")
RemiLib.log.error("Cannot load file: #{err}")
|
| ︙ | | |
225
226
227
228
229
230
231
232
233
|
229
230
231
232
233
234
235
236
237
238
239
|
+
+
|
abstract def totalSamples : UInt64
# Ensures the underlying file context used for decoding is in memory.
abstract def ensureFile : Bool
# Unloads the underlying file context from memory.
abstract def unload : Nil
abstract def decode(dest : Array(Float32)) : Int32
end
end
|
Changes to src/audio-formats/qoafile.cr.
| ︙ | | |
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
-
-
-
-
|
protected def initialize(@filename : String)
ensureFile || raise PlayableFileError.new("Cannot load file: #{@filename}")
@taginfo = TagInfo.create(self)
_ = self.timeLength
end
def finalize
@ctx = nil
end
def self.test(filename : Path|String) : Bool
if qoa = Qoa::Decoder.test(filename)
qoa.channels == 2
else
false
end
rescue Exception
|
| ︙ | | |
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
-
+
|
def channels
ensureFile
@ctx.not_nil!.qoa.channels
end
def totalSamples : UInt64
ensureFile
@ctx.not_nil!.totalSamples
@ctx.not_nil!.totalSamples.to_u64
end
def totalFrames : UInt64
ensureFile
@ctx.not_nil!.totalFrames.to_u64!
end
|
| ︙ | | |
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
|
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
-
+
-
-
+
-
+
|
raise NotImplementedError.new("Use #framePos= for QoaFile")
end
# Seeks to the given frame.
@[AlwaysInline]
def framePos=(value) : Nil
ensureFile
@ctx.not_nil!.seek(value) # Note: value is a FRAME INDEX here
@ctx.not_nil!.pos = value # Note: value is a FRAME INDEX here
@samplesDecoded = @ctx.not_nil!.framesDecoded.to_i64 * Qoa::FRAME_LEN
@samplesDecoded *= @ctx.not_nil!.qoa.channels
end
@[AlwaysInline]
def decode(dest : Array(Float32)) : Int64
def decode(dest : Array(Float32)) : Int32
ensureFile
numRead = @ctx.not_nil!.decode(dest)
if numRead < dest.size
dest.fill(0.0f32, numRead..)
end
@samplesDecoded += numRead.tdiv(@ctx.not_nil!.qoa.channels)
numRead.to_i64!
numRead.to_i32!
end
end
end
|
Changes to src/audio-formats/vgmfile.cr.
| ︙ | | |
114
115
116
117
118
119
120
121
122
|
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
+
+
+
+
+
|
@timeLength = (self.totalSamples / self.sampleRate).seconds
else
perLoop : Int64 = self.samplesPerLoop.to_i64 * (loops - 1)
@timeLength = ((self.totalSamples.to_i64 + perLoop) / self.sampleRate).seconds
@timeLength = @timeLength.not_nil! + Benben.config.fadeoutSeconds.seconds
end
end
def decode(dest : Array(Float32)) : Int32
# TODO
raise NotImplementedError.new("Use a Yuno::VgmPlayer directly for now")
end
end
end
|
Changes to src/audio-formats/vorbisfile.cr.
| ︙ | | |
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
|
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
|
-
-
+
|
#### 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
#### Affero General Public License for more details.
####
#### You should have received a copy of the GNU Affero General Public License
#### along with this program. If not, see <https://www.gnu.org/licenses/>.
require "./ogg"
require "./playablefile"
require "./bindings/vorbis"
####
#### Simple wrapper for Vorbis files.
####
module Benben
# A simple wrapper for Vorbis files.
class VorbisFile < PlayableFile
VORBIS_HEADER = "vorbis".to_slice
VORBIS_COMMENT_PACKET_TYPE = 0x03
@io : IO?
@demuxer : OggDemuxer?
@demuxer : RemiAudio::Demuxers::Ogg?
@decoder : Vorbis::Decoder?
@channels : UInt8 = 0u8
@tagPacket : Bytes = Bytes.new(0)
@sampleRate : UInt32 = 0u32
@totalSamples : UInt64 = 0u64
@mutex : Mutex = Mutex.new
|
| ︙ | | |
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
-
+
|
RemiLib.assert(@demuxer.nil?)
if @io.nil?
@io = File.open(@filename, "rb")
else
@io.not_nil!.close
@io = File.open(@filename, "rb")
end
@demuxer = OggDemuxer.new(@io.not_nil!, UInt16::MAX)
@demuxer = RemiAudio::Demuxers::Ogg.new(@io.not_nil!, UInt16::MAX)
end
private def startDecoder : Nil
@mutex.synchronize do
# Consistency check
RemiLib.assert(@demuxer.nil?)
RemiLib.assert(@decoder.nil?)
|
| ︙ | | |
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
-
+
|
end
def totalSamples : UInt64
ensureFile
@totalSamples
end
def demuxer : OggDemuxer
def demuxer : RemiAudio::Demuxers::Ogg
ensureFile
@demuxer.not_nil!
end
def decoder : Vorbis::Decoder
ensureFile
@decoder.not_nil!
|
| ︙ | | |
158
159
160
161
162
163
164
165
166
|
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
+
+
+
+
-
-
+
+
+
|
def unload : Nil
Benben.dlog!("Unloading Vorbis file: #{@filename}")
@decoder = nil
@demuxer = nil
@io.try(&.close)
@io = nil
end
def decode(dest : Array(Float32)) : Int32
# TODO
raise NotImplementedError.new("Use decoder directly for now")
end
end
end
end
end
|
Added src/audio-formats/wavpackfile.cr.