-
-
Save mistydemeo/76900f8e7b9f7e1592ee to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require_relative 'dsl' | |
module Paula | |
# Select the appropriate player, then supply the player object | |
def self.factory song | |
# Stop the last player before creating a new one | |
# TODO This probably breaks if the laster player is garbage collected | |
@last_player.stop if @last_player | |
@last_player = eval "Paula::#{@formats[File.extname(song).downcase]}::API.new song" | |
end | |
# allow libraries to make themselves known | |
def self.declare hash | |
@formats ||= {} | |
@prefer ||= {} | |
hash[:extensions].each do |ext| | |
@formats.update ext => hash[:library] unless prefer? ext | |
end | |
end | |
# Extensions declared by "prefer" will not be overwritten by other declarations | |
# This should *never* be used in a library declaration, only by client players | |
def self.prefer hash | |
@prefer ||= {} | |
@formats ||= {} | |
hash[:extensions].each do |ext| | |
@formats.update ext => hash[:library] | |
@prefer.update ext => true | |
end | |
end | |
def self.prefer? ext | |
@prefer[ext] | |
end | |
def self.rate | |
@rate ||= 48000 | |
end | |
def self.rate= rate | |
@rate = rate | |
end | |
def self.loops | |
@loops ||= 2 | |
end | |
def self.loops= loops | |
@loops = loops | |
end | |
def self.default_duration | |
@default_duration ||= 300000 | |
end | |
def self.default_duration= duration | |
@default_duration = duration | |
end | |
def self.sample_size | |
@sample_size ||= 16384 | |
end | |
def self.sample_size= size | |
@sample_size = size | |
end | |
class Library | |
attr_accessor :song, :duration, :elapsed, :samples | |
def initialize song, opts={buffer: true} | |
@song = song | |
# set up the byte buffer; this works the same for all libraries | |
if opts[:buffer] | |
@mutex = Mutex.new | |
@buffer = '' | |
@byte_position = 0 | |
@render_thread = Thread.new do | |
@unbuffered_samples.each do |sample| | |
@buffer << sample | |
end | |
end | |
@samples = Enumerator.new do |yielder| | |
yielder.yield @buffer[@byte_position..@byte_position+Paula.sample_size] | |
@byte_position += Paula.sample_size | |
@elapsed = (@byte_position / ((Paula.rate * 16 * 2) / 8).to_f * 1000).to_i | |
end | |
end | |
end | |
def title | |
File.basename @song | |
end | |
def stop | |
end | |
class << self | |
def buffer opts={} | |
size == opts[:size] || 16384 | |
@raw_buffer = opts[:type], *opts[:arguments] | |
# assumes stereo, 16-bit | |
@buffers_per_second = ((Paula.rate * 16 * 2) / 8) / size.to_f | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'ffi' | |
Paula.declare library: 'MDXMini', extensions: ['.mdx'] | |
module Paula | |
module MDXMini | |
extend FFI::Library | |
ffi_lib "mdxmini" | |
attach_function :mdx_open, [:pointer, :string, :string], :int | |
attach_function :mdx_set_rate, [:int], :void | |
attach_function :mdx_set_max_loop, [:pointer, :int], :void | |
attach_function :mdx_next_frame, [:pointer], :int | |
attach_function :mdx_frame_length, [:pointer], :int | |
attach_function :mdx_make_buffer, [:pointer, :int], :void | |
attach_function :mdx_calc_sample, [:pointer, :pointer, :int], :int | |
attach_function :mdx_get_title, [:pointer, :pointer], :void | |
# this returns a value in seconds | |
attach_function :mdx_get_length, [:pointer], :int | |
attach_function :mdx_get_tracks, [:pointer], :int | |
attach_function :mdx_get_current_notes, [:pointer, :pointer, :int], :void | |
attach_function :mdx_stop, [:pointer], :void | |
attach_function :mdx_get_sample_size, [:pointer], :int | |
attach_function :mdx_get_buffer_size, [:pointer], :int | |
# t_mdxmini struct used in C library | |
class T_mdxmini < FFI::Struct | |
layout :samples, :int, | |
:channels, :int, | |
:mdx, :pointer, | |
:pdx, :pointer, | |
:self, :pointer | |
end | |
class API < Paula::Library | |
#buffer type: FFI::MemoryPointer, | |
# arguments: [:short, 16384/2, true], size: 16384 | |
def initialize song | |
# mdx_set_rate *must* come before mdx_open | |
Paula::MDXMini.mdx_set_rate Paula.rate | |
@mini = Paula::MDXMini::T_mdxmini.new | |
Paula::MDXMini.mdx_open @mini, song, File.dirname(song) | |
Paula::MDXMini.mdx_set_max_loop @mini, Paula.loops | |
@sample_buffer = FFI::MemoryPointer.new :short, 16384/2, true | |
@buffers_per_second = ((Paula.rate * 16 * 2) / 8) / @sample_buffer.size.to_f | |
@duration = 1000 * Paula::MDXMini.mdx_get_length(@mini) | |
@unbuffered_samples= Enumerator.new do |yielder| | |
buffers = 0 | |
begin | |
buffers += 1 | |
MDXMini.mdx_calc_sample @mini, @sample_buffer, 4096 | |
yielder.yield @sample_buffer.read_bytes(@sample_buffer.size) | |
end while buffers < (@duration * @buffers_per_second / 1000) | |
end | |
super | |
end | |
def title | |
ptr = FFI::MemoryPointer.new :char, 100 | |
Paula::MDXMini.mdx_get_title @mini, ptr | |
ptr.read_bytes(100).force_encoding('Shift_JIS').encode!('UTF-8').rstrip! | |
rescue | |
"Unable to retrieve title..." | |
end | |
def stop | |
Paula::MDXMini.mdx_stop @mini | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'ffi' | |
Paula.declare library: 'PMDMini', extensions: ['.m', '.mz', '.m2'] | |
module Paula | |
module PMDMini | |
extend FFI::Library | |
ffi_lib "pmdmini" | |
attach_function :pmd_init, [ ], :void | |
attach_function :pmd_setrate, [ :int ], :void | |
attach_function :pmd_is_pmd, [ :string ], :int | |
# pmd_play name is somewhat misleading; this loads a song from disk | |
attach_function :pmd_play, [ :string ], :int | |
# pmd_length_sec and pmd_loop_sec have reduced precision compared to | |
# internal length; use pmd_length and pmd_loop instead | |
attach_function :pmd_length_sec, [ ], :int | |
attach_function :pmd_loop_sec, [ ], :int | |
attach_function :pmd_renderer, [ :pointer, :int ], :void | |
attach_function :pmd_stop, [ ], :void | |
attach_function :pmd_get_title, [ :pointer ], :void | |
attach_function :pmd_get_compo, [ :pointer ], :void | |
attach_function :pmd_get_tracks, [ ], :int | |
attach_function :pmd_get_current_notes, [ :pointer, :int ], :void | |
# full song length should be calculated by combining these two variables | |
# values are in milliseconds | |
attach_variable :pmd_length, :int | |
attach_variable :pmd_loop, :int | |
class API < Paula::Library | |
#buffer type: FFI::MemoryPointer, | |
# arguments: [:short, 16384/2, true], size: 16384 | |
def initialize song | |
@sample_buffer = FFI::MemoryPointer.new :char, 16384, true | |
@buffers_per_second = ((Paula.rate * 16 * 2) / 8) / @sample_buffer.size.to_f | |
Paula::PMDMini.pmd_init | |
# pmd_setrate comes *after* pmd_ini, *before* pmd_play | |
Paula::PMDMini.pmd_setrate Paula.rate | |
Paula::PMDMini.pmd_play song | |
@duration = Paula::PMDMini.pmd_length + Paula::PMDMini.pmd_loop | |
@unbuffered_samples = Enumerator.new do |yielder| | |
buffers = 0 | |
begin | |
buffers += 1 | |
@elapsed = (buffers / @buffers_per_second * 1000).to_i | |
Paula::PMDMini.pmd_renderer @sample_buffer, 4096 | |
yielder.yield @sample_buffer.read_bytes(@sample_buffer.size) | |
end while buffers < (@duration * @buffers_per_second / 1000) | |
end | |
super | |
end | |
def title | |
ptr = FFI::MemoryPointer.new :char, 1024, true | |
Paula::PMDMini.pmd_get_title ptr | |
ptr.read_bytes(1024).force_encoding('Shift_JIS').encode!('UTF-8').rstrip! | |
rescue | |
"Unable to retrieve title..." | |
end | |
def stop | |
Paula::PMDMini.pmd_stop | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'coreaudio' | |
require_relative 'paula' | |
class SamplePlayer | |
class << self; include Paula::DSL; end | |
loops 2 | |
default_duration 300000 # milliseconds | |
def load song | |
@player = Paula.factory(File.expand_path song) | |
end | |
def play opts | |
@player.samples.each do |sample| | |
# print the current playtime | |
print "#{backspace*10}#{@player.elapsed / 1000}/#{@player.duration / 1000}" | |
# write the audio to the buffer | |
opts[:to] << NArray.to_narray(sample, NArray::SINT, 2, 4096) | |
end | |
end | |
def backspace | |
"\b" | |
end | |
end | |
# initialize the sound card | |
dev = CoreAudio.default_output_device | |
outbuf = dev.output_buffer 1024 | |
outbuf.start | |
# create a player object, load the song, and play | |
player = SamplePlayer.new | |
player.load ARGV[0] | |
player.play to: outbuf | |
outbuf.stop | |
puts |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment