Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch merge-playablefile-virtualfile Excluding Merge-Ins
This is equivalent to a diff from 13b4854065 to b1f5b6074e
|
2024-08-05
| ||
| 05:01 | Merge merge-playablefile-virtualfile (but don't integrate). Update NEWS. check-in: 1986a4b965 user: alexa tags: trunk | |
| 04:57 | Update TRUNKSTATUS Closed-Leaf check-in: b1f5b6074e user: alexa tags: merge-playablefile-virtualfile | |
| 04:56 | Add a TODO check-in: 20ae9e67c9 user: alexa tags: merge-playablefile-virtualfile | |
| 03:57 | Merge the VirtualFile class into the PlayableFile class. Remove HTTP/Gemini loading. The two classes were overlapping in functionality. By combining them, we simplify some of the file handling code. check-in: 32db9d5345 user: alexa tags: merge-playablefile-virtualfile | |
| 01:19 | Add a check for the UI theme being an array check-in: 13b4854065 user: alexa tags: trunk | |
| 01:18 | Check for an empty array as a theme. Use the default theme in this case. check-in: 4bfcddb0b8 user: alexa tags: trunk | |
Changes to TRUNKSTATUS.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 | Loading ======= * VGM loading: working * Module loading: working * XSPF/JSPF: working, seems to use more RAM than I would like * Local files: working | | | | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | Loading ======= * VGM loading: working * Module loading: working * XSPF/JSPF: working, seems to use more RAM than I would like * Local files: working * HTTP: removed * Gemini: removed ============= Audio/Formats ============= * VGMs: working * Modules (libxmp): working |
| ︙ | ︙ |
Changes to src/audio-formats/flacfile.cr.
| ︙ | ︙ | |||
24 25 26 27 28 29 30 |
# 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?
| | | > < | > > > > > > > > > > > | 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 |
# 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?
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 == 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!)
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
ensureFile
@flac.not_nil!
end
|
| ︙ | ︙ |
Changes to src/audio-formats/midifile.cr.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#### Simple wrapper for MIDI files.
####
module Benben
class MidiFile < PlayableFile
class_getter! globalSoundfont : Haematite::SoundFont?
class_getter globalSoundfontPath : String = ""
getter! soundfont : Haematite::SoundFont?
getter soundfontPath : String = ""
getter! synth : Haematite::Synthesizer?
getter! seq : Haematite::Sequencer?
@file : Haematite::SequencedFile?
@arraypool : RemiLib::ArrayPool(Float64) = RemiLib::ArrayPool(Float64).new(0.0)
| > | | > > > > > > > > | | | | | | | | | | | | | | | | | | | | | | | | > | 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 |
#### Simple wrapper for MIDI files.
####
module Benben
class MidiFile < PlayableFile
class_getter! globalSoundfont : Haematite::SoundFont?
class_getter globalSoundfontPath : String = ""
@@globalSoundfontMut : Mutex = Mutex.new
getter! soundfont : Haematite::SoundFont?
getter soundfontPath : String = ""
getter! synth : Haematite::Synthesizer?
getter! seq : Haematite::Sequencer?
@file : Haematite::SequencedFile?
@arraypool : RemiLib::ArrayPool(Float64) = RemiLib::ArrayPool(Float64).new(0.0)
protected def initialize(@filename : String)
MidiFile.test(@filename) || raise PlayableFileError.new("Cannot load file: #{@filename}")
@taginfo = TagInfo.create(self)
_ = self.timeLength
end
def self.test(filename : Path|String) : Bool
Haematite::SequencedFile.validFile?(filename)
end
private def maybeSetGlobalSoundfont(path : Path|String) : Bool
# TODO: This technically does not set @@globalSoundfontPath to
# Benben.config.midiAll[0].soundfont (the actual real global soundfont).
@@globalSoundfontMut.synchronize do
# We can use the global SoundFont if these conditions hold:
# 1. No global SoundFont is loaded
# 2. The global SoundFont is not an MmapSoundFont (which it never should
# be)
# 3. The global SoundFont path is empty, or the same as the requested
# path.
if @@globalSoundfontPath == ""
if @@globalSoundfont.nil?
@@globalSoundfont = Haematite::SoundFont.new(path)
@@globalSoundfontPath = path
@soundfont = @@globalSoundfont
true
else
raise "We have a global SoundFont path, but no global SoundFont"
end
elsif @@globalSoundfontPath == path
if @@globalSoundfont.is_a?(Haematite::MmapSoundFont)
raise "Global SoundFont somehow set to an MmapSoundFont"
else
@soundfont = @@globalSoundfont
true
end
else
false
end
end
end
# Sets the SoundFont for this `MidiFile` instance. This method never
# utilizes a global SoundFont to save RAM.
def soundfont=(path : String|Path) : Nil
raise "Empty SoundFont path" if path.strip.empty?
|
| ︙ | ︙ | |||
92 93 94 95 96 97 98 |
if (sf = @soundfont).is_a?(Haematite::MmapSoundFont)
sf.close
end
@soundfont = Haematite::SoundFont.new(@soundfontPath)
end
end
| > > > > > > > > > > > > > > > > > > | > > > > | > > > > > > > | | > < | | | | 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 |
if (sf = @soundfont).is_a?(Haematite::MmapSoundFont)
sf.close
end
@soundfont = Haematite::SoundFont.new(@soundfontPath)
end
end
def ensureFile : Bool
# KLUDGE: MIDI files are handled differently because of how SoundFonts are
# loaded. The ensureFile method loads only the MIDI file into RAM without
# creating a MIDI synthesizer.
#
# To actually ensure the file is in RAM and a synth is created, the
# ensureFileInRAM method is instead used, but only internally. This must
# be done after a SoundFont is loaded.
ensureMIDIFileInRAM
true
rescue err : Exception
{% if flag?(:benben_debug) %}
RemiLib.log.error("Cannot load MIDI #{@filename}: #{err}\nBacktrace:\n#{err.backtrace.join('\n')}")
{% else %}
RemiLib.log.error("Cannot load MIDI #{@filename}: #{err}")
{% end %}
false
end
private def ensureMIDIFileInRAM : Nil
@file = Haematite::SequencedFile.load(@filename) if @file.nil?
end
private def ensureFileInRAM : Nil
RemiLib.assert("No SoundFont set", !@soundfont.nil?)
if @synth.nil?
@synth = Haematite::Synthesizer.new(@soundfont.not_nil!, Benben.config.sampleRate.to_i32)
@synth.not_nil!.applySettings(Benben.config.midi.toSynthSettings)
end
@seq = Haematite::Sequencer.new(@synth.not_nil!) if @seq.nil?
@file = Haematite::SequencedFile.load(@filename) if @file.nil?
rescue err : Exception
{% if flag?(:benben_debug) %}
RemiLib.log.error("Cannot load MIDI #{@filename}: #{err}\nBacktrace:\n#{err.backtrace.join('\n')}")
{% else %}
RemiLib.log.error("Cannot load MIDI #{@filename}: #{err}")
{% end %}
end
def totalSamples : UInt64
ensureMIDIFileInRAM
((@file.not_nil!.length / 1000000) * Benben.config.sampleRate).to_u64!
end
def position : UInt64
ensureFileInRAM
((@seq.not_nil!.position[0] / 1000000) * Benben.config.sampleRate).to_u64!
end
def unload : Nil
Benben.dlog!("Unloading MIDI file: #{@filename}")
@file = nil
@seq = nil
@synth = nil
if (sf = @soundfont).is_a?(Haematite::MmapSoundFont)
sf.clearPages
end
@soundfont = nil
end
def play(loop? : Bool) : Nil
ensureFileInRAM
@seq.not_nil!.play(@file.not_nil!, loop?)
end
def finished? : Bool
@seq.not_nil!.finished?
end
@[AlwaysInline]
def decode(dest : Array(Float32)) : Int64
ensureFileInRAM
@seq.not_nil!.render(dest, @arraypool)
dest.size.to_i64!
end
@[AlwaysInline]
def decode(dest : Array(Float64)) : Int64
ensureFileInRAM
@seq.not_nil!.render(dest, @arraypool)
dest.size.to_i64!
end
end
end
|
Changes to src/audio-formats/modulefile.cr.
| ︙ | ︙ | |||
26 27 28 29 30 31 32 |
class ModuleFile < PlayableFile
alias Xmp = RemiXmp
@ctx : Xmp::Context = Xmp::Context.new
@loaded : Bool = false
@dtinfo : RemiXmp::ModuleInfo?
| | | > | 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
class ModuleFile < PlayableFile
alias Xmp = RemiXmp
@ctx : Xmp::Context = Xmp::Context.new
@loaded : Bool = false
@dtinfo : RemiXmp::ModuleInfo?
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
info = RemiXmp.test(filename)
!info[0].starts_with?("ID3")
end
|
| ︙ | ︙ | |||
76 77 78 79 80 81 82 |
else
IO.copy(file, io)
end
end
io.to_slice
end
| < | > > > > | 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
else
IO.copy(file, io)
end
end
io.to_slice
end
def ensureFile : Bool
unless @loaded
@ctx.load(loadData)
@loaded = true
end
true
rescue err : Exception
RemiLib.log.error("Cannot load module file #{@filename}: #{err}")
false
end
def defaultPan=(value) : Nil
raise "Too late to set panning" if @loaded
@ctx.defaultPan = value
end
|
| ︙ | ︙ | |||
121 122 123 124 125 126 127 128 129 130 |
end
def volume=(value)
@ctx.volume = value
end
def unload : Nil
@dtinfo = nil
@ctx.unload if @loaded
@loaded = false
| > < | 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
end
def volume=(value)
@ctx.volume = value
end
def unload : Nil
Benben.dlog!("Unloading module file: #{@filename}")
@dtinfo = nil
@ctx.unload if @loaded
@loaded = false
end
def totalSamples : UInt64
@ctx.info.lengthSamples
end
def pattern
|
| ︙ | ︙ |
Changes to src/audio-formats/mpeg1file.cr.
| ︙ | ︙ | |||
20 21 22 23 24 25 26 |
#### Simple wrapper for MPEG-1 files.
####
module Benben
class Mpeg1File < PlayableFile
@decoder : RemiMpg123::Decoder?
| | | > < | > > > > > > > > > | 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 |
#### 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
else
false
end
end
def ensureFile : Bool
if @decoder.nil?
@decoder = RemiMpg123::Decoder.new(@filename)
end
true
rescue err : Exception
RemiLib.log.error("Cannot load MPEG-1 #{@filename}: #{err}")
false
end
def unload : Nil
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
|
| ︙ | ︙ |
Changes to src/audio-formats/opusfile.cr.
| ︙ | ︙ | |||
36 37 38 39 40 41 42 |
@outputGain : Float32 = 0.0f32
@channels : UInt8 = 0u8
@tagPacket : Bytes = Bytes.new(0)
getter? needResampling : Bool = false
@sampleRate : UInt32 = 48000
@totalSamples : UInt64 = 0u64
| | | > | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
@outputGain : Float32 = 0.0f32
@channels : UInt8 = 0u8
@tagPacket : Bytes = Bytes.new(0)
getter? needResampling : Bool = false
@sampleRate : UInt32 = 48000
@totalSamples : UInt64 = 0u64
protected def initialize(@filename : String)
ensureFile || raise PlayableFileError.new("Cannot load file: #{@filename}")
@taginfo = TagInfo.create(self)
_ = self.timeLength
end
# Tests to see if the file at *filename* is a supported Opus file. Returns
# `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
|
| ︙ | ︙ | |||
144 145 146 147 148 149 150 |
# Opus at 48Khz then resample.
@needResampling = true
@sampleRate = 48000
Opus::Decoder.new(48000, 2)
end
end
| | > > > > | 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 |
# Opus at 48Khz then resample.
@needResampling = true
@sampleRate = 48000
Opus::Decoder.new(48000, 2)
end
end
def ensureFile : Bool
if @demuxer.nil?
RemiLib.assert(@decoder.nil?)
startDecoder
else
RemiLib.assert(!@decoder.nil?)
end
true
rescue err : Exception
RemiLib.log.error("Cannot load Opus #{@filename}: #{err}")
false
end
def demuxer : OggDemuxer
ensureFile
@demuxer.not_nil!
end
|
| ︙ | ︙ | |||
202 203 204 205 206 207 208 209 210 211 212 |
def rewind : Nil
unload
ensureFile
end
def unload : Nil
@decoder = nil
@demuxer = nil
@io.try(&.close)
@io = nil
| > < | 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
def rewind : Nil
unload
ensureFile
end
def unload : Nil
Benben.dlog!("Unloading Opus file: #{@filename}")
@decoder = nil
@demuxer = nil
@io.try(&.close)
@io = nil
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.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 |
#### Wrapper for PCM files (e.g. RIFF WAVE and Au files)
####
module Benben
class PcmFile < PlayableFile
@file : RemiAudio::Formats::AudioFile?
| | | > < | > > > > > > > > > | 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 |
#### Wrapper for PCM files (e.g. RIFF WAVE and Au files)
####
module Benben
class PcmFile < PlayableFile
@file : RemiAudio::Formats::AudioFile?
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
def ensureFile : Bool
@file = RemiAudio::Formats::AudioFile.open(filename) if @file.nil?
true
rescue err : Exception
RemiLib.log.error("Cannot load PCM #{@filename}: #{err}")
false
end
def unload : Nil
Benben.dlog!("Unloading PCM file: #{@filename}")
@file = nil
end
def type : String
ensureFile
case @file.not_nil!
in RemiAudio::Formats::AuFile then "Sun Au"
in RemiAudio::Formats::WavFile then "RIFF WAVE"
|
| ︙ | ︙ |
Changes to src/audio-formats/playablefile.cr.
| ︙ | ︙ | |||
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 "../taginfo" #### #### Simple wrapper for MIDI files. #### module Benben abstract class PlayableFile | > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
#### 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
abstract class PlayableFile
class PlayableFileError < BenbenError
end
# Where a `PlayableFile` comes from.
enum Source
# A local file.
Local
# A file that is downloaded over HTTP/HTTPS.
#Http
# A file that is downloaded over Gemini.
#Gemini
end
#getter source : Source = Source::Local
getter filename : String = ""
@taginfo : TagInfo?
@timeLength : Time::Span?
protected def self.getFileTypeGuess(filename : Path|String) : FileType?
# Try to use the extension as an educated guess to speed this up.
ext = case filename
in Path then filename.extension
in String then Path[filename].extension
end
case ext.downcase
when ".vgzst", ".vgm", ".vgb", ".vgz"
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 ".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)
else nil # Using the extension didn't yield a result
end
rescue Exception
nil
end
# Determines if **filename** is a file type that is supported by Benben, and
# if it is a supported filename, is also a supported file. This returns the
# `FileType`, which will be `FileType::Unknown` if it's unsupported.
def self.getFileType(filename : Path|String) : FileType
ret : FileType = FileType::Unknown
return ret if !File.exists?(filename) || !File.readable?(filename) # Easy out
# Try to use a faster method first
if luckyGuess = getFileTypeGuess(filename)
return luckyGuess
end
# If we've made it here, then the filename extension didn't help us. Do
# more intensive checks.
case
when VgmFile.test(filename)
ret = FileType::Vgm
when MidiFile.test(filename)
ret = FileType::Midi
when RemiXmp.test(filename)
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 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
else Benben.dlog!("Couldn't determine filetype for loading: #{filename}")
end
Benben.dlog!("File type #{ret}: #{filename}") unless ret.unknown?
ret
rescue err : Exception
Benben.dlog!("Could not determine file type due to an exception: #{err} (backtrace: #{err.backtrace}")
FileType::Unknown
end
def self.create(filename : String) : PlayableFile?
case getFileType(filename)
in .vgm? then return VgmFile.new(filename)
in .module? then return ModuleFile.new(filename)
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 .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}")
nil
rescue err : Exception
Benben.dlog!("Error loading file: #{err} (#{err.backtrace}")
RemiLib.log.error("Cannot load file: #{err}")
nil
end
def filename : String
case ret = @filename
in String then ret
in Path then ret.to_s
end
end
|
| ︙ | ︙ | |||
45 46 47 48 49 50 51 |
else
@taginfo = TagInfo.create(self)
end
end
abstract def totalSamples : UInt64
| > > > > > > > > | > > > > > > > > > > > > > > > | > > | > > > | > | 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 |
else
@taginfo = TagInfo.create(self)
end
end
abstract def totalSamples : UInt64
def timeLength : Time::Span
if ret = @timeLength
ret
else
calcTimeLength
@timeLength.not_nil!
end
end
# Calculates the total time length of this track, storing the value in the
# `@timeLength` field.
#
# The calculation takes into account the number of loops, and thus
# temporarily loads any applicable song config into RAM. This song config
# is **NOT** applied to the global `EphemeralConfig` singleton
# (`Benben.config`).
#
# This is not thread safe.
protected def calcTimeLength : Nil
songConf = Benben.config.getSongConfig(self.filename)
loops = if songConf && songConf.maxLoops_present?
songConf.maxLoops
else
Benben.config.maxLoopsAll[0]
end
@timeLength = ((self.totalSamples * Math.max(1, loops)) / self.sampleRate).seconds
end
abstract def ensureFile : Bool
abstract def unload : Nil
end
end
|
Changes to src/audio-formats/vgmfile.cr.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 | #### #### Simple wrapper for VGM files. #### module Benben class VgmFile < PlayableFile | < > | | > | | | > > > > > > > > > > > > > | 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 |
####
#### Simple wrapper for VGM files.
####
module Benben
class VgmFile < PlayableFile
@fromIO : Bool = false
@file : Yuno::VgmFile?
@totalSamples : UInt64? = nil
@samplesPerLoop : UInt64? = nil
protected def initialize(@filename : String)
ensureFile || raise PlayableFileError.new("Cannot load file: #{@filename}")
@taginfo = TagInfo.create(self)
_ = self.timeLength
end
def initialize(@filename : String, io : IO)
@file = Yuno::VgmFile.fromIO(io)
@fromIO = true
end
def self.test(filename : Path|String) : Bool
Yuno::VgmFile.validVgmFile?(filename)
rescue Exception
false
end
def ensureFile : Bool
if @fromIO
raise "@file was not set but @fromIO is true" if @file.nil?
elsif @file.nil?
@file = Yuno::VgmFile.load(@filename)
end
true
rescue err : Exception
RemiLib.log.error("Cannot load VGM #{@filename}: #{err}")
false
end
def unload : Nil
unless @fromIO
Benben.dlog!("Unloading VGM file: #{@filename}")
@file = nil
else
Benben.dlog!("Skipping unload of VGM file (@fromIO is true): #{@filename}")
end
end
@[AlwaysInline]
def file : Yuno::VgmFile
ensureFile
@file.not_nil!
end
|
| ︙ | ︙ | |||
68 69 70 71 72 73 74 |
ensureFile
outputRateMul, outputRateDiv, _, _ = Yuno::VgmPlayer.calcResampling(Benben.config.sampleRate, @file.not_nil!)
@totalSamples = Yuno::VgmFile.vgmSampleToPcmSample(@file.not_nil!.header.totalSamples.to_i64!,
outputRateDiv.to_i64!,
outputRateMul.to_i64!).to_u64!
end
end
| > > > > > > > > > > > | | > > > > > > > > > > > > > > > > > > > > | 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 |
ensureFile
outputRateMul, outputRateDiv, _, _ = Yuno::VgmPlayer.calcResampling(Benben.config.sampleRate, @file.not_nil!)
@totalSamples = Yuno::VgmFile.vgmSampleToPcmSample(@file.not_nil!.header.totalSamples.to_i64!,
outputRateDiv.to_i64!,
outputRateMul.to_i64!).to_u64!
end
end
# Returns the total number of samples for each loop.
def samplesPerLoop : UInt64
if ret = @samplesPerLoop
ret
else
ensureFile
outputRateMul, outputRateDiv, _, _ = Yuno::VgmPlayer.calcResampling(Benben.config.sampleRate, @file.not_nil!)
@samplesPerLoop = Yuno::VgmFile.vgmSampleToPcmSample(@file.not_nil!.header.loopSamples.to_i64!,
outputRateDiv.to_i64!,
outputRateMul.to_i64!).to_u64!
end
end
# :inherited:
protected def calcTimeLength : Nil
songConf = Benben.config.getSongConfig(self.filename)
loops = if songConf && songConf.maxLoops_present?
songConf.maxLoops
else
Benben.config.maxLoopsAll[0]
end
if loops <= 1
@timeLength = (self.totalSamples / self.sampleRate).seconds
else
@timeLength = ((self.totalSamples + (self.samplesPerLoop * loops - 1)) / self.sampleRate).seconds
end
@timeLength = @timeLength.not_nil! + Benben.config.fadeoutSeconds.seconds
end
end
end
|
Changes to src/audio-formats/vorbisfile.cr.
| ︙ | ︙ | |||
32 33 34 35 36 37 38 |
@decoder : Vorbis::Decoder?
@channels : UInt8 = 0u8
@tagPacket : Bytes = Bytes.new(0)
@sampleRate : UInt32 = 0u32
@totalSamples : UInt64 = 0u64
@mutex : Mutex = Mutex.new
| | | > | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
@decoder : Vorbis::Decoder?
@channels : UInt8 = 0u8
@tagPacket : Bytes = Bytes.new(0)
@sampleRate : UInt32 = 0u32
@totalSamples : UInt64 = 0u64
@mutex : Mutex = Mutex.new
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
testVorb = VorbisFile.new(filename)
testVorb.channels == 2 # We only support stereo Vorbis files
rescue Exception
false
|
| ︙ | ︙ | |||
97 98 99 100 101 102 103 |
demux.rewind
demux.nextPacket
demux.nextPacket
demux.nextPacket
end
end
| | > > > > | 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 |
demux.rewind
demux.nextPacket
demux.nextPacket
demux.nextPacket
end
end
def ensureFile : Bool
if @demuxer.nil?
RemiLib.assert(@decoder.nil?)
startDecoder
else
RemiLib.assert(!@decoder.nil?)
end
true
rescue err : Exception
RemiLib.log.error("Cannot load Vorbis #{@filename}: #{err}")
false
end
def sampleRate : UInt32
ensureFile
@sampleRate
end
|
| ︙ | ︙ | |||
147 148 149 150 151 152 153 154 155 156 157 |
def rewind : Nil
unload
ensureFile
end
def unload : Nil
@decoder = nil
@demuxer = nil
@io.try(&.close)
@io = nil
| > < | 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
def rewind : Nil
unload
ensureFile
end
def unload : Nil
Benben.dlog!("Unloading Vorbis file: #{@filename}")
@decoder = nil
@demuxer = nil
@io.try(&.close)
@io = nil
end
end
end
|
Changes to src/common.cr.
| ︙ | ︙ | |||
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# :nodoc:
macro dlog!(msg)
{% if flag?(:benben_debug) %}
RemiLib.log.dlog!({{msg}})
RemiLib.log.debugStream.flush
{% end %}
end
end
require "./audio-formats/modulefile"
require "./audio-formats/flacfile"
require "./audio-formats/opusfile"
require "./audio-formats/vorbisfile"
require "./audio-formats/mpeg1file"
require "./audio-formats/midifile"
require "./audio-formats/vgmfile"
require "./audio-formats/pcmfile"
####
#### Common Stuff and Globals
####
module Benben
| > > > > > > > > > > > > > < < < < < < < < < < < < < | 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 |
# :nodoc:
macro dlog!(msg)
{% if flag?(:benben_debug) %}
RemiLib.log.dlog!({{msg}})
RemiLib.log.debugStream.flush
{% end %}
end
###
### Exceptions and Aliases
###
class BenbenError < Exception
end
alias RAFlac = RemiAudio::Codecs::FLAC
alias MVerb = RemiAudio::DSP::MVerb
alias Zita = RemiAudio::DSP::ZitaReverb
alias ReverbPreset = MVerb::Preset|Zita::Preset
alias ThemeColor = UInt8|Tuple(UInt8, UInt8, UInt8)|String
end
require "./audio-formats/modulefile"
require "./audio-formats/flacfile"
require "./audio-formats/opusfile"
require "./audio-formats/vorbisfile"
require "./audio-formats/mpeg1file"
require "./audio-formats/midifile"
require "./audio-formats/vgmfile"
require "./audio-formats/pcmfile"
####
#### Common Stuff and Globals
####
module Benben
###
### Program Constants
###
SHORT_NAME = "benben"
FANCY_NAME = "Benben Audio Player"
VERSION = "0.6.0-dev"
|
| ︙ | ︙ |
Changes to src/filemanager.cr.
| ︙ | ︙ | |||
11 12 13 14 15 16 17 | #### 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 "uri" require "./common" | | < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < | > | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | > > > > < < < < < | | < < < < < < < < < < | | > | > | | > | | < | | < | | > > > | > < > | < < < | | | > | > > > > > < < < < | | | < < > | < > | | > > > | | | | < < | | | | | > > > | | < < < | | | < < | | > > | | > > > | < | | < > | > | | > > > | > | | < | < < < < < > > | 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 |
#### 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 "uri"
require "./common"
require "./audio-formats/playablefile"
####
#### Toplevel PlayableFile Handling
####
#### This includes the "playlist" functionality, where the program can go back
#### and forth through the list of songs.
####
module Benben
# The `FileManager` class loads playable files from various sources into RAM,
# then allows them to be selected in a linear way. In other words, it acts as
# a loader, as well as the current play queue.
class FileManager
@files : Array(PlayableFile) = [] of PlayableFile
protected getter currentTrack : Atomic(Int32) = Atomic(Int32).new(-1)
@errors : Array(String) = [] of String
@repeatList : Bool = false
@lastSpeedUpdate : Time::Span = Time.monotonic
@totalTime : Time::Span? = nil
# Creates a new `FileManager` instance. This will check for bad files and
# printing the results as-needed, as well as create the virtual play queue
# based on the command line arguments.
def initialize
Benben.dlog!("File handler initializing")
# Attempt to load each positional argument as a file or directory.
Benben.args.positionalArgs.each { |arg| addFile(arg) }
# Are we playing MIDI files? Ensure we have a SoundFont.
if @files.any?(&.is_a?(MidiFile))
if Benben.config.midi.soundfont.empty?
raise ConfigError.new("You have MIDI files to play, but have no SoundFont specified in your config file")
elsif !File.exists?(Benben.config.midi.soundfont)
raise ConfigError.new("You have MIDI files to play, but the specified SoundFont does not exist")
elsif !File.readable?(Benben.config.midi.soundfont)
raise ConfigError.new("You have MIDI files to play, but the specified SoundFont cannot be read")
end
end
##
## Handle other command-line arguments
##
@repeatList = if Benben.args["no-repeat"].called?
false
else
Benben.args["repeat"].called? || Benben.config.repeatList?
end
if Benben.args["shuffle"].called? && !Benben.args["render"].called?
@files.shuffle!
end
end
def self.getMagic(filename : String, count = 4) : Bytes
ret = Bytes.new(count)
File.open(filename, "rb") do |f|
numRead = f.read(ret)
if numRead < count
return ret[..numRead]
end
end
ret
end
private def addLocal(file : PlayableFile) : Nil
@files << file
end
private def addFile(arg : String) : Nil
case
when File.exists?(arg) && !Dir.exists?(arg)
Benben.dlog!("Adding local file: #{arg}")
if arg.downcase.ends_with?(".xspf")
loadXspfFile(Path[arg])
elsif arg.downcase.ends_with?(".jspf")
loadJspfFile(Path[arg])
elsif newFile = PlayableFile.create(arg)
newFile.unload
addLocal(newFile)
end
when Dir.exists?(arg)
# Sort the children since the filesystem may not report the files in
# sorted order, but rather in the order they were inserted into the
# filesystem (which may not be in-order).
Dir.children(arg).sort.each do |child|
childPath = Path[arg, child].to_s
# Only add the child if it's a file.
addFile(childPath) if File.exists?(childPath) && !Dir.exists?(childPath)
end
when arg.starts_with?("https://") || arg.starts_with?("http://")
RemiLib.log.warn("HTTP/HTTPS no longer supported: #{arg}")
# Benben.dlog!("Downloading from an HTTP/HTTPS URL: #{arg}")
#addHttp(URI.parse(arg))
when arg.starts_with?("gemini://")
RemiLib.log.warn("Gemini no longer supported: #{arg}")
# Benben.dlog!("Downloading from a Gemini URL: #{arg}")
# addGemini(URI.parse(arg))
else
# arg doesn't hold an existing filename, and it isn't a link. It may
# be a directory, or simply a file that does't exist.
if Dir.exists?(arg)
RemiLib.log.error("Cannot load file: #{arg} is a directory, not a file")
else
RemiLib.log.error("Cannot load file: #{arg} does not exist")
end
end
end
# Loads a playlist in XSPF format. Returns `true` if at least one track
# from the playlist was loaded, or `false` otherwise.
protected def loadXspfFile(playlistFile : Path) : Bool
RemiLib.log.log("Parsing #{playlistFile}")
playlist = File.open(playlistFile, "r") { |file| RemiXspf::Playlist.read(file) }
parseXspf(playlist)
rescue err : Exception
RemiLib.log.error("Cannot load playlist file #{playlistFile}: #{err}")
false
end
# Loads a playlist in JSPF format. Returns `true` if at least one track
# from the playlist was loaded, or `false` otherwise.
protected def loadJspfFile(playlistFile : Path) : Bool
RemiLib.log.log("Parsing #{playlistFile}")
playlist = File.open(playlistFile, "r") do |file|
RemiXspf::Playlist.read(file, format: RemiXspf::Playlist::Format::Json)
end
parseXspf(playlist)
rescue err : Exception
RemiLib.log.error("Cannot load playlist file #{playlistFile}: #{err}")
false
end
private def parseXspf(playlist : RemiXspf::Playlist) : Bool
loaded : Bool = false
numLoaded = 0
playlist.tracks.each_with_index do |track, idx|
track.locations.each do |loc|
loaded = case loc.scheme
when "gemini"
RemiLib.log.warn("Gemini no longer supported: #{loc}")
false
# addGemini(loc)
# true
when "http", "https"
RemiLib.log.warn("HTTP/HTTPS no longer supported: #{loc}")
false
# addHttp(loc)
# true
when "file"
if newFile = PlayableFile.create(URI.decode(loc.path))
newFile.unload
addLocal(newFile)
true
else
false
end
else
false
end
break if loaded
end
unless loaded
# Try to get some sort of identifier for the track.
str : String = if track.title
track.title.not_nil!
elsif track.trackNumber
"track #{track.trackNumber.not_nil!}"
elsif track.locations.size == 1
track.locations[0].to_s
elsif track.identifiers.size == 1
track.identifiers[0].to_s
else
""
end
# If we have a way of identifying the track, do so in the error
# message.
unless str.empty?
RemiLib.log.error("Don't know how to load track from playlist: #{str}")
else
# No way of identifying it, so just list its position in the
# playlist.
RemiLib.log.error("Don't know how to load track at position #{idx + 1} in the playlist")
end
else
numLoaded += 1
end
end
numLoaded != 0
end
############################################################################
# Returns the number of files that this `FileManager` knows about.
def size
@files.size
|
| ︙ | ︙ | |||
474 475 476 477 478 479 480 |
#
# If the queue is set to repeat, and the `FileManager` is currently at the
# end of the queue, then it loops back and returns the first entry.
# Otherwise, if it is not set to repeat and it's at the end of the queue,
# this returns `nil`.
#
# This may load a file if it is not already in memory.
| | | | | | < | > > > > > | | | | | > | < < | 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 |
#
# If the queue is set to repeat, and the `FileManager` is currently at the
# end of the queue, then it loops back and returns the first entry.
# Otherwise, if it is not set to repeat and it's at the end of the queue,
# this returns `nil`.
#
# This may load a file if it is not already in memory.
def next : PlayableFile?
origStart = @currentTrack.lazy_get
loop do
@currentTrack.add(1)
if @currentTrack.lazy_get == origStart
RemiLib.log.error("Reached the original position with no files loadable")
return nil
end
if @currentTrack.lazy_get >= @files.size
if @repeatList && !Benben.args["render"].called?
@currentTrack.set(0)
else
return nil
end
end
if (file = @files[@currentTrack.lazy_get]).ensureFile
return file
else
Benben.ui.queueError("Can't load file: #{currentFileBasename}")
end
end
end
def get(index : Int) : VirtualFile
@files[index]
end
def getFilename(index : Int) : String
@files[index].filename
end
def getFileBasename(index : Int) : String
Path[@files[index].filename].basename
end
# Unloads the current file from RAM.
def unloadCurrent : Nil
@files[@currentTrack.lazy_get].unload
end
# Returns the previous PlayableFile in the queue and its type.
#
# If the queue is set to repeat, and the `FileManager` is currently at the
# beginning of the queue, then it loops to the end and returns the last
# entry. Otherwise, if it is not set to repeat and it's at the beginning of
# the queue, this repeatedly returns the first entry.
#
# This may load a file if it is not already in memory.
def prev : PlayableFile?
origStart = @currentTrack.lazy_get
loop do
@currentTrack.sub(1)
if @currentTrack.lazy_get == origStart
RemiLib.log.error("Reached the original position with no files loadable")
return nil
end
if @currentTrack.lazy_get < 0
# Too far back
if @files.empty?
# ... and no files
@currentTrack.set(-1)
return nil
end
# No need to check for --render here since calling #prev isn't possible
# when rendering.
if @repeatList
# Go to the last file
@currentTrack.set(@files.size - 1)
else
# Force the first file
@currentTrack.set(0)
end
end
if (file = @files[@currentTrack.lazy_get]).ensureFile
return file
else
Benben.ui.queueError("Can't load file: #{currentFileBasename}")
if @currentTrack.lazy_get == 0
# The first file is something that can't be loaded, so just return
# nil.
#
# TODO Might be better to handle things like @repeatList or moving
# to the following file here.
return nil
end
end
end
end
# Returns the filename associated with the current PlayableFile.
def currentFilename : String
if @currentTrack.lazy_get >= @files.size || @currentTrack.lazy_get < 0
|
| ︙ | ︙ |
Deleted src/loaders/gemini.cr.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted src/loaders/http.cr.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted src/loaders/normal.cr.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted src/loaders/xspf.cr.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to src/playermanager.cr.
| ︙ | ︙ | |||
51 52 53 54 55 56 57 |
delegate loopIndefinitely, to: @player
delegate currentLoop, to: @player
delegate infoLine, to: @player
def initialize
end
| | | | | | | | | | | | > | | | 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 |
delegate loopIndefinitely, to: @player
delegate currentLoop, to: @player
delegate infoLine, to: @player
def initialize
end
def play(file : PlayableFile) : Nil
case file
in VgmFile
Benben.dlog!("PlayerManager: Playing a VGM file")
@player = VgmPlayer.new unless @player.is_a?(VgmPlayer)
raise "Expected a VGM file, but got a #{file.class}" unless file.is_a?(VgmFile)
@player.play(file)
in ModuleFile
Benben.dlog!("PlayerManager: Playing a module file")
@player = ModulePlayer.new unless @player.is_a?(ModulePlayer)
raise "Expected a module file, but got a #{file.class}" unless file.is_a?(ModuleFile)
@player.play(file)
in FlacFile
Benben.dlog!("PlayerManager: Playing a FLAC file")
@player = FlacPlayer.new unless @player.is_a?(FlacPlayer)
raise "Expected a FLAC file, but got a #{file.class}" unless file.is_a?(FlacFile)
@player.play(file)
in OpusFile
Benben.dlog!("PlayerManager: Playing an Opus file")
@player = OpusPlayer.new unless @player.is_a?(OpusPlayer)
raise "Expected an Opus file, but got a #{file.class}" unless file.is_a?(OpusFile)
@player.play(file)
in VorbisFile
Benben.dlog!("PlayerManager: Playing an Ogg Vorbis file")
@player = VorbisPlayer.new unless @player.is_a?(VorbisPlayer)
raise "Expected an Ogg Vorbis file, but got a #{file.class}" unless file.is_a?(VorbisFile)
@player.play(file)
in Mpeg1File
Benben.dlog!("PlayerManager: Playing an MPEG-1 file")
@player = Mpeg1Player.new unless @player.is_a?(Mpeg1Player)
raise "Expected an MPEG-1 file, but got a #{file.class}" unless file.is_a?(Mpeg1File)
@player.play(file)
in MidiFile
Benben.dlog!("PlayerManager: Playing a MIDI file")
@player = MidiPlayer.new unless @player.is_a?(MidiPlayer)
raise "Expected a MIDI file, but got a #{file.class}" unless file.is_a?(MidiFile)
@player.play(file)
in PcmFile
Benben.dlog!("PlayerManager: Playing a PCM file")
@player = PcmPlayer.new unless @player.is_a?(PcmPlayer)
raise "Expected a PCM file, but got a #{file.class}" unless file.is_a?(PcmFile)
@player.play(file)
in PlayableFile
raise "Cannot play an unknown file type"
end
end
def getNextFile
Benben.fileHandler.unloadCurrent
if @goBackOneFile
@goBackOneFile = false
Benben.fileHandler.prev
else
Benben.fileHandler.next
end
end
def start
until @earlyExit
Benben.dlog!("PlayerManager: Getting a new file")
nextFile = getNextFile
unless nextFile
Benben.dlog!("PlayerManager: nextFile is nil, breaking")
break
end
Benben.ui.initialUpdateFinished.set(false)
Benben.config.withSongConfig(Benben.fileHandler.currentFilename) do
play(nextFile)
# Flush input
Benben.manager.fromPlayer.send(ManagerCommand::FlushInput)
# Main rendering loop
Fiber.yield
Benben.dlog!("Playback commencing")
|
| ︙ | ︙ |
Changes to src/rendering/renderer.cr.
| ︙ | ︙ | |||
144 145 146 147 148 149 150 |
jobs : Array(Job) = [] of Job
# Used to check for duplicate output filenames.
outputPaths : Hash(Path, Bool) = {} of Path => Bool
# Get each file
loop do
| | < < < < < < < < < < < < < < < < < | < < | 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
jobs : Array(Job) = [] of Job
# Used to check for duplicate output filenames.
outputPaths : Hash(Path, Bool) = {} of Path => Bool
# Get each file
loop do
file = Benben.fileHandler.next
break if file.nil?
if file.is_a?(PcmFile)
RemiLib.log.warn("File is already a PCM file, ignoring: " \
"#{Benben.fileHandler.currentFilename}")
next
end
# Get the output path
outputPath : String = getOutputPath
outputPath = Job.adjustOutputPathForFile(outputPath, file)
# Check if the output path exists, and if it doesn't, attempt to create
|
| ︙ | ︙ | |||
214 215 216 217 218 219 220 |
unless Benben.args["overwrite"].called?
RemiLib.log.error("File exists: #{filename}")
next
end
end
# All good, store the new job
| | | | | | | | | | | | | 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 |
unless Benben.args["overwrite"].called?
RemiLib.log.error("File exists: #{filename}")
next
end
end
# All good, store the new job
case file
in VgmFile
jobs << VgmJob.new(file.as(VgmFile), Path[Benben.fileHandler.currentFilename], filename, jobChan)
in ModuleFile
jobs << ModuleJob.new(file.as(ModuleFile), Path[Benben.fileHandler.currentFilename], filename, jobChan)
in FlacFile
jobs << FlacJob.new(file.as(FlacFile), Path[Benben.fileHandler.currentFilename], filename, jobChan)
in OpusFile
jobs << OpusJob.new(file.as(OpusFile), Path[Benben.fileHandler.currentFilename], filename, jobChan)
in VorbisFile
jobs << VorbisJob.new(file.as(VorbisFile), Path[Benben.fileHandler.currentFilename], filename, jobChan)
in Mpeg1File
jobs << Mpeg1Job.new(file.as(Mpeg1File), Path[Benben.fileHandler.currentFilename], filename, jobChan)
in MidiFile
jobs << MidiJob.new(file.as(MidiFile), Path[Benben.fileHandler.currentFilename], filename, jobChan)
in PcmFile
raise "Should have been checked earlier"
in PlayableFile
raise "Unexpectedly received nil PlayableFile"
end
Fiber.yield
end
jobs
end
|
| ︙ | ︙ |