n65 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/LICENSE +340 -0
- data/README.md +126 -0
- data/Rakefile +2 -0
- data/bin/n65 +11 -0
- data/data/opcodes.yaml +1030 -0
- data/examples/beep.asm +24 -0
- data/examples/mario2.asm +260 -0
- data/examples/mario2.char +0 -0
- data/examples/music_driver.asm +202 -0
- data/examples/noise.asm +93 -0
- data/examples/pulse_chord.asm +213 -0
- data/images/assembler_demo.png +0 -0
- data/lib/n65.rb +243 -0
- data/lib/n65/directives/ascii.rb +42 -0
- data/lib/n65/directives/bytes.rb +102 -0
- data/lib/n65/directives/dw.rb +86 -0
- data/lib/n65/directives/enter_scope.rb +55 -0
- data/lib/n65/directives/exit_scope.rb +35 -0
- data/lib/n65/directives/inc.rb +67 -0
- data/lib/n65/directives/incbin.rb +51 -0
- data/lib/n65/directives/ines_header.rb +53 -0
- data/lib/n65/directives/label.rb +46 -0
- data/lib/n65/directives/org.rb +47 -0
- data/lib/n65/directives/segment.rb +45 -0
- data/lib/n65/directives/space.rb +46 -0
- data/lib/n65/front_end.rb +90 -0
- data/lib/n65/instruction.rb +308 -0
- data/lib/n65/instruction_base.rb +29 -0
- data/lib/n65/memory_space.rb +150 -0
- data/lib/n65/opcodes.rb +9 -0
- data/lib/n65/parser.rb +85 -0
- data/lib/n65/regexes.rb +33 -0
- data/lib/n65/symbol_table.rb +198 -0
- data/lib/n65/version.rb +3 -0
- data/n65.gemspec +23 -0
- data/nes_lib/nes.sym +105 -0
- data/test/test_memory_space.rb +82 -0
- data/test/test_symbol_table.rb +238 -0
- data/utils/midi/Makefile +3 -0
- data/utils/midi/c_scale.mid +0 -0
- data/utils/midi/convert +0 -0
- data/utils/midi/guitar.mid +0 -0
- data/utils/midi/include/event.h +93 -0
- data/utils/midi/include/file.h +57 -0
- data/utils/midi/include/helpers.h +14 -0
- data/utils/midi/include/track.h +45 -0
- data/utils/midi/lil_melody.mid +0 -0
- data/utils/midi/mi_feabhra.mid +0 -0
- data/utils/midi/midi_to_nes.rb +204 -0
- data/utils/midi/source/convert.cpp +16 -0
- data/utils/midi/source/event.cpp +96 -0
- data/utils/midi/source/file.cpp +37 -0
- data/utils/midi/source/helpers.cpp +46 -0
- data/utils/midi/source/track.cpp +37 -0
- data/utils/opcode_table_to_yaml.rb +91 -0
- metadata +133 -0
data/utils/midi/Makefile
ADDED
Binary file
|
data/utils/midi/convert
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1,93 @@
|
|
1
|
+
#ifndef MIDI_EVENT_H
|
2
|
+
#define MIDI_EVENT_H
|
3
|
+
|
4
|
+
#include <stdio.h>
|
5
|
+
#include <stdlib.h>
|
6
|
+
#include "helpers.h"
|
7
|
+
|
8
|
+
namespace Midi {
|
9
|
+
|
10
|
+
enum MidiMessageType {Midi = 0, MidiNoteOff = 0x8, MidiNoteOn, MidiPolyAftertouch,
|
11
|
+
MidiControlChange, MidiProgramChange, MidiChannelAftertouch,
|
12
|
+
MidiPitchWheel, MidiMetaEvent = 0xFF, MidiSysex1 = 0xF0, MidiSysex2 = 0xF7};
|
13
|
+
|
14
|
+
|
15
|
+
enum MidiMetaType {MetaSequenceNumber = 0x0, MetaTextEvent = 0x1, MetaCopyright = 0x2,
|
16
|
+
MetaTrackName = 0x3, MetaInstrumentName = 0x4, MetaLyricText = 0x5,
|
17
|
+
MetaMarkerText = 0x6, MetaCuePoint = 0x7, MetaChannelPrefixAssignment = 0x20,
|
18
|
+
MetaEndOfTrack = 0x2F, MetaTempoSetting = 0x51, MetaSMPTEOffset = 0x54,
|
19
|
+
MetaTimeSignature = 0x58, MetaKeySignature = 0x59, MetaSequenceSpecific = 0x7F};
|
20
|
+
|
21
|
+
/***
|
22
|
+
* Spec:
|
23
|
+
* MidiTrackEvent = <delta_time (Variable)> + <MidiEvent> | <MetaEvent> | <SysexEvent>
|
24
|
+
* MidiEvent = <MidiMessageType (4 bits)> + <channel (4 bits)> + <parameter1 (1 byte)> <parameter2 (1 byte)>
|
25
|
+
* MetaEvent = 0xFF + <MidiMetaType> + <size (Variable)> + <data>
|
26
|
+
* SysexEvent = 0xF0 + <data> + 0xF7 -or-
|
27
|
+
* SysexEvent = 0xF7 + <data> 0xF7
|
28
|
+
***/
|
29
|
+
|
30
|
+
class Event {
|
31
|
+
private:
|
32
|
+
unsigned int m_delta;
|
33
|
+
unsigned int m_size;
|
34
|
+
unsigned int m_data_size;
|
35
|
+
unsigned char m_status;
|
36
|
+
unsigned char m_meta_type;
|
37
|
+
unsigned char m_parameter1;
|
38
|
+
unsigned char m_parameter2;
|
39
|
+
void *m_data;
|
40
|
+
|
41
|
+
public:
|
42
|
+
Event();
|
43
|
+
Event(int delta, int status, int parameter1, int parameter2);
|
44
|
+
void init_midi(int delta, int status, int parameter1, int parameter2);
|
45
|
+
void init_from_file(FILE *fp, unsigned char last_status);
|
46
|
+
~Event();
|
47
|
+
|
48
|
+
// Some easy inline functions
|
49
|
+
static bool is_status_byte(unsigned char byte) { return ((byte & 0x80) >> 7) == 1; }
|
50
|
+
unsigned int delta() const { return m_delta; }
|
51
|
+
unsigned char status() const { return m_status; }
|
52
|
+
unsigned int bytes_read() const { return m_size; }
|
53
|
+
unsigned char parameter1() const { return m_parameter1; }
|
54
|
+
unsigned char parameter2() const { return m_parameter2; }
|
55
|
+
int message_type() const { return (m_status &0xF0) >> 4; }
|
56
|
+
int channel() const { return (m_status &0xF); }
|
57
|
+
void *sysex_data() const { return m_data; }
|
58
|
+
|
59
|
+
|
60
|
+
void print_yaml(){
|
61
|
+
printf(" - :delta: 0x%X\n", m_delta);
|
62
|
+
printf(" :status: 0x%X\n", m_status);
|
63
|
+
|
64
|
+
switch(m_status){
|
65
|
+
case 0xFF:
|
66
|
+
printf(" :meta_type: 0x%X\n", m_meta_type);
|
67
|
+
printf(" :meta_data_size: 0x%X\n", m_data_size);
|
68
|
+
break;
|
69
|
+
case 0xF0:
|
70
|
+
case 0xF7:
|
71
|
+
printf(" :sysex_data_size: %d\n", m_data_size);
|
72
|
+
break;
|
73
|
+
default:
|
74
|
+
printf(" :parameter1: 0x%X\n", m_parameter1);
|
75
|
+
printf(" :parameter2: 0x%X\n", m_parameter2);
|
76
|
+
}
|
77
|
+
/* Printing out the actual data as a string confuses the YAML parser
|
78
|
+
* So let's just not do that.
|
79
|
+
if(m_data){
|
80
|
+
printf(" :data: \"");
|
81
|
+
|
82
|
+
for(int i = 0; i < m_data_size; i++){
|
83
|
+
printf("%c", ((unsigned char*)m_data)[i]);
|
84
|
+
}
|
85
|
+
printf("\"\n");
|
86
|
+
}
|
87
|
+
*/
|
88
|
+
}
|
89
|
+
};
|
90
|
+
|
91
|
+
}
|
92
|
+
|
93
|
+
#endif
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#ifndef MIDI_FILE_H
|
2
|
+
#define MIDI_FILE_H
|
3
|
+
|
4
|
+
#include <vector>
|
5
|
+
#include "track.h"
|
6
|
+
|
7
|
+
namespace Midi {
|
8
|
+
|
9
|
+
/***
|
10
|
+
* Spec:
|
11
|
+
* MidiFile = <midi_header_t> + <MidiTrack> [+ <MidiTrack> ...]
|
12
|
+
*
|
13
|
+
* midi_header_t = "MThd" + <size (4 bytes)> + <format (2 bytes)> + <track_count (2 bytes)> + <ticks_per_quarter_note (2 bytes)>
|
14
|
+
*
|
15
|
+
***/
|
16
|
+
|
17
|
+
typedef struct midi_header_t {
|
18
|
+
char cookie[4];
|
19
|
+
unsigned int size;
|
20
|
+
unsigned short format;
|
21
|
+
unsigned short track_count;
|
22
|
+
unsigned short ticks_per_quarter_note;
|
23
|
+
} __attribute__((packed)) midi_header_t;
|
24
|
+
|
25
|
+
|
26
|
+
class File {
|
27
|
+
private:
|
28
|
+
midi_header_t m_header;
|
29
|
+
|
30
|
+
public:
|
31
|
+
File(void);
|
32
|
+
~File(void);
|
33
|
+
std::vector<Track *> m_tracks;
|
34
|
+
void init_from_file(const char *filename);
|
35
|
+
|
36
|
+
void print_yaml(){
|
37
|
+
printf("---\n");
|
38
|
+
printf(":midi_file:\n");
|
39
|
+
printf(" :header: Mthd\n");
|
40
|
+
printf(" :size: %u\n", m_header.size);
|
41
|
+
printf(" :format: %u\n", m_header.format);
|
42
|
+
printf(" :track_count: %u\n", m_header.track_count);
|
43
|
+
printf(" :ticks_per_quarter_note: %u\n", m_header.ticks_per_quarter_note);
|
44
|
+
printf(" :tracks:\n");
|
45
|
+
|
46
|
+
for(int i = 0; i < m_tracks.size(); i++){
|
47
|
+
if(i == 0){
|
48
|
+
m_tracks[i]->print_yaml();
|
49
|
+
}else{
|
50
|
+
m_tracks[i]->print_yaml();
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
};
|
55
|
+
|
56
|
+
}
|
57
|
+
#endif
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#ifndef MIDI_HELPERS_H
|
2
|
+
#define MIDI_HELPERS_H
|
3
|
+
|
4
|
+
#include <stdio.h>
|
5
|
+
#include <stdlib.h>
|
6
|
+
|
7
|
+
namespace Midi {
|
8
|
+
|
9
|
+
unsigned int read_variable_length(FILE *fp, unsigned int *value_size);
|
10
|
+
short swap_endian_16(short big_endian);
|
11
|
+
int swap_endian_32(int big_endian);
|
12
|
+
|
13
|
+
}
|
14
|
+
#endif
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#ifndef MIDI_TRACK_H
|
2
|
+
#define MIDI_TRACK_H
|
3
|
+
|
4
|
+
#include "event.h"
|
5
|
+
#include <vector>
|
6
|
+
|
7
|
+
namespace Midi {
|
8
|
+
|
9
|
+
/***
|
10
|
+
* Spec:
|
11
|
+
* MidiTrack = <midi_track_header_t> + <MidiEvent> [+ <MidiEvent> ...]
|
12
|
+
* midi_track_header_t = "MTrk" + <size (4 bytes)>
|
13
|
+
*
|
14
|
+
***/
|
15
|
+
|
16
|
+
typedef struct midi_track_header_t {
|
17
|
+
char cookie[4];
|
18
|
+
unsigned int size;
|
19
|
+
} __attribute__((packed)) midi_track_header_t;
|
20
|
+
|
21
|
+
|
22
|
+
class Track {
|
23
|
+
private:
|
24
|
+
unsigned int m_total_size;
|
25
|
+
midi_track_header_t m_header;
|
26
|
+
|
27
|
+
public:
|
28
|
+
Track(void);
|
29
|
+
~Track(void);
|
30
|
+
void init_from_file(FILE *fp);
|
31
|
+
std::vector<Event *> m_events;
|
32
|
+
|
33
|
+
void print_yaml(){
|
34
|
+
printf(" - :header: MTrk\n");
|
35
|
+
printf(" :total_size: %u\n", m_header.size);
|
36
|
+
printf(" :events:\n");
|
37
|
+
|
38
|
+
for(int i = 0; i < m_events.size(); i++){
|
39
|
+
m_events[i]->print_yaml();
|
40
|
+
}
|
41
|
+
}
|
42
|
+
};
|
43
|
+
|
44
|
+
}
|
45
|
+
#endif
|
Binary file
|
Binary file
|
@@ -0,0 +1,204 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
class MidiToNES
|
6
|
+
|
7
|
+
#### Custom Exceptions
|
8
|
+
class MidiFormatNotSupported < StandardError; end
|
9
|
+
|
10
|
+
#### Some Constants
|
11
|
+
NoteOff = 0x8
|
12
|
+
NoteOn = 0x9
|
13
|
+
|
14
|
+
#### A440 Tuning, and NES CPU speed in hz
|
15
|
+
Tuning = 440.0
|
16
|
+
CPU = 1789773.0
|
17
|
+
|
18
|
+
|
19
|
+
#### LSB Address registers of the APU, MSB is always 0x40
|
20
|
+
Pulse1Control = 0x00
|
21
|
+
Pulse1FT = 0x2
|
22
|
+
Pulse1CT = 0x3
|
23
|
+
|
24
|
+
|
25
|
+
####
|
26
|
+
## Initialize from a yaml file
|
27
|
+
def self.init_from_file(filename, bpm)
|
28
|
+
self.new(File.read(filename), bpm)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
####
|
33
|
+
## Initialize with a yaml string
|
34
|
+
def initialize(yaml_string, bpm)
|
35
|
+
@bpm = bpm.to_f
|
36
|
+
@midi_data = YAML.load(yaml_string)[:midi_file]
|
37
|
+
unless @midi_data[:format].zero?
|
38
|
+
fail(MidiFormatNotSupported, "Currently only supports format 0 Midi Files")
|
39
|
+
end
|
40
|
+
@ticks_per_quarter_note = @midi_data[:ticks_per_quarter_note]
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
####
|
45
|
+
## Write to binary file
|
46
|
+
def write_binary(filename)
|
47
|
+
binary = convert
|
48
|
+
File.open(filename, 'wb') do |fp|
|
49
|
+
fp.write(binary)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
####
|
55
|
+
## For now assume one track
|
56
|
+
def convert
|
57
|
+
tick_count = 1
|
58
|
+
events = []
|
59
|
+
|
60
|
+
track = @midi_data[:tracks].first
|
61
|
+
track[:events].each do |event|
|
62
|
+
|
63
|
+
delta, status, note, velocity = event.values_at(:delta, :status, :parameter1, :parameter2)
|
64
|
+
|
65
|
+
## The status byte contains both the Midi message type, and channel.
|
66
|
+
type = (status & 0b11110000) >> 4
|
67
|
+
channel = status & 0b00001111
|
68
|
+
|
69
|
+
## We only care about note on and off, and only care about channel 0 for now.
|
70
|
+
next unless type == NoteOn || type == NoteOff
|
71
|
+
next unless channel.zero?
|
72
|
+
|
73
|
+
## Update the total time
|
74
|
+
tick_count += delta
|
75
|
+
|
76
|
+
## Ok this is a note either turning on or off
|
77
|
+
if type == NoteOff || velocity.zero?
|
78
|
+
#event = {:start => tick_count, :note => note, :velocity => 0}
|
79
|
+
#events << event
|
80
|
+
else
|
81
|
+
event = {:start => tick_count, :note => note, :velocity => velocity}
|
82
|
+
events << event
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
## Finally sort event list by start time
|
87
|
+
events.sort! do |a, b|
|
88
|
+
a[:start] <=> b[:start]
|
89
|
+
end
|
90
|
+
|
91
|
+
## Now convert these events to a bytestream for our NES sound engine
|
92
|
+
events_to_byte_stream(events)
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
####
|
97
|
+
## This converts a list of note events into a byte stream for updating NES APU registers
|
98
|
+
def events_to_byte_stream(events)
|
99
|
+
last_tick = 1
|
100
|
+
byte_stream = []
|
101
|
+
|
102
|
+
events.each do |event|
|
103
|
+
## Work out the delta again
|
104
|
+
delta = event[:start] - last_tick
|
105
|
+
byte_stream << midi_tick_to_vblank(delta) # Delta
|
106
|
+
byte_stream << pulse_control_value(event) # Value
|
107
|
+
if event[:velocity].zero?
|
108
|
+
#byte_stream << 0 # Off with 0 frequency timer
|
109
|
+
#byte_stream << 0
|
110
|
+
else
|
111
|
+
byte_stream << pulse_ft_value(event) # Value
|
112
|
+
byte_stream << pulse_ct_value(event) # Value
|
113
|
+
end
|
114
|
+
last_tick += delta
|
115
|
+
end
|
116
|
+
byte_stream.pack('C*')
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
####
|
121
|
+
## Given an event, produce a value for register nes.apu.pulse1.control
|
122
|
+
## DDLC VVVV
|
123
|
+
## Duty (D), envelope loop / length counter halt (L), constant volume (C), volume/envelope (V)
|
124
|
+
def pulse_control_value(event)
|
125
|
+
## Start with 50% duty cycle, length counter halt is on
|
126
|
+
## Constant volume is On, and volume is determined by bit-reducing the event velocity to 4-bit
|
127
|
+
value = 0b10000111
|
128
|
+
|
129
|
+
#four_bit_max = (2**4 - 1)
|
130
|
+
#seven_bit_max = (2**7 - 1)
|
131
|
+
|
132
|
+
#volume_float = event[:velocity] / seven_bit_max.to_f
|
133
|
+
#volume_4_bit = (volume_float * four_bit_max).round & 0b00001111
|
134
|
+
|
135
|
+
#value | volume_4_bit
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
####
|
140
|
+
## Given an event, produce a value for register nes.apu.pulse1.ft
|
141
|
+
## TTTT TTTT
|
142
|
+
## This is the low byte of the timer, the higher few bits being in pulse1.ct
|
143
|
+
def pulse_ft_value(event)
|
144
|
+
midi_note_to_nes_timer(event[:note]) & 0xff
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
####
|
149
|
+
## Given an event, produce a value for register nes.apu.pulse1.ct
|
150
|
+
## LLLL LTTT
|
151
|
+
## This has the higher 3 bits of the timer, and L is the length counter.
|
152
|
+
## For now let's just use duration as the length counter.
|
153
|
+
def pulse_ct_value(event)
|
154
|
+
value = 0b11111000
|
155
|
+
|
156
|
+
## We will grab the high 3 bits of the 11-bit timer value now
|
157
|
+
timer_high_3bit = midi_note_to_nes_timer(event[:note]) & 0b11100000000
|
158
|
+
value | (timer_high_3bit >> 8)
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
####
|
163
|
+
## Midi note to NES timer
|
164
|
+
def midi_note_to_nes_timer(midi_note)
|
165
|
+
frequency = Tuning * 2**((midi_note - 69) / 12.0)
|
166
|
+
timer = (CPU / (16 * frequency)) - 1
|
167
|
+
if timer > (2**11 - 1)
|
168
|
+
fail("midi note #{midi_note} is too big at #{timer}")
|
169
|
+
end
|
170
|
+
timer.round
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
####
|
175
|
+
## Convert a MIDI tick delta to an NES vblank delta.
|
176
|
+
def midi_tick_to_vblank(midi_tick)
|
177
|
+
quarter_note_in_seconds = 60 / @bpm
|
178
|
+
vblanks_per_quarter_note = quarter_note_in_seconds / (1/60.0)
|
179
|
+
tick_normalized = midi_tick / @ticks_per_quarter_note.to_f
|
180
|
+
vblanks = tick_normalized * vblanks_per_quarter_note
|
181
|
+
vblanks.round
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
if __FILE__ == $0
|
187
|
+
unless ARGV.size == 2
|
188
|
+
STDERR.puts("Usage #{$0} <bpm> <music.mid>")
|
189
|
+
exit(1)
|
190
|
+
end
|
191
|
+
|
192
|
+
bpm, midi_file = ARGV
|
193
|
+
|
194
|
+
## Run the midi file through my converter written in C++
|
195
|
+
IO.popen("./convert #{midi_file}") do |io|
|
196
|
+
midi_to_nes = MidiToNES.new(io.read, bpm.to_i)
|
197
|
+
midi_to_nes.write_binary('../../data.mus')
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
|
204
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include "file.h"
|
3
|
+
|
4
|
+
int main(int argc, char **argv){
|
5
|
+
|
6
|
+
if(argc < 1){
|
7
|
+
printf("Need a midi file argument\n");
|
8
|
+
exit(1);
|
9
|
+
}
|
10
|
+
|
11
|
+
Midi::File midi_file;
|
12
|
+
midi_file.init_from_file(argv[1]);
|
13
|
+
midi_file.print_yaml();
|
14
|
+
|
15
|
+
return 0;
|
16
|
+
}
|