n65 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +340 -0
  5. data/README.md +126 -0
  6. data/Rakefile +2 -0
  7. data/bin/n65 +11 -0
  8. data/data/opcodes.yaml +1030 -0
  9. data/examples/beep.asm +24 -0
  10. data/examples/mario2.asm +260 -0
  11. data/examples/mario2.char +0 -0
  12. data/examples/music_driver.asm +202 -0
  13. data/examples/noise.asm +93 -0
  14. data/examples/pulse_chord.asm +213 -0
  15. data/images/assembler_demo.png +0 -0
  16. data/lib/n65.rb +243 -0
  17. data/lib/n65/directives/ascii.rb +42 -0
  18. data/lib/n65/directives/bytes.rb +102 -0
  19. data/lib/n65/directives/dw.rb +86 -0
  20. data/lib/n65/directives/enter_scope.rb +55 -0
  21. data/lib/n65/directives/exit_scope.rb +35 -0
  22. data/lib/n65/directives/inc.rb +67 -0
  23. data/lib/n65/directives/incbin.rb +51 -0
  24. data/lib/n65/directives/ines_header.rb +53 -0
  25. data/lib/n65/directives/label.rb +46 -0
  26. data/lib/n65/directives/org.rb +47 -0
  27. data/lib/n65/directives/segment.rb +45 -0
  28. data/lib/n65/directives/space.rb +46 -0
  29. data/lib/n65/front_end.rb +90 -0
  30. data/lib/n65/instruction.rb +308 -0
  31. data/lib/n65/instruction_base.rb +29 -0
  32. data/lib/n65/memory_space.rb +150 -0
  33. data/lib/n65/opcodes.rb +9 -0
  34. data/lib/n65/parser.rb +85 -0
  35. data/lib/n65/regexes.rb +33 -0
  36. data/lib/n65/symbol_table.rb +198 -0
  37. data/lib/n65/version.rb +3 -0
  38. data/n65.gemspec +23 -0
  39. data/nes_lib/nes.sym +105 -0
  40. data/test/test_memory_space.rb +82 -0
  41. data/test/test_symbol_table.rb +238 -0
  42. data/utils/midi/Makefile +3 -0
  43. data/utils/midi/c_scale.mid +0 -0
  44. data/utils/midi/convert +0 -0
  45. data/utils/midi/guitar.mid +0 -0
  46. data/utils/midi/include/event.h +93 -0
  47. data/utils/midi/include/file.h +57 -0
  48. data/utils/midi/include/helpers.h +14 -0
  49. data/utils/midi/include/track.h +45 -0
  50. data/utils/midi/lil_melody.mid +0 -0
  51. data/utils/midi/mi_feabhra.mid +0 -0
  52. data/utils/midi/midi_to_nes.rb +204 -0
  53. data/utils/midi/source/convert.cpp +16 -0
  54. data/utils/midi/source/event.cpp +96 -0
  55. data/utils/midi/source/file.cpp +37 -0
  56. data/utils/midi/source/helpers.cpp +46 -0
  57. data/utils/midi/source/track.cpp +37 -0
  58. data/utils/opcode_table_to_yaml.rb +91 -0
  59. metadata +133 -0
@@ -0,0 +1,3 @@
1
+
2
+ build: convert
3
+ clang++ source/*.cpp -I include -o convert
Binary file
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
+ }