diff --git a/main.cpp b/main.cpp index 704e959..ce91aa6 100644 --- a/main.cpp +++ b/main.cpp @@ -2,6 +2,7 @@ #include #include "vm.h" #include "vdp.h" +#include "sound.h" #include #include "debug.h" @@ -44,6 +45,11 @@ int main(int argc, char** argv) { vm_register_out_port(20, input_data_in); vm_register_in_port(21, input_data_out); + sound_init(); + vm_register_out_port(30, sound_data_out); + vm_register_out_port(31, sound_cmd_out); + vm_register_in_port(32, sound_in); + debug_init(vm_get_memory()); const bool* breakpoints = debug_get_breakpoints(); diff --git a/parser.cpp b/parser.cpp index 1606259..217e0c7 100644 --- a/parser.cpp +++ b/parser.cpp @@ -747,6 +747,28 @@ static void parse_wait() { emmit(OP_SLEEP); } +static void parse_setmusic() { + parse_expression(); + parse_concatenation(); + emmit(OP_JSR); + emmit_w(get_label_address("_sys_spu_setmusic")); +} + +static void parse_playmusic() { + emmit(OP_JSR); + emmit_w(get_label_address("_sys_spu_playmusic")); +} + +static void parse_stopmusic() { + emmit(OP_JSR); + emmit_w(get_label_address("_sys_spu_stopmusic")); +} + +static void parse_ismusicplaying() { + emmit(OP_JSR); + emmit_w(get_label_address("_sys_spu_ismusicplaying")); +} + static void parse_statements() { while (!parser_finished) { @@ -817,6 +839,14 @@ static void parse_statements() { tkn_next(); parse_clrscr(); break; case TOKEN_WAIT: tkn_next(); parse_wait(); break; + case TOKEN_SETMUSIC: + tkn_next(); parse_setmusic(); break; + case TOKEN_PLAYMUSIC: + tkn_next(); parse_playmusic(); break; + case TOKEN_STOPMUSIC: + tkn_next(); parse_stopmusic(); break; + case TOKEN_ISMUSICPLAYING: + tkn_next(); parse_ismusicplaying(); break; case TOKEN_IDENTIFIER: for (int i = 0; i < num_external_functions; i++) { diff --git a/rom.bin b/rom.bin index 61f49fe..c1147cc 100644 Binary files a/rom.bin and b/rom.bin differ diff --git a/rom.lbl b/rom.lbl index 5dbc3f5..ecfd8b8 100644 Binary files a/rom.lbl and b/rom.lbl differ diff --git a/sound.cpp b/sound.cpp new file mode 100644 index 0000000..93d9909 --- /dev/null +++ b/sound.cpp @@ -0,0 +1,214 @@ +#include "sound.h" +#include +#include +#include + +#define BUFFER_SIZE 512 + +static const float lengths[10] = { 0.0f, 0.00390625f, 0.0078125f, 0.015625f, 0.03125f, 0.0625f, 0.125f, 0.25f, 0.5f, 1.0f }; +// 9 = redona, 8 = blanca, 7 = negra, 6 = corchea, 5 = semicorchea, 4 = fusa, 3 = semifusa, 2 = garrapatea, 1 = semigarrapatea +static const Uint16 tempos[10] = { 13230, 8820, 6615, 5292, 4410, 3780, 3308, 2940, 2646, 2406 }; +static const float periods[108] = { 1348.49207, 1272.80688, 1201.37, 1133.94214, 1070.29871, 1010.22772, 953.527893, 900.010376, 849.496887, 801.818176, 756.815613, 714.338745, 674.246033, 636.403564, 600.684875, 566.971069, 535.149475, 505.11377, 476.763947, 450.005249, 424.748352, 400.909088, 378.407806, 357.169373, 337.123016, 318.201782, 300.342438, 283.485535, 267.574738, 252.556885, 238.381973, 225.002625, 212.374176, 200.454544, 189.203888, 178.584702, 168.561508, 159.100876, 150.171234, 141.742767, 133.787354, 126.278458, 119.190987, 112.501305, 106.187096, 100.227272, 94.6019516, 89.2923508, 84.2807541, 79.5504379, 75.0856171, 70.8713837, 66.8936768, 63.139225, 59.5954933, 56.2506561, 53.0935478, 50.113636, 47.3009758, 44.6461754, 42.140377, 39.775219, 37.5428085, 35.4356918, 33.4468384, 31.5696125, 29.7977467, 28.1253281, 26.5467739, 25.056818, 23.650486, 22.3230877, 21.0701885, 19.8876095, 18.7714043, 17.7178459, 16.7234192, 15.7848072, 14.8988733, 14.0626631, 13.273387, 12.528409, 11.8252439, 11.1615429, 10.5350943, 9.94380569, 9.38570118, 8.85892296, 8.36171055, 7.89240265, 7.44943666, 7.03133202, 6.636693, 6.2642045, 5.91262197, 5.58077145, 5.26754713, 4.97190285, 4.69285059, 4.42946148, 4.18085527, 3.94620132, 3.72471833, 3.51566601, 3.3183465, 3.13210225, 2.95631051, 2.7903862 }; + +static class c_channel { +private: + Uint8 volume{ 64 }; + Uint8 octave{ 4 }; + Uint32 tempo{ 44100 }; + float default_length{ 0.25f }; + char song[256]; + Uint8 pos{ 0 }; + + Uint32 length{ 0 }; + Uint16 period{ 0 }; + bool active{ false }; + + void program_pipeline(const char note, const char len, const float multip = 1.0f) { + length = (len == -1 ? default_length : lengths[len]) * multip * tempo; + period = note == 100 ? 0 : periods[note + octave * 12]; + } + + bool parse_song() { + while (true) { + float multip = 1.0f; + char note = 0; + char param = -1; + switch (song[pos++]) { + case 'b': note += 2; + case 'a': note += 2; + case 'g': note += 2; + case 'f': note += 1; + case 'e': note += 2; + case 'd': note += 2; + case 'c': + param = song[pos]; + if (param == '#' || param == '+') { note++; param = song[++pos]; } else if (param == '-') { note--; param = song[++pos]; } + if (param >= 48 && param <= 57) { param -= 48; pos++; } else { param = -1; } + if (song[pos] == '.') { multip = 1.5f; pos++; } + program_pipeline(note, param, multip); + return true; + case 'r': + param = song[pos]; + if (param >= 48 && param <= 57) { param -= 48; pos++; } else { param = -1; } + if (song[pos] == '.') { multip = 1.5f; pos++; } + program_pipeline(100, param, multip); + return true; + case 'o': + param = song[pos]; + if (param >= 48 && param <= 57) { octave = (param - 48) % 8; pos++; } + break; + case '>': + octave = (octave + 1) % 8; + break; + case '<': + octave = (octave - 1) % 8; + break; + case 'l': + param = song[pos]; + if (param >= 48 && param <= 57) { default_length = lengths[param - 48]; pos++; } + else if (param == '+') { default_length *= 2; pos++; } + else if (param == '-') { default_length /= 2; pos++; } + break; + case 'v': + param = song[pos]; + if (param >= 48 && param <= 57) { volume = (param - 48) << 4; pos++; } + break; + case 't': + param = song[pos]; + if (param >= 48 && param <= 57) { tempo = tempos[param - 48] * 10; pos++; } + break; + case '\0': + active = false; + return false; + default: + break; + }; + } + } + +public: + + Sint8 get_datum() { + if (!active) return 0; + if (length == 0) if (!parse_song()) { active = false; return 0; } + length--; + return period == 0 ? -128 : ((length % period) < (period >> 1) ? volume : - volume); + } +/* Uint8 get_datum() { + if (!active) return 0; + if (length == 0) if (!parse_song()) return 0; + length--; + return period == 0 ? 0 : ((length % period) < (period >> 1) ? 128 + volume : 128 - volume); + }*/ + Sint8 get_noise() { + static Sint8 last_noise; + if (!active) return 0; + if (length == 0) if (!parse_song()) { active = false; return 0; } + length--; + if (period == 0) return -128; + if ((length % period) < (period >> 1)) { last_noise = rand() % (volume * 2) - volume; } + return last_noise; + } + void set_song(const char* melody) { + int len = SDL_strlen(melody); + SDL_memcpy(song, melody, len); + song[len] = 0; + pos = 0; volume = 64; octave = 4; tempo = 44100; default_length = 0.25f; + active = true; + } + bool is_active() { + return active; + } + void stop() { + length = 0; + period = 0; + active = false; + } +}; + +static c_channel channels[4]; + +static int sign(int number) { return number > 0 ? 1 : -1; } +static bool samesign(int a, int b) { return (a > 0 && b > 0) || (a < 0 && b < 0); } + +static void audio_callback(void* userdata, Uint8* stream, int len) { + Uint8 datum = 0; + for (int i = 0; i < len; i++) { + //for (auto& channel : channels) datum += channel.get_datum(); + Sint8 a = channels[0].get_datum(); + Sint8 b = channels[1].get_datum(); + Sint8 c = channels[2].get_datum(); + Sint8 d = channels[3].get_noise(); + //Sint16 dat = samesign(a,b) ? (a + b) - ((a * b) / sign(a)*127) : a + b; + Sint16 dat = a + b + c + d; + //Uint16 dat = (a + b) - ((a * b) / 256); + if (dat > 127) dat = 127; if (dat < -128) dat = -128; + //datum = dat; + datum = (dat + 128); + *stream = datum; + stream++; + } + //SDL_MixAudio(tstream, channel.pos, actual_len, SDL_MIX_MAXVOLUME); + if (!(channels[0].is_active() || channels[1].is_active() || channels[2].is_active() || channels[3].is_active())) + SDL_PauseAudio(1); +} + +void sound_init() { + SDL_Init(SDL_INIT_AUDIO); + + SDL_AudioSpec spec; + spec.freq = 22050; + spec.format = AUDIO_U8; + spec.channels = 1; + spec.samples = BUFFER_SIZE; + spec.callback = audio_callback; + spec.userdata = NULL; + + SDL_OpenAudio(&spec, NULL); + SDL_PauseAudio(1); +} + +void sound_load(const int channel, const char* string) { + channels[channel].set_song(string); +} + +void sound_play() { + //for (auto& channel : channels) channel.set_active(); + SDL_PauseAudio(0); +} + +const bool sound_isplaying() { + return channels[0].is_active() || channels[1].is_active() || channels[2].is_active() || channels[3].is_active(); // song_ended == 0; +} + +static unsigned char in_buffer[256]; +static unsigned char in_buffer_pos = 0; + +#define CMD_SETMUSIC 0 +#define CMD_PLAYMUSIC 1 +#define CMD_STOPMUSIC 2 +#define CMD_ISMUSICPLAYING 3 + +void sound_data_out(const unsigned char& value) { + in_buffer[in_buffer_pos++] = value; +} + +void sound_cmd_out(const unsigned char& value) { + unsigned char channel; + char revbuf[256]; char* prb = revbuf; + switch (value) { + case CMD_SETMUSIC: + channel = in_buffer[--in_buffer_pos]; + while (in_buffer_pos > 0) { *prb++ = in_buffer[--in_buffer_pos]; } *prb = 0; + channels[channel].set_song(revbuf); + break; + case CMD_PLAYMUSIC: sound_play(); break; + case CMD_STOPMUSIC: for (auto& channel: channels) channel.stop(); SDL_PauseAudio(1); break; + case CMD_ISMUSICPLAYING: in_buffer[0] = channels[0].is_active() || channels[1].is_active() || channels[2].is_active() || channels[3].is_active() ? 1 : 0; break; + } + in_buffer_pos = 0; +} + +unsigned char sound_in() { + return in_buffer[0]; +} diff --git a/sound.h b/sound.h new file mode 100644 index 0000000..1445be8 --- /dev/null +++ b/sound.h @@ -0,0 +1,10 @@ +#pragma once + +void sound_init(); +void sound_load(const int channel, const char* string); +void sound_play(); +const bool sound_isplaying(); + +void sound_data_out(const unsigned char& value); +void sound_cmd_out(const unsigned char& value); +unsigned char sound_in(); diff --git a/test.bas b/test.bas index 3e2edd1..36bd19b 100644 --- a/test.bas +++ b/test.bas @@ -5,6 +5,8 @@ LOCATE 4 3 PRINT CHR &h85, CHR &h85, CHR &h85, CHR &h85, CHR &h85, CHR &h85, CHR &h85, CHR &h84 LOCATE 4 4 PRINT CHR &h87, CHR &h81, CHR &h8D, CHR &h85, CHR &h85, CHR &h85, CHR &h8D, CHR &h85 W = 0 + SETMUSIC 0 "cdef" + PLAYMUSIC MENU_BLINK: LOCATE 3 8 PRINT "PRESS CTRL" LOCATE 4 9 PRINT "TO PLAY" diff --git a/tokenizer.cpp b/tokenizer.cpp index bf25bc5..cef4393 100644 --- a/tokenizer.cpp +++ b/tokenizer.cpp @@ -64,6 +64,10 @@ t_token_op tkn_tokens[] = { { "border", TOKEN_BORDER }, { "cls", TOKEN_CLS }, { "wait", TOKEN_WAIT }, + { "setmusic", TOKEN_SETMUSIC }, + { "playmusic", TOKEN_PLAYMUSIC }, + { "stopmusic", TOKEN_STOPMUSIC }, + { "ismusicplaying", TOKEN_ISMUSICPLAYING }, }; static char hex_digit(const char digit) { return digit - (digit <= 57 ? 48 : 55); } diff --git a/tokenizer.h b/tokenizer.h index 6087a31..f5bbef3 100644 --- a/tokenizer.h +++ b/tokenizer.h @@ -69,6 +69,11 @@ enum t_tokentype { TOKEN_BORDER, TOKEN_CLS, TOKEN_WAIT, + + TOKEN_SETMUSIC, + TOKEN_PLAYMUSIC, + TOKEN_STOPMUSIC, + TOKEN_ISMUSICPLAYING, }; void tkn_init(const char* buffer); diff --git a/vbvm.vcxproj b/vbvm.vcxproj index 5cf9c19..9437084 100644 --- a/vbvm.vcxproj +++ b/vbvm.vcxproj @@ -83,6 +83,7 @@ + @@ -94,6 +95,7 @@ + diff --git a/vbvm.vcxproj.filters b/vbvm.vcxproj.filters index 0595cb9..c17bd43 100644 --- a/vbvm.vcxproj.filters +++ b/vbvm.vcxproj.filters @@ -39,6 +39,9 @@ Source Files + + Source Files + @@ -71,5 +74,8 @@ Header Files + + Header Files + \ No newline at end of file