Start with z80 tracker
This commit is contained in:
1
tracker/.gitignore
vendored
Normal file
1
tracker/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build
|
||||
43
tracker/Makefile
Normal file
43
tracker/Makefile
Normal file
@@ -0,0 +1,43 @@
|
||||
TARGET := tracker
|
||||
|
||||
BUILDDIR := build
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
SRCDIRS := src
|
||||
SRCFILES := $(shell find $(SRCDIRS) -type f -name "*.c")
|
||||
OBJFILES := $(SRCFILES:%.c=$(BUILDDIR)/%.c.o)
|
||||
DEPFILES := $(SRCFILES:%.c=$(BUILDDIR)/%.c.d)
|
||||
|
||||
CC := gcc
|
||||
CFLAGS := -Wall -Wextra
|
||||
CPPFLAGS := -I src $(shell pkg-config --cflags sdl2)
|
||||
LDLIBS := $(shell pkg-config --libs sdl2)
|
||||
|
||||
.PHONY: $(TARGET) all clean
|
||||
all: $(TARGET)
|
||||
|
||||
# PHONY RULES
|
||||
# -----------
|
||||
|
||||
$(TARGET): $(BUILDDIR)/$(TARGET)
|
||||
|
||||
clean:
|
||||
echo CLEAN FILES FOR $(TARGET)
|
||||
rm -rf $(BUILDDIR)
|
||||
|
||||
# SPECIFIC BUILD RULES
|
||||
# --------------------
|
||||
|
||||
$(BUILDDIR)/$(TARGET): $(OBJFILES)
|
||||
echo 'LD ' $@
|
||||
mkdir -p $(@D)
|
||||
$(CC) $(LDFLAGS) -o $@ $(OBJFILES) $(LDLIBS)
|
||||
|
||||
$(OBJFILES): $(BUILDDIR)/%.c.o: %.c
|
||||
echo 'CC ' $@
|
||||
mkdir -p $(@D)
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -o $@ -c $<
|
||||
|
||||
# Header dependencies generated by -MMD. Keep this after the real targets so
|
||||
# included files can never accidentally become Make's default goal.
|
||||
-include $(DEPFILES)
|
||||
303
tracker/src/emu76489.c
Normal file
303
tracker/src/emu76489.c
Normal file
@@ -0,0 +1,303 @@
|
||||
/****************************************************************************
|
||||
|
||||
emu76489.c -- SN76489 emulator by Mitsutaka Okazaki 2001-2016
|
||||
|
||||
2001 08-13 : Version 1.00
|
||||
2001 10-03 : Version 1.01 -- Added SNG_set_quality().
|
||||
2004 05-23 : Version 1.10 -- Implemented GG stereo mode by RuRuRu
|
||||
2004 06-07 : Version 1.20 -- Improved the noise emulation.
|
||||
2015 12-13 : Version 1.21 -- Changed own integer types to C99 stdint.h types.
|
||||
2016 09-06 : Version 1.22 -- Support per-channel output.
|
||||
|
||||
References:
|
||||
SN76489 data sheet
|
||||
sn76489.c -- from MAME
|
||||
sn76489.txt -- from http://www.smspower.org/
|
||||
|
||||
*****************************************************************************/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "emu76489.h"
|
||||
|
||||
static uint32_t voltbl[16] = {
|
||||
0xff, 0xcb, 0xa1, 0x80, 0x65, 0x50, 0x40, 0x33, 0x28, 0x20, 0x19, 0x14, 0x10, 0x0c, 0x0a, 0x00
|
||||
};
|
||||
|
||||
#define GETA_BITS 24
|
||||
|
||||
static void
|
||||
internal_refresh (SNG * sng)
|
||||
{
|
||||
if (sng->quality)
|
||||
{
|
||||
sng->base_incr = 1 << GETA_BITS;
|
||||
sng->realstep = (uint32_t) ((1 << 31) / sng->rate);
|
||||
sng->sngstep = (uint32_t) ((1 << 31) / (sng->clk / 16));
|
||||
sng->sngtime = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
sng->base_incr = (uint32_t) ((double) sng->clk * (1 << GETA_BITS) / (16 * sng->rate));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SNG_set_rate (SNG * sng, uint32_t r)
|
||||
{
|
||||
sng->rate = r ? r : 44100;
|
||||
internal_refresh (sng);
|
||||
}
|
||||
|
||||
void
|
||||
SNG_set_quality (SNG * sng, uint32_t q)
|
||||
{
|
||||
sng->quality = q;
|
||||
internal_refresh (sng);
|
||||
}
|
||||
|
||||
SNG *
|
||||
SNG_new (uint32_t c, uint32_t r)
|
||||
{
|
||||
SNG *sng;
|
||||
|
||||
sng = (SNG *) malloc (sizeof (SNG));
|
||||
if (sng == NULL)
|
||||
return NULL;
|
||||
|
||||
sng->clk = c;
|
||||
sng->rate = r ? r : 44100;
|
||||
SNG_set_quality (sng, 0);
|
||||
|
||||
return sng;
|
||||
}
|
||||
|
||||
void
|
||||
SNG_reset (SNG * sng)
|
||||
{
|
||||
int i;
|
||||
|
||||
sng->base_count = 0;
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
sng->count[i] = 0;
|
||||
sng->freq[i] = 0;
|
||||
sng->edge[i] = 0;
|
||||
sng->volume[i] = 0x0f;
|
||||
sng->mute[i] = 0;
|
||||
}
|
||||
|
||||
sng->adr = 0;
|
||||
|
||||
sng->noise_seed = 0x8000;
|
||||
sng->noise_count = 0;
|
||||
sng->noise_freq = 0;
|
||||
sng->noise_volume = 0x0f;
|
||||
sng->noise_mode = 0;
|
||||
sng->noise_fref = 0;
|
||||
|
||||
sng->out = 0;
|
||||
sng->stereo = 0xFF;
|
||||
|
||||
sng->ch_out[0] = sng->ch_out[1] = sng->ch_out[2] = sng->ch_out[3] = 0;
|
||||
}
|
||||
|
||||
void
|
||||
SNG_delete (SNG * sng)
|
||||
{
|
||||
free (sng);
|
||||
}
|
||||
|
||||
void
|
||||
SNG_writeIO (SNG * sng, uint32_t val)
|
||||
{
|
||||
if (val & 0x80)
|
||||
{
|
||||
sng->adr = (val & 0x70) >> 4;
|
||||
switch (sng->adr)
|
||||
{
|
||||
case 0:
|
||||
case 2:
|
||||
case 4:
|
||||
sng->freq[sng->adr >> 1] = (sng->freq[sng->adr >> 1] & 0x3F0) | (val & 0x0F);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
case 3:
|
||||
case 5:
|
||||
sng->volume[(sng->adr - 1) >> 1] = val & 0xF;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
sng->noise_mode = (val & 4) >> 2;
|
||||
|
||||
if ((val & 0x03) == 0x03) {
|
||||
sng->noise_freq = sng->freq[2];
|
||||
sng->noise_fref = 1;
|
||||
} else {
|
||||
sng->noise_freq = 32 << (val & 0x03);
|
||||
sng->noise_fref = 0;
|
||||
}
|
||||
|
||||
if (sng->noise_freq == 0)
|
||||
sng->noise_freq = 1;
|
||||
|
||||
sng->noise_seed = 0x8000;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
sng->noise_volume = val & 0x0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sng->freq[sng->adr >> 1] = ((val & 0x3F) << 4) | (sng->freq[sng->adr >> 1] & 0x0F);
|
||||
}
|
||||
}
|
||||
|
||||
static inline int parity(int val) {
|
||||
val^=val>>8;
|
||||
val^=val>>4;
|
||||
val^=val>>2;
|
||||
val^=val>>1;
|
||||
return val&1;
|
||||
};
|
||||
|
||||
static inline void
|
||||
update_output (SNG * sng)
|
||||
{
|
||||
|
||||
int i;
|
||||
uint32_t incr;
|
||||
|
||||
sng->base_count += sng->base_incr;
|
||||
incr = (sng->base_count >> GETA_BITS);
|
||||
sng->base_count &= (1 << GETA_BITS) - 1;
|
||||
|
||||
/* Noise */
|
||||
sng->noise_count += incr;
|
||||
if (sng->noise_count & 0x100)
|
||||
{
|
||||
if (sng->noise_mode) /* White */
|
||||
sng->noise_seed = (sng->noise_seed>>1) | (parity(sng->noise_seed&0x0009)<<15);
|
||||
else /* Periodic */
|
||||
sng->noise_seed = (sng->noise_seed>>1) | ((sng->noise_seed&1)<<15);
|
||||
|
||||
if(sng->noise_fref)
|
||||
sng->noise_count -= sng->freq[2];
|
||||
else
|
||||
sng->noise_count -= sng->noise_freq;
|
||||
}
|
||||
|
||||
if (sng->noise_seed & 1)
|
||||
{
|
||||
sng->ch_out[3] += voltbl[sng->noise_volume] << 4;
|
||||
}
|
||||
sng->ch_out[3] >>= 1;
|
||||
|
||||
/* Tone */
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
sng->count[i] += incr;
|
||||
if (sng->count[i] & 0x400)
|
||||
{
|
||||
if (sng->freq[i] > 1)
|
||||
{
|
||||
sng->edge[i] = !sng->edge[i];
|
||||
sng->count[i] -= sng->freq[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
sng->edge[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (sng->edge[i] && !sng->mute[i])
|
||||
{
|
||||
sng->ch_out[i] += voltbl[sng->volume[i]] << 4;
|
||||
}
|
||||
|
||||
sng->ch_out[i] >>= 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static inline int16_t
|
||||
mix_output (SNG * sng)
|
||||
{
|
||||
sng->out = sng->ch_out[0] + sng->ch_out[1] + sng->ch_out[2] + sng->ch_out[3];
|
||||
return (int16_t) sng->out;
|
||||
}
|
||||
|
||||
int16_t
|
||||
SNG_calc (SNG * sng)
|
||||
{
|
||||
if (!sng->quality) {
|
||||
update_output(sng);
|
||||
return mix_output(sng);
|
||||
}
|
||||
|
||||
/* Simple rate converter */
|
||||
while (sng->realstep > sng->sngtime)
|
||||
{
|
||||
sng->sngtime += sng->sngstep;
|
||||
update_output(sng);
|
||||
}
|
||||
|
||||
sng->sngtime = sng->sngtime - sng->realstep;
|
||||
|
||||
return mix_output(sng);
|
||||
}
|
||||
|
||||
static inline void
|
||||
mix_output_stereo (SNG * sng, int32_t out[2])
|
||||
{
|
||||
int i;
|
||||
|
||||
out[0] = out[1] = 0;
|
||||
if((sng->stereo>>4)&0x08) {
|
||||
out[0] += sng->ch_out[3];
|
||||
}
|
||||
if(sng->stereo & 0x08) {
|
||||
out[1] += sng->ch_out[3];
|
||||
}
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
if ((sng->stereo >> (i+4)) & 0x01) {
|
||||
out[0] += sng->ch_out[i];
|
||||
}
|
||||
if ((sng->stereo >> i) & 0x01) {
|
||||
out[1] += sng->ch_out[i];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
SNG_calc_stereo (SNG *sng, int32_t out[2])
|
||||
{
|
||||
if (!sng->quality) {
|
||||
update_output(sng);
|
||||
mix_output_stereo(sng,out);
|
||||
return;
|
||||
}
|
||||
|
||||
while (sng->realstep > sng->sngtime)
|
||||
{
|
||||
sng->sngtime += sng->sngstep;
|
||||
update_output(sng);
|
||||
}
|
||||
|
||||
sng->sngtime = sng->sngtime - sng->realstep;
|
||||
mix_output_stereo(sng,out);
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
SNG_writeGGIO(SNG *sng, uint32_t val)
|
||||
{
|
||||
sng->stereo = val;
|
||||
}
|
||||
60
tracker/src/emu76489.h
Normal file
60
tracker/src/emu76489.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef _EMU76489_H_
|
||||
#define _EMU76489_H_
|
||||
#include "stdint.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct __SNG {
|
||||
|
||||
int32_t out ;
|
||||
|
||||
uint32_t clk, rate, base_incr, quality ;
|
||||
|
||||
uint32_t count[3] ;
|
||||
uint32_t volume[3] ;
|
||||
uint32_t freq[3] ;
|
||||
uint32_t edge[3] ;
|
||||
uint32_t mute[3] ;
|
||||
|
||||
uint32_t noise_seed ;
|
||||
uint32_t noise_count ;
|
||||
uint32_t noise_freq ;
|
||||
uint32_t noise_volume ;
|
||||
uint32_t noise_mode ;
|
||||
uint32_t noise_fref ;
|
||||
|
||||
uint32_t base_count ;
|
||||
|
||||
/* rate converter */
|
||||
uint32_t realstep ;
|
||||
uint32_t sngtime ;
|
||||
uint32_t sngstep ;
|
||||
|
||||
uint32_t adr ;
|
||||
|
||||
uint32_t stereo;
|
||||
|
||||
int16_t ch_out[4];
|
||||
|
||||
} SNG ;
|
||||
|
||||
SNG *SNG_new(uint32_t clk, uint32_t rate) ;
|
||||
void SNG_delete(SNG *) ;
|
||||
void SNG_reset(SNG *) ;
|
||||
void SNG_set_rate(SNG *,uint32_t rate) ;
|
||||
void SNG_set_quality(SNG *,uint32_t q) ;
|
||||
void SNG_writeIO(SNG *SNG, uint32_t val) ;
|
||||
uint8_t SNG_readReg(SNG *, uint32_t reg) ;
|
||||
uint8_t SNG_readIO(SNG *SNG) ;
|
||||
int16_t SNG_calc(SNG *) ;
|
||||
void SNG_setVolumeMode(SNG *SNG, int type) ;
|
||||
void SNG_calc_stereo(SNG *, int32_t out[2]) ;
|
||||
void SNG_writeGGIO(SNG *SNG, uint32_t val) ;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
211
tracker/src/main.c
Normal file
211
tracker/src/main.c
Normal file
@@ -0,0 +1,211 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "emu76489.h"
|
||||
#include "player.h"
|
||||
|
||||
#define SAMPLE_RATE 48000
|
||||
#define MUSIC_HZ 50
|
||||
|
||||
typedef struct {
|
||||
SNG *psg;
|
||||
uint32_t samples_until_tick;
|
||||
} AudioState;
|
||||
|
||||
SNG * g_psg = NULL;
|
||||
|
||||
typedef struct {
|
||||
SDL_atomic_t running;
|
||||
int old_stdin_flags;
|
||||
int terminal_is_raw;
|
||||
struct termios old_terminal;
|
||||
} InputState;
|
||||
|
||||
void psg_write(uint8_t value){
|
||||
SNG_writeIO(g_psg, value);
|
||||
}
|
||||
|
||||
static int input_thread(void *userdata){
|
||||
InputState *state = userdata;
|
||||
|
||||
while(SDL_AtomicGet(&state->running)){
|
||||
uint8_t input;
|
||||
ssize_t bytes_read = read(STDIN_FILENO, &input, sizeof(input));
|
||||
|
||||
if(bytes_read == 1){
|
||||
input_interrupt(input);
|
||||
}else if(bytes_read == 0 || errno == EAGAIN || errno == EWOULDBLOCK){
|
||||
SDL_Delay(10);
|
||||
}else if(errno != EINTR){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int start_terminal_input(InputState *state){
|
||||
SDL_zero(*state);
|
||||
SDL_AtomicSet(&state->running, 1);
|
||||
|
||||
state->old_stdin_flags = fcntl(STDIN_FILENO, F_GETFL);
|
||||
if(state->old_stdin_flags == -1 ||
|
||||
fcntl(STDIN_FILENO, F_SETFL, state->old_stdin_flags | O_NONBLOCK) == -1){
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(isatty(STDIN_FILENO)){
|
||||
if(tcgetattr(STDIN_FILENO, &state->old_terminal) == -1){
|
||||
fcntl(STDIN_FILENO, F_SETFL, state->old_stdin_flags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct termios raw_terminal = state->old_terminal;
|
||||
raw_terminal.c_lflag &= (tcflag_t) ~(ICANON | ECHO);
|
||||
raw_terminal.c_cc[VMIN] = 0;
|
||||
raw_terminal.c_cc[VTIME] = 0;
|
||||
|
||||
if(tcsetattr(STDIN_FILENO, TCSANOW, &raw_terminal) == -1){
|
||||
fcntl(STDIN_FILENO, F_SETFL, state->old_stdin_flags);
|
||||
return -1;
|
||||
}
|
||||
state->terminal_is_raw = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stop_terminal_input(InputState *state, SDL_Thread *thread){
|
||||
SDL_AtomicSet(&state->running, 0);
|
||||
SDL_WaitThread(thread, NULL);
|
||||
|
||||
if(state->terminal_is_raw){
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &state->old_terminal);
|
||||
}
|
||||
fcntl(STDIN_FILENO, F_SETFL, state->old_stdin_flags);
|
||||
}
|
||||
|
||||
void serial_write(char c){
|
||||
write(STDOUT_FILENO, &c, 1);
|
||||
}
|
||||
|
||||
static void audio_callback(void *userdata, Uint8 *stream, int len){
|
||||
AudioState *state = userdata;
|
||||
int16_t *output = (int16_t *)stream;
|
||||
int sample_count = len / sizeof(int16_t);
|
||||
|
||||
for (int i = 0; i < sample_count; i++){
|
||||
/*
|
||||
* SAMPLE_RATE = 48000
|
||||
* MUSIC_HZ = 50
|
||||
*
|
||||
* 48000 / 50 = 960
|
||||
*
|
||||
* Therefore music_tick() happens once every
|
||||
* 960 generated audio samples.
|
||||
*/
|
||||
if(state->samples_until_tick == 0){
|
||||
music_tick_interrupt();
|
||||
|
||||
state->samples_until_tick = SAMPLE_RATE / MUSIC_HZ;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ask emu76489 to generate one PCM sample.
|
||||
*/
|
||||
output[i] = SNG_calc(state->psg);
|
||||
|
||||
state->samples_until_tick--;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv){
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
if(SDL_Init(SDL_INIT_AUDIO) != 0){
|
||||
fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
g_psg = SNG_new(PSG_CLOCK, SAMPLE_RATE);
|
||||
if(!g_psg){
|
||||
fprintf(stderr, "Could not create SN76489 emulator\n");
|
||||
SDL_Quit();
|
||||
return 1;
|
||||
}
|
||||
SNG_reset(g_psg);
|
||||
|
||||
AudioState state = {
|
||||
.psg = g_psg,
|
||||
.samples_until_tick = 0
|
||||
};
|
||||
|
||||
SDL_AudioSpec wanted;
|
||||
SDL_zero(wanted);
|
||||
|
||||
wanted.freq = SAMPLE_RATE;
|
||||
wanted.format = AUDIO_S16SYS;
|
||||
wanted.channels = 1; /* mono */
|
||||
wanted.samples = 1024;
|
||||
|
||||
wanted.callback = audio_callback;
|
||||
wanted.userdata = &state;
|
||||
|
||||
SDL_AudioDeviceID device = SDL_OpenAudioDevice(
|
||||
NULL,
|
||||
0,
|
||||
&wanted,
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
|
||||
if(device == 0){
|
||||
fprintf(stderr, "SDL_OpenAudioDevice failed: %s\n", SDL_GetError());
|
||||
SNG_delete(g_psg);
|
||||
SDL_Quit();
|
||||
return 1;
|
||||
}
|
||||
SDL_PauseAudioDevice(device, 0);
|
||||
|
||||
InputState input_state;
|
||||
if(start_terminal_input(&input_state) != 0){
|
||||
fprintf(stderr, "Could not configure terminal input\n");
|
||||
SDL_CloseAudioDevice(device);
|
||||
SNG_delete(g_psg);
|
||||
SDL_Quit();
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_Thread *keyboard_thread = SDL_CreateThread(
|
||||
input_thread,
|
||||
"terminal input",
|
||||
&input_state
|
||||
);
|
||||
if(!keyboard_thread){
|
||||
fprintf(stderr, "Could not create keyboard input thread: %s\n", SDL_GetError());
|
||||
if(input_state.terminal_is_raw){
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &input_state.old_terminal);
|
||||
}
|
||||
fcntl(STDIN_FILENO, F_SETFL, input_state.old_stdin_flags);
|
||||
SDL_CloseAudioDevice(device);
|
||||
SNG_delete(g_psg);
|
||||
SDL_Quit();
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_Delay(1000);
|
||||
player_main();
|
||||
|
||||
stop_terminal_input(&input_state, keyboard_thread);
|
||||
SDL_PauseAudioDevice(device, 1);
|
||||
SDL_CloseAudioDevice(device);
|
||||
SNG_delete(g_psg);
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
}
|
||||
5
tracker/src/main.h
Normal file
5
tracker/src/main.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
void psg_write(uint8_t value);
|
||||
void serial_write(char c);
|
||||
110
tracker/src/notes.c
Normal file
110
tracker/src/notes.c
Normal file
@@ -0,0 +1,110 @@
|
||||
#include <stdint.h>
|
||||
#include "player.h"
|
||||
|
||||
#define NOTE_FREQUENCY(f) ((uint16_t)(PSG_CLOCK/(32*f)))
|
||||
|
||||
uint16_t note_periods[] = {
|
||||
NOTE_FREQUENCY(16.35), // C-0
|
||||
NOTE_FREQUENCY(17.32), // C#0
|
||||
NOTE_FREQUENCY(18.35), // D-0
|
||||
NOTE_FREQUENCY(19.45), // D#0
|
||||
NOTE_FREQUENCY(20.60), // E-0
|
||||
NOTE_FREQUENCY(21.83), // F-0
|
||||
NOTE_FREQUENCY(23.12), // F#0
|
||||
NOTE_FREQUENCY(24.50), // G-0
|
||||
NOTE_FREQUENCY(25.96), // G#0
|
||||
NOTE_FREQUENCY(27.50), // A-0
|
||||
NOTE_FREQUENCY(29.14), // A#0
|
||||
NOTE_FREQUENCY(30.87), // B-0
|
||||
|
||||
NOTE_FREQUENCY(32.70), // C-1
|
||||
NOTE_FREQUENCY(34.65), // C#1
|
||||
NOTE_FREQUENCY(36.71), // D-1
|
||||
NOTE_FREQUENCY(38.89), // D#1
|
||||
NOTE_FREQUENCY(41.20), // E-1
|
||||
NOTE_FREQUENCY(43.65), // F-1
|
||||
NOTE_FREQUENCY(46.25), // F#1
|
||||
NOTE_FREQUENCY(49.00), // G-1
|
||||
NOTE_FREQUENCY(51.91), // G#1
|
||||
NOTE_FREQUENCY(55.00), // A-1
|
||||
NOTE_FREQUENCY(58.27), // A#1
|
||||
NOTE_FREQUENCY(61.74), // B-1
|
||||
|
||||
NOTE_FREQUENCY(65.41), // C-2
|
||||
NOTE_FREQUENCY(69.30), // C#2
|
||||
NOTE_FREQUENCY(73.42), // D-2
|
||||
NOTE_FREQUENCY(77.78), // D#2
|
||||
NOTE_FREQUENCY(82.41), // E-2
|
||||
NOTE_FREQUENCY(87.31), // F-2
|
||||
NOTE_FREQUENCY(92.50), // F#2
|
||||
NOTE_FREQUENCY(98.00), // G-2
|
||||
NOTE_FREQUENCY(103.83), // G#2
|
||||
NOTE_FREQUENCY(110.00), // A-2
|
||||
NOTE_FREQUENCY(116.54), // A#2
|
||||
NOTE_FREQUENCY(123.47), // B-2
|
||||
|
||||
NOTE_FREQUENCY(130.81), // C-3
|
||||
NOTE_FREQUENCY(138.59), // C#3
|
||||
NOTE_FREQUENCY(146.83), // D-3
|
||||
NOTE_FREQUENCY(155.56), // D#3
|
||||
NOTE_FREQUENCY(164.81), // E-3
|
||||
NOTE_FREQUENCY(174.61), // F-3
|
||||
NOTE_FREQUENCY(185.00), // F#3
|
||||
NOTE_FREQUENCY(196.00), // G-3
|
||||
NOTE_FREQUENCY(207.65), // G#3
|
||||
NOTE_FREQUENCY(220.00), // A-3
|
||||
NOTE_FREQUENCY(233.08), // A#3
|
||||
NOTE_FREQUENCY(246.94), // B-3
|
||||
|
||||
NOTE_FREQUENCY(261.63), // C-4
|
||||
NOTE_FREQUENCY(277.18), // C#4
|
||||
NOTE_FREQUENCY(293.66), // D-4
|
||||
NOTE_FREQUENCY(311.13), // D#4
|
||||
NOTE_FREQUENCY(329.63), // E-4
|
||||
NOTE_FREQUENCY(349.23), // F-4
|
||||
NOTE_FREQUENCY(369.99), // F#4
|
||||
NOTE_FREQUENCY(392.00), // G-4
|
||||
NOTE_FREQUENCY(415.30), // G#4
|
||||
NOTE_FREQUENCY(440.00), // A-4
|
||||
NOTE_FREQUENCY(466.16), // A#4
|
||||
NOTE_FREQUENCY(493.88), // B-4
|
||||
|
||||
NOTE_FREQUENCY(523.25), // C-5
|
||||
NOTE_FREQUENCY(554.37), // C#5
|
||||
NOTE_FREQUENCY(587.33), // D-5
|
||||
NOTE_FREQUENCY(622.25), // D#5
|
||||
NOTE_FREQUENCY(659.26), // E-5
|
||||
NOTE_FREQUENCY(698.46), // F-5
|
||||
NOTE_FREQUENCY(739.99), // F#5
|
||||
NOTE_FREQUENCY(783.99), // G-5
|
||||
NOTE_FREQUENCY(830.61), // G#5
|
||||
NOTE_FREQUENCY(880.00), // A-5
|
||||
NOTE_FREQUENCY(932.33), // A#5
|
||||
NOTE_FREQUENCY(987.77), // B-5
|
||||
|
||||
NOTE_FREQUENCY(1046.50), // C-6
|
||||
NOTE_FREQUENCY(1108.73), // C#6
|
||||
NOTE_FREQUENCY(1174.66), // D-6
|
||||
NOTE_FREQUENCY(1244.51), // D#6
|
||||
NOTE_FREQUENCY(1318.51), // E-6
|
||||
NOTE_FREQUENCY(1396.91), // F-6
|
||||
NOTE_FREQUENCY(1479.98), // F#6
|
||||
NOTE_FREQUENCY(1567.98), // G-6
|
||||
NOTE_FREQUENCY(1661.22), // G#6
|
||||
NOTE_FREQUENCY(1760.00), // A-6
|
||||
NOTE_FREQUENCY(1864.66), // A#6
|
||||
NOTE_FREQUENCY(1975.53), // B-6
|
||||
|
||||
NOTE_FREQUENCY(2093.00), // C-7
|
||||
NOTE_FREQUENCY(2217.46), // C#7
|
||||
NOTE_FREQUENCY(2349.32), // D-7
|
||||
NOTE_FREQUENCY(2489.02), // D#7
|
||||
NOTE_FREQUENCY(2637.02), // E-7
|
||||
NOTE_FREQUENCY(2793.83), // F-7
|
||||
NOTE_FREQUENCY(2959.96), // F#7
|
||||
NOTE_FREQUENCY(3135.96), // G-7
|
||||
NOTE_FREQUENCY(3322.44), // G#7
|
||||
NOTE_FREQUENCY(3520.00), // A-7
|
||||
NOTE_FREQUENCY(3729.31), // A#7
|
||||
NOTE_FREQUENCY(3951.07), // B-7
|
||||
};
|
||||
6
tracker/src/notes.h
Normal file
6
tracker/src/notes.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define NR_NOTE_PERIODS 96
|
||||
|
||||
extern uint16_t note_periods[];
|
||||
158
tracker/src/player.c
Normal file
158
tracker/src/player.c
Normal file
@@ -0,0 +1,158 @@
|
||||
#include "player.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "main.h"
|
||||
#include "notes.h"
|
||||
#include "ui.h"
|
||||
|
||||
typedef struct{
|
||||
struct {
|
||||
uint16_t tone;
|
||||
uint8_t vol;
|
||||
} channel[4];
|
||||
} psg_state_t;
|
||||
|
||||
static uint8_t music_tick_interrupt_happened = 0;
|
||||
static uint8_t input_interrupt_happened = 0;
|
||||
static char input;
|
||||
static uint8_t playing = 0;
|
||||
static uint8_t tick = 0;
|
||||
static uint8_t row = 0;
|
||||
static pattern_t * pattern = NULL;
|
||||
static psg_state_t psg_state = {
|
||||
.channel = {
|
||||
{ .tone = 0, .vol = 15 },
|
||||
{ .tone = 0, .vol = 15 },
|
||||
{ .tone = 0, .vol = 15 },
|
||||
{ .tone = 0, .vol = 15 },
|
||||
}
|
||||
};
|
||||
|
||||
static void sn_set_tone(uint8_t channel, uint16_t period){
|
||||
period &= 0x03ff;
|
||||
uint8_t latch = 0x80 | ((channel & 3) << 5) | (period & 0x0f);
|
||||
uint8_t data = (period >> 4) & 0x3f;
|
||||
psg_write(latch);
|
||||
psg_write(data);
|
||||
}
|
||||
|
||||
static void sn_set_volume(uint8_t channel, uint8_t volume){
|
||||
uint8_t command = 0x90 | ((channel & 3) << 5) | (volume & 0x0f);
|
||||
psg_write(command);
|
||||
}
|
||||
|
||||
static void flush_psg(){
|
||||
for(uint8_t chan=0; chan<3; chan++){
|
||||
sn_set_tone(chan, psg_state.channel[chan].tone);
|
||||
sn_set_volume(chan, psg_state.channel[chan].vol);
|
||||
}
|
||||
sn_set_volume(3, psg_state.channel[3].vol);
|
||||
}
|
||||
|
||||
static void process_row(){
|
||||
row_t * current = &pattern->rows[row];
|
||||
for(uint8_t chan=0; chan<3; chan++){
|
||||
if(current->channel[chan].note < 12 && current->channel[chan].note >= NR_NOTE_PERIODS+12){
|
||||
// Only turn off note if it isnt 0
|
||||
if(current->channel[chan].note != 0)
|
||||
psg_state.channel[chan].vol = 15;
|
||||
}else{
|
||||
psg_state.channel[chan].tone = note_periods[current->channel[chan].note-12];
|
||||
psg_state.channel[chan].vol = 15-current->channel[chan].volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void next_pattern(){
|
||||
playing = 0;
|
||||
}
|
||||
|
||||
static void perform_music_tick(){
|
||||
if(!playing || !pattern)
|
||||
return;
|
||||
|
||||
flush_psg();
|
||||
|
||||
tick++;
|
||||
|
||||
if(tick>=pattern->speed){
|
||||
tick = 0;
|
||||
row ++;
|
||||
if(row>=PATTERN_ROWS){
|
||||
row = 0;
|
||||
next_pattern();
|
||||
}
|
||||
process_row();
|
||||
}
|
||||
}
|
||||
|
||||
// 50Hz music tick interrupt
|
||||
void music_tick_interrupt(){
|
||||
music_tick_interrupt_happened = 1;
|
||||
}
|
||||
|
||||
// Input interrupt
|
||||
void input_interrupt(char _input){
|
||||
input = _input;
|
||||
input_interrupt_happened = 1;
|
||||
}
|
||||
|
||||
void player_main(){
|
||||
flush_psg();
|
||||
ui_init();
|
||||
|
||||
pattern_t p = {
|
||||
.speed = 12,
|
||||
.rows = {
|
||||
{.channel = {{60, 15}, {0, 0}, {0, 0}, {0, 0}}},
|
||||
{.channel = {{67, 15}, {0, 0}, {0, 0}, {0, 0}}},
|
||||
{.channel = {{69, 15}, {0, 0}, {0, 0}, {0, 0}}},
|
||||
{.channel = {{67, 15}, {0, 0}, {0, 0}, {0, 0}}},
|
||||
{.channel = {{60, 15}, {0, 0}, {0, 0}, {0, 0}}},
|
||||
}
|
||||
};
|
||||
|
||||
pattern = &p;
|
||||
ui_set_pattern(&p);
|
||||
|
||||
for(;;){
|
||||
if(music_tick_interrupt_happened){
|
||||
music_tick_interrupt_happened = 0;
|
||||
perform_music_tick();
|
||||
}
|
||||
if(input_interrupt_happened){
|
||||
input_interrupt_happened = 0;
|
||||
ui_input(input);
|
||||
}
|
||||
uint8_t action = do_ui();
|
||||
if(action != ACTION_NONE){
|
||||
if(action == ACTION_PLAY_ONCE){
|
||||
tick = 0;
|
||||
row = 0;
|
||||
process_row();
|
||||
flush_psg();
|
||||
playing = 1;
|
||||
}
|
||||
if(action == ACTION_STOP){
|
||||
playing = 0;
|
||||
tick = 0;
|
||||
row = 0;
|
||||
psg_state = (psg_state_t){
|
||||
.channel = {
|
||||
{ .tone = 0, .vol = 15 },
|
||||
{ .tone = 0, .vol = 15 },
|
||||
{ .tone = 0, .vol = 15 },
|
||||
{ .tone = 0, .vol = 15 },
|
||||
}
|
||||
};
|
||||
flush_psg();
|
||||
}
|
||||
if(action == ACTION_QUIT){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
23
tracker/src/player.h
Normal file
23
tracker/src/player.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define PSG_CLOCK 1000000
|
||||
#define PATTERN_ROWS 16
|
||||
|
||||
typedef struct{
|
||||
uint8_t note;
|
||||
uint8_t volume;
|
||||
} cell_t;
|
||||
|
||||
typedef struct{
|
||||
cell_t channel[4];
|
||||
} row_t;
|
||||
|
||||
typedef struct{
|
||||
row_t rows[PATTERN_ROWS];
|
||||
uint8_t speed;
|
||||
} pattern_t;
|
||||
|
||||
void player_main();
|
||||
void music_tick_interrupt();
|
||||
void input_interrupt(char input);
|
||||
472
tracker/src/ui.c
Normal file
472
tracker/src/ui.c
Normal file
@@ -0,0 +1,472 @@
|
||||
#include "ui.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "main.h"
|
||||
#include "player.h"
|
||||
|
||||
#define ROWS 25
|
||||
#define COLS 80
|
||||
|
||||
#define TRACK_ROWS 16
|
||||
#define CHANNELS 4
|
||||
|
||||
static uint8_t dirty = 1;
|
||||
static uint8_t clear = 1;
|
||||
|
||||
static uint8_t cursor_row = 0;
|
||||
static uint8_t cursor_channel = 0;
|
||||
|
||||
static uint8_t input_state = 0;
|
||||
static uint8_t input_octave = 4;
|
||||
|
||||
static uint8_t do_action = ACTION_NONE;
|
||||
static pattern_t * pattern = NULL;
|
||||
|
||||
|
||||
static void serial_puts(char * s){
|
||||
while(*s!='\0'){
|
||||
serial_write(*s);
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
static void cursor_set(uint8_t x, uint8_t y){
|
||||
// Buffer for "\x1b[yyy;xxxH\0" -> max 12 bytes needed
|
||||
char buf[12];
|
||||
char *p = buf;
|
||||
|
||||
// 1. Write ANSI prefix
|
||||
*p++ = 0x1B; // ESC
|
||||
*p++ = '[';
|
||||
|
||||
// 2. Convert Y (Row) to ASCII digits
|
||||
if (y >= 100) {
|
||||
*p++ = '0' + (y / 100);
|
||||
y %= 100;
|
||||
*p++ = '0' + (y / 10);
|
||||
} else if (y >= 10) {
|
||||
*p++ = '0' + (y / 10);
|
||||
}
|
||||
*p++ = '0' + (y % 10);
|
||||
|
||||
// 3. Write separator
|
||||
*p++ = ';';
|
||||
|
||||
// 4. Convert X (Col) to ASCII digits
|
||||
if (x >= 100) {
|
||||
*p++ = '0' + (x / 100);
|
||||
x %= 100;
|
||||
*p++ = '0' + (x / 10);
|
||||
} else if (x >= 10) {
|
||||
*p++ = '0' + (x / 10);
|
||||
}
|
||||
*p++ = '0' + (x % 10);
|
||||
|
||||
// 5. Write ANSI suffix and null-terminator
|
||||
*p++ = 'H';
|
||||
*p = '\0';
|
||||
|
||||
// 6. Send via your serial routine
|
||||
serial_puts(buf);
|
||||
}
|
||||
|
||||
static void cls(){
|
||||
serial_puts("\033[2J");
|
||||
cursor_set(1, 1);
|
||||
}
|
||||
|
||||
static void reverse_on(void){
|
||||
serial_puts("\x1b[7m");
|
||||
}
|
||||
|
||||
static void reverse_off(void){
|
||||
serial_puts("\x1b[0m");
|
||||
}
|
||||
|
||||
static void cursor_hide(void){
|
||||
serial_puts("\x1b[?25l");
|
||||
}
|
||||
|
||||
static void cursor_show(void){
|
||||
serial_puts("\x1b[?25h");
|
||||
}
|
||||
|
||||
static void print_hex_digit(uint8_t n){
|
||||
n &= 0x0f;
|
||||
|
||||
if (n < 10)
|
||||
serial_write('0' + n);
|
||||
else
|
||||
serial_write('A' + (n - 10));
|
||||
}
|
||||
|
||||
static void print_hex_byte(uint8_t n){
|
||||
print_hex_digit(n >> 4);
|
||||
print_hex_digit(n);
|
||||
}
|
||||
|
||||
static void print_note(uint8_t note){
|
||||
static const char note_names[12][2] = {
|
||||
{'C', '-'},
|
||||
{'C', '#'},
|
||||
{'D', '-'},
|
||||
{'D', '#'},
|
||||
{'E', '-'},
|
||||
{'F', '-'},
|
||||
{'F', '#'},
|
||||
{'G', '-'},
|
||||
{'G', '#'},
|
||||
{'A', '-'},
|
||||
{'A', '#'},
|
||||
{'B', '-'}
|
||||
};
|
||||
|
||||
uint8_t n;
|
||||
uint8_t octave;
|
||||
|
||||
if (note == 0) {
|
||||
serial_puts("---");
|
||||
return;
|
||||
}
|
||||
|
||||
if (note == 0xff) {
|
||||
serial_puts("OFF");
|
||||
return;
|
||||
}
|
||||
|
||||
note -= 12;
|
||||
|
||||
n = note % 12;
|
||||
octave = (note / 12);
|
||||
|
||||
serial_write(note_names[n][0]);
|
||||
serial_write(note_names[n][1]);
|
||||
|
||||
if (octave < 10)
|
||||
serial_write('0' + octave);
|
||||
else
|
||||
serial_write('?');
|
||||
}
|
||||
|
||||
static void draw_cell(uint8_t row, uint8_t channel){
|
||||
cell_t *cell = &pattern->rows[row].channel[channel];
|
||||
|
||||
if (row == cursor_row && channel == cursor_channel)
|
||||
reverse_on();
|
||||
|
||||
print_note(cell->note);
|
||||
|
||||
serial_write(' ');
|
||||
|
||||
// if (cell->instrument == 0) {
|
||||
serial_puts("--");
|
||||
// }
|
||||
// else {
|
||||
// print_hex_byte(cell->instrument);
|
||||
// }
|
||||
|
||||
if (row == cursor_row && channel == cursor_channel)
|
||||
reverse_off();
|
||||
}
|
||||
|
||||
static void draw_row(uint8_t row){
|
||||
uint8_t channel;
|
||||
|
||||
print_hex_byte(row);
|
||||
serial_puts(" ");
|
||||
|
||||
for (channel = 0; channel < CHANNELS; channel++) {
|
||||
draw_cell(row, channel);
|
||||
|
||||
if (channel != CHANNELS - 1)
|
||||
serial_puts(" ");
|
||||
}
|
||||
|
||||
serial_puts("\r\n");
|
||||
}
|
||||
|
||||
static void draw(void){
|
||||
uint8_t row;
|
||||
|
||||
if (clear) {
|
||||
cls();
|
||||
clear = 0;
|
||||
}else {
|
||||
cursor_set(1, 1);
|
||||
}
|
||||
|
||||
cursor_hide();
|
||||
|
||||
serial_puts("Z80 TRACKER\r\n");
|
||||
serial_puts("O [");
|
||||
print_hex_digit(input_octave);
|
||||
serial_puts("]\r\n");
|
||||
|
||||
serial_puts(" CH1 CH2 CH3 NOISE\r\n");
|
||||
serial_puts(" ------- ------- ------- -------\r\n");
|
||||
|
||||
for (row = 0; row < TRACK_ROWS; row++) {
|
||||
draw_row(row);
|
||||
}
|
||||
|
||||
serial_puts("\r\n");
|
||||
serial_puts("Arrows: move A-K: notes X: clear Space: off\r\n");
|
||||
serial_puts("P: play O: stop Q: quit Numbers: oct\r\n");
|
||||
|
||||
/*
|
||||
* Keep the real terminal cursor out of the tracker area.
|
||||
*/
|
||||
cursor_set(1, 25);
|
||||
}
|
||||
|
||||
static void move_up(void){
|
||||
if (cursor_row > 0)
|
||||
cursor_row--;
|
||||
|
||||
dirty = 1;
|
||||
}
|
||||
|
||||
static void move_down(void){
|
||||
if (cursor_row < TRACK_ROWS - 1)
|
||||
cursor_row++;
|
||||
|
||||
dirty = 1;
|
||||
}
|
||||
|
||||
static void move_left(void){
|
||||
if (cursor_channel > 0)
|
||||
cursor_channel--;
|
||||
|
||||
dirty = 1;
|
||||
}
|
||||
|
||||
static void move_right(void){
|
||||
if (cursor_channel < CHANNELS - 1)
|
||||
cursor_channel++;
|
||||
|
||||
dirty = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Very simple piano mapping:
|
||||
*
|
||||
* W E T Y U
|
||||
* C#D# F#G#A#
|
||||
*
|
||||
* A S D F G H J K
|
||||
* C D E F G A B C
|
||||
*
|
||||
* This currently enters octave 4.
|
||||
*/
|
||||
static uint8_t key_to_note(char key){
|
||||
uint8_t semitone;
|
||||
|
||||
switch (key) {
|
||||
case 'a': case 'A': semitone = 0; break; /* C */
|
||||
case 'w': case 'W': semitone = 1; break; /* C# */
|
||||
case 's': case 'S': semitone = 2; break; /* D */
|
||||
case 'e': case 'E': semitone = 3; break; /* D# */
|
||||
case 'd': case 'D': semitone = 4; break; /* E */
|
||||
case 'f': case 'F': semitone = 5; break; /* F */
|
||||
case 't': case 'T': semitone = 6; break; /* F# */
|
||||
case 'g': case 'G': semitone = 7; break; /* G */
|
||||
case 'y': case 'Y': semitone = 8; break; /* G# */
|
||||
case 'h': case 'H': semitone = 9; break; /* A */
|
||||
case 'u': case 'U': semitone = 10; break; /* A# */
|
||||
case 'j': case 'J': semitone = 11; break; /* B */
|
||||
case 'k': case 'K': semitone = 12; break; /* C */
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 12+(input_octave*12) + semitone;
|
||||
}
|
||||
|
||||
static void enter_note(uint8_t note){
|
||||
cell_t *cell = &pattern->rows[cursor_row].channel[cursor_channel];
|
||||
|
||||
cell->note = note;
|
||||
cell->volume = 15;
|
||||
|
||||
/*
|
||||
* Just give every entered note instrument 1 for now.
|
||||
*/
|
||||
//if (cell->instrument == 0)
|
||||
// cell->instrument = 1;
|
||||
|
||||
/*
|
||||
* Tracker-style behavior: advance after entering a note.
|
||||
*/
|
||||
if (cursor_row < TRACK_ROWS - 1)
|
||||
cursor_row++;
|
||||
|
||||
dirty = 1;
|
||||
}
|
||||
|
||||
void ui_input(char input){
|
||||
uint8_t note;
|
||||
|
||||
/*
|
||||
* State 0:
|
||||
* normal keyboard input.
|
||||
*/
|
||||
if (input_state == 0) {
|
||||
|
||||
if ((uint8_t)input == 0x1b) {
|
||||
input_state = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear cell.
|
||||
*/
|
||||
if (input == 'x' || input == 'X') {
|
||||
pattern->rows[cursor_row].channel[cursor_channel].note = 0;
|
||||
|
||||
dirty = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Space = note off.
|
||||
*/
|
||||
if (input == ' ') {
|
||||
pattern->rows[cursor_row].channel[cursor_channel].note = 0xff;
|
||||
|
||||
if (cursor_row < TRACK_ROWS - 1)
|
||||
cursor_row++;
|
||||
|
||||
dirty = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* P = play once
|
||||
*/
|
||||
if (input == 'p' || input=='P') {
|
||||
do_action = ACTION_PLAY_ONCE;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* O = stop
|
||||
*/
|
||||
if (input == 'o' || input=='O') {
|
||||
do_action = ACTION_STOP;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Q = quit
|
||||
*/
|
||||
if (input == 'q' || input=='Q') {
|
||||
do_action = ACTION_QUIT;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 0-7 = octaves
|
||||
*/
|
||||
if (input >= '0' && input<='7') {
|
||||
input_octave = input-'0';
|
||||
dirty = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Piano keyboard.
|
||||
*/
|
||||
note = key_to_note(input);
|
||||
|
||||
if (note != 0) {
|
||||
enter_note(note);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* State 1:
|
||||
*
|
||||
* We received ESC.
|
||||
*
|
||||
* Arrow keys normally send '[' next.
|
||||
*/
|
||||
if (input_state == 1) {
|
||||
|
||||
if (input == '[') {
|
||||
input_state = 2;
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Unknown escape sequence.
|
||||
*/
|
||||
input_state = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* State 2:
|
||||
*
|
||||
* ESC [
|
||||
*
|
||||
* Last byte tells us which arrow.
|
||||
*/
|
||||
if (input_state == 2) {
|
||||
|
||||
switch (input) {
|
||||
case 'A':
|
||||
move_up();
|
||||
break;
|
||||
|
||||
case 'B':
|
||||
move_down();
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
move_right();
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
move_left();
|
||||
break;
|
||||
}
|
||||
|
||||
input_state = 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t do_ui(){
|
||||
if(dirty){
|
||||
dirty = 0;
|
||||
draw();
|
||||
}
|
||||
if(do_action != ACTION_NONE){
|
||||
uint8_t action = do_action;
|
||||
do_action = ACTION_NONE;
|
||||
return action;
|
||||
}
|
||||
return ACTION_NONE;
|
||||
}
|
||||
|
||||
void ui_set_pattern(pattern_t * _pattern){
|
||||
pattern = _pattern;
|
||||
}
|
||||
|
||||
void ui_init(){
|
||||
uint8_t row;
|
||||
uint8_t channel;
|
||||
|
||||
dirty = 1;
|
||||
clear = 1;
|
||||
|
||||
cursor_row = 0;
|
||||
cursor_channel = 0;
|
||||
input_state = 0;
|
||||
}
|
||||
13
tracker/src/ui.h
Normal file
13
tracker/src/ui.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "player.h"
|
||||
|
||||
#define ACTION_NONE 0
|
||||
#define ACTION_PLAY_ONCE 1
|
||||
#define ACTION_STOP 2
|
||||
#define ACTION_QUIT 3
|
||||
|
||||
void ui_input(char input);
|
||||
uint8_t do_ui();
|
||||
void ui_init();
|
||||
void ui_set_pattern(pattern_t * _pattern);
|
||||
Reference in New Issue
Block a user