Last active
April 1, 2019 11:13
-
-
Save astellon/b91a3cd72a6ea2a585b9c14aeb87a731 to your computer and use it in GitHub Desktop.
simple sin wave synthesizer + sequencer in Crystal
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 "socket" | |
require "osc-crystal" | |
lib LibC | |
fun nanosleep(req : Timespec*, rem : Timespec*) : Int32 | |
end | |
# helper | |
def note_to_heltz(note : Int) | |
(440*Math.exp2((note - 69.0)/12.0)).to_f32 | |
end | |
class Sequencer(T) | |
getter seqs : T | |
getter pos : Int32 | |
getter client : OSC::Client | |
property running : Bool | |
def initialize(@client, @seqs : T) forall T | |
@pos = 0 | |
@running = false | |
end | |
def start(bpm) | |
spawn do | |
@running = true | |
while @running | |
process(@seqs[@pos], bpm, @seqs.size) | |
@pos = (@pos + 1) % seqs.size | |
end | |
end | |
end | |
def stop | |
@running = @running ^ false | |
end | |
def process(seq : Int32, bpm, n) | |
client.send(OSC::Bundle.new(Time.utc_now + Time::Span.new(nanoseconds: 10_000), OSC::Message.new("/note", seq + 60))) # mid C | |
puts seq | |
wait(bpm, n) | |
end | |
def process(seq : Tuple, bpm, n) | |
seq.each do |i| | |
process(i, bpm, n * seq.size) | |
end | |
end | |
def wait(bpm, n) | |
milisec = 60.0 * 1000.0 / bpm * 4.0 / n | |
sec = (milisec / 1000).to_i32 | |
nse = ((milisec % 1000) * 1000000).to_i32 | |
req = LibC::Timespec.new(tv_sec: sec, tv_nsec: nse) | |
rem = LibC::Timespec.new(tv_sec: 0, tv_nsec: 0) | |
LibC.nanosleep(pointerof(req), pointerof(rem)) | |
end | |
end | |
def main | |
client = UDPSocket.new | |
client.connect "127.0.0.1", 8000 | |
osc_client = OSC::Client.new(client) | |
s = Sequencer.new(osc_client, {0, {5, 7, 2, 5}, {4, 7}, 2}) | |
s.start(120) | |
at_exit { | |
s.stop | |
client.close | |
} | |
sleep | |
end | |
main |
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
# Standard library | |
require "socket" | |
# denpendencies | |
require "osc-crystal" | |
require "quartz" | |
include Quartz | |
# note number to helz | |
def note_to_heltz(note : Int) | |
(440*Math.exp2((note - 69.0)/12.0)).to_f32 | |
end | |
# streaming sin wave | |
class Synth | |
property phase = 0.0_f32 | |
property delta_p = 0.0_f32 | |
property time = 0.0 | |
property freq_queue = Deque(Tuple(Float64, Float32)).new | |
def callback(input, output, nframe) | |
update() | |
@time += nframe / 44100.0 | |
(0..nframe - 1).each do |i| | |
output[2*i] = output[2*i + 1] = Math.sin(@phase) * 0.5 | |
@phase += @delta_p | |
end | |
@phase = @phase.modulo(2 * Math::PI) | |
return 0 | |
end | |
def update | |
return if @freq_queue.size == 0 | |
t, freq = @freq_queue.first | |
if @time > t | |
@delta_p = (2 * Math::PI * freq / 44100.0).to_f32 | |
@freq_queue.shift | |
puts @phase | |
end | |
end | |
end | |
def main | |
synth = Synth.new | |
# server | |
server = UDPSocket.new | |
server.bind "localhost", 8000 | |
stream = Quartz::AudioStream(Float32).new(2, 2, 44100.0, 256_u64) | |
at_exit { | |
running = false | |
server.close | |
} | |
stream.start(synth) | |
running = true | |
start_time = Time.utc_now | |
while running | |
message, address = server.receive | |
bundle = OSC::Bundle.new(message.bytes) | |
time = bundle.time - start_time # => Time::Span | |
msg1 = bundle.elm(0).as(OSC::Message) | |
case note = msg1.arg(0) | |
when Int32 | |
synth.freq_queue.push({time.total_seconds, note_to_heltz note}) | |
end | |
end | |
end | |
main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment