Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 78 additions & 13 deletions constants.v
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,82 @@ const note_names = [
]!

const note_colors = [
gx.Color{ r: 226, g: 1, b: 68, a: 255 }
gx.Color{ r: 233, g: 67, b: 19, a: 255 }
gx.Color{ r: 236, g: 137, b: 10, a: 255 }
gx.Color{ r: 252, g: 205, b: 16, a: 255 }
gx.Color{ r: 247, g: 232, b: 20, a: 255 }
gx.Color{ r: 190, g: 214, b: 1, a: 255 }
gx.Color{ r: 134, g: 185, b: 20, a: 255 }
gx.Color{ r: 100, g: 178, b: 83, a: 255 }
gx.Color{ r: 70, g: 130, b: 190, a: 255 }
gx.Color{ r: 73, g: 85, b: 155, a: 255 }
gx.Color{ r: 151, g: 107, b: 168, a: 255 }
gx.Color{ r: 189, g: 82, b: 150, a: 255 }
gx.Color{ r: 89, g: 82, b: 150, a: 255 }
gx.Color{
r: 226
g: 1
b: 68
a: 255
},
gx.Color{
r: 233
g: 67
b: 19
a: 255
},
gx.Color{
r: 236
g: 137
b: 10
a: 255
},
gx.Color{
r: 252
g: 205
b: 16
a: 255
},
gx.Color{
r: 247
g: 232
b: 20
a: 255
},
gx.Color{
r: 190
g: 214
b: 1
a: 255
},
gx.Color{
r: 134
g: 185
b: 20
a: 255
},
gx.Color{
r: 100
g: 178
b: 83
a: 255
},
gx.Color{
r: 70
g: 130
b: 190
a: 255
},
gx.Color{
r: 73
g: 85
b: 155
a: 255
},
gx.Color{
r: 151
g: 107
b: 168
a: 255
},
gx.Color{
r: 189
g: 82
b: 150
a: 255
},
gx.Color{
r: 89
g: 82
b: 150
a: 255
},
]!
32 changes: 16 additions & 16 deletions main.v
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import gg
import os
import time

import os.font
import audio
import vidi

struct App {
mut:
gg &gg.Context = voidptr(0)
vidi &vidi.Context = voidptr(0)
gg &gg.Context = voidptr(0)
vidi &vidi.Context = voidptr(0)
audio &audio.Context = voidptr(0)
sustained bool
win_width int = 1280
Expand All @@ -17,19 +17,18 @@ mut:
key_height f32 = 200
white_key_count int
keys [128]Key
start_note byte = 41

start_note u8 = 41
// whether the current song is paused
paused bool
// the current tempo multiplier
tempo f32 = 1.0
tempo f32 = 1.0
// info about the current song:
// the notes that make it up
notes []Note
// the current timestamp (in ns)
t u64
t u64
// the index of the first note currently being played in the song
i u32
i u32
// the current song's length in ns
song_len u64
}
Expand All @@ -38,14 +37,14 @@ struct Note {
mut:
start u64
len u32
midi byte
vel byte
midi u8
vel u8
}

fn (n Note) str() string {
a := int(n.midi)
b := f64(n.start) / time.second
c := f64(n.start+n.len) / time.second
b := f64(n.start) / time.second
c := f64(n.start + n.len) / time.second
return '\n [$a] $b - $c'
}

Expand Down Expand Up @@ -77,18 +76,18 @@ fn main() {
frame_fn: frame
event_fn: event
user_data: app
font_path: gg.system_font_path()
font_path: font.default()
sample_count: 4
)

if os.args.len > 1 {
app.parse_midi_file(os.args[1]) or {
app.parse_midi_file(os.args[1]) or {
eprintln('failed to parse midi file `${os.args[1]}`: $err')
return
}

mut song_len := u64(0)
mut notes_needed := map[byte]u16{}
mut notes_needed := map[u8]u16{}
for note in app.notes {
if note.start + note.len > song_len {
song_len = note.start + note.len
Expand All @@ -100,7 +99,8 @@ fn main() {

notes_per_second := f64(app.notes.len) / f64(app.song_len) * f64(time.second)
difficulties := ['easy', 'medium', 'hard', 'very hard', 'extreme']!
println('total notes: $app.notes.len (${notes_per_second:.1f} notes/sec, difficulty: ${difficulties[clamp<byte>(byte(notes_per_second / 3.3), 0, 4)]})')
println('total notes: $app.notes.len (${notes_per_second:.1f} notes/sec, difficulty: ${difficulties[clamp<u8>(u8(notes_per_second / 3.3),
0, 4)]})')

mut keys := notes_needed.keys()
keys.sort()
Expand Down
73 changes: 39 additions & 34 deletions midi.v
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
import time

import vidi

[inline]
fn midi2name(midi byte) string {
fn midi2name(midi u8) string {
x := ['bass', 'mid', 'high']!
oct := if is_playable(midi) { x[clamp(midi/12 - 4, 0, x.len-1)] } else { '(unplayable)' }
note := note_names[midi%12]
oct := if is_playable(midi) { x[clamp(midi / 12 - 4, 0, x.len - 1)] } else { '(unplayable)' }
note := note_names[midi % 12]
return '$oct $note'
}

// byte.is_playable returns true if a note is playable using a Boomwhackers set
// u8.is_playable returns true if a note is playable using a Boomwhackers set
[inline]
fn is_playable(n byte) bool {
fn is_playable(n u8) bool {
return n >= 48 && n <= 76
}

[inline]
fn (mut app App) play_note(note byte, vol_ byte) {
if app.keys[note].pressed { return }
fn (mut app App) play_note(note u8, vol_ u8) {
if app.keys[note].pressed {
return
}

app.keys[note].pressed = true
vol := f32(vol_) / 127
app.audio.play(note, vol)
}

[inline]
fn (mut app App) pause_note(note byte) {
fn (mut app App) pause_note(note u8) {
app.keys[note].pressed = false
app.audio.pause(note)
}
Expand All @@ -36,12 +37,12 @@ fn (mut app App) pause_all() {
for note, mut key in app.keys {
if key.pressed {
key.pressed = false
app.audio.pause(byte(note))
app.audio.pause(u8(note))
}
}
}

fn (mut app App) note_down(note byte, velocity byte) {
fn (mut app App) note_down(note u8, velocity u8) {
if velocity == 0 {
app.pause_note(note)
} else {
Expand All @@ -63,7 +64,7 @@ fn (mut app App) sustain() {
fn (mut app App) unsustain() {
if app.sustained {
app.sustained = false
for midi in 0 .. byte(app.keys.len) {
for midi in 0 .. u8(app.keys.len) {
app.pause_note(midi)
}
}
Expand Down Expand Up @@ -94,32 +95,31 @@ fn (mut app App) parse_midi_file(name string) ? {
cache[event.note] = Note{}
} else {
// play
cache[event.note] = {
cache[event.note] = Note{
start: t
midi: event.note
vel: event.velocity
}
}
}
vidi.Controller {
match event.controller_type {
0x40, 0x17 {
if event.value > 0x40 {
is_sustain = true
} else {
is_sustain = false
for mut note in sustained_notes {
note.len = u32(t - note.start)
}
app.notes << sustained_notes
sustained_notes.clear()
match event.controller_type {
0x40, 0x17 {
if event.value > 0x40 {
is_sustain = true
} else {
is_sustain = false
for mut note in sustained_notes {
note.len = u32(t - note.start)
}
}
else {
// println('Control change (control=$control, value=$value)')
app.notes << sustained_notes
sustained_notes.clear()
}
}

else {
// println('Control change (control=$control, value=$value)')
}
}
}
vidi.SetTempo {
mpqn = u32(midi.mpqn(event.microseconds))
Expand All @@ -135,17 +135,17 @@ fn (mut app App) parse_midi_file(name string) ? {
}

fn (mut app App) play() {
mut sw := time.new_stopwatch({})
mut sw := time.new_stopwatch()
for app.t < app.song_len + lookahead + u64(time.second) {
time.sleep(50*time.microsecond)
time.sleep(50 * time.microsecond)
if app.paused {
sw.restart()
continue
}
app.t += u64(f64(sw.elapsed()) * app.tempo)
sw.restart()
mut is_at_start := true
for i := app.i; i < app.notes.len ; i++ {
for i := app.i; i < app.notes.len; i++ {
note := app.notes[i]
key := app.keys[note.midi]
end := note.start + note.len
Expand All @@ -159,9 +159,11 @@ fn (mut app App) play() {
is_at_start = false
}
}

$if !unplayable ? {
if !is_playable(note.midi) { continue }
if !is_playable(note.midi) {
continue
}
}

if note.start <= lt && end > lt && !key.pressed {
Expand All @@ -172,8 +174,11 @@ fn (mut app App) play() {
app.pause_note(note.midi)
}

if note.start > lt { break }
if note.start > lt {
break
}
}
}

// exit(0)
}
Loading