1
0
mirror of https://github.com/safiire/n65.git synced 2024-12-11 08:49:42 +00:00

Update for Ruby 2.4

This commit is contained in:
Safiire 2016-12-13 15:12:39 -08:00
parent 2293fd2251
commit a48ce496e8
20 changed files with 4 additions and 854 deletions

View File

@ -1,202 +0,0 @@
;------------------------------------------------------------------------------
; An NES music engine that understands the binary stream outputted from my
; MIDI converter :)
;;;;
; Create an iNES header
.ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 0}
;;;;
; Include all the symbols in the nes library
.inc <nes.sym>
;;;;
; Let's put a data structure to control the sound engine in the zero page
.org $0000
.scope sound_engine
; Where we are reading from ROM
.space stream_read_ptr_lo 1
.space stream_read_ptr_hi 1
; Where we are writing in the APU
.space stream_write_ptr_lo 1
.space stream_write_ptr_hi 1
.space delta 1
.
;;;;
; Open the prog section bank 0
.segment prog 0
;;;;
; Setup the interrupt vectors
.org $FFFA
.dw vblank
.dw main
.dw irq
;;;;
; Here is our code entry point, which we'll call main.
.org $C000
.scope main
; Disable interrupts and decimal flag
sei
cld
; Wait for 2 vblanks
wait_vb1:
lda nes.ppu.status
bpl wait_vb1
wait_vb2:
lda nes.ppu.status
bpl wait_vb2
; Now we want to initialize the hardware to a known state
lda #%00
ldx #$00
clear_segments:
sta $0, x
sta $100, x
sta $200, x
sta $300, x
sta $400, x
sta $500, x
sta $600, x
sta $700, x
inx
bne clear_segments
; Reset the stack pointer
ldx #$FF
txs
; Disable all graphics and vblank nmi
lda #$00
sta nes.ppu.control
sta nes.ppu.mask
jsr init_sound
; Resume interrupts and NMI and loop here forever
lda #%10000000
sta nes.ppu.control
cli
forever:
jmp forever
.
;;;;
; Initialize the APU to enable Pulse1
.scope init_sound
lda #$00
ldy #$00
clear_apu:
sta nes.apu, y
iny
cpy #$10
bne clear_apu
lda #>music_buffer
ldx #<music_buffer
sta sound_engine.stream_read_ptr_hi
stx sound_engine.stream_read_ptr_lo
lda #$40
ldx #$00
sta sound_engine.stream_write_ptr_hi
stx sound_engine.stream_write_ptr_lo
lda #$01
sta sound_engine.delta
lda #$01
sta nes.apu.channel_enable
rts
.
;;;;
; VBlank reads our music buffer
.scope vblank
; Backup our registers
pha
txa
pha
tya
pha
jsr sound_engine_driver
; Restore the registers
pla
tay
pla
tax
pla
rti
.
;;;;
; Sound driver that updates the APU registers
; via a stream of APU write commands
.scope sound_engine_driver
dec sound_engine.delta
bne done
read_event:
; Load the new delta from the stream
ldy #$00
lda (sound_engine.stream_read_ptr_lo), Y
sta sound_engine.delta
; Read pulse1 control register value
ldy #$01
lda (sound_engine.stream_read_ptr_lo), Y
sta nes.apu.pulse1.control
; Read the value for pulse1.ft
ldy #$02
lda (sound_engine.stream_read_ptr_lo), Y
sta nes.apu.pulse1.ft
; Read the value for pulse1.ct
ldy #$03
lda (sound_engine.stream_read_ptr_lo), Y
sta nes.apu.pulse1.ct
; Advance the 16-bit stream pointer by number of bytes read
lda sound_engine.stream_read_ptr_lo
clc
adc #$04
sta sound_engine.stream_read_ptr_lo
bcc done
inc sound_engine.stream_read_ptr_hi
; If the very next event is 0 delta away, do it now too
; Read the value for pulse1.ct
ldy #$04
lda (sound_engine.stream_read_ptr_lo), Y
beq read_event
done:
rts
.
;;;;
; IRQ Does nothing
.scope irq
rti
.
;;;;
; Include the music buffer stream
.org $D000
music_buffer:
.incbin "data.mus"

View File

@ -52,7 +52,7 @@ module N65
## Try to execute it now, or setup the promise to return
case @value
when Fixnum
when Integer
bytes = [@value & 0xFFFF].pack('S').bytes
assembler.write_memory(bytes)
when String

View File

@ -57,7 +57,7 @@ module N65
:example => 'AAA $FF, Y',
:display => '%s $%.2X, Y',
:regex => /^#{Mnemonic}\s+#{Num8}\s?,\s?#{YReg}$/,
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg} zp$/
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg}\s+zp$/
},
:absolute => {
@ -220,7 +220,7 @@ module N65
end
case @arg
when Fixnum, NilClass
when Integer, NilClass
assembler.write_memory(emit_bytes)
when String
begin

View File

@ -1,3 +1,3 @@
module N65
VERSION = "0.5.0"
VERSION ||= "1.0.0"
end

View File

@ -1,3 +0,0 @@
build: convert
clang++ source/*.cpp -I include -o convert

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,93 +0,0 @@
#ifndef MIDI_EVENT_H
#define MIDI_EVENT_H
#include <stdio.h>
#include <stdlib.h>
#include "helpers.h"
namespace Midi {
enum MidiMessageType {Midi = 0, MidiNoteOff = 0x8, MidiNoteOn, MidiPolyAftertouch,
MidiControlChange, MidiProgramChange, MidiChannelAftertouch,
MidiPitchWheel, MidiMetaEvent = 0xFF, MidiSysex1 = 0xF0, MidiSysex2 = 0xF7};
enum MidiMetaType {MetaSequenceNumber = 0x0, MetaTextEvent = 0x1, MetaCopyright = 0x2,
MetaTrackName = 0x3, MetaInstrumentName = 0x4, MetaLyricText = 0x5,
MetaMarkerText = 0x6, MetaCuePoint = 0x7, MetaChannelPrefixAssignment = 0x20,
MetaEndOfTrack = 0x2F, MetaTempoSetting = 0x51, MetaSMPTEOffset = 0x54,
MetaTimeSignature = 0x58, MetaKeySignature = 0x59, MetaSequenceSpecific = 0x7F};
/***
* Spec:
* MidiTrackEvent = <delta_time (Variable)> + <MidiEvent> | <MetaEvent> | <SysexEvent>
* MidiEvent = <MidiMessageType (4 bits)> + <channel (4 bits)> + <parameter1 (1 byte)> <parameter2 (1 byte)>
* MetaEvent = 0xFF + <MidiMetaType> + <size (Variable)> + <data>
* SysexEvent = 0xF0 + <data> + 0xF7 -or-
* SysexEvent = 0xF7 + <data> 0xF7
***/
class Event {
private:
unsigned int m_delta;
unsigned int m_size;
unsigned int m_data_size;
unsigned char m_status;
unsigned char m_meta_type;
unsigned char m_parameter1;
unsigned char m_parameter2;
void *m_data;
public:
Event();
Event(int delta, int status, int parameter1, int parameter2);
void init_midi(int delta, int status, int parameter1, int parameter2);
void init_from_file(FILE *fp, unsigned char last_status);
~Event();
// Some easy inline functions
static bool is_status_byte(unsigned char byte) { return ((byte & 0x80) >> 7) == 1; }
unsigned int delta() const { return m_delta; }
unsigned char status() const { return m_status; }
unsigned int bytes_read() const { return m_size; }
unsigned char parameter1() const { return m_parameter1; }
unsigned char parameter2() const { return m_parameter2; }
int message_type() const { return (m_status &0xF0) >> 4; }
int channel() const { return (m_status &0xF); }
void *sysex_data() const { return m_data; }
void print_yaml(){
printf(" - :delta: 0x%X\n", m_delta);
printf(" :status: 0x%X\n", m_status);
switch(m_status){
case 0xFF:
printf(" :meta_type: 0x%X\n", m_meta_type);
printf(" :meta_data_size: 0x%X\n", m_data_size);
break;
case 0xF0:
case 0xF7:
printf(" :sysex_data_size: %d\n", m_data_size);
break;
default:
printf(" :parameter1: 0x%X\n", m_parameter1);
printf(" :parameter2: 0x%X\n", m_parameter2);
}
/* Printing out the actual data as a string confuses the YAML parser
* So let's just not do that.
if(m_data){
printf(" :data: \"");
for(int i = 0; i < m_data_size; i++){
printf("%c", ((unsigned char*)m_data)[i]);
}
printf("\"\n");
}
*/
}
};
}
#endif

View File

@ -1,57 +0,0 @@
#ifndef MIDI_FILE_H
#define MIDI_FILE_H
#include <vector>
#include "track.h"
namespace Midi {
/***
* Spec:
* MidiFile = <midi_header_t> + <MidiTrack> [+ <MidiTrack> ...]
*
* midi_header_t = "MThd" + <size (4 bytes)> + <format (2 bytes)> + <track_count (2 bytes)> + <ticks_per_quarter_note (2 bytes)>
*
***/
typedef struct midi_header_t {
char cookie[4];
unsigned int size;
unsigned short format;
unsigned short track_count;
unsigned short ticks_per_quarter_note;
} __attribute__((packed)) midi_header_t;
class File {
private:
midi_header_t m_header;
public:
File(void);
~File(void);
std::vector<Track *> m_tracks;
void init_from_file(const char *filename);
void print_yaml(){
printf("---\n");
printf(":midi_file:\n");
printf(" :header: Mthd\n");
printf(" :size: %u\n", m_header.size);
printf(" :format: %u\n", m_header.format);
printf(" :track_count: %u\n", m_header.track_count);
printf(" :ticks_per_quarter_note: %u\n", m_header.ticks_per_quarter_note);
printf(" :tracks:\n");
for(int i = 0; i < m_tracks.size(); i++){
if(i == 0){
m_tracks[i]->print_yaml();
}else{
m_tracks[i]->print_yaml();
}
}
}
};
}
#endif

View File

@ -1,14 +0,0 @@
#ifndef MIDI_HELPERS_H
#define MIDI_HELPERS_H
#include <stdio.h>
#include <stdlib.h>
namespace Midi {
unsigned int read_variable_length(FILE *fp, unsigned int *value_size);
short swap_endian_16(short big_endian);
int swap_endian_32(int big_endian);
}
#endif

View File

@ -1,45 +0,0 @@
#ifndef MIDI_TRACK_H
#define MIDI_TRACK_H
#include "event.h"
#include <vector>
namespace Midi {
/***
* Spec:
* MidiTrack = <midi_track_header_t> + <MidiEvent> [+ <MidiEvent> ...]
* midi_track_header_t = "MTrk" + <size (4 bytes)>
*
***/
typedef struct midi_track_header_t {
char cookie[4];
unsigned int size;
} __attribute__((packed)) midi_track_header_t;
class Track {
private:
unsigned int m_total_size;
midi_track_header_t m_header;
public:
Track(void);
~Track(void);
void init_from_file(FILE *fp);
std::vector<Event *> m_events;
void print_yaml(){
printf(" - :header: MTrk\n");
printf(" :total_size: %u\n", m_header.size);
printf(" :events:\n");
for(int i = 0; i < m_events.size(); i++){
m_events[i]->print_yaml();
}
}
};
}
#endif

Binary file not shown.

Binary file not shown.

View File

@ -1,204 +0,0 @@
#!/usr/bin/env ruby
require 'yaml'
class MidiToNES
#### Custom Exceptions
class MidiFormatNotSupported < StandardError; end
#### Some Constants
NoteOff = 0x8
NoteOn = 0x9
#### A440 Tuning, and NES CPU speed in hz
Tuning = 440.0
CPU = 1789773.0
#### LSB Address registers of the APU, MSB is always 0x40
Pulse1Control = 0x00
Pulse1FT = 0x2
Pulse1CT = 0x3
####
## Initialize from a yaml file
def self.init_from_file(filename, bpm)
self.new(File.read(filename), bpm)
end
####
## Initialize with a yaml string
def initialize(yaml_string, bpm)
@bpm = bpm.to_f
@midi_data = YAML.load(yaml_string)[:midi_file]
unless @midi_data[:format].zero?
fail(MidiFormatNotSupported, "Currently only supports format 0 Midi Files")
end
@ticks_per_quarter_note = @midi_data[:ticks_per_quarter_note]
end
####
## Write to binary file
def write_binary(filename)
binary = convert
File.open(filename, 'wb') do |fp|
fp.write(binary)
end
end
####
## For now assume one track
def convert
tick_count = 1
events = []
track = @midi_data[:tracks].first
track[:events].each do |event|
delta, status, note, velocity = event.values_at(:delta, :status, :parameter1, :parameter2)
## The status byte contains both the Midi message type, and channel.
type = (status & 0b11110000) >> 4
channel = status & 0b00001111
## We only care about note on and off, and only care about channel 0 for now.
next unless type == NoteOn || type == NoteOff
next unless channel.zero?
## Update the total time
tick_count += delta
## Ok this is a note either turning on or off
if type == NoteOff || velocity.zero?
#event = {:start => tick_count, :note => note, :velocity => 0}
#events << event
else
event = {:start => tick_count, :note => note, :velocity => velocity}
events << event
end
end
## Finally sort event list by start time
events.sort! do |a, b|
a[:start] <=> b[:start]
end
## Now convert these events to a bytestream for our NES sound engine
events_to_byte_stream(events)
end
####
## This converts a list of note events into a byte stream for updating NES APU registers
def events_to_byte_stream(events)
last_tick = 1
byte_stream = []
events.each do |event|
## Work out the delta again
delta = event[:start] - last_tick
byte_stream << midi_tick_to_vblank(delta) # Delta
byte_stream << pulse_control_value(event) # Value
if event[:velocity].zero?
#byte_stream << 0 # Off with 0 frequency timer
#byte_stream << 0
else
byte_stream << pulse_ft_value(event) # Value
byte_stream << pulse_ct_value(event) # Value
end
last_tick += delta
end
byte_stream.pack('C*')
end
####
## Given an event, produce a value for register nes.apu.pulse1.control
## DDLC VVVV
## Duty (D), envelope loop / length counter halt (L), constant volume (C), volume/envelope (V)
def pulse_control_value(event)
## Start with 50% duty cycle, length counter halt is on
## Constant volume is On, and volume is determined by bit-reducing the event velocity to 4-bit
value = 0b10000111
#four_bit_max = (2**4 - 1)
#seven_bit_max = (2**7 - 1)
#volume_float = event[:velocity] / seven_bit_max.to_f
#volume_4_bit = (volume_float * four_bit_max).round & 0b00001111
#value | volume_4_bit
end
####
## Given an event, produce a value for register nes.apu.pulse1.ft
## TTTT TTTT
## This is the low byte of the timer, the higher few bits being in pulse1.ct
def pulse_ft_value(event)
midi_note_to_nes_timer(event[:note]) & 0xff
end
####
## Given an event, produce a value for register nes.apu.pulse1.ct
## LLLL LTTT
## This has the higher 3 bits of the timer, and L is the length counter.
## For now let's just use duration as the length counter.
def pulse_ct_value(event)
value = 0b11111000
## We will grab the high 3 bits of the 11-bit timer value now
timer_high_3bit = midi_note_to_nes_timer(event[:note]) & 0b11100000000
value | (timer_high_3bit >> 8)
end
####
## Midi note to NES timer
def midi_note_to_nes_timer(midi_note)
frequency = Tuning * 2**((midi_note - 69) / 12.0)
timer = (CPU / (16 * frequency)) - 1
if timer > (2**11 - 1)
fail("midi note #{midi_note} is too big at #{timer}")
end
timer.round
end
####
## Convert a MIDI tick delta to an NES vblank delta.
def midi_tick_to_vblank(midi_tick)
quarter_note_in_seconds = 60 / @bpm
vblanks_per_quarter_note = quarter_note_in_seconds / (1/60.0)
tick_normalized = midi_tick / @ticks_per_quarter_note.to_f
vblanks = tick_normalized * vblanks_per_quarter_note
vblanks.round
end
end
if __FILE__ == $0
unless ARGV.size == 2
STDERR.puts("Usage #{$0} <bpm> <music.mid>")
exit(1)
end
bpm, midi_file = ARGV
## Run the midi file through my converter written in C++
IO.popen("./convert #{midi_file}") do |io|
midi_to_nes = MidiToNES.new(io.read, bpm.to_i)
midi_to_nes.write_binary('../../data.mus')
end
end

View File

@ -1,16 +0,0 @@
#include <stdio.h>
#include "file.h"
int main(int argc, char **argv){
if(argc < 1){
printf("Need a midi file argument\n");
exit(1);
}
Midi::File midi_file;
midi_file.init_from_file(argv[1]);
midi_file.print_yaml();
return 0;
}

View File

@ -1,96 +0,0 @@
#include "event.h"
namespace Midi {
////
// Default constructor
Event::Event(void) :
m_delta(0), m_size(0),
m_data_size(0), m_status(0),
m_meta_type(0), m_parameter1(0),
m_parameter2(0), m_data(NULL) { }
////
// Constructor to set specific fields
Event::Event(int delta, int status, int parameter1, int parameter2) :
m_delta(delta), m_size(4),
m_data_size(0), m_status(status),
m_meta_type(0), m_parameter1(parameter1),
m_parameter2(parameter2), m_data(NULL) { }
////
// Use a file pointer to initialize
void Event::init_from_file(FILE *fp, unsigned char last_status){
fpos_t saved_position;
unsigned int value_size;
// All types of events are preceeded by a uintvar delta time
m_delta = read_variable_length(fp, &value_size);
m_size += value_size;
// All types then have a status byte, unless they are reusing
// the previous status, get ready to rewind if this is the case.
fgetpos(fp, &saved_position);
fread(&m_status, sizeof(unsigned char), 1, fp);
m_size++;
if(!is_status_byte(m_status)){
// This is not a status byte, so it must be reusing the previous one, Rewind
m_status = last_status;
fsetpos(fp, &saved_position);
m_size--;
}
switch(m_status){
case 0xFF: // Meta
// This will have a meta sub type
fread(&m_meta_type, sizeof(unsigned char), 1, fp);
m_size++;
// Now a variable size for data
m_data_size = read_variable_length(fp, &value_size);
m_size += value_size;
// Finally, read the meta data
m_data = malloc(sizeof(unsigned char) * m_data_size);
fread(m_data, sizeof(unsigned char), m_data_size, fp);
m_size += m_data_size;
break;
case 0xF0: // Sysex
case 0xF7: // Sysex
// Sysex data runs until the next 0xF0 or 0xF7, count how many byte to allocate, and rewind
fgetpos(fp, &saved_position);
while(m_status != fgetc(fp)){
m_data_size++;
}
fsetpos(fp, &saved_position);
m_data = malloc(sizeof(unsigned char) * m_data_size);
fread(m_data, sizeof(unsigned char), m_data_size, fp);
m_size += (m_data_size + 1);
fgetc(fp); // Throw away the end byte, although it counts for size
break;
default: // Midi
fread(&m_parameter1, sizeof(unsigned char), 1, fp);
fread(&m_parameter2, sizeof(unsigned char), 1, fp);
m_size += 2;
break;
}
}
void Event::init_midi(int delta, int status, int parameter1, int parameter2){
m_delta = delta;
m_status = status;
m_parameter1 = parameter1;
m_parameter2 = parameter2;
m_size = 4;
}
Event::~Event(void){
if(m_data){
free(m_data);
}
}
}

View File

@ -1,37 +0,0 @@
#include "file.h"
namespace Midi {
File::File(void){
m_header.cookie[0] = m_header.cookie[1] = m_header.cookie[2] = m_header.cookie[3] = 0;
m_header.size = 0;
}
void File::init_from_file(const char *filename){
FILE *fp = fopen(filename, "rb");
fread(&m_header, sizeof(midi_header_t), 1, fp);
// Fix up this Big Endian stuff
m_header.size = swap_endian_32(m_header.size);
m_header.format = swap_endian_16(m_header.format);
m_header.track_count = swap_endian_16(m_header.track_count);
m_header.ticks_per_quarter_note = swap_endian_16(m_header.ticks_per_quarter_note);
// Read each track
Track *track;
for(int i = 0; i < m_header.track_count; i++){
track = new Track();
track->init_from_file(fp);
m_tracks.push_back(track);
}
}
File::~File(void){
// Free all the midi tracks
for(unsigned int i = 0; i < m_tracks.size(); i++){
delete m_tracks[i];
}
}
}

View File

@ -1,46 +0,0 @@
#include "helpers.h"
namespace Midi {
////
// I have eliminated any doubt that this function works on variable
// length uintvars up to 4 bytes.
unsigned int read_variable_length(FILE *fp, unsigned int *value_size) {
unsigned int value;
*value_size = 1;
if((value = fgetc(fp)) & 0x80){
unsigned char c;
value &= 0x7F;
do{
(*value_size)++;
value = (value << 7) + ((c = fgetc(fp)) & 0x7F);
} while (c & 0x80);
}
return(value);
}
////
// Swap 4 bytes
int swap_endian_32(int big_endian){
register int little_endian;
little_endian = (big_endian & 0x000000FF);
little_endian = ((big_endian & 0x0000FF00) >> 0x08) | (little_endian << 0x08);
little_endian = ((big_endian & 0x00FF0000) >> 0x10) | (little_endian << 0x08);
little_endian = ((big_endian & 0xFF000000) >> 0x18) | (little_endian << 0x08);
return(little_endian);
}
////
// Swap 2 bytes
short swap_endian_16(short big_endian){
register short little_endian;
little_endian = (big_endian & 0x00FF);
little_endian = ((big_endian & 0xFF00) >> 0x08) | (little_endian << 0x08);
return(little_endian);
}
}

View File

@ -1,37 +0,0 @@
#include "track.h"
namespace Midi {
Track::Track(void){
m_header.cookie[0] = m_header.cookie[1] = m_header.cookie[2] = m_header.cookie[3] = 0;
m_header.size = 0;
m_total_size = 0;
}
void Track::init_from_file(FILE *fp){
Event *track_event;
unsigned int bytes_read = 0;
unsigned char last_status = 0x0;
fread(&m_header, sizeof(midi_track_header_t), 1, fp);
m_header.size = swap_endian_32(m_header.size);
if(m_header.size == 0) return;
while(bytes_read < m_header.size){
track_event = new Event();
track_event->init_from_file(fp, last_status);
m_events.push_back(track_event);
last_status = track_event->status();
bytes_read += track_event->bytes_read();
}
}
Track::~Track(void){
// Free all the track events
for(unsigned int i = 0; i < m_events.size(); i++){
delete m_events[i];
}
}
}