diff options
Diffstat (limited to 'v_windows/v/examples/sokol/sounds')
-rw-r--r-- | v_windows/v/examples/sokol/sounds/melody.v | 75 | ||||
-rw-r--r-- | v_windows/v/examples/sokol/sounds/simple_sin_tones.v | 42 | ||||
-rw-r--r-- | v_windows/v/examples/sokol/sounds/uhoh.wav | bin | 0 -> 68562 bytes | |||
-rw-r--r-- | v_windows/v/examples/sokol/sounds/wav_player.v | 209 |
4 files changed, 326 insertions, 0 deletions
diff --git a/v_windows/v/examples/sokol/sounds/melody.v b/v_windows/v/examples/sokol/sounds/melody.v new file mode 100644 index 0000000..aa5ebc4 --- /dev/null +++ b/v_windows/v/examples/sokol/sounds/melody.v @@ -0,0 +1,75 @@ +import gg +import gx +import sokol.audio + +const credits = 'Based on the ByteBeat formula from: https://www.youtube.com/watch?v=V4GfkFbDojc \n "Techno" by Gabriel Miceli' + +struct AppState { +mut: + gframe int // the current graphical frame + frame_0 int // offset of the current audio frames, relative to the start of the music + frames [2048]f32 // a copy of the last rendered audio frames + gg &gg.Context // used for drawing +} + +fn my_audio_stream_callback(buffer &f32, num_frames int, num_channels int, mut acontext AppState) { + mut soundbuffer := unsafe { buffer } + for frame := 0; frame < num_frames; frame++ { + t := int(f32(acontext.frame_0 + frame) * 0.245) + // "Techno" by Gabriel Miceli + y := (t * (((t / 10 | 0) ^ ((t / 10 | 0) - 1280)) % 11) / 2 & 127) + + (t * (((t / 640 | 0) ^ ((t / 640 | 0) - 2)) % 13) / 2 & 127) + for ch := 0; ch < num_channels; ch++ { + idx := frame * num_channels + ch + unsafe { + a := f32(byte(y) - 127) / 255.0 + soundbuffer[idx] = a + acontext.frames[idx & 2047] = a + } + } + } + acontext.frame_0 += num_frames +} + +fn main() { + println(credits) + mut state := &AppState{ + gg: 0 + } + audio.setup( + stream_userdata_cb: my_audio_stream_callback + user_data: state + ) + state.gg = gg.new_context( + bg_color: gx.rgb(50, 50, 50) + width: 1024 + height: 400 + create_window: true + window_title: 'ByteBeat Music' + frame_fn: graphics_frame + user_data: state + ) + state.gg.run() + audio.shutdown() +} + +fn graphics_frame(mut state AppState) { + state.gframe++ + state.gg.begin() + state.draw() + state.gg.end() +} + +[inline] +fn (mut state AppState) bsample(idx int) byte { + return byte(127 + state.frames[(state.gframe + idx) & 2047] * 128) +} + +fn (mut state AppState) draw() { + // first, reset and setup ortho projection + for x in 0 .. 1024 { + mut y := 100 * (state.frames[2 * x] + state.frames[2 * x + 1]) + state.gg.draw_line(x, 200, x, 200 + y, gx.rgba(state.bsample(x), state.bsample(x + 300), + state.bsample(x + 700), 255)) + } +} diff --git a/v_windows/v/examples/sokol/sounds/simple_sin_tones.v b/v_windows/v/examples/sokol/sounds/simple_sin_tones.v new file mode 100644 index 0000000..c327117 --- /dev/null +++ b/v_windows/v/examples/sokol/sounds/simple_sin_tones.v @@ -0,0 +1,42 @@ +import time +import math +import sokol.audio + +const ( + sw = time.new_stopwatch() + sw_start_ms = sw.elapsed().milliseconds() +) + +[inline] +fn sintone(periods int, frame int, num_frames int) f32 { + return math.sinf(f32(periods) * (2 * math.pi) * f32(frame) / f32(num_frames)) +} + +fn my_audio_stream_callback(buffer &f32, num_frames int, num_channels int) { + ms := sw.elapsed().milliseconds() - sw_start_ms + unsafe { + mut soundbuffer := buffer + for frame := 0; frame < num_frames; frame++ { + for ch := 0; ch < num_channels; ch++ { + idx := frame * num_channels + ch + if ms < 250 { + soundbuffer[idx] = 0.5 * sintone(20, frame, num_frames) + } else if ms < 300 { + soundbuffer[idx] = 0.5 * sintone(25, frame, num_frames) + } else if ms < 1500 { + soundbuffer[idx] *= sintone(22, frame, num_frames) + } else { + soundbuffer[idx] = 0.5 * sintone(25, frame, num_frames) + } + } + } + } +} + +fn main() { + audio.setup( + stream_cb: my_audio_stream_callback + ) + time.sleep(2000 * time.millisecond) + audio.shutdown() +} diff --git a/v_windows/v/examples/sokol/sounds/uhoh.wav b/v_windows/v/examples/sokol/sounds/uhoh.wav Binary files differnew file mode 100644 index 0000000..cff9e3c --- /dev/null +++ b/v_windows/v/examples/sokol/sounds/uhoh.wav diff --git a/v_windows/v/examples/sokol/sounds/wav_player.v b/v_windows/v/examples/sokol/sounds/wav_player.v new file mode 100644 index 0000000..3c9419b --- /dev/null +++ b/v_windows/v/examples/sokol/sounds/wav_player.v @@ -0,0 +1,209 @@ +import os +import time +import sokol.audio + +struct Player { +mut: + samples []f32 + pos int + finished bool +} + +fn main() { + if os.args.len < 2 { + eprintln('Usage: play_wav file1.wav file2.wav ...') + play_sounds([os.resource_abs_path('uhoh.wav')]) ? + exit(1) + } + play_sounds(os.args[1..]) ? +} + +fn play_sounds(files []string) ? { + mut player := Player{} + player.init() + for f in files { + if !os.exists(f) || os.is_dir(f) { + eprintln('skipping "$f" (does not exist)') + continue + } + fext := os.file_ext(f).to_lower() + if fext != '.wav' { + eprintln('skipping "$f" (not a .wav file)') + continue + } + player.play_wav_file(f) ? + } + player.stop() +} + +// +fn audio_player_callback(buffer &f32, num_frames int, num_channels int, mut p Player) { + if p.finished { + return + } + ntotal := num_channels * num_frames + nremaining := p.samples.len - p.pos + nsamples := if nremaining < ntotal { nremaining } else { ntotal } + if nsamples <= 0 { + p.finished = true + return + } + unsafe { C.memcpy(buffer, &p.samples[p.pos], nsamples * int(sizeof(f32))) } + p.pos += nsamples +} + +fn (mut p Player) init() { + audio.setup( + num_channels: 2 + stream_userdata_cb: audio_player_callback + user_data: p + ) +} + +fn (mut p Player) stop() { + audio.shutdown() + p.free() +} + +fn (mut p Player) play_wav_file(fpath string) ? { + println('> play_wav_file: $fpath') + samples := read_wav_file_samples(fpath) ? + p.finished = true + p.samples << samples + p.finished = false + for !p.finished { + time.sleep(16 * time.millisecond) + } + p.free() +} + +fn (mut p Player) free() { + p.finished = false + p.samples = []f32{} + p.pos = 0 +} + +// The read_wav_file_samples function below is based on the following sources: +// http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html +// http://www.lightlink.com/tjweber/StripWav/WAVE.html +// http://www.lightlink.com/tjweber/StripWav/Canon.html +// https://tools.ietf.org/html/draft-ema-vpim-wav-00 +// NB: > The chunks MAY appear in any order except that the Format chunk +// > MUST be placed before the Sound data chunk (but not necessarily +// > contiguous to the Sound data chunk). +struct RIFFHeader { + riff [4]byte + file_size u32 + form_type [4]byte +} + +struct RIFFChunkHeader { + chunk_type [4]byte + chunk_size u32 + chunk_data voidptr +} + +struct RIFFFormat { + format_tag u16 // PCM = 1; Values other than 1 indicate some form of compression. + nchannels u16 // Nc ; 1 = mono ; 2 = stereo + sample_rate u32 // F + avg_bytes_per_second u32 // F * M*Nc + nblock_align u16 // M*Nc + bits_per_sample u16 // 8 * M + cbsize u16 // Size of the extension: 22 + valid_bits_per_sample u16 // at most 8*M + channel_mask u32 // Speaker position mask + sub_format [16]byte // GUID +} + +fn read_wav_file_samples(fpath string) ?[]f32 { + mut res := []f32{} + // eprintln('> read_wav_file_samples: $fpath -------------------------------------------------') + mut bytes := os.read_bytes(fpath) ? + mut pbytes := &byte(bytes.data) + mut offset := u32(0) + rh := unsafe { &RIFFHeader(pbytes) } + // eprintln('rh: $rh') + if rh.riff != [byte(`R`), `I`, `F`, `F`]! { + return error('WAV should start with `RIFF`') + } + if rh.form_type != [byte(`W`), `A`, `V`, `E`]! { + return error('WAV should have `WAVE` form type') + } + if rh.file_size + 8 != bytes.len { + return error('WAV should have valid lenght') + } + offset += sizeof(RIFFHeader) + mut rf := &RIFFFormat(0) + for { + if offset >= bytes.len { + break + } + // + ch := unsafe { &RIFFChunkHeader(pbytes + offset) } + offset += 8 + ch.chunk_size + // eprintln('ch: $ch') + // eprintln('p: $pbytes | offset: $offset | bytes.len: $bytes.len') + // //////// + if ch.chunk_type == [byte(`L`), `I`, `S`, `T`]! { + continue + } + // + if ch.chunk_type == [byte(`i`), `d`, `3`, ` `]! { + continue + } + // + if ch.chunk_type == [byte(`f`), `m`, `t`, ` `]! { + // eprintln('`fmt ` chunk') + rf = unsafe { &RIFFFormat(&ch.chunk_data) } + // eprintln('fmt riff format: $rf') + if rf.format_tag != 1 { + return error('only PCM encoded WAVs are supported') + } + if rf.nchannels < 1 || rf.nchannels > 2 { + return error('only mono or stereo WAVs are supported') + } + if rf.bits_per_sample !in [u16(8), 16] { + return error('only 8 or 16 bits per sample WAVs are supported') + } + continue + } + // + if ch.chunk_type == [byte(`d`), `a`, `t`, `a`]! { + if rf == 0 { + return error('`data` chunk should be after `fmt ` chunk') + } + // eprintln('`fmt ` chunk: $rf\n`data` chunk: $ch') + mut doffset := 0 + mut dp := unsafe { &byte(&ch.chunk_data) } + for doffset < ch.chunk_size { + for c := 0; c < rf.nchannels; c++ { + mut x := f32(0.0) + mut step := 0 + ppos := unsafe { dp + doffset } + if rf.bits_per_sample == 8 { + d8 := unsafe { &byte(ppos) } + x = (f32(*d8) - 128) / 128.0 + step = 1 + doffset++ + } + if rf.bits_per_sample == 16 { + d16 := unsafe { &i16(ppos) } + x = f32(*d16) / 32768.0 + step = 2 + } + doffset += step + if doffset < ch.chunk_size { + res << x + if rf.nchannels == 1 { + // Duplicating single channel mono sounds, + // produces a stereo sound, simplifying further processing: + res << x + } + } + } + } + } + } + return res +} |