CLASS _SOUND_CHANNEL
VAR SONG: SONG
VAR TRACK_INDEX: INT
VAR NEXT_PATTERN_INDEX: INT
VAR SYNC_DATA: INT
VAR EVENTS: TRACK_EVENT[]
VAR NEXT_EVENT_INDEX: INT
VAR INSTRUMENT: IO_INSTRUMENT
VAR WRITE_INDEX: BYTE
VAR IS_PLAYING: BOOL
VAR LOOPING: BOOL
END CLASS
MODULE SOUND
VAR _SOUND_CHANNELS: _SOUND_CHANNEL[]
VAR _NOTE_TO_PITCH: PAIR[]
FUNC INIT()
NEW _SOUND_CHANNEL[](6) -> SOUND::_SOUND_CHANNELS
SOUND::_SOUND_CHANNELS.RESIZE(6)
0 -> IO::JAMDAC_ENABLE
VAR QUEUE: ^IO_AUDIO_QUEUE
VAR I: INT
0 -> I
LOOP
NEW _SOUND_CHANNEL() -> SOUND::_SOUND_CHANNELS[I]
IO::AUDIO_QUEUES[I] -> QUEUE
TO_ADDRESS(QUEUE.EVENTS) -> QUEUE.WRITE_ADDRESS
QUEUE.WRITE_ADDRESS -> QUEUE.READ_ADDRESS
0 -> QUEUE.PURGE_READ_ADDRESS
I + 1 -> I
IF I >= 6
DO DROP
END LOOP
IO::AUDIO_QUEUES[0] -> QUEUE
VAR NEW_AUDIO_EVENT: ^IO_AUDIO_EVENT
QUEUE.EVENTS[0] -> NEW_AUDIO_EVENT
7 -> NEW_AUDIO_EVENT.KIND
TO_ADDRESS(ART::REVERB) -> NEW_AUDIO_EVENT.OPERAND
QUEUE.EVENTS[1] -> NEW_AUDIO_EVENT
3 -> NEW_AUDIO_EVENT.KIND
TO_ADDRESS(ART::ENVELOPE_3) -> NEW_AUDIO_EVENT.OPERAND
QUEUE.EVENTS[2] -> NEW_AUDIO_EVENT
5 -> NEW_AUDIO_EVENT.KIND
3 -> NEW_AUDIO_EVENT.OPERAND
TO_ADDRESS(QUEUE.EVENTS[3]) -> QUEUE.WRITE_ADDRESS
3 -> SOUND::_SOUND_CHANNELS[0].WRITE_INDEX
1 -> IO::JAMDAC_ENABLE
KERNEL::TRACE_NUM(0)
LOOP
IF QUEUE.READ_ADDRESS = QUEUE.WRITE_ADDRESS
DO DROP
END LOOP
END FUNC
FUNC PLAY_TRACK(TRACK: TRACK, CHANNEL_INDEX: INT)
SOUND::_PLAY_TRACK(TRACK, CHANNEL_INDEX, FALSE)
END FUNC
FUNC LOOP_TRACK(TRACK: TRACK, CHANNEL_INDEX: INT)
SOUND::_PLAY_TRACK(TRACK, CHANNEL_INDEX, TRUE)
END FUNC
FUNC _PLAY_TRACK(TRACK: TRACK, CHANNEL_INDEX: INT, LOOPING: BOOL)
IF SOUND::_SOUND_CHANNELS = NULL
DO RETURN
SOUND::STOP_CHANNEL(CHANNEL_INDEX)
VAR CHANNEL: _SOUND_CHANNEL
SOUND::_SOUND_CHANNELS[CHANNEL_INDEX] -> CHANNEL
NULL -> CHANNEL.SONG
LOOPING -> CHANNEL.LOOPING
TRUE -> CHANNEL.IS_PLAYING
0 -> CHANNEL.TRACK_INDEX
0 -> CHANNEL.NEXT_PATTERN_INDEX
0 -> CHANNEL.SYNC_DATA
TRACK.EVENTS -> CHANNEL.EVENTS
0 -> CHANNEL.NEXT_EVENT_INDEX
END FUNC
FUNC STOP_CHANNEL(CHANNEL_INDEX: INT)
IF SOUND::_SOUND_CHANNELS = NULL
DO RETURN
VAR CHANNEL: _SOUND_CHANNEL
SOUND::_SOUND_CHANNELS[CHANNEL_INDEX] -> CHANNEL
NULL -> CHANNEL.SONG
FALSE -> CHANNEL.IS_PLAYING
NULL -> CHANNEL.EVENTS
NULL -> CHANNEL.INSTRUMENT
VAR QUEUE: ^IO_AUDIO_QUEUE
IO::AUDIO_QUEUES[CHANNEL_INDEX] -> QUEUE
VAR WRITE_INDEX: BYTE, WRITE_ADDRESS: INT, READ_ADDRESS: INT
QUEUE.READ_ADDRESS -> READ_ADDRESS
QUEUE.WRITE_ADDRESS -> WRITE_ADDRESS
IF READ_ADDRESS <> WRITE_ADDRESS THEN
QUEUE.WRITE_ADDRESS -> QUEUE.PURGE_READ_ADDRESS
END IF
CHANNEL.WRITE_INDEX -> WRITE_INDEX
VAR NEW_AUDIO_EVENT: ^IO_AUDIO_EVENT
QUEUE.EVENTS[WRITE_INDEX] -> NEW_AUDIO_EVENT
0 -> NEW_AUDIO_EVENT.KIND
0 -> NEW_AUDIO_EVENT.OPERAND
TO_BYTE(WRITE_INDEX + 1) -> WRITE_INDEX
IF WRITE_INDEX >= 100
DO 0 -> WRITE_INDEX
TO_ADDRESS(QUEUE.EVENTS[WRITE_INDEX]) -> WRITE_ADDRESS
WRITE_INDEX -> CHANNEL.WRITE_INDEX
WRITE_ADDRESS -> QUEUE.WRITE_ADDRESS
END FUNC
FUNC PLAY_SONG(SONG: SONG)
SOUND::_PLAY_SONG(SONG, FALSE)
END FUNC
FUNC LOOP_SONG(SONG: SONG)
SOUND::_PLAY_SONG(SONG, TRUE)
END FUNC
FUNC _PLAY_SONG(SONG: SONG, LOOPING: BOOL)
IF SOUND::_SOUND_CHANNELS = NULL
DO RETURN
VAR I: INT, TRACK_COUNT: INT
VAR PATTERN: SONG_PATTERN
VAR SYNC_DATA: INT
SONG.PATTERNS[0] -> PATTERN
PATTERN.TRACKS.SIZE -> TRACK_COUNT
IF TRACK_COUNT > 1 THEN
TRACK_COUNT -> SYNC_DATA
ELSE
0 -> SYNC_DATA
END IF
0 -> I
LOOP
IF I >= TRACK_COUNT
DO DROP
SOUND::STOP_CHANNEL(I)
VAR CHANNEL: _SOUND_CHANNEL
SOUND::_SOUND_CHANNELS[I] -> CHANNEL
SONG -> CHANNEL.SONG
LOOPING -> CHANNEL.LOOPING
TRUE -> CHANNEL.IS_PLAYING
I -> CHANNEL.TRACK_INDEX
1 -> CHANNEL.NEXT_PATTERN_INDEX
SYNC_DATA -> CHANNEL.SYNC_DATA
PATTERN.TRACKS[I].EVENTS -> CHANNEL.EVENTS
0 -> CHANNEL.NEXT_EVENT_INDEX
NULL -> CHANNEL.INSTRUMENT
I + 1 -> I
END LOOP
END FUNC
FUNC _PROCESS_CHANNEL(
| CHANNEL: _SOUND_CHANNEL,
| QUEUE: ^IO_AUDIO_QUEUE)
VAR WRITE_INDEX: BYTE, WRITE_ADDRESS: INT, READ_ADDRESS: INT
VAR TEMP: INT
VAR NEW_AUDIO_EVENT: ^IO_AUDIO_EVENT
IF CHANNEL.EVENTS = NULL AND NOT CHANNEL.IS_PLAYING
DO RETURN
CHANNEL.WRITE_INDEX -> WRITE_INDEX
TO_ADDRESS(QUEUE.EVENTS[WRITE_INDEX]) -> WRITE_ADDRESS
IF WRITE_ADDRESS <> QUEUE.WRITE_ADDRESS
DO KERNEL::FAIL("_PROCESS_CHANNEL() WRITE_ADDRESS OUT OF SYNC")
IF QUEUE.PURGE_READ_ADDRESS <> 0
DO RETURN
QUEUE.READ_ADDRESS -> READ_ADDRESS
IF CHANNEL.EVENTS <> NULL THEN
LOOP
IF READ_ADDRESS <= WRITE_ADDRESS THEN
READ_ADDRESS + 500 -> TEMP
ELSE
READ_ADDRESS -> TEMP
END IF
IF TEMP - WRITE_ADDRESS < 30
DO DROP
IF CHANNEL.NEXT_EVENT_INDEX >= CHANNEL.EVENTS.SIZE THEN
IF CHANNEL.SONG <> NULL THEN
VAR PATTERNS: SONG_PATTERN[]
CHANNEL.SONG.PATTERNS -> PATTERNS
IF CHANNEL.NEXT_PATTERN_INDEX >= PATTERNS.SIZE THEN
IF CHANNEL.LOOPING THEN
0 -> CHANNEL.NEXT_PATTERN_INDEX
ELSE
NULL -> CHANNEL.EVENTS
DROP
END IF
END IF
PATTERNS[CHANNEL.NEXT_PATTERN_INDEX].TRACKS[CHANNEL.TRACK_INDEX].EVENTS -> CHANNEL.EVENTS
CHANNEL.NEXT_PATTERN_INDEX + 1 -> CHANNEL.NEXT_PATTERN_INDEX
0 -> CHANNEL.NEXT_EVENT_INDEX
ELSE
IF CHANNEL.LOOPING THEN
0 -> CHANNEL.NEXT_EVENT_INDEX
ELSE
NULL -> CHANNEL.EVENTS
DROP
END IF
END IF
IF CHANNEL.SYNC_DATA <> 0 THEN
QUEUE.EVENTS[WRITE_INDEX] -> NEW_AUDIO_EVENT
9 -> NEW_AUDIO_EVENT.KIND
CHANNEL.SYNC_DATA -> NEW_AUDIO_EVENT.OPERAND
TO_BYTE(WRITE_INDEX + 1) -> WRITE_INDEX
IF WRITE_INDEX >= 100
DO 0 -> WRITE_INDEX
END IF
END IF
VAR TRACK_EVENT: TRACK_EVENT
CHANNEL.EVENTS[CHANNEL.NEXT_EVENT_INDEX] -> TRACK_EVENT
CHANNEL.NEXT_EVENT_INDEX + 1 -> CHANNEL.NEXT_EVENT_INDEX
VAR WAIT_TIME_SAMPLES: INT
459 * TRACK_EVENT.DURATION_TICKS -> WAIT_TIME_SAMPLES
VAR TRACK_EVENT_NOTE: BYTE
TRACK_EVENT.NOTE -> TRACK_EVENT_NOTE
IF TRACK_EVENT_NOTE < 254 THEN
WAIT_TIME_SAMPLES - 30 -> WAIT_TIME_SAMPLES
QUEUE.EVENTS[WRITE_INDEX] -> NEW_AUDIO_EVENT
10 -> NEW_AUDIO_EVENT.KIND
0 -> NEW_AUDIO_EVENT.OPERAND
TO_BYTE(WRITE_INDEX + 1) -> WRITE_INDEX
IF WRITE_INDEX >= 100
DO 0 -> WRITE_INDEX
VAR INSTRUMENT: IO_INSTRUMENT
ART::INSTRUMENTS[TRACK_EVENT.INSTRUMENT] -> INSTRUMENT
IF CHANNEL.INSTRUMENT <> INSTRUMENT THEN
INSTRUMENT -> CHANNEL.INSTRUMENT
WAIT_TIME_SAMPLES - 30 -> WAIT_TIME_SAMPLES
QUEUE.EVENTS[WRITE_INDEX] -> NEW_AUDIO_EVENT
0 -> NEW_AUDIO_EVENT.KIND
TO_ADDRESS(INSTRUMENT) -> NEW_AUDIO_EVENT.OPERAND
TO_BYTE(WRITE_INDEX + 1) -> WRITE_INDEX
IF WRITE_INDEX >= 100
DO 0 -> WRITE_INDEX
END IF
WAIT_TIME_SAMPLES - 30 -> WAIT_TIME_SAMPLES
QUEUE.EVENTS[WRITE_INDEX] -> NEW_AUDIO_EVENT
1 -> NEW_AUDIO_EVENT.KIND
SOUND::_NOTE_TO_PITCH[TRACK_EVENT_NOTE] -> TEMP
MATH::BIT_OR(TEMP, MATH::SHIFT_LEFT(TRACK_EVENT.MOD_BYTE, 20)) -> TEMP
TEMP -> NEW_AUDIO_EVENT.OPERAND
TO_BYTE(WRITE_INDEX + 1) -> WRITE_INDEX
IF WRITE_INDEX >= 100
DO 0 -> WRITE_INDEX
END IF
WAIT_TIME_SAMPLES - 30 -> WAIT_TIME_SAMPLES
QUEUE.EVENTS[WRITE_INDEX] -> NEW_AUDIO_EVENT
8 -> NEW_AUDIO_EVENT.KIND
WAIT_TIME_SAMPLES -> NEW_AUDIO_EVENT.OPERAND
TO_BYTE(WRITE_INDEX + 1) -> WRITE_INDEX
IF WRITE_INDEX >= 100
DO 0 -> WRITE_INDEX
TO_ADDRESS(QUEUE.EVENTS[WRITE_INDEX]) -> WRITE_ADDRESS
END LOOP
WRITE_INDEX -> CHANNEL.WRITE_INDEX
WRITE_ADDRESS -> QUEUE.WRITE_ADDRESS
END IF
IF WRITE_ADDRESS = READ_ADDRESS THEN
FALSE -> CHANNEL.IS_PLAYING
ELSE
TRUE -> CHANNEL.IS_PLAYING
END IF
END FUNC
FUNC IS_PLAYING(CHANNEL_INDEX: INT): BOOL
VAR CHANNEL: _SOUND_CHANNEL, RESULT: BOOL
IF SOUND::_SOUND_CHANNELS = NULL THEN
FALSE -> RESULT
ELSE
SOUND::_SOUND_CHANNELS[CHANNEL_INDEX] -> CHANNEL
CHANNEL.IS_PLAYING -> RESULT
END IF
RETURN RESULT
END FUNC
FUNC THINK()
IF SOUND::_SOUND_CHANNELS = NULL
DO RETURN
VAR I: INT
0 -> I
LOOP
SOUND::_PROCESS_CHANNEL(SOUND::_SOUND_CHANNELS[I],
| IO::AUDIO_QUEUES[I])
I + 1 -> I
IF I >= 6
DO DROP
END LOOP
END FUNC
END MODULE
DATA SOUND::_NOTE_TO_PITCH
[
19, 20, 21, 23, 24, 25, 27, 29, 30, 32,
34, 36, 38, 40, 43, 45, 48, 51, 54, 57,
60, 64, 68, 72, 76, 81, 85, 91, 96, 102,
108, 114, 121, 128, 136, 144, 152, 161, 171, 181,
192, 203, 215, 228, 242, 256, 271, 287, 304, 323,
342, 362, 384, 406, 431, 456, 483, 512, 542, 575,
609, 645, 683, 724, 767, 813, 861, 912, 967, 1024,
1085, 1149, 1218, 1290, 1367, 1448, 1534, 1625, 1722, 1825,
1933, 2048, 2170, 2299, 2435, 2580, 2734, 2896, 3069, 3251,
3444, 3649, 3866, 4096, 4340, 4598, 4871, 5161, 5468, 5793,
6137, 6502, 6889, 7298, 7732, 8192, 8679, 9195, 9742, 10321,
10935, 11585, 12274, 13004, 13777, 14596, 15464, 16384, 17358, 18390,
19484, 20643, 21870, 23170, 24548, 26008, 27554, 29193
]
END DATA