sscharter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1019 @@
1
+ # Tutorial for sscharter
2
+
3
+ ## Introduction
4
+
5
+ Sscharter is a Ruby DSL serving as a charting tool for
6
+ [Sunniesnow](https://sunniesnow.github.io).
7
+ Different from other charting tools, sscharter does not have a GUI.
8
+
9
+ By using a programming language, charters can write charts more creatively.
10
+ If the chart has many patterns, it can be much more efficient than using a GUI charting tool.
11
+ However, programming languages can be difficult to learn.
12
+
13
+ ## Prerequisites
14
+
15
+ Here are some prerequisites for using sscharter.
16
+ Check all of them before continuing this tutorial:
17
+
18
+ - You need to have a basic knowledge of how to use a command line interface (CLI).
19
+ If you do not, you may find [this introduction](https://tutorial.djangogirls.org/en/intro_to_command_line/) helpful.
20
+ - You need to [install Ruby](https://www.ruby-lang.org/en/documentation/installation/).
21
+ - You need to has a basic understanding of how to program.
22
+ If you have programmed before, you will be fine because Ruby is a easy-to-begin language.
23
+ If you have not, [this](https://pine.fm/LearnToProgram/)
24
+ is a good place to start.
25
+ - You need a basic audio editing software and a basic text editing software.
26
+ If you do not have any, you can use
27
+ [Audacity](https://www.audacityteam.org/) and
28
+ [Visual Studio Code](https://code.visualstudio.com/).
29
+ - You need to be familiar with the game Sunniesnow
30
+ (which I assume you do because otherwise you would not be reading this now).
31
+ Better, you should be familiar with how a level file of Sunniesnow is structured
32
+ by reading [the documentation](https://sunniesnow.github.io/doc/).
33
+
34
+ ## Install sscharter
35
+
36
+ Assume you have installed Ruby.
37
+ Then, run
38
+
39
+ ```shell
40
+ gem install sscharter
41
+ ```
42
+
43
+ You are now good to go!
44
+
45
+ ## Step 0: decide the music
46
+
47
+ You need to decide the music you want to chart for!
48
+ Because you are assumed to be familiar with rhythm games,
49
+ I do not need to tell you what kinds of music are suitable for rhythm games.
50
+ However, I need to remind you that before you start charting, either
51
+
52
+ - you make sure the artist announced publicly that anyone can chart for the music
53
+ or permitted some sorts of usage that covers charting, or
54
+ - you got contact with the artist,
55
+ explained to them that you want to chart for the music and how you would distribute your chart,
56
+ and got their permission.
57
+
58
+ If you did not do any of the above, you need to keep in mind that you are a bad boy
59
+ and may face some sort of consequences
60
+ (I do not know. I am not a lawyer).
61
+
62
+ For this tutorial, I will use
63
+ [*Big-D* by Shaolin Dub](https://freemusicarchive.org/music/Shaolin_Dub/joint-force/big-d/).
64
+ It is licensed under [CC-BY-NC-ND-4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/).
65
+ You can download it from the link or use the following command
66
+ (if you have installed [cURL](https://curl.se/)).
67
+ By downloading it, you accept its license.
68
+
69
+ ```shell
70
+ curl https://files.freemusicarchive.org/storage-freemusicarchive-org/tracks/MQ8JO0BqKl2UADzKg74rwoY7mapqBT4uWpQYciTJ.mp3 -o 'Shaolin Dub - Big-D.mp3'
71
+ ```
72
+
73
+ ## Step 1: create a project
74
+
75
+ If you have already [installed sscharter](#install-sscharter)
76
+ and downloaded the audio file for your music using the command above,
77
+ you can now create a project for your chart by running the following command:
78
+
79
+ ```shell
80
+ sscharter init big-d 'Shaolin Dub - Big-D.mp3'
81
+ ```
82
+
83
+ The parameter `big-d` is the path to the project directory,
84
+ and its basename will be set as the name of the project
85
+ (but you are free to change it later).
86
+ The parameter `Shaolin Dub - Big-D.mp3` is the path to the audio file.
87
+ You can continue to add more parameters to the command
88
+ to include more files that you want t o include in the final level file.
89
+ See [the documentation](https://sunniesnow.github.io/doc/)
90
+ for what you may want to include.
91
+ If you forget to include some of the files that you want to include
92
+ at this step, you can add them later whenever you want.
93
+
94
+ After running the command, the audio file will be copied into the project directory,
95
+ and it is no longer needed for the rest of the tutorial.
96
+ You may delete it if you want.
97
+
98
+ Open the directory `big-d`, and you will see this directory structure:
99
+
100
+ ```plain
101
+ big-d
102
+ ├── files
103
+ │   └── Shaolin Dub - Big-D.mp3
104
+ ├── Gemfile
105
+ ├── .gitignore
106
+ ├── Rakefile
107
+ ├── README.md
108
+ ├── src
109
+ │   └── master.rb
110
+ └── .sscharter.yml
111
+ ```
112
+
113
+ Here are some explanation for each of them.
114
+
115
+ - `Gemfile` specifies the gem dependencies of this project.
116
+ By default your project depends on sscharter, bundler, and rake.
117
+ You can add more dependencies if you want.
118
+ For more information about it,
119
+ see [the official documentation](https://bundler.io/man/gemfile.5.html).
120
+ - `.gitignore` specifies the files that should not be tracked by Git.
121
+ If you do not use Git as the version manager for your project, it does nothing.
122
+ - `Rakefile` contains some tasks that can be run by [Rake](https://ruby.github.io/rake/).
123
+ - `README.md` is the README file of your project.
124
+ - `.sscharter.yml` is the sscharter configuration for your project.
125
+ - `files` is the directory whose files will be included in the final level file.
126
+ You can change the directory in `.sscharter.yml`.
127
+ - `src` is the directory that contains the source codes of your project.
128
+ You can change the directory in `.sscharter.yml`.
129
+
130
+ Open `.sscharter.yml` using your text editor.
131
+ Here are the contents that you should see:
132
+
133
+ ```yaml
134
+ ---
135
+ project_name: big-d
136
+ build_dir: build
137
+ files_dir: files
138
+ sources_dir: src
139
+ include:
140
+ - README.md
141
+ ```
142
+
143
+ Here are some explanation for each of them.
144
+
145
+ - `project_name` is the name of your project.
146
+ It will be the filename without extension of the generated level file.
147
+ - `build_dir` is the directory that will contain the generated level file.
148
+ - `files_dir` is the directory whose files will be included in the final level file.
149
+ - `sources_dir` is the directory that contains the source codes of your project.
150
+ - `include` is the list of files outside `files_dir` that will be included in the final level file.
151
+ You can use wildcard characters in the filenames.
152
+
153
+ You can also use your text editor to open the file `src/master.rb` for what a source codes file look like:
154
+
155
+ ```ruby
156
+ # frozen_string_literal: true
157
+
158
+ Sunniesnow::Charter.open 'master' do
159
+
160
+ title 'The title of the music'
161
+ artist 'The artist of the music'
162
+ charter 'Your name'
163
+ difficulty_name 'Master'
164
+ difficulty_color '#8c68f3'
165
+ difficulty '12'
166
+
167
+ offset 0
168
+ bpm 120
169
+
170
+ tp_chain 0, 0, 1 do
171
+ t -50, 0, 'hello'
172
+ b 1 # proceed by 1 beat
173
+ t 50, 0, 'world'
174
+ end
175
+
176
+ end
177
+ ```
178
+
179
+ You will learn what these codes mean soon later!
180
+
181
+ ## Step 2: set up workflow
182
+
183
+ Now, in the project directory, run
184
+
185
+ ```shell
186
+ bundle install
187
+ ```
188
+
189
+ It should be done quickly if your network is good.
190
+ Change the gem source in `Gemfile` if you have trouble retrieving from the default source.
191
+
192
+ After that, run
193
+
194
+ ```shell
195
+ rake
196
+ ```
197
+
198
+ Then, you should see a file `build/big-d.ssc` generated.
199
+ This file is a level file of Sunniesnow.
200
+ You can upload it on the webpage of Sunniesnow to play it.
201
+
202
+ However, it is kind of inconvenient if you have to run `rake`
203
+ and reupload the level file every time you make a change to the source codes.
204
+ You can use the following command to make it more convenient:
205
+
206
+ ```shell
207
+ rake serve
208
+ ```
209
+
210
+ This command will open the Sunniesnow webpage in your browser for you.
211
+ The `online` field of Sunniesnow is already filled with the address to the generated level file.
212
+ Every time you save changes to the source codes,
213
+ the program will automatically rebuild the level file.
214
+ You just need to hit "load" again to reload the level file.
215
+
216
+ The port of the local server is 8011 by default.
217
+ If you need to change the port to 1314 for example, you need to run
218
+
219
+ ```shell
220
+ bundle exec sscharter serve 1314
221
+ ```
222
+
223
+ ## What does each line in `src/master.rb` mean?
224
+
225
+ Now, you are ready to write the chart!
226
+ Open `src/master.rb` using your text editor.
227
+ Here I explain what does each line mean.
228
+
229
+ ```ruby
230
+ Sunniesnow::Charter.open 'master' do
231
+ # ...
232
+ end
233
+ ```
234
+
235
+ This means that you are opening a chart called "master".
236
+ This will lead to a chart file called `master.json` in the generated level file.
237
+ You can open multiple charts in a single source codes file,
238
+ and you can also open the same chart in multiple source codes files
239
+ (but in that case the loading order matters).
240
+
241
+ The contents inside the `do`...`end` block are the contents of the chart.
242
+ You can write anything you like there.
243
+
244
+ ```ruby
245
+ title 'The title of the music'
246
+ artist 'The artist of the music'
247
+ charter 'Your name'
248
+ difficulty_name 'Master'
249
+ difficulty_color '#8c68f3'
250
+ difficulty '12'
251
+ ```
252
+
253
+ These lines are the metadata of the chart.
254
+ They already explains pretty much themselves.
255
+ Just fill them in!
256
+
257
+ By the way, there is a trick about the `difficulty_color` for your convenience.
258
+ If you want default difficulty colors from Lyrica, you can use one of these:
259
+
260
+ ```ruby
261
+ difficulty_color :easy
262
+ difficulty_color :normal
263
+ difficulty_color :hard
264
+ difficulty_color :master
265
+ difficulty_color :special
266
+ ```
267
+
268
+ Then, here comes the offset and BPM part:
269
+
270
+ ```ruby
271
+ offset 0
272
+ bpm 120
273
+ ```
274
+
275
+ This means:
276
+
277
+ - The zeroth beat of the music starts at time equaling to 0 seconds.
278
+ - The BPM of the music starting at the zeroth beat is 120.
279
+
280
+ You can change the BPM later at any beat by using `bpm`.
281
+ You can also use `offset` multiple times, but every time you run `offset`,
282
+ the beat is reset to zero, and you have to set the BPM over again.
283
+
284
+ Now here comes the main part:
285
+
286
+ ```ruby
287
+ tp_chain 0, 0, 1 do
288
+ t -50, 0, 'hello'
289
+ b 1 # proceed by 1 beat
290
+ t 50, 0, 'world'
291
+ end
292
+ ```
293
+
294
+ This means:
295
+
296
+ - Start a tip point chain.
297
+ The tip point spawns at:
298
+ - coordinates $(0,0)$ relative to the first tip-pointable event in the chain, and
299
+ - time 1 second before the first tip-pointable event in the chain.
300
+ - Add a tap note at coordinates $(-50,0)$ to the tip point chain.
301
+ The tap note has text "hello" on it.
302
+ - Proceed by 1 beat.
303
+ Now the current beat is 1 instead of 0.
304
+ - Add a tap note at coordinates $(50,0)$ to the tip point chain.
305
+ The tap note has text "world" on it.
306
+ - End the tip point chain.
307
+
308
+ They are actually very straightforward!
309
+
310
+ Now, you are ready to write your own chart.
311
+
312
+ ## Step 3: write the chart metadata
313
+
314
+ Delete all contents inside the outmost `do`...`end` block.
315
+ Then, write the metadata of the chart:
316
+
317
+ ```ruby
318
+ title 'Big-D'
319
+ artist 'Shaolin Dub'
320
+ charter 'UlyssesZhan' # replace this with your name
321
+ difficulty_name 'Master'
322
+ difficulty_color :master
323
+ difficulty '10'
324
+ ```
325
+
326
+ You are then writing the chart for the master difficulty,
327
+ with difficulty being 10.
328
+
329
+ Now, the whole source codes file should look like this:
330
+
331
+ ```ruby
332
+ # frozen_string_literal: true
333
+
334
+ Sunniesnow::Charter.open 'master' do
335
+
336
+ title 'Big-D'
337
+ artist 'Shaolin Dub'
338
+ charter 'UlyssesZhan' # replace this with your name
339
+ difficulty_name 'Master'
340
+ difficulty_color :master
341
+ difficulty '10'
342
+
343
+ end
344
+ ```
345
+
346
+ ## Step 4: determine the offset and BPM
347
+
348
+ Now, you need to determine the offset and BPM of the music
349
+ and write the information into the source codes file.
350
+ Use your favorite audio editing software to open the music
351
+ (I use Audacity here).
352
+
353
+ To determine them, inside a section of the music with consistent BPM,
354
+ find a beat that is clear to recognize (up to milliseconds precision for the best).
355
+
356
+ ![First clear beat](https://i.imgur.com/rI5UXgm.png)
357
+
358
+ Then, find the time of the beat.
359
+ You can read off the time at the "Audio Position" field in Audacity,
360
+ which says 7.023 seconds here.
361
+
362
+ Then, find the last beat that is clear to recognize.
363
+
364
+ ![Last clear beat](https://i.imgur.com/eJw6ZaJ.png)
365
+
366
+ Then, find the time of the beat.
367
+ It is 180.071 seconds.
368
+
369
+ You now need to know how many beats are there between the two beats.
370
+ You can first approximately measure the length of, say, 8 beats,
371
+ and then divide the length between the two beats by the length of 8 beats
372
+ to estimate how many "8 beats" are there.
373
+ Because we can easily hear that number of beats modulo 4 is 2,
374
+ we can use the fact to check our result of calculation
375
+ (the result of the division should be close to something point 25 or something point 75).
376
+ The result should be approximately $49.75$.
377
+ Then, we can calculate the BPM now:
378
+
379
+ ```math
380
+ 60\,\mathrm{s/min}\cdot
381
+ \frac{49.75\times8\,\mathrm{beats}}{180.071\,\mathrm s-7.023\,\mathrm s}
382
+ \approx137.996\,\mathrm{beats/min}.
383
+ ```
384
+
385
+ So the BPM is approximately 138.
386
+
387
+ To get the offset, we just need to extrapolate the time of the zeroth beat.
388
+ We can count that the first clear beat we chose is the 16th beat,
389
+ so we can use the BPM and the time of the 16th beat to extrapolate the time of the zeroth beat:
390
+
391
+ ```math
392
+ 7.023\,\mathrm s-16\,\mathrm{beats}\cdot
393
+ \frac{60\,\mathrm{s/min}}{138\,\mathrm{beats/min}}
394
+ \approx0.066\,\mathrm s.
395
+ ```
396
+
397
+ Now that we got the offset and BPM, we can write them into the source codes file:
398
+
399
+ ```ruby
400
+ offset 0.066
401
+ bpm 138
402
+ ```
403
+
404
+ The whole source codes file should now look like this:
405
+
406
+ ```ruby
407
+ # frozen_string_literal: true
408
+
409
+ Sunniesnow::Charter.open 'master' do
410
+
411
+ title 'Big-D'
412
+ artist 'Shaolin Dub'
413
+ charter 'UlyssesZhan' # replace this with your name
414
+ difficulty_name 'Master'
415
+ difficulty_color :master
416
+ difficulty '10'
417
+
418
+ offset 0.066
419
+ bpm 138
420
+
421
+ end
422
+ ```
423
+
424
+ ## Step 5: write the chart
425
+
426
+ Now, you are ready to write the main part of the chart!
427
+
428
+ ### Write notes
429
+
430
+ Write the following code:
431
+
432
+ ```ruby
433
+ hold -25, 0, 1/2r # a hold note at (-25,0) with the duration of 1/2 beats
434
+ beat 3/4r # go to beat 3/4
435
+ hold 25, 0, 1/2r # a hold note at (25,0) with the duration of 1/2 beats
436
+ beat 1/4r + 2 # go to beat 3
437
+ hold 0, 25, 1/2r # a hold note at (0,25) with the duration of 1/2 beats
438
+ beat 1 # go to beat 4
439
+
440
+ hold -25, -25, 1/2r
441
+ beat 3/4r
442
+ hold 25, -25, 1/2r
443
+ beat 1/4r + 2
444
+ hold 0, -50, 1/2r
445
+ beat 1
446
+ ```
447
+
448
+ By writing these, you added 6 hold notes to the chart.
449
+ Each `hold` adds a hold note to the chart.
450
+ The first argument is the $x$-coordinate of the hold note,
451
+ the second argument is the $y$-coordinate of the hold note,
452
+ and the third argument is the duration of the hold note in beats.
453
+ There is an optional fourth argument, which is the text on the hold note
454
+ (defaults to empty string).
455
+ Each `beat` call proceeds the current beat by the argument you specifies.
456
+
457
+ > Here is a notice for those who are not familiar with Ruby:
458
+ expression like `1/2r` means the rational number $1/2$.
459
+ In sscharter, you need to specify beats in integers or rational numbers
460
+ to avoid rounding errors.
461
+ Here is an example of how rational numbers avoid rounding errors:
462
+ >
463
+ > ```ruby
464
+ > 1/10r + 1/5r == 3/10r # => true
465
+ > 0.1 + 0.2 == 0.3 # => false
466
+ > ```
467
+ >
468
+ > The rounding error would not be huge even if being added up many times,
469
+ but it is still better to avoid it.
470
+ >
471
+ > If you are tired of appending `r` to every rational number, you may use
472
+ >
473
+ > ```ruby
474
+ > Integer.alias_method :/, :quo
475
+ > ```
476
+ >
477
+ > to make the division operation between integers automatically return rational numbers.
478
+
479
+ You can also abbreviate `hold` to `h` and `beat` to `b`, like this:
480
+
481
+ ```ruby
482
+ h -25, 0, 1/2r; b 3/4r
483
+ h 25, 0, 1/2r; b 1/4r + 2
484
+ h 0, 25, 1/2r; b 1
485
+ ```
486
+
487
+ > Here is another notice for those who are not familiar with Ruby.
488
+ In Ruby, the semicolon `;` can be used to separate multiple statements in a single line.
489
+ Therefore, `h -25, 0, 1/2r; b 3/4r` is equivalent to
490
+ >
491
+ > ```ruby
492
+ > h -25, 0, 1/2r
493
+ > b 3/4r
494
+ > ```
495
+
496
+ Here is a list of all the note types you can use:
497
+
498
+ | Note type | Syntax | Abbreviation |
499
+ |-|-|-|
500
+ | tap | `tap x, y, text=""` | `t` |
501
+ | hold | `hold x, y, duration_beats, text=""` | `h` |
502
+ | drag | `drag x, y` | `d` |
503
+ | flick | `flick x, y, direction, text=""` | `f` |
504
+ | background note | `bg_note x, y, duration_beats=0, text=""` | |
505
+
506
+ Here are some notices:
507
+
508
+ - The direction of a flick note can be a number specifying the angle in **radians**
509
+ (zero is to the right, and increasing angle is counterclockwise),
510
+ or it can be one of the following 8 symbols:
511
+ `:right`, `:up_right`, `:up`, `:up_left`, `:left`, `:down_left`, `:down`, `:down_right`.
512
+ - The duration of a background note can be zero, but that of a hold note cannot.
513
+
514
+ > Here is another notice for those who are not familiar with Ruby.
515
+ > In Ruby, the parentheses around the arguments of a method call can be omitted.
516
+ > Therefore, `tap 25, 0, 'hi'` is equivalent to `tap(25, 0, 'hi')`.
517
+
518
+ Now, the whole source codes file should look like this:
519
+
520
+ ```ruby
521
+ # frozen_string_literal: true
522
+
523
+ Sunniesnow::Charter.open 'master' do
524
+
525
+ title 'Big-D'
526
+ artist 'Shaolin Dub'
527
+ charter 'UlyssesZhan' # replace this with your name
528
+ difficulty_name 'Master'
529
+ difficulty_color :master
530
+ difficulty '10'
531
+
532
+ offset 0.066
533
+ bpm 138
534
+
535
+ h -25, 0, 1/2r; b 3/4r
536
+ h 25, 0, 1/2r; b 1/4r + 2
537
+ h 0, 25, 1/2r; b 1
538
+
539
+ h -25, -25, 1/2r; b 3/4r
540
+ h 25, -25, 1/2r; b 1/4r + 2
541
+ h 0, -50, 1/2r; b 1
542
+
543
+ end
544
+ ```
545
+
546
+ You may build the level file and play it on Sunniesnow to see what you have done!
547
+ If everything is alright, you should see a chart like this:
548
+
549
+ ![The chart](https://i.imgur.com/zPUtgGr.png)
550
+
551
+ If you see something similar, congratulations!
552
+
553
+ ### Background patterns
554
+
555
+ There are totally 7 different kinds of background patterns:
556
+
557
+ | Pattern | Syntax |
558
+ |-|-|
559
+ | big text | `big_text duration_beats=0, text` |
560
+ | grid | `grid duration_beats=0` |
561
+ | hexagon | `hexagon duration_beats=0` |
562
+ | checkerboard | `checkerboard duration_beats=0` |
563
+ | diamond grid | `diamond_grid duration_beats=0` |
564
+ | pentagon | `pentagon duration_beats=0` |
565
+ | turntable | `turntable duration_beats=0` |
566
+
567
+ Most of them only has one optional argument, specifying the duration.
568
+ The big text is different in that it needs an additional required argument,
569
+ specifying the contents of the texts.
570
+
571
+ The mathematical details about the shapes of these patterns are
572
+ specified in [the documentation](https://sunniesnow.github.io/doc/chart.html).
573
+
574
+ You may now try adding a grid pattern to the chart:
575
+
576
+ ```ruby
577
+ offset 0.066
578
+ bpm 138
579
+
580
+ grid 16 # add a grid pattern with duration 16 beats
581
+
582
+ # notes...
583
+ h -25, 0, 1/2r; b 3/4r
584
+ ```
585
+
586
+ ### Groups, duplicates, and transformations
587
+
588
+ You have already written the chart for the first 8 beats of *Big-D*.
589
+ Then, you want to have the same for the next 8 beats.
590
+ How would you do that?
591
+
592
+ Well, an obvious solution is to copy and paste the code you have written.
593
+ However, this has some drawbacks:
594
+
595
+ - If at some point you want to change the pattern,
596
+ you have to change it at multiple places.
597
+ - When you read the codes, it would not be obvious at a glance that
598
+ "hey, this part is the same as that part!"
599
+
600
+ Come on, you are programming! Are not there some better ways?
601
+ Fortunately, there are, and there are multiple ways to do that.
602
+
603
+ First, you can define a method, and call that method twice:
604
+
605
+ ```ruby
606
+ def pattern # the method name `pattern` is arbitrary, set what you like
607
+ h -25, 0, 1/2r; b 3/4r
608
+ h 25, 0, 1/2r; b 1/4r + 2
609
+ h 0, 25, 1/2r; b 1
610
+
611
+ h -25, -25, 1/2r; b 3/4r
612
+ h 25, -25, 1/2r; b 1/4r + 2
613
+ h 0, -50, 1/2r; b 1
614
+ end
615
+ pattern
616
+ pattern
617
+ ```
618
+
619
+ Another way of doing this is to create a group of notes and duplicate them.
620
+ You can write these:
621
+
622
+ ```ruby
623
+ notes = group do # the variable name `notes` is arbitrary, set what you like
624
+ h -25, 0, 1/2r; b 3/4r
625
+ h 25, 0, 1/2r; b 1/4r + 2
626
+ h 0, 25, 1/2r; b 1
627
+
628
+ h -25, -25, 1/2r; b 3/4r
629
+ h 25, -25, 1/2r; b 1/4r + 2
630
+ h 0, -50, 1/2r; b 1
631
+ end
632
+ new_notes = duplicate notes # duplicate them to get a group of new notes
633
+ transform(new_notes) { beat_translate 8 } # translate those new notes by 8 beats
634
+ b 8 # calling `transform` does not affect current beat, so proceed by 8 beats now
635
+ ```
636
+
637
+ In this example, the variable `notes` is assigned the return value of `group`,
638
+ which is actually an array of the events inside the code block in the call of `group`.
639
+ The variable `new_notes` is assigned the return value of `duplicate`,
640
+ which is another array of other events, which are copies of the events in `notes`.
641
+
642
+ > In Ruby, code blocks can be written as a `do`...`end` block **or** a curly-enclosed block (`{`...`}`).
643
+ They have equivalent effects but have different precedences,
644
+ and by convention, if the code block contains only one line of codes,
645
+ people use a curly-enclosed block to make it seem more compact.
646
+
647
+ Transformations are more powerful than just translating in time.
648
+ There are many other transformations you can apply to notes.
649
+ Here is a list:
650
+
651
+ | Syntax | Meaning |
652
+ |-|-|
653
+ | `translate dx, dy` | translating in space by vector $(\mathtt{dx},\mathtt{dy})$ |
654
+ | `horizontal_flip` | flipping horizontally |
655
+ | `vertical_flip` | flipping vertically |
656
+ | `rotate angle` | rotating by `angle` (in radians) counterclockwise |
657
+ | `scale sx, sy=sx` | scaling by `sx` in $x$ and by `sy` in $y$; negative values are also valid |
658
+ | `compound_linear xx, xy, yx, yy` | applying a linear transformation specified by numbers in a matrix |
659
+ | `beat_translate delta_beat` | translating in time by `delta_beat` beats |
660
+
661
+ > For those who are not familiar with linear algebra,
662
+ some of the transformations on a 2-dimensional shape can be specified by a $2\times2$ matrix.
663
+ Those transformations are called **linear transformations**.
664
+ The matrix is called the **transformation matrix**.
665
+ The transformation is done like this:
666
+ >
667
+ > ```math
668
+ > x'=\mathtt{xx}\cdot x+\mathtt{xy}\cdot y,\quad
669
+ > y'=\mathtt{yx}\cdot x+\mathtt{yy}\cdot y.
670
+ > ```
671
+
672
+ *Notice*: When doing spatial transformations,
673
+ you do not need to worry about the direction of flick notes.
674
+ They are taken care of as well.
675
+
676
+ *Another notice*: For background patterns,
677
+ they are ignored when doing spatial transformations.
678
+ They are still affected by temporal transformations, though.
679
+
680
+ Therefore, you can also apply, say, a horizontal flip on the duplicated notes like this:
681
+
682
+ ```ruby
683
+ notes = group do
684
+ # ...
685
+ end
686
+ transform duplicate notes do
687
+ horizontal_flip
688
+ beat_translate 8
689
+ end
690
+ ```
691
+
692
+ Transformations can also be used to bulk edit notes.
693
+ Imagine that you have already written a bunch of notes with their coordinates,
694
+ but you then decided them move them (say, translating by vector $(25,25)$).
695
+ You can use `transform` to do that:
696
+
697
+ ```ruby
698
+ notes = group do
699
+ # ...
700
+ end
701
+ transform(notes) { translate 25, 25 }
702
+ ```
703
+
704
+ ### Navigate among beats
705
+
706
+ You have already learned how to use `beat` to proceed the current beat
707
+ by a certain amount of beats.
708
+ It can also navigate backwards by using negative numbers.
709
+
710
+ You can get the current beat number by using `beat` without arguments:
711
+
712
+ ```ruby
713
+ offset 0.066 # using `offset` will reset the current beat to zero
714
+ p beat # this will output 0 in the terminal, because the current beat is 0
715
+ p b # `b` is an abbreviation of `beat`, so this will also output 0
716
+ beat 1 # proceed the current beat by 1 beat
717
+ p beat # this will output 1 in the terminal, because the current beat is 1
718
+ b 3/4r # proceed the current beat by 3/4 beats
719
+ p b # this will output 7/4 in the terminal, because the current beat is 7/4
720
+ b -1/2r # go back by 1/2 beats
721
+ p b # outputs 5/4
722
+ b -2 # go back by 2 beats, even if the current beat is only 5/4
723
+ p b # outputs -3/4
724
+ ```
725
+
726
+ > In Ruby, the method `p` is used to output any object in the terminal
727
+ in a human-readable form.
728
+ You can use it to debug your program.
729
+
730
+ You can use `beat!` (whose abbreviation is `b!`) to set the current beat directly,
731
+ instead of specifying the amount of beats to proceed relative to the current beat:
732
+
733
+ ```ruby
734
+ beat! 10 # set the current beat to 10
735
+ p beat # outputs 10
736
+ b! -5 # set the current beat to -5
737
+ p b # outputs -5
738
+ p beat! # this outputs -5 because you may also use `beat!` without arguments to get the current beat
739
+ p b! # this also outputs -5
740
+ ```
741
+
742
+ You can use `time_at` to get the time (in seconds) of a certain beat:
743
+
744
+ ```ruby
745
+ offset 0.1
746
+ bpm 120
747
+ p time_at 0 # outputs 0.1, meaning that beat 0 is at 0.1 seconds
748
+ p time_at 1 # outputs 0.6, meaning that beat 1 is at 0.6 seconds
749
+ b! 10
750
+ p time_at # when using `time_at` without arguments, it will use the current beat; outputs 5.1
751
+
752
+ offset 0.2 # this resets current beat to 0
753
+ bpm 120 # calling `offset` clears BPM info, so you need to set it again
754
+ p time_at # outputs 0.2, meaning that beat 0 is at 0.2 seconds
755
+ ```
756
+
757
+ Having `beat!`, you can now travel back to some point in time
758
+ to write some notes there:
759
+
760
+ ```ruby
761
+ checkpoint = b
762
+
763
+ # write something here...
764
+
765
+ b! checkpoint # travel back to the checkpoint
766
+
767
+ # write something here...
768
+ ```
769
+
770
+ However, there is another way of doing this.
771
+ You can encapsulate the first set of notes in a `group` that does
772
+ *not preserve beat*,
773
+ which means that the current beat is set to how it was at the start of the group when the group finishes,
774
+ like this:
775
+
776
+ ```ruby
777
+ group preserve_beat: false do
778
+ # write something here...
779
+ end
780
+ # write something here...
781
+ ```
782
+
783
+ ### BPM changes
784
+
785
+ *Notice*: because *Big-D* does not have BPM changes,
786
+ this part of the tutorial will not be used in the actual chart of *Big-D*.
787
+ You may still try it out, though.
788
+
789
+ You can use `bpm` to set the BPM starting at the current beat.
790
+
791
+ ```ruby
792
+ offset 0
793
+ bpm 120
794
+ p time_at # outputs 0.0
795
+ b 1 # proceed by 1 beat
796
+ p time_at # outputs 0.5
797
+ bpm 60 # set the BPM starting at the current beat to 60
798
+ b 1
799
+ p time_at # outputs 1.5
800
+ ```
801
+
802
+ As we can see, BPM changes only affect the beats **after** they were set.
803
+ Beats before the BPM changes are not affected.
804
+ However, this means that you cannot get the time before the first BPM you set:
805
+
806
+ ```ruby
807
+ offset 0
808
+ bpm 120
809
+ p time_at -1 # raises an error!
810
+ ```
811
+
812
+ ### Tip points
813
+
814
+ Sscharter regards tap, hold, drag, and flick as *tip-pointable* events.
815
+ Note that although Sunniesnow also regards background notes as tip-pointable,
816
+ sscharter does not.
817
+ Tip-pointable events can be connected by tip points,
818
+ and one tip-pointable event can be connected by at most one tip point.
819
+ A set of events that are connected by the same tip point compose a *tip point chain*.
820
+
821
+ There are two methods for you to write tip points:
822
+ `tip_point_chain` and `tip_point_drop`,
823
+ and they are respectively abbreviated as `tp_chain` and `tp_drop`.
824
+ Before introducing them, I need to first explain how to specify
825
+ where and when a tip point spawns.
826
+ There are two ways to specify where the tip point spawns:
827
+
828
+ - Specifying the coordinates relative to the first tip-pointable event in the tip point chain.
829
+ This is the default way.
830
+ You specify the $x$-coordinate and $y$-coordinate
831
+ by the first two arguments of `tip_point_chain` or `tip_point_drop`.
832
+ - Specifying the absolute coordinates of the position where the tip point spawns.
833
+ To specify the position in this way,
834
+ you need to add a keyword argument `relative: false` to the call of `tip_point_chain` or `tip_point_drop`.
835
+
836
+ There are four ways to specify when the tip point spawns:
837
+
838
+ - Specifying how much time, in seconds,
839
+ before the first tip-pointable event in the tip point chain the tip point will have spawned.
840
+ This is the default way.
841
+ The time is specified by the third argument in the call of `tip_point_chain` or `tip_point_drop`.
842
+ - Specifying the speed of the tip point between its spawning and the first tip-pointable event in the tip point chain.
843
+ To specify the speed, you need to use the keyword argument `speed:`
844
+ in the call of `tip_point_chain` or `tip_point_drop`.
845
+ - Specifying how many beats before the first tip-pointable event
846
+ in the tip point chain the tip point will have spawned.
847
+ To specify the beats, you need to use the keyword argument `relative_beat:`
848
+ in the call of `tip_point_chain` or `tip_point_drop`.
849
+ - Specifying the beat speed (unit length per beat)
850
+ of the tip point between its spawning and the first tip-pointable event in the tip point chain.
851
+ To specify the beat speed, you need to use the keyword argument `beat_speed:`
852
+ in the call of `tip_point_chain` or `tip_point_drop`.
853
+ The beat speed may be specified as a float number,
854
+ and this is the only case where something related to beats is not preferred to be specified as a rational number.
855
+
856
+ We can categorize the four ways into a table:
857
+
858
+ <table>
859
+ <tr>
860
+ <th>Method</th>
861
+ <td><code>tip_point_chain</code></td>
862
+ <td><code>tip_point_drop</code></td>
863
+ </tr>
864
+ <tr>
865
+ <th><code>relative_time</code></th>
866
+ <td><code>tip_point_chain x=0, y=0, relative_time=0.0, relative: true</code></td>
867
+ <td><code>tip_point_drop x=0, y=0, relative_time=0.0, relative: true</code></td>
868
+ </tr>
869
+ <tr>
870
+ <th><code>speed</code></th>
871
+ <td><code>tip_point_chain x=0, y=0, relative: true, speed:</code></td>
872
+ <td><code>tip_point_drop x=0, y=0, relative: true, speed:</code></td>
873
+ </tr>
874
+ <tr>
875
+ <th><code>relative_beat</code></th>
876
+ <td><code>tip_point_chain x=0, y=0, relative: true, relative_beat:</code></td>
877
+ <td><code>tip_point_drop x=0, y=0, relative: true, relative_beat:</code></td>
878
+ </tr>
879
+ <tr>
880
+ <th><code>beat_speed</code></th>
881
+ <td><code>tip_point_chain x=0, y=0, relative: true, beat_speed:</code></td>
882
+ <td><code>tip_point_drop x=0, y=0, relative: true, beat_speed:</code></td>
883
+ </tr>
884
+ </table>
885
+
886
+ You can only choose one of the four ways to specify the position and time of the spawning of a tip point.
887
+ If you specify more than one of them, an error will be raised.
888
+
889
+ > In Ruby, keyword arguments are specified after the normal arguments.
890
+ You need to explicitly write the name of the keyword argument followed by a colon
891
+ in the method call.
892
+ For example, in the expression
893
+ >
894
+ > ```ruby
895
+ > tp_chain 0, 100, speed: 100 do
896
+ > # ...
897
+ > end
898
+ > ```
899
+ >
900
+ > the argument `speed:` is a keyword argument in the call of `tp_chain`.
901
+ The value of the keyword argument is `100`.
902
+
903
+ Knowing how to specify the position and time of the spawning of a tip point,
904
+ then creating tip points is just as easy as filling events in the code block
905
+ in the call of `tip_point_chain` or `tip_point_drop`.
906
+
907
+ Now I need to explain the difference of `tip_point_chain` and `tip_point_drop`.
908
+ `tip_point_chain` creates only one tip point chain,
909
+ and the tip point connects every tip-pointable event in the code block;
910
+ `tip_point_drop` creates as many tip point chains as the number of tip-pointable events in the code block,
911
+ and each tip point chain connects only one tip-pointable event.
912
+
913
+ As an example, let's connect the notes in the first 8 beats of *Big-D* by tip points:
914
+
915
+ ```ruby
916
+ tp_chain 0, 100, speed: 100 do
917
+ h -25, 0, 1/2r; b 3/4r
918
+ h 25, 0, 1/2r; b 1/4r + 2
919
+ h 0, 25, 1/2r; b 1
920
+
921
+ h -25, -25, 1/2r; b 3/4r
922
+ h 25, -25, 1/2r; b 1/4r + 2
923
+ h 0, -50, 1/2r; b 1
924
+ end
925
+ ```
926
+
927
+ If you want to duplicate those tip-pointed notes, you do not need to encapsulate them with another layer of `group`.
928
+ The return value of `tip_point_chain` or `tip_point_drop`
929
+ is already an array of the events in the code block.
930
+ Therefore, you can just do this:
931
+
932
+ ```ruby
933
+ notes = tp_chain 0, 100, speed: 100 do
934
+ # notes...
935
+ end
936
+ transform duplicate notes do
937
+ # transforms...
938
+ end
939
+ ```
940
+
941
+ *Tips*:
942
+ Just like `group`, `tip_point_chain` and `tip_point_drop`
943
+ may also not preserve beat.
944
+ You can let them not preserve beat by specifying the keyword argument `preserve_beat: false`:
945
+
946
+ ```ruby
947
+ tp_chain 0, 100, speed: 100, preserve_beat: false do
948
+ # notes...
949
+ end
950
+ ```
951
+
952
+ Now, the whole source codes file should look like this:
953
+
954
+ ```ruby
955
+ # frozen_string_literal: true
956
+
957
+ Sunniesnow::Charter.open 'master' do
958
+
959
+ title 'Big-D'
960
+ artist 'Shaolin Dub'
961
+ charter 'UlyssesZhan' # replace this with your name
962
+ difficulty_name 'Master'
963
+ difficulty_color :master
964
+ difficulty '10'
965
+
966
+ offset 0.066
967
+ bpm 138
968
+
969
+ grid 16
970
+
971
+ notes = tp_chain 0, 100, speed: 100 do
972
+ h -25, 0, 1/2r; b 3/4r
973
+ h 25, 0, 1/2r; b 1/4r + 2
974
+ h 0, 25, 1/2r; b 1
975
+
976
+ h -25, -25, 1/2r; b 3/4r
977
+ h 25, -25, 1/2r; b 1/4r + 2
978
+ h 0, -50, 1/2r; b 1
979
+ end
980
+ transform duplicate notes do
981
+ horizontal_flip
982
+ beat_translate 8
983
+ end
984
+
985
+ end
986
+ ```
987
+
988
+ You may build the level file and play it on Sunniesnow to see what you have done!
989
+
990
+ ![Second chart](https://i.imgur.com/ksu5sVW.png)
991
+
992
+ *Notice*:
993
+ When duplicating tip-pointed events,
994
+ the duplicated events are not connected by the same tip point as the original events.
995
+ However, if two original events are connected by the same tip point,
996
+ their duplicates are also connected by the same tip point, too.
997
+
998
+ ## Advanced charting techniques
999
+
1000
+ TODO.
1001
+
1002
+ ## Step 6: review your chart and write the README
1003
+
1004
+ Now, you have finished writing the chart!
1005
+ Please always play the chart yourself on Sunniesnow to see if it is enjoyable to play it.
1006
+ You should also try playing it in mirror mode (horizontal flip and/or vertical flip)
1007
+ so that you do not apply too much of your charting memories to playing the chart.
1008
+
1009
+ Then, it is time to finish writing the README.
1010
+ A template for the README file should have already been generated for you by sscharter.
1011
+ Use your text editor to edit it!
1012
+
1013
+ ## Step 7: distribute the level file
1014
+
1015
+ Congratulations! You finished your chart.
1016
+ Now, you can distribute the level file to people who may want to play it.
1017
+
1018
+ If you want your chart to be available as an online level of Sunniesnow,
1019
+ you need to [contact the author of Sunniesnow](mailto:UlyssesZhan <ulysseszhan@gmail.com>).