Documentation ¶
Index ¶
- type LoadModuleConfig
- type Stream
- func (s *Stream) GetInfo() StreamInfo
- func (s *Stream) LoadModule(m *xmfile.Module, config LoadModuleConfig) error
- func (s *Stream) Read(b []byte) (int, error)
- func (s *Stream) Rewind()
- func (s *Stream) Seek(offset int64, whence int) (int64, error)
- func (s *Stream) SetEventHandler(f func(e StreamEvent))
- func (s *Stream) SetLooping(loop bool)
- func (s *Stream) SetVolume(v float64)
- type StreamEvent
- type StreamEventKind
- type StreamInfo
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type LoadModuleConfig ¶
type LoadModuleConfig struct { // LinearInterpolation enables sub-samples that will make some music sound smoother. // On average, this option will make loaded track to require ~x2 memory. // // The best way to figure out whether you need it or not is to listen to the results. // Most XM players you can find have linear interpolation (lerp) enabled by default. // // A zero value means "no interpolation". // // This should not be confused with volume ramping. // The volume ramping is always enabled and can't be turned off. LinearInterpolation bool // BPM sets the playback speed. // Higher BPM will make the music play faster. // // A zero value will use the XM module default BPM value. // If that value is zero as well, a value of 120 will be used. BPM uint // Tempo (called "Spd" in MilkyTracker) specifies the number of ticks per pattern row. // Perhaps a bit counter-intuitively, higher values make // the song play slower as there are more resolution steps inside a // single pattern row. // // A zero value will use the XM module default Tempo value. // If that value is zero as well, a value of 6 will be used. // (6 is a default value in MilkyTracker.) Tempo uint // The sound device sample rate. // If you're using Ebitengine, it's the same value that // was used to create an audio context. // The most common value is 44100. // // A zero value will assume a sample rate of 44100. // // Note: only two values are supported right now, 44100 and 0. // Therefore, you can only play XM tracks at sample rate of 44100. // This limitation can go away later. SampleRate uint }
LoadModuleConfig configures the XM module loading.
These settings can't be changed after a module is loaded.
Some extra configurations are available via Stream methods:
- Stream.SetVolume()
- Stream.SetLooping()
These extra configuration methods can be used even after a module is loaded.
type Stream ¶
type Stream struct {
// contains filtered or unexported fields
}
Stream wraps the compiled XM module, making it possible to Read() its PCM bytes.
The Read() method produces 16-bit little endian PCM bytes; this is what ebiten/audio package extects. Use Stream as an io.Reader argument for audio.NewPlayer().
func NewStream ¶
func NewStream() *Stream
NewPlayer allocates a player that can load and play XM tracks. Use LoadModule method to finish player initialization.
func (*Stream) GetInfo ¶
func (s *Stream) GetInfo() StreamInfo
GetInfo returns stream-related info. See StreamInfo for more details.
func (*Stream) LoadModule ¶
func (s *Stream) LoadModule(m *xmfile.Module, config LoadModuleConfig) error
LoadModule assigns a new XM module to this stream.
Loading a module involves its compilation which is a slow process. You want to load modules as rarely as possible (preferably exactly once) and then play them via streams without ever releasing the memory.
func (*Stream) Read ¶
Read puts next PCM bytes into provided slice.
The slice is expected to fit at least a single tick. With BPM=120, Tempo=10 and SampleRate=44100 a single tick would require 882*bytesPerSample*numChannels = 2208 bytes. Note that this library only supports stereo output (numChannels=2) and it produces 16-bit (2 bytes per sample) LE PCM data. If you need to have precise info, use Stream.GetInfo() method.
If there is a tail in b that was not written to due to the lack of space for a whole tick, n<len(b) will be returned. It doesn't make send to pass a slice that is smaller than a single tick chunk (2k+ bytes), but it makes sense to pass a bigger slice as this method will try to fit as many ticks as possible.
When stream has no bytes to produce, io.EOF error is returned.
func (*Stream) Rewind ¶
func (s *Stream) Rewind()
Rewind prepares the stream to play the module right from the start. Doing rewind is relatively cheap.
func (*Stream) Seek ¶
Seek partially implements io.Seeker.
You can use it for two things:
- (0, SeekStart) for rewind
- (0, SeekCurrent) to get the byte pos inside the stream
func (*Stream) SetEventHandler ¶
func (s *Stream) SetEventHandler(f func(e StreamEvent))
SetEventHandler installs an event listener to the stream.
f is called on every stream event.
Events are produced when the XM track is being played. Therefore, calling Read() may produce multiple events.
Experimental: the events handling API may change significantly in the future.
func (*Stream) SetLooping ¶
SetLooping enables a simple looping from the beginning of the stream. When looping is enables, Read will never return EOF.
Note that some XM tracks include the trailing jump/pattern break effect that will make it loop in a more beautiful way. Use this looping flag only if XM track does not have one. You may need to perform some XM editing if there is a jump, but you still want to use this basic looping scheme.
Note: prefer this option to the InfiniteLoop provided by Ebitengine audio. This native way of looping is ~free while InfiniteLoop has some overhead.
type StreamEvent ¶
type StreamEvent struct { Kind StreamEventKind // Channel is an event channel ID. // They may not match the exact XM module channel IDs, // but different channel IDs are guaranteed to have unique IDs. // // Some events may be channel-independent. Channel int // Time represents the playback offset in seconds. // Time=2.5 means that this event happened somewhere around 2.5 seconds. Time float64 // contains filtered or unexported fields }
StreamEvent holds a single Stream event data. This object is an argument to the Stream.SetEventHandler function.
To handle the event correctly, you must first check its kind. For an event of kind EventNote there is a NoteEventData method that will return the associated data. For EventSync there is a SyncEventData.
Every event has a Time value. This is a moment when this event happened in relation to the XM track start (in seconds). The user application needs to calculate the time deltas on its own and handle these events in the right moment.
Experimental: the events handling API may change significantly in the future.
func (StreamEvent) NoteEventData ¶
func (e StreamEvent) NoteEventData() (note, instrument int, vol float32)
NoteEventData returns the event data if e.Kind=EventSync. The return values are: note, instrument (id), volume. If there is no instrument, -1 is returned.
func (StreamEvent) SyncEventData ¶
func (e StreamEvent) SyncEventData() (t float64)
SyncEventData returns the event data if e.Kind=EventNote. The return values are: a time to synchronize to.
type StreamEventKind ¶
type StreamEventKind int
StreamEventKind is an event tag that should be used to differentiate between different event types. See StreamEvent docs for more info.
Experimental: the events handling API may change significantly in the future.
const ( // EventUnknown is a sentinel value. // You should never receive an event of this kind. EventUnknown StreamEventKind = iota // EventNote is emitted every time a channel starts to play some note. // It can be triggered even of a "ghost note", so it's up to the application // to decide whether they need to handle that note or not. // // Use StreamEvent.NoteEventData to get the event data. // // Experimental: the events handling API may change significantly in the future. EventNote // EventSync tells the application to update their time counter to the specified value. // // As any other event, the sync event has a Time field that you should use as a // description of when the counter should be updated. // Therefore, a sync event with Time=2.0 and data argument of 2.5 should // force the application to set its time counter to 2.5, but only if // it already reached a time counter value of 2.0. // // Use StreamEvent.EventSyncData to get the event data. // // Experimental: the events handling API may change significantly in the future. EventSync )
type StreamInfo ¶
type StreamInfo struct { // BytesPerTick tell how much bytes this stream needs to fit a single XM tick. // This value is important, since any slice smaller than this will give no effect // for Read() function. Any greater values will work OK for it. BytesPerTick uint // MemoryUsage approximates the compiled XM module size in bytes. // This can be important if you want to analyze linear interpolation (sub-samples) // effect on your modules. MemoryUsage uint }
StreamInfo contains a compiled XM module stream information like bytes per tick, etc.