Skip to content

Commit

Permalink
Add support for SDL2_mixer
Browse files Browse the repository at this point in the history
  • Loading branch information
jwoertink authored and ysbaddaden committed Jun 13, 2017
1 parent 99e071c commit d40de31
Show file tree
Hide file tree
Showing 10 changed files with 440 additions and 5 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ Most of the bindings have been implemented, though most haven't been verified,
yet. Please see the `samples` directory for examples, ported from the tutorials
found at <http://lazyfoo.net/tutorials/SDL/index.php>. You are welcome to port
more samples, and the necessary corrections!

## Requirements

- SDL2 is required;
- Optional bindings for `SDL2_Mixer`, `SDL2_Image` and `SDL2_TTF`;
- Crystal > 0.22.0 is required for `SDL2_Mixer` to work correctly.
62 changes: 62 additions & 0 deletions samples/21_sound_and_effects.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require "../src/sdl"
require "../src/image"
require "../src/mix"

SDL.init(SDL::Init::VIDEO | SDL::Init::AUDIO); at_exit { SDL.quit }
SDL::Mix.init(SDL::Mix::Init::FLAC); at_exit { SDL::Mix.quit }
SDL::Mix.open

DATA_DIR = File.join(__DIR__, "data")

music = SDL::Mix::Music.new(File.join(DATA_DIR, "beat.wav"))

samples = {} of String => SDL::Mix::Sample
channels = {} of String => SDL::Mix::Channel

%w(high medium low scratch).each_with_index do |name, idx|
samples[name] = SDL::Mix::Sample.new(File.join(DATA_DIR, "#{name}.wav"))
channels[name] = SDL::Mix::Channel.new(idx)
end

window = SDL::Window.new("SDL Tutorial", 640, 480)
png = SDL::IMG.load(File.join(__DIR__, "data", "prompt.png"))
png = png.convert(window.surface)
activekey = [] of LibSDL::Keycode

loop do
case event = SDL::Event.wait
when SDL::Event::Quit
music.stop
break
when SDL::Event::Keyboard
key = event.sym
unless activekey.includes? key
case key
when .key_1?
SDL::Mix::Channel.play(samples["high"]) # allocate any free channel
when .key_2?
channels["medium"].play(samples["medium"]) # play through specific channel
when .key_3?
channels["low"].play(samples["low"])
when .key_4?
channels["scratch"].play(samples["scratch"])
when .key_9?
if music.paused?
music.resume
elsif music.playing?
music.pause
else
music.play
end
when .key_0?
music.resume if music.paused?
music.stop
end
activekey << key
end
activekey.delete key if event.keyup?
end

png.blit(window.surface)
window.update
end
6 changes: 3 additions & 3 deletions samples/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SDL2 samples

Adapted from the tutorials found at <http://lazyfoo.net/tutorials/SDL/index.php>
Adapted from the tutorials found at <http://lazyfoo.net/tutorials/SDL/index.php>.

BPM and PNG images aren't included, to avoid bloating the repository. Download
and extract them as see fit.
Data files aren't included, to avoid bloating the repository. Download
and extract them as you see fit in to `samples/data` directory.
2 changes: 1 addition & 1 deletion shard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ libraries:
libSDL2: ~> 2.0.2
libSDL2_image: ~> 2.0.0
#libSDL2_net: ~> 2.0.0
#libSDL2_mixer: ~> 2.0.0
libSDL2_mixer: ~> 2.0.0
#libSDL2_ttf: ~> 2.0.12
120 changes: 120 additions & 0 deletions src/lib_mix.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
require "./lib_sdl"

@[Link("SDL2_mixer")]
lib LibMix
alias Int = LibC::Int
alias Char = LibC::Char
alias RWops = LibSDL::RWops

VERSION = {% `pkg-config SDL2_mixer --modversion`.strip %}
MAJOR = {% VERSION.split('.')[0] %}
MINOR = {% VERSION.split('.')[1] %}
PATCH = {% VERSION.split('.')[2] %}
MIN_MAX_VOLUME = 128
Mix_DEFAULT_FORMAT = LibSDL::AUDIO_S16LSB

@[Flags]
enum Init
FLAC = 0x00000001
MOD = 0x00000002
MODPLUG = 0x00000004
MP3 = 0x00000008
OGG = 0x00000010
FLUIDSYNTH = 0x00000020
end

enum FadeStatus
NO_FADING
FADING_OUT
FADING_IN
end

enum MusicType
MUS_NONE
MUS_CMD
MUS_WAV
MUS_MOD
MUS_MID
MUS_OGG
MUS_MP3
MUS_MP3_MAD
MUS_FLAC
MUS_MODPLUG
end

struct MusicCMD
file : Int*
cmd : Int*
end

struct Mix_Chunk
allocated : Int
abuf : UInt8*
alen : UInt32
volume : UInt8
end
alias Chunk = Mix_Chunk

struct WAVStream
src : RWops*
freesrc : Bool
numloops : Int
end

union Data
cmd : MusicCMD*
wave : WAVStream*
#mp3 : SMPEG*
#ogg : OGG_music*
end

struct Mix_Music
type : MusicType
data : Data
end
alias Music = Mix_Music

fun init = Mix_Init(flags : Init) : Int
fun quit = Mix_Quit()

fun open_audio = Mix_OpenAudio(frequency : Int, format : UInt16, channels : Int, chunksize : Int) : Int
fun close_audio = Mix_CloseAudio()
fun open_audio_device = Mix_OpenAudioDevice(frequency : Int, format : UInt16) : Int

fun allocate_channels = Mix_AllocateChannels(numchans : Int) : Int
fun query_spec = Mix_QuerySpec(frequency : Int*, format : UInt16*, channels : Int*) : Int
fun load_wav_rw = Mix_LoadWAV_RW(src : RWops*, freesrc : Int) : Chunk*
fun load_mus = Mix_LoadMUS(file : Char*) : Music*
fun load_mus_rw = Mix_LoadMUS_RW(src : RWops*, freesrc : Int) : Music*
fun load_mus_type_rw = Mix_LoadMUSType_RW(src : RWops*, type : MusicType, freesrc : Int) : Music*
fun quick_load_wav = Mix_QuickLoad_WAV(mem : UInt8*) : Chunk*
fun quick_load_raw = Mix_QuickLoad_RAW(mem : UInt8*, len : UInt32) : Chunk*
fun free_chunk = Mix_FreeChunk(chunk : Chunk*)
fun free_music = Mix_FreeMusic(music : Music*)
fun get_num_chunk_decoders = Mix_GetNumChunkDecoders() : Int
fun get_chunk_decoder = Mix_GetChunkDecoder(index : Int) : Char*
fun get_num_music_decoders = Mix_GetNumMusicDecoders() : Int
fun get_music_decoder = Mix_GetMusicDecoder(index : Int) : Char*
fun get_music_type = Mix_GetMusicType(music : Music*) : MusicType
fun get_music_hook_data = Mix_GetMusicHookData()
fun play_channel = Mix_PlayChannel(channel : Int, chunk : Chunk*, loops : Int) : Int
fun play_channel_timed = Mix_PlayChannelTimed(channel : Int, chunk : Chunk*, loops : Int, ticks : Int) : Int
fun fade_in_channel = Mix_FadeInChannelTimed(channel : Int, chunk : Chunk*, loops : Int, ticks : Int, dur : Int) : Int
fun fade_out_channel = Mix_FadeOutChannel(channel : Int, dur : Int) : Int
fun fading_channel = Mix_FadingChannel(channel : Int) : Int
fun expire_channel = Mix_ExpireChannel(channel : Int, ticks : Int) : Int
fun cb_channel_finished = Mix_ChannelFinished(f : Int32 -> Nil)
fun channel_volume = Mix_Volume(channel : Int, volume : Int) : Int
fun channel_paused = Mix_Paused(channel : Int) : Int
fun play_music = Mix_PlayMusic(music : Music*, loops : Int) : Int
fun music_playing = Mix_PlayingMusic() : Int
fun music_paused = Mix_PausedMusic() : Int
fun rewind_music = Mix_RewindMusic()
fun resume_music = Mix_ResumeMusic()
fun pause_music = Mix_PauseMusic()
fun halt_music = Mix_HaltMusic() : Int
fun fade_in_music = Mix_FadeInMusic(music : Music*, loops : Int, ms : Int)
fun fade_out_music = Mix_FadeOutMusic(ms : Int)
fun music_volume = Mix_VolumeMusic(volume : Int) : Int
fun get_error = Mix_GetError() : Char*
end
2 changes: 1 addition & 1 deletion src/lib_sdl/audio.cr
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ lib LibSDL
) : Int
fun convert_audio = SDL_ConvertAudio(cvt : AudioCVT*) : Int

MIX_MAXVOLUME = 128
Mix_MAXVOLUME = 128
fun mix_audio = SDL_MixAudio(dst : UInt8*, src : UInt8*, len : UInt32, volume : Int)
fun mix_audio_format = SDL_MixAudioFormat(dst : UInt8*, src : UInt8*, format : AudioFormat, len : UInt32, volume : Int)

Expand Down
55 changes: 55 additions & 0 deletions src/mix.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
require "./lib_mix"
require "./sdl"
require "./mix/channel"
require "./mix/music"

module SDL
module Mix
MAX_VOLUME = LibMix::MIN_MAX_VOLUME
DEFAULT_FORMAT = LibMix::Mix_DEFAULT_FORMAT

alias Init = LibMix::Init

enum Type
AIFF
FLAC
MIDI
MOD
MP3
OGG
VOC
WAV
end

# Loads support for audio decoders. `#quit` should be called during
# application cleanup.
def self.init(flags : Init)
ret = LibMix.init(flags)
unless (ret & flags.value) == flags.value
raise SDL::Error.new("Mix_Init failed to init #{flags}")
end
end

def self.quit
LibMix.quit
end

# This is required to initialize SDL_Mixer. It must be called before using
# any other function, but AFTER SDL has been initialized.
def self.open(freq = 44100, format = DEFAULT_FORMAT, channels = 2, sample_size = 2048)
ret = LibMix.open_audio(freq, format, channels, sample_size)
raise SDL::Error.new("Mix_OpenAudio") unless ret == 0
ret
end

def self.query_spec(freq = 44100, format = 0, channels = 2)
audio_open_count = LibMix.query_spec(freq, format, channels)
raise SDL::Error.new("Mix_QuerySpec: #{LibMix.get_error}") if audio_open_count < 1
audio_open_count
end

def self.close
LibMix.close_audio
end
end
end
85 changes: 85 additions & 0 deletions src/mix/channel.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
require "./sample"

module SDL
module Mix
class Channel
ANY = new(-1)

@@channel_count = 8
@@reserved_count = 0

property id : Int32

def initialize(@id)
end

def self.allocate_channels(count)
@@channel_count = count
LibMix.allocate_channels(count)
end

def self.reserve_channels(count)
@@reserved_count = count
LibMix.reserve_channels(count)
end

def self.channels
@@channel_count
end

def self.reserved
@@reserved_count
end

def self.finished(func)
LibMix.cb_channel_finished(func)
end

{% for method in %w(play fade_in fade_out resume expire volume paused_count) %}
def self.{{method.id}}(*args)
ANY.{{method.id}}(*args)
end
{% end %}

def self.volume=(value)
ANY.volume = value
end

def play(sample : Sample, repeats = 0)
LibMix.play_channel(id, sample, repeats)
end

def play(sample : Sample, repeats = 0, ticks = -1)
LibMix.play_channel_timed(id, sample, repeats, ticks)
end

def fade_in(sample : Sample, loops = 0, ms = 1000, ticks = -1)
LibMix.fade_in_channel(id, sample, loops, ms, ticks)
end

def fade_out(ms = 1000)
LibMix.fade_out_channel(id, ms)
end

def expire
LibMix.channel_expire(id, ticks)
end

def fading?
LibMix.fading? id
end

def paused?
LibMix.channel_paused(id) == 1
end

def volume=(volume)
LibMix.channel_volume(id, volume > MAX_VOLUME ? MAX_VOLUME : volume)
end

def volume
LibMix.channel_volume(id, -1)
end
end
end
end
Loading

0 comments on commit d40de31

Please sign in to comment.