n65 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }